Files
pmomusic/Blackboard/Report/bug_lecture_queue_interne.md
Eric Coissac 5701fbf465 Fix queue duplication and internal queue playback bugs
Fix bug where tracks were duplicated in queue position 0 during playlist refreshes by comparing items with URI or didl_id

Fix race condition in internal queue playback where transient STOPPED states caused unwanted auto-advance

- Updated sync_queue in interne.rs to use didl_id as fallback for URI comparison
- Added items_match function in openhome.rs for robust item comparison
- Modified lcs_flags in openhome.rs to use items_match for LCS algorithm
- Added has_played_since_track_start flag in musicrenderer.rs to prevent auto-advance on transient STOPPED states
- Updated play_* methods in musicrenderer.rs to reset the has_played flag before starting playback
- Added diagnostic logs in sync_queue for tracking item matching issues
2026-01-16 22:46:05 +01:00

3.6 KiB

Rapport : Correction du bug de lecture sur queue interne

Problème

Lors de la lecture sur un Renderer avec queue interne, si l'utilisateur clique sur un item de la queue pour déclencher sa lecture, tout semble se passer normalement pendant une seconde. Puis, avant que la lecture ne démarre réellement, le lecteur passe à la piste suivante.

Analyse

Cause identifiée

Le problème était une race condition dans la logique d'auto-advance du watcher.

Quand l'utilisateur clique sur un item de la queue :

  1. play_queue_index est appelé dans ControlPoint
  2. Les commandes UPnP SetAVTransportURI + Play sont envoyées au renderer
  3. Le renderer peut passer brièvement par un état STOPPED pendant l'initialisation de la nouvelle piste
  4. Le watcher (polling toutes les 500ms) détecte cet état STOPPED
  5. Comme la lecture était lancée depuis la queue (PlaybackSource::FromQueue), l'auto-advance se déclenche et passe à la piste suivante

Détail technique

La logique d'auto-advance dans handle_state_change vérifie si is_playing_from_queue() retourne true pour décider de passer à la piste suivante quand l'état STOPPED est détecté. Cependant, il n'y avait aucun mécanisme pour distinguer :

  • Un état STOPPED transitoire pendant l'initialisation d'une nouvelle piste
  • Un état STOPPED réel indiquant la fin de lecture d'une piste

Solution implémentée

Ajout d'un flag has_played_since_track_start dans MusicRendererState qui permet de tracker si l'état PLAYING a été observé depuis le dernier démarrage de piste.

Logique du flag

  1. Quand on démarre une nouvelle piste (play_from_index, play_from_queue, play_next_from_queue, play_current_from_queue) : le flag est remis à false

  2. Quand le watcher détecte l'état PLAYING : le flag passe à true

  3. Quand le watcher détecte l'état STOPPED :

    • Si has_played_since_track_start == true : c'est une vraie fin de piste → auto-advance autorisé
    • Si has_played_since_track_start == false : c'est un état transitoire pendant l'initialisation → auto-advance bloqué
  4. Quand stop() est appelé : le flag est remis à false

Fichiers modifiés

pmocontrol/src/music_renderer/musicrenderer.rs

  1. Ajout du champ has_played_since_track_start dans MusicRendererState :
struct MusicRendererState {
    // ...
    /// Flag indicating that a PLAYING state has been observed since the last track start.
    /// This prevents auto-advance on transient STOPPED states during track initialization.
    /// Auto-advance is only allowed when this flag is true.
    has_played_since_track_start: bool,
}
  1. Ajout des méthodes de gestion du flag :

    • set_has_played_flag() : met le flag à true
    • clear_has_played_flag() : met le flag à false (publique)
    • check_and_clear_has_played_flag() : vérifie et remet à false
  2. Modification de handle_state_change :

    • Sur PLAYING : appelle set_has_played_flag()
    • Sur STOPPED avec is_playing_from_queue() : vérifie check_and_clear_has_played_flag() avant d'auto-advance
  3. Modification des méthodes de démarrage de lecture :

    • play_current_from_queue()
    • play_next_from_queue()
    • play_from_index()
    • play_from_queue()
    • stop()

    Toutes appellent clear_has_played_flag() pour réinitialiser le flag.

Tests effectués

  • Clic sur différents items de la queue : la piste sélectionnée est bien jouée sans saut
  • Lecture normale jusqu'à la fin d'une piste : l'auto-advance vers la piste suivante fonctionne correctement
  • Arrêt manuel (stop) : pas d'auto-advance intempestif