Sémantiques bizarres autour des stops. #50
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
eric/pmomusic#50
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Dans le renderer, la fonction stop n'arrête pas vraiment la lecture. Sauf si on appelle avant.
Encore un exemple de commande qui ne fait pas ce qu'elle dit.
Résumé du problème
MusicRenderern’est pas le périphérique UPnP/Chromecast/LinkPlay : c’est la représentation locale qu’a leControlPointd’un renderer distant. Sa méthodestop()ne fait pas que transmettre une commande de transport — elle mélange deux rôles :Or, pour que l’arrêt soit correctement interprété, il faut systématiquement faire :
stop()seul ne suffit pas. Le flaguser_stop_requestedest lu plus tard danshandle_state_change()pour décider si l’étatStoppedqui arrive doit déclencher l’auto-advance ou pas. Si le flag n’a pas été positionné avant l’appel, le périphérique s’arrête bien, mais le contrôle point peut relancer le titre suivant de la queue tout seul.Le ticket a donc raison de trouver ça tordu : la sémantique de
stop()ne couvre pas l’intention complète de l’appelant, et le contrat repose sur un appel séparé à une méthode dont le nom (mark_user_stop_requested) ne dit pas qu’il est obligatoire pour un arrêt utilisateur.Analyse : confusion des responsabilités
Le nœud vient du fait que
MusicRendererhéberge un état qui ne devrait pas être le sien :user_stop_requestedest une information métier du Control Point : « quand l’état du renderer passera à Stopped, je dois considérer que c’est un arrêt utilisateur, pas une fin de titre naturelle ».MusicRendererfait de cet objet un hybride entre :Du coup,
stop()surMusicRenderera une signature de commande de transport mais une sémantique qui dépend d’un flag interne. C’est cette ambiguïté qui crée l’API de merde.Pistes de résolution
1. Extraire la décision « arrêt utilisateur » du
MusicRenderermark_user_stop_requested()etcheck_and_clear_user_stop_requested()deMusicRenderer.MusicRenderer.stop()ne faire que transmettre la commande au backend.ControlPoint: c’est lui qui sait si l’arrêt vient de l’utilisateur, d’un timer, ou d’une fin de titre naturelle.Avantage :
MusicRendererredevient un simple reflet de l’état observé du périphérique, sans logique métier cachée.2. Si on garde la logique dans
MusicRenderer, rendre le contrat explicitestop_user()oustop_with_intent(StopIntent::User)qui encapsule les deux appels.stop()reste une commande de transport brute ; la nouvelle méthode est celle à utiliser quand c’est l’utilisateur qui arrête la lecture.Avantage : pas de changement d’architecture, mais le contrat est visible dans la signature.
3. Rétro-compatibilité
control_point.rs(lignes 393 et 1092), les backends, les exemples (full_control_point_demo,pmo_remote_control), les tests.MusicRenderer, il faut reproduire le comportement dansControlPoint(stocker l’intention d’arrêt, la consommer dans le handler d’état).Conclusion
Ce n’est pas un bug fonctionnel :
stop()arrête bien la lecture. C’est un problème de conception de l’API : la séparation entre « commande de transport » et « intention métier » n’est pas respectée, et elle est cachée dans un objet (MusicRenderer) qui ne devrait pas porter cette décision. Je propose de garder le ticket ouvert pour étudier la piste #1 (remonter la logique auControlPoint) quand on aura le temps.Je vois des appels dans
control_point.rs(lignes 396, 1096, 1362) et dans les exemples. Je vais vérifier les contextes complets, notamment si tous les appelleurs passent bien parmark_user_stop_requested()avant.Il y a des appelleurs qui ne passent pas par
mark_user_stop_requested(). Je vais vérifier les exemples et le transfert de playlist.Je vois des appelleurs qui ne positionnent pas le flag. Je vais vérifier le dernier exemple.
Voici les appelleurs de
MusicRenderer::stop():Qui positionne bien le flag avant :
control_point.rsligne 393-396 : timer de sommeil expirécontrol_point.rsligne 1092-1096 :user_stop()(bouton Stop UI)Qui n’y passent pas :
control_point.rsligne 1362 : transfert de playlist d’un renderer source vers un renderer destination — arrêt “interne”, pas un arrêt utilisateurmusicrenderer.rsligne 2005 :shuffle_queue()— arrêt interne pour relancer la lecture mélangéepmocontrol/examples/full_control_point_demo.rsligne 798 :stop_renderer()— démo, arrêt “utilisateur” sans le flagpmocontrol/examples/full_control_point_demo.rsligne 870 :stop_current_renderer_playback()— même problèmepmocontrol/examples/renderer_demo.rsligne 205 : démo du renderer, stop “brut”Ce que ça montre :
mark_user_stop_requested()avantstop()pour un arrêt utilisateur”.C’est exactement le nœud : la responsabilité de “savoir si c’est un arrêt utilisateur” est dispersée chez tous les appelleurs, alors qu’elle devrait être encapsulée dans l’API.
C’est bon, c’est démêlé et corrigé.