From a753fbf910f8a35a2a7af369866e1a2c208e61a0 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Mon, 22 Dec 2025 10:26:34 +0100 Subject: [PATCH] =?UTF-8?q?Debug,=20mise=20=C3=A0=20jour=20des=20playlists?= =?UTF-8?q?=20Open=20Home.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pmocontrol/src/control_point.rs | 43 ++++++++++--------- .../src/control_point/openhome_queue.rs | 26 ++++++++--- pmocontrol/src/openhome_client.rs | 6 +-- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/pmocontrol/src/control_point.rs b/pmocontrol/src/control_point.rs index 4d860ddc..5fb7a75a 100644 --- a/pmocontrol/src/control_point.rs +++ b/pmocontrol/src/control_point.rs @@ -435,6 +435,7 @@ impl ControlPoint { Arc::clone(&runtime), event_bus.clone(), oh_event_tx, + Arc::clone(&playlist_bindings), )?; // Worker thread to process MediaServerEvent and trigger queue refreshes @@ -2669,6 +2670,7 @@ fn spawn_openhome_event_runtime( runtime: Arc, event_bus: RendererEventBus, event_tx: Sender, + playlist_bindings: Arc>>, ) -> io::Result<()> { let listener = TcpListener::bind("0.0.0.0:0")?; let listener_addr = listener @@ -2689,6 +2691,7 @@ fn spawn_openhome_event_runtime( notify_rx, event_tx, listener_addr.port(), + playlist_bindings, ); thread::Builder::new() @@ -2707,6 +2710,7 @@ struct OpenHomeEventRuntime { http_timeout: Duration, subscriptions: HashMap, path_index: HashMap, + playlist_bindings: Arc>>, } impl OpenHomeEventRuntime { @@ -2717,6 +2721,7 @@ impl OpenHomeEventRuntime { notify_rx: Receiver, event_tx: Sender, listener_port: u16, + playlist_bindings: Arc>>, ) -> Self { Self { registry, @@ -2728,6 +2733,7 @@ impl OpenHomeEventRuntime { http_timeout: Duration::from_secs(5), subscriptions: HashMap::new(), path_index: HashMap::new(), + playlist_bindings, } } @@ -2889,30 +2895,27 @@ impl OpenHomeEventRuntime { .any(|(name, _)| is_id_array_property(name)) { if self.runtime.uses_openhome_playlist(&entry.renderer.id) { - self.runtime - .invalidate_openhome_cache(&entry.renderer.id); + // Check if this renderer has an active playlist binding + // If it does, skip sync because refresh_attached_queue_for() handles it + let has_active_binding = { + let bindings = self.playlist_bindings.lock().unwrap(); + bindings.contains_key(&entry.renderer.id) + }; - match openhome_renderer_from_registry(&self.registry, &entry.renderer.id) { - Ok(renderer) => match renderer.openhome_playlist_len() { - Ok(queue_len) => { - self.event_bus.broadcast(RendererEvent::QueueUpdated { - id: entry.renderer.id.clone(), - queue_length: queue_len, - }); - } - Err(err) => { - warn!( - renderer = entry.renderer.friendly_name.as_str(), - error = %err, - "Failed to read OpenHome playlist length after IdArray event" - ); - } - }, - Err(err) => { + if !has_active_binding { + // Synchronize the local queue with the renderer's playlist state + // This ensures our local mirror stays in sync when the renderer + // playlist changes from other control points or manual edits + if let Err(err) = sync_openhome_playlist( + &self.registry, + &self.runtime, + &self.event_bus, + &entry.renderer.id, + ) { warn!( renderer = entry.renderer.friendly_name.as_str(), error = %err, - "Failed to build OpenHome renderer after IdArray event" + "Failed to sync OpenHome playlist after IdArray event" ); } } diff --git a/pmocontrol/src/control_point/openhome_queue.rs b/pmocontrol/src/control_point/openhome_queue.rs index 9d19e45d..73457fd4 100644 --- a/pmocontrol/src/control_point/openhome_queue.rs +++ b/pmocontrol/src/control_point/openhome_queue.rs @@ -445,12 +445,26 @@ impl QueueBackend for OpenHomeQueue { "LCS computed: minimizing OpenHome playlist operations" ); - for idx in (0..self.track_ids.len()).rev() { - if !keep_current[idx] { - let track_id = self.track_ids[idx]; - self.playlist.delete_id(track_id)?; - self.track_ids.remove(idx); - self.items.remove(idx); + // If we're replacing everything (keep=0), use delete_all() instead of + // individual delete_id() calls. This is much more robust for live playlists + // where track IDs can become invalid between refresh and deletion. + if items_to_keep == 0 && items_to_delete > 0 { + debug!( + renderer = self.renderer_id.0.as_str(), + "Using delete_all() for complete replacement (more robust for live playlists)" + ); + self.playlist.delete_all()?; + self.track_ids.clear(); + self.items.clear(); + } else { + // Selective deletion when keeping some items + for idx in (0..self.track_ids.len()).rev() { + if !keep_current[idx] { + let track_id = self.track_ids[idx]; + self.playlist.delete_id(track_id)?; + self.track_ids.remove(idx); + self.items.remove(idx); + } } } diff --git a/pmocontrol/src/openhome_client.rs b/pmocontrol/src/openhome_client.rs index 324a6f2e..c698758e 100644 --- a/pmocontrol/src/openhome_client.rs +++ b/pmocontrol/src/openhome_client.rs @@ -276,7 +276,7 @@ impl OhPlaylistClient { } } - info!( + debug!( control_url = self.control_url.as_str(), track_count = entries.len(), expected_count = ids.len(), @@ -562,7 +562,7 @@ impl OhProductClient { let sources = self.source_xml()?; // Log all available sources for diagnostics - info!( + debug!( control_url = self.control_url.as_str(), source_count = sources.len(), "OpenHome Product sources available" @@ -594,7 +594,7 @@ impl OhProductClient { // Log current source state let current_source = sources.get(current_index as usize); - info!( + debug!( control_url = self.control_url.as_str(), current_index, current_source_name = current_source.map(|s| s.name.as_str()).unwrap_or("unknown"),