From 90af7cf772d108dd3fd54fb8c93f77727f32e535 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sat, 17 Jan 2026 09:17:05 +0100 Subject: [PATCH] Corrige le transfert de queue entre renderers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le transfert de la queue de lecture entre renderers était corrompu car le binding de playlist était réinitialisé, effaçant les items transférés. Correction en transférant directement le binding sans déclencher de rafraîchissement, préservant ainsi le current_index et la position de lecture. - Fichiers modifiés : pmocontrol/src/control_point.rs - Version mise à jour : 0.3.7 --- Blackboard/Done/bug_transfer_queue.md | 90 ++++++++++++++++++++++++ Blackboard/Report/bug_transfer_queue.md | 91 +++++++++++++++++++++++++ Cargo.lock | 2 +- PMOMusic/Cargo.toml | 2 +- pmocontrol/src/control_point.rs | 22 ++++-- version.txt | 2 +- 6 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 Blackboard/Done/bug_transfer_queue.md create mode 100644 Blackboard/Report/bug_transfer_queue.md diff --git a/Blackboard/Done/bug_transfer_queue.md b/Blackboard/Done/bug_transfer_queue.md new file mode 100644 index 00000000..f03404d9 --- /dev/null +++ b/Blackboard/Done/bug_transfer_queue.md @@ -0,0 +1,90 @@ +# Bug transfert de queue entre renderers - RESOLU + +## Tâche originale + +**Crate concernée** : pmocontrol + +**Problème rapporté** : Le transfert de la queue de lecture d'un renderer vers un autre ne semblait plus fonctionnel, avec des comportements erratiques. Le problème était potentiellement lié aux derniers changements sur le passage du renderer à un état stateless. L'interface utilisateur mettait également un certain temps à réagir. + +--- + +## Synthèse de la résolution + +### Cause racine + +Dans la fonction `transfer_queue()` (`control_point.rs:1192-1291`), lorsqu'un binding de playlist existait sur le renderer source, le code appelait `attach_queue_to_playlist()` sur la destination **après** avoir rempli la queue avec les items sources. + +Le problème : `attach_queue_to_playlist_internal()` effectue : +1. `clear_for_playlist_attach()` - efface la queue du renderer +2. `clear_queue()` - efface la queue locale +3. `refresh_attached_queue_for()` - browse le serveur et **remplace** la queue + +Cela **écrasait complètement** les items transférés avec `replace_queue()`, perdant le `current_index` et la position de lecture. + +### Séquence problématique (avant correction) + +``` +1. source_snapshot = get_renderer_queue_snapshot(source) // items + current_index +2. clear_renderer_queue(dest) +3. dest.replace_queue(source_snapshot.items, current_index) // Queue remplie OK +4. attach_queue_to_playlist(dest, server, container) // ÉCRASE TOUT + └─> clear_for_playlist_attach() + └─> clear_queue() + └─> refresh_attached_queue_for() → browse serveur → replace queue +5. play() sur destination avec mauvaise queue +``` + +### Solution + +Remplacement de l'appel `attach_queue_to_playlist()` par un transfert direct du binding sans déclencher de refresh : + +```rust +// AVANT (problématique) +if let Some((server_id, container_id, _)) = source_binding { + self.attach_queue_to_playlist(dest_renderer_id, server_id, container_id)?; +} + +// APRÈS (corrigé) +if let Some((server_id, container_id, has_seen_update)) = source_binding.clone() { + let binding = PlaylistBinding { + server_id, + container_id, + has_seen_update, + pending_refresh: false, // Pas de refresh immédiat + auto_play_on_refresh: false, + }; + dest_renderer.set_playlist_binding(Some(binding)); +} +``` + +### Séquence corrigée + +``` +1. source_snapshot = get_renderer_queue_snapshot(source) +2. clear_renderer_queue(dest) +3. dest.replace_queue(source_snapshot.items, current_index) // Queue remplie OK +4. dest.set_playlist_binding(binding avec pending_refresh=false) // Binding transféré OK +5. play() sur destination avec bonne queue OK +``` + +### Analyse des événements + +L'analyse a confirmé que les émissions d'événements étaient correctes : + +| Méthode | Événement émis | +|---------|----------------| +| `replace_queue()` | `QueueUpdated` | +| `enqueue_items()` | `QueueUpdated` | +| `clear_queue()` | `QueueUpdated` | +| `set_playlist_binding()` | `BindingChanged` | +| `clear_playlist_binding()` | `BindingChanged` | + +Le problème de lenteur UI était lié au fait que la queue était écrasée puis re-remplie, causant plusieurs événements successifs et une confusion dans l'état affiché. + +### Fichier modifié + +- `pmocontrol/src/control_point.rs` : Modification de `transfer_queue()` lignes 1223-1244 + +--- + +**Statut** : Corrigé et testé diff --git a/Blackboard/Report/bug_transfer_queue.md b/Blackboard/Report/bug_transfer_queue.md new file mode 100644 index 00000000..6a3a040a --- /dev/null +++ b/Blackboard/Report/bug_transfer_queue.md @@ -0,0 +1,91 @@ +# Bug: Transfert de Queue entre Renderers + +**Date**: 2026-01-17 +**Fichier principal**: `pmocontrol/src/control_point.rs` +**Fonction affectée**: `transfer_queue()` + +## Symptôme + +Le transfert de la queue de lecture d'un renderer vers un autre ne fonctionnait plus correctement, avec des comportements erratiques. La queue transférée était écrasée et le current_index perdu. + +## Cause Racine + +Dans `transfer_queue()` (lignes 1192-1291), lorsqu'un binding de playlist existait sur le renderer source, le code appelait `attach_queue_to_playlist()` sur la destination après avoir rempli la queue. + +**Problème**: `attach_queue_to_playlist_internal()` effectue les opérations suivantes: +1. `clear_for_playlist_attach()` - efface la queue du renderer +2. `clear_queue()` - efface la queue locale +3. `refresh_attached_queue_for()` - browse le serveur et remplace la queue + +Cela **écrasait complètement** les items qu'on venait de transférer avec `replace_queue()`. + +### Séquence problématique (avant correction) + +``` +1. source_snapshot = get_renderer_queue_snapshot(source) // items + current_index +2. clear_renderer_queue(dest) +3. dest.replace_queue(source_snapshot.items, current_index) // Queue remplie ✓ +4. attach_queue_to_playlist(dest, server, container) // ÉCRASE TOUT ✗ + └─> clear_for_playlist_attach() + └─> clear_queue() + └─> refresh_attached_queue_for() → browse serveur → replace queue +5. play() sur destination avec mauvaise queue +``` + +## Correction Appliquée + +Remplacement de l'appel `attach_queue_to_playlist()` par un transfert direct du binding sans déclencher de refresh: + +```rust +// AVANT (problématique) +if let Some((server_id, container_id, _)) = source_binding { + self.attach_queue_to_playlist(dest_renderer_id, server_id, container_id)?; +} + +// APRÈS (corrigé) +if let Some((server_id, container_id, has_seen_update)) = source_binding.clone() { + let binding = PlaylistBinding { + server_id, + container_id, + has_seen_update, + pending_refresh: false, // Pas de refresh immédiat + auto_play_on_refresh: false, + }; + dest_renderer.set_playlist_binding(Some(binding)); +} +``` + +### Séquence corrigée + +``` +1. source_snapshot = get_renderer_queue_snapshot(source) +2. clear_renderer_queue(dest) +3. dest.replace_queue(source_snapshot.items, current_index) // Queue remplie ✓ +4. dest.set_playlist_binding(binding avec pending_refresh=false) // Binding transféré ✓ +5. play() sur destination avec bonne queue ✓ +``` + +## Événements + +L'analyse a également confirmé que les émissions d'événements sont correctes: + +| Méthode | Événement émis | +|---------|----------------| +| `replace_queue()` | `QueueUpdated` ✓ | +| `enqueue_items()` | `QueueUpdated` ✓ | +| `clear_queue()` | `QueueUpdated` ✓ | +| `set_playlist_binding()` | `BindingChanged` ✓ | +| `clear_playlist_binding()` | `BindingChanged` ✓ | + +Le problème de lenteur UI mentionné était probablement lié au fait que la queue était écrasée puis re-remplie, causant plusieurs événements successifs et une confusion dans l'état affiché. + +## Impact + +- Transfert de queue fonctionnel à nouveau +- Préservation du current_index lors du transfert +- Binding de playlist correctement transféré sans perte de synchronisation +- UI réactive car un seul cycle d'événements cohérent + +## Fichiers Modifiés + +- `pmocontrol/src/control_point.rs`: Modification de `transfer_queue()` lignes 1223-1244 diff --git a/Cargo.lock b/Cargo.lock index b6a884ce..0fdd53a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "PMOMusic" -version = "0.3.6" +version = "0.3.7" dependencies = [ "axum 0.8.7", "console-subscriber", diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index 65750eae..88936a2b 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "PMOMusic" -version = "0.3.6" +version = "0.3.7" edition = "2024" [dependencies] diff --git a/pmocontrol/src/control_point.rs b/pmocontrol/src/control_point.rs index 8b879c58..adca73c0 100644 --- a/pmocontrol/src/control_point.rs +++ b/pmocontrol/src/control_point.rs @@ -1221,15 +1221,29 @@ impl ControlPoint { dest_renderer .replace_queue(source_snapshot.items.clone(), source_snapshot.current_index)?; - // 4. Recreate playlist binding on destination if source had one - if let Some((server_id, container_id, _)) = source_binding { + // 4. Transfer playlist binding to destination if source had one + // IMPORTANT: We transfer the binding directly WITHOUT triggering a refresh. + // The queue items have already been copied in step 3, so we just need to + // set the binding so future server updates will sync to the destination. + // Using attach_queue_to_playlist() would clear the queue and re-fetch from server, + // losing the current playback position. + if let Some((server_id, container_id, has_seen_update)) = source_binding.clone() { tracing::debug!( dest = dest_renderer_id.0.as_str(), server = server_id.0.as_str(), container = container_id.as_str(), - "Recreating playlist binding on destination renderer" + "Transferring playlist binding to destination renderer (without refresh)" ); - self.attach_queue_to_playlist(dest_renderer_id, server_id, container_id)?; + + // Create binding with pending_refresh=false to avoid re-fetching + let binding = PlaylistBinding { + server_id, + container_id, + has_seen_update, + pending_refresh: false, + auto_play_on_refresh: false, + }; + dest_renderer.set_playlist_binding(Some(binding)); } // 5. Stop playback on source renderer diff --git a/version.txt b/version.txt index 449d7e73..0f826853 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.6 +0.3.7