From 4b793cec59aa64222007297e4b528ac9a5c5ddcb Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 12 Apr 2026 19:20:43 +0200 Subject: [PATCH] refactor: replace unwrap() with expect for mutex locks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace all `lock().unwrap()` calls on Mutex guards with explicit error messages using `.expect("... mutex poisoned")`. This improves robustness by providing clear diagnostics when a thread panics while holding the lock, preventing silent failures. Affected modules: events.rs (Renderer/MediaServer), all renderer backends, queue backend/implementation files. Also adds documentation to marker traits (HasQueue、 HasContinuousStream) and clarifies error handling in watcher loop with panic catching. --- pmocontrol/src/music_renderer/arylic_tcp.rs | 28 ++- pmocontrol/src/music_renderer/capabilities.rs | 15 +- .../src/music_renderer/chromecast_renderer.rs | 28 ++- .../src/music_renderer/linkplay_renderer.rs | 28 ++- pmocontrol/src/music_renderer/mod.rs | 7 +- .../src/music_renderer/musicrenderer.rs | 198 +++--------------- .../src/music_renderer/openhome_renderer.rs | 23 +- .../src/music_renderer/stream_detection.rs | 6 +- .../src/music_renderer/upnp_renderer.rs | 74 ++++--- pmocontrol/src/queue/backend.rs | 6 +- pmocontrol/src/queue/mod.rs | 2 +- 11 files changed, 152 insertions(+), 263 deletions(-) diff --git a/pmocontrol/src/music_renderer/arylic_tcp.rs b/pmocontrol/src/music_renderer/arylic_tcp.rs index 29fdf180..a9167029 100644 --- a/pmocontrol/src/music_renderer/arylic_tcp.rs +++ b/pmocontrol/src/music_renderer/arylic_tcp.rs @@ -12,13 +12,14 @@ use crate::errors::ControlPointError; use crate::linkplay_client::extract_linkplay_host; use crate::model::{PlaybackState, RendererInfo}; use crate::music_renderer::capabilities::{ - PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend, - TransportControl, VolumeControl, + HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, + QueueTransportControl, TransportControl, VolumeControl, }; use crate::music_renderer::musicrenderer::MusicRendererBackend; use crate::music_renderer::time_utils::{format_hhmmss, ms_to_seconds, parse_hhmmss_strict}; +use crate::music_renderer::HasQueue; use crate::music_renderer::RendererFromMediaRendererInfo; -use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; +use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; use crate::DeviceIdentity; /// Raw response from Arylic MCU+PINFGET command @@ -300,13 +301,12 @@ impl PlaybackPosition for ArylicTcpRenderer { } } -impl RendererBackend for ArylicTcpRenderer { - fn queue(&self) -> &Arc> { - &self.queue - } -} impl QueueTransportControl for ArylicTcpRenderer { + fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> { + self.play_uri(&item.uri, "") + } + fn play_from_queue(&self) -> Result<(), ControlPointError> { let mut queue = self.queue.lock().unwrap(); @@ -326,10 +326,12 @@ impl QueueTransportControl for ArylicTcpRenderer { .get_item(current_index)? .ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?; - let uri = item.uri.clone(); drop(queue); - self.play_uri(&uri, "") + let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri); + *self.continuous_stream.lock().unwrap() = is_stream; + + self.play_item(&item) } fn play_next(&self) -> Result<(), ControlPointError> { @@ -370,6 +372,12 @@ impl HasQueue for ArylicTcpRenderer { } } +impl HasContinuousStream for ArylicTcpRenderer { + fn continuous_stream(&self) -> &Arc> { + &self.continuous_stream + } +} + #[derive(Debug)] struct ArylicPlaybackInfo { status_raw: String, diff --git a/pmocontrol/src/music_renderer/capabilities.rs b/pmocontrol/src/music_renderer/capabilities.rs index 1e760681..c164c052 100644 --- a/pmocontrol/src/music_renderer/capabilities.rs +++ b/pmocontrol/src/music_renderer/capabilities.rs @@ -1,22 +1,19 @@ // pmocontrol/src/capabilities.rs use std::sync::{Arc, Mutex}; -use crate::queue::{HasQueue, MusicQueue}; +use crate::queue::{MusicQueue, QueueBackend}; use crate::{errors::ControlPointError, model::PlaybackState, PlaybackItem}; +/// Trait for types that have access to a MusicQueue. +pub trait HasQueue { + fn queue(&self) -> &Arc>; +} + /// Trait for types that track whether they're playing a continuous stream. pub trait HasContinuousStream { fn continuous_stream(&self) -> &Arc>; } -/// Backend-specific operations for renderers. -/// -/// This trait provides access to backend-specific resources like the queue. -pub trait RendererBackend { - /// Returns a reference to the queue associated with this backend. - fn queue(&self) -> &Arc>; -} - /// Queue-aware transport control operations. /// /// These operations combine queue management with transport control, diff --git a/pmocontrol/src/music_renderer/chromecast_renderer.rs b/pmocontrol/src/music_renderer/chromecast_renderer.rs index 39c1f5bd..a0754a52 100644 --- a/pmocontrol/src/music_renderer/chromecast_renderer.rs +++ b/pmocontrol/src/music_renderer/chromecast_renderer.rs @@ -23,13 +23,14 @@ use crate::discovery::chromecast_discovery::{ use crate::errors::ControlPointError; use crate::model::{PlaybackState, RendererInfo}; use crate::music_renderer::capabilities::{ - PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend, - TransportControl, VolumeControl, + HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, + QueueTransportControl, TransportControl, VolumeControl, }; use crate::music_renderer::musicrenderer::MusicRendererBackend; use crate::music_renderer::time_utils::{format_hhmmss_f64, parse_hhmmss_strict}; +use crate::music_renderer::HasQueue; use crate::music_renderer::RendererFromMediaRendererInfo; -use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; +use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; use crate::DeviceIdentity; use rust_cast::{ @@ -799,13 +800,12 @@ impl VolumeControl for ChromecastRenderer { } } -impl RendererBackend for ChromecastRenderer { - fn queue(&self) -> &Arc> { - &self.queue - } -} impl QueueTransportControl for ChromecastRenderer { + fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> { + self.play_uri(&item.uri, "") + } + fn play_from_queue(&self) -> Result<(), ControlPointError> { let mut queue = self.queue.lock().unwrap(); @@ -825,10 +825,12 @@ impl QueueTransportControl for ChromecastRenderer { .get_item(current_index)? .ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?; - let uri = item.uri.clone(); drop(queue); - self.play_uri(&uri, "") + let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri); + *self.continuous_stream.lock().unwrap() = is_stream; + + self.play_item(&item) } fn play_next(&self) -> Result<(), ControlPointError> { @@ -868,3 +870,9 @@ impl HasQueue for ChromecastRenderer { &self.queue } } + +impl HasContinuousStream for ChromecastRenderer { + fn continuous_stream(&self) -> &Arc> { + &self.continuous_stream + } +} diff --git a/pmocontrol/src/music_renderer/linkplay_renderer.rs b/pmocontrol/src/music_renderer/linkplay_renderer.rs index 4ce747b2..134c89f2 100644 --- a/pmocontrol/src/music_renderer/linkplay_renderer.rs +++ b/pmocontrol/src/music_renderer/linkplay_renderer.rs @@ -10,13 +10,14 @@ use crate::linkplay_client::{ }; use crate::model::{PlaybackState, RendererInfo}; use crate::music_renderer::capabilities::{ - PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend, - TransportControl, VolumeControl, + HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, + QueueTransportControl, TransportControl, VolumeControl, }; use crate::music_renderer::musicrenderer::MusicRendererBackend; use crate::music_renderer::time_utils::parse_hhmmss_strict; +use crate::music_renderer::HasQueue; use crate::music_renderer::RendererFromMediaRendererInfo; -use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; +use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend}; use crate::DeviceIdentity; const DEFAULT_HTTP_TIMEOUT_SECS: u64 = 3; @@ -178,13 +179,12 @@ impl PlaybackPosition for LinkPlayRenderer { } } -impl RendererBackend for LinkPlayRenderer { - fn queue(&self) -> &Arc> { - &self.queue - } -} impl QueueTransportControl for LinkPlayRenderer { + fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> { + self.play_uri(&item.uri, "") + } + fn play_from_queue(&self) -> Result<(), ControlPointError> { let mut queue = self.queue.lock().unwrap(); @@ -204,10 +204,12 @@ impl QueueTransportControl for LinkPlayRenderer { .get_item(current_index)? .ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?; - let uri = item.uri.clone(); drop(queue); - self.play_uri(&uri, "") + let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri); + *self.continuous_stream.lock().unwrap() = is_stream; + + self.play_item(&item) } fn play_next(&self) -> Result<(), ControlPointError> { @@ -247,3 +249,9 @@ impl HasQueue for LinkPlayRenderer { &self.queue } } + +impl HasContinuousStream for LinkPlayRenderer { + fn continuous_stream(&self) -> &Arc> { + &self.continuous_stream + } +} diff --git a/pmocontrol/src/music_renderer/mod.rs b/pmocontrol/src/music_renderer/mod.rs index 916496ff..228c2a8b 100644 --- a/pmocontrol/src/music_renderer/mod.rs +++ b/pmocontrol/src/music_renderer/mod.rs @@ -6,7 +6,7 @@ mod upnp_renderer; mod openhome; mod openhome_renderer; -mod capabilities; +pub mod capabilities; mod chromecast_renderer; mod musicrenderer; @@ -18,13 +18,14 @@ pub mod watcher; use std::sync::{Arc, Mutex}; pub use crate::music_renderer::capabilities::{ - PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, + HasContinuousStream, HasQueue, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, + QueueTransportControl, TransportControl, VolumeControl, }; pub use crate::music_renderer::musicrenderer::{MusicRenderer, PlaylistBinding}; pub use crate::music_renderer::sleep_timer::SleepTimer; pub use crate::music_renderer::stream_detection::is_continuous_stream_url; use crate::{ - RendererInfo, errors::ControlPointError, music_renderer::musicrenderer::MusicRendererBackend, + errors::ControlPointError, music_renderer::musicrenderer::MusicRendererBackend, RendererInfo, }; pub trait RendererFromMediaRendererInfo { diff --git a/pmocontrol/src/music_renderer/musicrenderer.rs b/pmocontrol/src/music_renderer/musicrenderer.rs index c5490710..1da5e749 100644 --- a/pmocontrol/src/music_renderer/musicrenderer.rs +++ b/pmocontrol/src/music_renderer/musicrenderer.rs @@ -19,10 +19,6 @@ use crate::events::RendererEventBus; use crate::model::RendererEvent; use crate::model::{PlaybackSource, PlaybackState, RendererInfo, RendererProtocol, TrackMetadata}; use crate::music_renderer::arylic_tcp::ArylicTcpRenderer; -use crate::music_renderer::capabilities::{ - PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend, - TransportControl, VolumeControl, -}; use crate::music_renderer::chromecast_renderer::ChromecastRenderer; use crate::music_renderer::linkplay_renderer::LinkPlayRenderer; use crate::music_renderer::openhome_renderer::OpenHomeRenderer; @@ -33,6 +29,10 @@ use crate::music_renderer::watcher::{ WatchedState, }; use crate::music_renderer::RendererFromMediaRendererInfo; +use crate::music_renderer::{ + HasContinuousStream, HasQueue, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, + QueueTransportControl, TransportControl, VolumeControl, +}; use crate::online::DeviceConnectionState; use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; use crate::{DeviceId, DeviceIdentity, DeviceOnline}; @@ -998,7 +998,7 @@ impl MusicRenderer { /// Get a clone of the queue Arc (for async sync operations). pub fn queue(&self) -> Arc> { let backend = self.lock_backend_for("queue"); - crate::music_renderer::capabilities::RendererBackend::queue(&*backend).clone() + crate::music_renderer::capabilities::HasQueue::queue(&*backend).clone() } /// Get the current queue item without advancing. @@ -2274,7 +2274,7 @@ impl PlaybackPosition for MusicRendererBackend { } } -impl RendererBackend for MusicRendererBackend { +impl HasQueue for MusicRendererBackend { fn queue(&self) -> &Arc> { match self { MusicRendererBackend::Upnp(r) => r.queue(), @@ -2287,7 +2287,31 @@ impl RendererBackend for MusicRendererBackend { } } +impl HasContinuousStream for MusicRendererBackend { + fn continuous_stream(&self) -> &Arc> { + match self { + MusicRendererBackend::Upnp(r) => r.continuous_stream(), + MusicRendererBackend::OpenHome(r) => r.continuous_stream(), + MusicRendererBackend::LinkPlay(r) => r.continuous_stream(), + MusicRendererBackend::ArylicTcp(r) => r.continuous_stream(), + MusicRendererBackend::Chromecast(cc) => cc.continuous_stream(), + MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.continuous_stream(), + } + } +} + impl QueueTransportControl for MusicRendererBackend { + fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> { + match self { + MusicRendererBackend::Upnp(r) => r.play_item(item), + MusicRendererBackend::OpenHome(r) => r.play_item(item), + MusicRendererBackend::LinkPlay(r) => r.play_item(item), + MusicRendererBackend::ArylicTcp(r) => r.play_item(item), + MusicRendererBackend::Chromecast(cc) => cc.play_item(item), + MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.play_item(item), + } + } + fn play_from_queue(&self) -> Result<(), ControlPointError> { match self { MusicRendererBackend::Upnp(r) => r.play_from_queue(), @@ -2332,165 +2356,3 @@ impl QueueTransportControl for MusicRendererBackend { } } } - -impl QueueBackend for MusicRendererBackend { - fn len(&self) -> Result { - match self { - MusicRendererBackend::Upnp(r) => r.len(), - MusicRendererBackend::OpenHome(r) => r.len(), - MusicRendererBackend::LinkPlay(r) => r.len(), - MusicRendererBackend::ArylicTcp(r) => r.len(), - MusicRendererBackend::Chromecast(cc) => cc.len(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.len(), - } - } - - fn track_ids(&self) -> Result, ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.track_ids(), - MusicRendererBackend::OpenHome(r) => r.track_ids(), - MusicRendererBackend::LinkPlay(r) => r.track_ids(), - MusicRendererBackend::ArylicTcp(r) => r.track_ids(), - MusicRendererBackend::Chromecast(cc) => cc.track_ids(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.track_ids(), - } - } - - fn id_to_position(&self, id: u32) -> Result { - match self { - MusicRendererBackend::Upnp(r) => r.id_to_position(id), - MusicRendererBackend::OpenHome(r) => r.id_to_position(id), - MusicRendererBackend::LinkPlay(r) => r.id_to_position(id), - MusicRendererBackend::ArylicTcp(r) => r.id_to_position(id), - MusicRendererBackend::Chromecast(cc) => cc.id_to_position(id), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.id_to_position(id), - } - } - - fn position_to_id(&self, id: usize) -> Result { - match self { - MusicRendererBackend::Upnp(r) => r.position_to_id(id), - MusicRendererBackend::OpenHome(r) => r.position_to_id(id), - MusicRendererBackend::LinkPlay(r) => r.position_to_id(id), - MusicRendererBackend::ArylicTcp(r) => r.position_to_id(id), - MusicRendererBackend::Chromecast(cc) => cc.position_to_id(id), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.position_to_id(id), - } - } - - fn current_track(&self) -> Result, ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.current_track(), - MusicRendererBackend::OpenHome(r) => r.current_track(), - MusicRendererBackend::LinkPlay(r) => r.current_track(), - MusicRendererBackend::ArylicTcp(r) => r.current_track(), - MusicRendererBackend::Chromecast(cc) => cc.current_track(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.current_track(), - } - } - - fn current_index(&self) -> Result, ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.current_index(), - MusicRendererBackend::OpenHome(r) => r.current_index(), - MusicRendererBackend::LinkPlay(r) => r.current_index(), - MusicRendererBackend::ArylicTcp(r) => r.current_index(), - MusicRendererBackend::Chromecast(cc) => cc.current_index(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.current_index(), - } - } - - fn queue_snapshot(&self) -> Result { - match self { - MusicRendererBackend::Upnp(r) => r.queue_snapshot(), - MusicRendererBackend::OpenHome(r) => r.queue_snapshot(), - MusicRendererBackend::LinkPlay(r) => r.queue_snapshot(), - MusicRendererBackend::ArylicTcp(r) => r.queue_snapshot(), - MusicRendererBackend::Chromecast(cc) => cc.queue_snapshot(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.queue_snapshot(), - } - } - - fn set_index(&mut self, index: Option) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.set_index(index), - MusicRendererBackend::OpenHome(r) => r.set_index(index), - MusicRendererBackend::LinkPlay(r) => r.set_index(index), - MusicRendererBackend::ArylicTcp(r) => r.set_index(index), - MusicRendererBackend::Chromecast(cc) => cc.set_index(index), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.set_index(index), - } - } - - fn replace_queue( - &mut self, - items: Vec, - current_index: Option, - ) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.replace_queue(items, current_index), - MusicRendererBackend::OpenHome(r) => r.replace_queue(items, current_index), - MusicRendererBackend::LinkPlay(r) => r.replace_queue(items, current_index), - MusicRendererBackend::ArylicTcp(r) => r.replace_queue(items, current_index), - MusicRendererBackend::Chromecast(cc) => cc.replace_queue(items, current_index), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => { - upnp.replace_queue(items, current_index) - } - } - } - - fn sync_queue( - &mut self, - items: Vec, - cancel_token: &Arc, - on_ready: Option>, - ) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.sync_queue(items, cancel_token, on_ready), - MusicRendererBackend::OpenHome(r) => r.sync_queue(items, cancel_token, on_ready), - MusicRendererBackend::LinkPlay(r) => r.sync_queue(items, cancel_token, on_ready), - MusicRendererBackend::ArylicTcp(r) => r.sync_queue(items, cancel_token, on_ready), - MusicRendererBackend::Chromecast(cc) => cc.sync_queue(items, cancel_token, on_ready), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => { - upnp.sync_queue(items, cancel_token, on_ready) - } - } - } - - fn get_item(&self, index: usize) -> Result, ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.get_item(index), - MusicRendererBackend::OpenHome(r) => r.get_item(index), - MusicRendererBackend::LinkPlay(r) => r.get_item(index), - MusicRendererBackend::ArylicTcp(r) => r.get_item(index), - MusicRendererBackend::Chromecast(cc) => cc.get_item(index), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.get_item(index), - } - } - - fn replace_item(&mut self, index: usize, item: PlaybackItem) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.replace_item(index, item), - MusicRendererBackend::OpenHome(r) => r.replace_item(index, item), - MusicRendererBackend::LinkPlay(r) => r.replace_item(index, item), - MusicRendererBackend::ArylicTcp(r) => r.replace_item(index, item), - MusicRendererBackend::Chromecast(cc) => cc.replace_item(index, item), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.replace_item(index, item), - } - } - - fn enqueue_items( - &mut self, - items: Vec, - mode: EnqueueMode, - ) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.enqueue_items(items, mode), - MusicRendererBackend::OpenHome(r) => r.enqueue_items(items, mode), - MusicRendererBackend::LinkPlay(r) => r.enqueue_items(items, mode), - MusicRendererBackend::ArylicTcp(r) => r.enqueue_items(items, mode), - MusicRendererBackend::Chromecast(cc) => cc.enqueue_items(items, mode), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.enqueue_items(items, mode), - } - } -} diff --git a/pmocontrol/src/music_renderer/openhome_renderer.rs b/pmocontrol/src/music_renderer/openhome_renderer.rs index d0dde366..46f7fca8 100644 --- a/pmocontrol/src/music_renderer/openhome_renderer.rs +++ b/pmocontrol/src/music_renderer/openhome_renderer.rs @@ -2,8 +2,8 @@ use std::sync::{atomic::AtomicBool, Arc, Mutex}; use std::time::SystemTime; use crate::music_renderer::capabilities::{ - PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend, - TransportControl, VolumeControl, + HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, + QueueTransportControl, TransportControl, VolumeControl, }; use crate::music_renderer::time_utils::{format_hhmmss_u32, parse_time_flexible}; use crate::DeviceIdentity; @@ -15,8 +15,9 @@ use crate::music_renderer::openhome::{ build_info_client, build_playlist_client, build_product_client, build_radio_client, build_time_client, build_volume_client, }; +use crate::music_renderer::HasQueue; use crate::music_renderer::RendererFromMediaRendererInfo; -use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; +use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; use crate::upnp_clients::{ OhInfoClient, OhPlaylistClient, OhProductClient, OhRadioClient, OhTimeClient, OhVolumeClient, OPENHOME_PLAYLIST_HEAD_ID, @@ -266,11 +267,6 @@ impl RendererFromMediaRendererInfo for OpenHomeRenderer { } } -impl RendererBackend for OpenHomeRenderer { - fn queue(&self) -> &Arc> { - &self.queue - } -} impl TransportControl for OpenHomeRenderer { fn play_uri(&self, uri: &str, meta: &str) -> Result<(), ControlPointError> { @@ -529,6 +525,11 @@ pub(crate) fn map_openhome_state(raw: &str) -> PlaybackState { } impl QueueTransportControl for OpenHomeRenderer { + fn play_item(&self, _item: &PlaybackItem) -> Result<(), ControlPointError> { + let playlist = self.playlist_client_for("play_item")?; + playlist.play() + } + fn play_from_queue(&self) -> Result<(), ControlPointError> { { let queue = self @@ -632,6 +633,12 @@ impl HasQueue for OpenHomeRenderer { } } +impl HasContinuousStream for OpenHomeRenderer { + fn continuous_stream(&self) -> &Arc> { + &self.continuous_stream + } +} + impl OpenHomeRenderer { pub fn replace_queue_with_background( &mut self, diff --git a/pmocontrol/src/music_renderer/stream_detection.rs b/pmocontrol/src/music_renderer/stream_detection.rs index cb0959a2..9cda7ff1 100644 --- a/pmocontrol/src/music_renderer/stream_detection.rs +++ b/pmocontrol/src/music_renderer/stream_detection.rs @@ -185,7 +185,11 @@ fn check_stream_headers(url: &str) -> Result { trace!( "Stream detection for {}: content-length={}, chunked={}, streaming_mime={}, is_stream={}", - url, has_content_length, is_chunked, is_streaming_mime, is_stream + url, + has_content_length, + is_chunked, + is_streaming_mime, + is_stream ); Ok(is_stream) diff --git a/pmocontrol/src/music_renderer/upnp_renderer.rs b/pmocontrol/src/music_renderer/upnp_renderer.rs index bf4098e8..408038f5 100644 --- a/pmocontrol/src/music_renderer/upnp_renderer.rs +++ b/pmocontrol/src/music_renderer/upnp_renderer.rs @@ -4,11 +4,12 @@ use crate::errors::ControlPointError; use crate::model::PlaybackState; use crate::music_renderer::capabilities::{ HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, - QueueTransportControl, RendererBackend, TransportControl, VolumeControl, + QueueTransportControl, TransportControl, VolumeControl, }; use crate::music_renderer::musicrenderer::{build_didl_lite_metadata, MusicRendererBackend}; +use crate::music_renderer::HasQueue; use crate::music_renderer::RendererFromMediaRendererInfo; -use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; +use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; use crate::upnp_clients::{ AvTransportClient, ConnectionInfo, ConnectionManagerClient, PositionInfo, ProtocolInfo, RenderingControlClient, @@ -174,17 +175,37 @@ impl RendererFromMediaRendererInfo for UpnpRenderer { } } -impl RendererBackend for UpnpRenderer { - fn queue(&self) -> &Arc> { - &self.queue - } -} impl QueueTransportControl for UpnpRenderer { + fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> { + let metadata = if let Some(ref track_metadata) = item.metadata { + build_didl_lite_metadata(track_metadata, &item.uri, &item.protocol_info) + } else { + format!( + r#"{}"#, + item.protocol_info, item.uri + ) + }; + + let duration = parse_didl_duration(&metadata); + if let Some(ref dur) = duration { + tracing::debug!("Caching duration from queue DIDL: {}", dur); + *self.cached_duration.lock().unwrap() = Some(dur.clone()); + } else { + tracing::debug!("No duration to cache from queue DIDL"); + *self.cached_duration.lock().unwrap() = None; + } + + let avt = self.avtransport()?; + avt.set_av_transport_uri(&item.uri, &metadata)?; + avt.play(0, "1")?; + + Ok(()) + } + fn play_from_queue(&self) -> Result<(), ControlPointError> { let mut queue = self.queue.lock().unwrap(); - // Get or initialize current index let current_index = match queue.current_index()? { Some(idx) => idx, None => { @@ -197,29 +218,15 @@ impl QueueTransportControl for UpnpRenderer { } }; - // Get the item let item = queue .get_item(current_index)? .ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?; drop(queue); - // Build metadata - handle optional TrackMetadata - let metadata = if let Some(ref track_metadata) = item.metadata { - build_didl_lite_metadata(track_metadata, &item.uri, &item.protocol_info) - } else { - // Fallback to minimal DIDL-Lite if no metadata - format!( - r#"{}"#, - item.protocol_info, item.uri - ) - }; - - // Détecte si l'URL est un flux continu en interrogeant le serveur HTTP let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri); *self.continuous_stream.lock().unwrap() = is_stream; - // Log current queue state for debugging let queue_state = { let queue = self.queue.lock().unwrap(); let idx = queue.current_index().unwrap_or(None); @@ -237,22 +244,7 @@ impl QueueTransportControl for UpnpRenderer { is_stream ); - // Parse et cache la durée du DIDL (fallback pour certains amplis) - let duration = parse_didl_duration(&metadata); - if let Some(ref dur) = duration { - tracing::debug!("Caching duration from queue DIDL: {}", dur); - *self.cached_duration.lock().unwrap() = Some(dur.clone()); - } else { - tracing::debug!("No duration to cache from queue DIDL"); - *self.cached_duration.lock().unwrap() = None; - } - - // UPNP: SetAVTransportURI + Play - let avt = self.avtransport()?; - avt.set_av_transport_uri(&item.uri, &metadata)?; - avt.play(0, "1")?; - - Ok(()) + self.play_item(&item) } fn play_next(&self) -> Result<(), ControlPointError> { @@ -313,6 +305,12 @@ impl HasQueue for UpnpRenderer { } } +impl HasContinuousStream for UpnpRenderer { + fn continuous_stream(&self) -> &Arc> { + &self.continuous_stream + } +} + /// Parse le DIDL-Lite pour extraire la durée du premier élément fn parse_didl_duration(didl: &str) -> Option { // Recherche de l'élément (avec ou sans espace après) diff --git a/pmocontrol/src/queue/backend.rs b/pmocontrol/src/queue/backend.rs index 2565f0aa..28e75bfa 100644 --- a/pmocontrol/src/queue/backend.rs +++ b/pmocontrol/src/queue/backend.rs @@ -28,15 +28,11 @@ //! - This identity is used by the sync helpers to preserve the current //! track across queue rebuilds when the MediaServer content changes. +use crate::music_renderer::HasQueue; use crate::queue::MusicQueue; use crate::{errors::ControlPointError, PlaybackItem, QueueSnapshot}; use std::sync::{atomic::AtomicBool, Arc, Mutex}; -/// Trait for types that have aMusicQueue. -pub trait HasQueue { - fn queue(&self) -> &Arc>; -} - /// Blanket implementation of QueueBackend for types that have a queue. /// All methods simply delegate to the underlying MusicQueue. impl QueueBackend for T { diff --git a/pmocontrol/src/queue/mod.rs b/pmocontrol/src/queue/mod.rs index 99d77bf0..2dfd125c 100644 --- a/pmocontrol/src/queue/mod.rs +++ b/pmocontrol/src/queue/mod.rs @@ -6,7 +6,7 @@ mod snapshot; use std::sync::{Arc, Mutex}; -pub use backend::{EnqueueMode, HasQueue, QueueBackend}; +pub use backend::{EnqueueMode, QueueBackend}; pub use music_queue::{MusicQueue, SyncScheduleOutcome}; pub use snapshot::{PlaybackItem, QueueSnapshot};