diff --git a/pmocontrol/src/music_renderer/arylic_tcp.rs b/pmocontrol/src/music_renderer/arylic_tcp.rs index a9167029..5906310f 100644 --- a/pmocontrol/src/music_renderer/arylic_tcp.rs +++ b/pmocontrol/src/music_renderer/arylic_tcp.rs @@ -307,63 +307,6 @@ impl QueueTransportControl for ArylicTcpRenderer { self.play_uri(&item.uri, "") } - fn play_from_queue(&self) -> Result<(), ControlPointError> { - let mut queue = self.queue.lock().unwrap(); - - let current_index = match queue.current_index()? { - Some(idx) => idx, - None => { - if queue.len()? > 0 { - queue.set_index(Some(0))?; - 0 - } else { - return Err(ControlPointError::QueueError("Queue is empty".into())); - } - } - }; - - let item = queue - .get_item(current_index)? - .ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?; - - drop(queue); - - 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> { - { - let mut queue = self.queue.lock().unwrap(); - if !queue.advance()? { - return Err(ControlPointError::QueueError("No next track".into())); - } - } - - self.play_from_queue() - } - - fn play_previous(&self) -> Result<(), ControlPointError> { - { - let mut queue = self.queue.lock().unwrap(); - if !queue.rewind()? { - return Err(ControlPointError::QueueError("No previous track".into())); - } - } - - self.play_from_queue() - } - - fn play_from_index(&self, index: usize) -> Result<(), ControlPointError> { - { - let mut queue = self.queue.lock().unwrap(); - queue.set_index(Some(index))?; - } - - self.play_from_queue() - } } impl HasQueue for ArylicTcpRenderer { diff --git a/pmocontrol/src/music_renderer/capabilities.rs b/pmocontrol/src/music_renderer/capabilities.rs index c164c052..b7e1559e 100644 --- a/pmocontrol/src/music_renderer/capabilities.rs +++ b/pmocontrol/src/music_renderer/capabilities.rs @@ -53,14 +53,35 @@ pub trait QueueTransportControl: HasQueue + HasContinuousStream { } /// Play the next track from the queue. - fn play_next(&self) -> Result<(), ControlPointError>; + fn play_next(&self) -> Result<(), ControlPointError> { + { + let mut queue = self.queue().lock().unwrap(); + if !queue.advance()? { + return Err(ControlPointError::QueueError("No next track".into())); + } + } + self.play_from_queue() + } /// Play the previous track from the queue. - #[allow(dead_code)] - fn play_previous(&self) -> Result<(), ControlPointError>; + fn play_previous(&self) -> Result<(), ControlPointError> { + { + let mut queue = self.queue().lock().unwrap(); + if !queue.rewind()? { + return Err(ControlPointError::QueueError("No previous track".into())); + } + } + self.play_from_queue() + } /// Play from a specific index in the queue. - fn play_from_index(&self, index: usize) -> Result<(), ControlPointError>; + fn play_from_index(&self, index: usize) -> Result<(), ControlPointError> { + { + let mut queue = self.queue().lock().unwrap(); + queue.set_index(Some(index))?; + } + self.play_from_queue() + } } /// Logical playback position across backends. diff --git a/pmocontrol/src/music_renderer/chromecast_renderer.rs b/pmocontrol/src/music_renderer/chromecast_renderer.rs index a0754a52..429054a2 100644 --- a/pmocontrol/src/music_renderer/chromecast_renderer.rs +++ b/pmocontrol/src/music_renderer/chromecast_renderer.rs @@ -806,63 +806,6 @@ impl QueueTransportControl for ChromecastRenderer { self.play_uri(&item.uri, "") } - fn play_from_queue(&self) -> Result<(), ControlPointError> { - let mut queue = self.queue.lock().unwrap(); - - let current_index = match queue.current_index()? { - Some(idx) => idx, - None => { - if queue.len()? > 0 { - queue.set_index(Some(0))?; - 0 - } else { - return Err(ControlPointError::QueueError("Queue is empty".into())); - } - } - }; - - let item = queue - .get_item(current_index)? - .ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?; - - drop(queue); - - 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> { - { - let mut queue = self.queue.lock().unwrap(); - if !queue.advance()? { - return Err(ControlPointError::QueueError("No next track".into())); - } - } - - self.play_from_queue() - } - - fn play_previous(&self) -> Result<(), ControlPointError> { - { - let mut queue = self.queue.lock().unwrap(); - if !queue.rewind()? { - return Err(ControlPointError::QueueError("No previous track".into())); - } - } - - self.play_from_queue() - } - - fn play_from_index(&self, index: usize) -> Result<(), ControlPointError> { - { - let mut queue = self.queue.lock().unwrap(); - queue.set_index(Some(index))?; - } - - self.play_from_queue() - } } impl HasQueue for ChromecastRenderer { diff --git a/pmocontrol/src/music_renderer/linkplay_renderer.rs b/pmocontrol/src/music_renderer/linkplay_renderer.rs index 134c89f2..5705bc87 100644 --- a/pmocontrol/src/music_renderer/linkplay_renderer.rs +++ b/pmocontrol/src/music_renderer/linkplay_renderer.rs @@ -184,66 +184,9 @@ 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(); - - let current_index = match queue.current_index()? { - Some(idx) => idx, - None => { - if queue.len()? > 0 { - queue.set_index(Some(0))?; - 0 - } else { - return Err(ControlPointError::QueueError("Queue is empty".into())); - } - } - }; - - let item = queue - .get_item(current_index)? - .ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?; - - drop(queue); - - 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> { - { - let mut queue = self.queue.lock().unwrap(); - if !queue.advance()? { - return Err(ControlPointError::QueueError("No next track".into())); - } - } - - self.play_from_queue() - } - - fn play_previous(&self) -> Result<(), ControlPointError> { - { - let mut queue = self.queue.lock().unwrap(); - if !queue.rewind()? { - return Err(ControlPointError::QueueError("No previous track".into())); - } - } - - self.play_from_queue() - } - - fn play_from_index(&self, index: usize) -> Result<(), ControlPointError> { - { - let mut queue = self.queue.lock().unwrap(); - queue.set_index(Some(index))?; - } - - self.play_from_queue() - } } + impl HasQueue for LinkPlayRenderer { fn queue(&self) -> &Arc> { &self.queue diff --git a/pmocontrol/src/music_renderer/musicrenderer.rs b/pmocontrol/src/music_renderer/musicrenderer.rs index 1da5e749..50656c77 100644 --- a/pmocontrol/src/music_renderer/musicrenderer.rs +++ b/pmocontrol/src/music_renderer/musicrenderer.rs @@ -2091,7 +2091,7 @@ impl RendererFromMediaRendererInfo for MusicRendererBackend { /// Extracts the duration attribute from the element in DIDL metadata. /// This is used as a fallback when the renderer doesn't provide track_duration /// in GetPositionInfo or similar calls. -fn parse_didl_duration(didl_xml: &str) -> Option { +pub(crate) fn parse_didl_duration(didl_xml: &str) -> Option { // Parse DIDL-Lite XML properly using pmodidl let didl = match DIDLLite::parse(didl_xml) { Ok(d) => d, @@ -2129,6 +2129,37 @@ fn parse_rfc3339_to_system_time(s: &str) -> Option { Some(std::time::UNIX_EPOCH + std::time::Duration::from_secs(secs as u64)) } + +/// Dispatch a method call to the inner backend, using the UPnP field for +/// HybridUpnpArylic. +macro_rules! dispatch_upnp { + ($self:expr, $method:ident($($arg:expr),*)) => { + match $self { + MusicRendererBackend::Upnp(r) => r.$method($($arg),*), + MusicRendererBackend::OpenHome(r) => r.$method($($arg),*), + MusicRendererBackend::LinkPlay(r) => r.$method($($arg),*), + MusicRendererBackend::ArylicTcp(r) => r.$method($($arg),*), + MusicRendererBackend::Chromecast(cc) => cc.$method($($arg),*), + MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.$method($($arg),*), + } + }; +} + +/// Dispatch a method call to the inner backend, using the Arylic field for +/// HybridUpnpArylic. +macro_rules! dispatch_arylic { + ($self:expr, $method:ident($($arg:expr),*)) => { + match $self { + MusicRendererBackend::Upnp(r) => r.$method($($arg),*), + MusicRendererBackend::OpenHome(r) => r.$method($($arg),*), + MusicRendererBackend::LinkPlay(r) => r.$method($($arg),*), + MusicRendererBackend::ArylicTcp(r) => r.$method($($arg),*), + MusicRendererBackend::Chromecast(cc) => cc.$method($($arg),*), + MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.$method($($arg),*), + } + }; +} + /// Transport control façade that dispatches to whichever backend can fulfill /// the request, returning a standardized error if the backend lacks support. impl TransportControl for MusicRendererBackend { @@ -2145,38 +2176,9 @@ impl TransportControl for MusicRendererBackend { } } - fn play(&self) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(upnp) => upnp.play(), - MusicRendererBackend::OpenHome(oh) => oh.play(), - MusicRendererBackend::LinkPlay(lp) => lp.play(), - MusicRendererBackend::ArylicTcp(ary) => ary.play(), - MusicRendererBackend::Chromecast(cc) => cc.play(), - MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.play(), - } - } - - fn pause(&self) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(upnp) => upnp.pause(), - MusicRendererBackend::OpenHome(oh) => oh.pause(), - MusicRendererBackend::LinkPlay(lp) => lp.pause(), - MusicRendererBackend::ArylicTcp(ary) => ary.pause(), - MusicRendererBackend::Chromecast(cc) => cc.pause(), - MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.pause(), - } - } - - fn stop(&self) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(upnp) => upnp.stop(), - MusicRendererBackend::OpenHome(oh) => oh.stop(), - MusicRendererBackend::LinkPlay(lp) => lp.stop(), - MusicRendererBackend::ArylicTcp(ary) => ary.stop(), - MusicRendererBackend::Chromecast(cc) => cc.stop(), - MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.stop(), - } - } + fn play(&self) -> Result<(), ControlPointError> { dispatch_arylic!(self, play()) } + fn pause(&self) -> Result<(), ControlPointError> { dispatch_arylic!(self, pause()) } + fn stop(&self) -> Result<(), ControlPointError> { dispatch_arylic!(self, stop()) } fn seek_rel_time(&self, hhmmss: &str) -> Result<(), ControlPointError> { match self { @@ -2197,49 +2199,10 @@ impl TransportControl for MusicRendererBackend { /// Hybrid backends may read via Arylic TCP and write via UPnP, but callers /// always depend on a single [`VolumeControl`] entry point. impl VolumeControl for MusicRendererBackend { - fn volume(&self) -> Result { - match self { - MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.volume(), - MusicRendererBackend::ArylicTcp(ary) => ary.volume(), - MusicRendererBackend::OpenHome(oh) => oh.volume(), - MusicRendererBackend::Upnp(upnp) => upnp.volume(), - MusicRendererBackend::LinkPlay(lp) => lp.volume(), - MusicRendererBackend::Chromecast(cc) => cc.volume(), - } - } - - fn set_volume(&self, vol: u16) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.set_volume(vol), - MusicRendererBackend::ArylicTcp(ary) => ary.set_volume(vol), - MusicRendererBackend::OpenHome(oh) => oh.set_volume(vol), - MusicRendererBackend::Upnp(upnp) => upnp.set_volume(vol), - MusicRendererBackend::LinkPlay(lp) => lp.set_volume(vol), - MusicRendererBackend::Chromecast(cc) => cc.set_volume(vol), - } - } - - fn mute(&self) -> Result { - match self { - MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.mute(), - MusicRendererBackend::OpenHome(r) => r.mute(), - MusicRendererBackend::Upnp(r) => r.mute(), - MusicRendererBackend::LinkPlay(r) => r.mute(), - MusicRendererBackend::ArylicTcp(r) => r.mute(), - MusicRendererBackend::Chromecast(cc) => cc.mute(), - } - } - - fn set_mute(&self, m: bool) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.set_mute(m), - MusicRendererBackend::OpenHome(r) => r.set_mute(m), - MusicRendererBackend::Upnp(r) => r.set_mute(m), - MusicRendererBackend::LinkPlay(r) => r.set_mute(m), - MusicRendererBackend::ArylicTcp(r) => r.set_mute(m), - MusicRendererBackend::Chromecast(cc) => cc.set_mute(m), - } - } + fn volume(&self) -> Result { dispatch_arylic!(self, volume()) } + fn set_volume(&self, vol: u16) -> Result<(), ControlPointError> { dispatch_upnp!(self, set_volume(vol)) } + fn mute(&self) -> Result { dispatch_arylic!(self, mute()) } + fn set_mute(&self, m: bool) -> Result<(), ControlPointError> { dispatch_arylic!(self, set_mute(m)) } } /// Playback-state queries sourced from the backend best suited for the job. @@ -2263,96 +2226,28 @@ impl PlaybackStatus for MusicRendererBackend { /// regardless of the backend providing the raw transport data. impl PlaybackPosition for MusicRendererBackend { fn playback_position(&self) -> Result { - match self { - MusicRendererBackend::Upnp(r) => r.playback_position(), - MusicRendererBackend::OpenHome(r) => r.playback_position(), - MusicRendererBackend::LinkPlay(r) => r.playback_position(), - MusicRendererBackend::ArylicTcp(r) => r.playback_position(), - MusicRendererBackend::Chromecast(cc) => cc.playback_position(), - MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.playback_position(), - } + dispatch_arylic!(self, playback_position()) } } impl HasQueue for MusicRendererBackend { - fn queue(&self) -> &Arc> { - match self { - MusicRendererBackend::Upnp(r) => r.queue(), - MusicRendererBackend::OpenHome(r) => r.queue(), - MusicRendererBackend::LinkPlay(r) => r.queue(), - MusicRendererBackend::ArylicTcp(r) => r.queue(), - MusicRendererBackend::Chromecast(cc) => cc.queue(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.queue(), - } - } + fn queue(&self) -> &Arc> { dispatch_upnp!(self, queue()) } } 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(), - } + dispatch_arylic!(self, 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), - } + dispatch_upnp!(self, play_item(item)) } - fn play_from_queue(&self) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.play_from_queue(), - MusicRendererBackend::OpenHome(r) => r.play_from_queue(), - MusicRendererBackend::LinkPlay(r) => r.play_from_queue(), - MusicRendererBackend::ArylicTcp(r) => r.play_from_queue(), - MusicRendererBackend::Chromecast(cc) => cc.play_from_queue(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.play_from_queue(), - } + dispatch_upnp!(self, play_from_queue()) } - - fn play_next(&self) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.play_next(), - MusicRendererBackend::OpenHome(r) => r.play_next(), - MusicRendererBackend::LinkPlay(r) => r.play_next(), - MusicRendererBackend::ArylicTcp(r) => r.play_next(), - MusicRendererBackend::Chromecast(cc) => cc.play_next(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.play_next(), - } - } - - fn play_previous(&self) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.play_previous(), - MusicRendererBackend::OpenHome(r) => r.play_previous(), - MusicRendererBackend::LinkPlay(r) => r.play_previous(), - MusicRendererBackend::ArylicTcp(r) => r.play_previous(), - MusicRendererBackend::Chromecast(cc) => cc.play_previous(), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.play_previous(), - } - } - fn play_from_index(&self, index: usize) -> Result<(), ControlPointError> { - match self { - MusicRendererBackend::Upnp(r) => r.play_from_index(index), - MusicRendererBackend::OpenHome(r) => r.play_from_index(index), - MusicRendererBackend::LinkPlay(r) => r.play_from_index(index), - MusicRendererBackend::ArylicTcp(r) => r.play_from_index(index), - MusicRendererBackend::Chromecast(cc) => cc.play_from_index(index), - MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.play_from_index(index), - } + dispatch_upnp!(self, play_from_index(index)) } } diff --git a/pmocontrol/src/music_renderer/openhome_renderer.rs b/pmocontrol/src/music_renderer/openhome_renderer.rs index 46f7fca8..80aafbaa 100644 --- a/pmocontrol/src/music_renderer/openhome_renderer.rs +++ b/pmocontrol/src/music_renderer/openhome_renderer.rs @@ -492,28 +492,6 @@ impl PlaybackPosition for OpenHomeRenderer { Ok(position_info) } } - -/// Parse duration from DIDL-Lite metadata XML (OpenHome version) -#[allow(dead_code)] -fn parse_didl_duration_openhome(didl: &str) -> Option { - // Search for duration attribute in element - let res_start = didl.find("')?; - let tag_attrs = &after_res[..tag_close]; - - if let Some(duration_start) = tag_attrs.find("duration=\"") { - let duration_offset = duration_start + "duration=\"".len(); - if let Some(duration_end) = tag_attrs[duration_offset..].find('"') { - let duration = &tag_attrs[duration_offset..duration_offset + duration_end]; - return Some(duration.to_string()); - } - } - - tracing::debug!("OpenHome: No duration found in DIDL metadata"); - None -} - pub(crate) fn map_openhome_state(raw: &str) -> PlaybackState { match raw.trim().to_ascii_uppercase().as_str() { "PLAYING" => PlaybackState::Playing, @@ -554,50 +532,6 @@ impl QueueTransportControl for OpenHomeRenderer { playlist.play() } - fn play_next(&self) -> Result<(), ControlPointError> { - { - let mut queue = self - .queue - .lock() - .map_err(|_| ControlPointError::QueueError("Queue mutex poisoned".into()))?; - let len = queue.len().unwrap_or(0); - let current = queue.current_index().ok().flatten(); - let current_track_id = queue.current_track().ok().flatten(); - let all_ids = queue.track_ids().ok().unwrap_or_default(); - tracing::trace!( - queue_len = len, - current_index = ?current, - current_track_id = ?current_track_id, - all_track_ids = ?all_ids, - "OpenHome play_next: advancing queue" - ); - if !queue.advance()? { - tracing::trace!( - queue_len = len, - current_index = ?current, - "OpenHome play_next: advance() returned false — no next track" - ); - return Err(ControlPointError::QueueError("No next track".into())); - } - } - - self.play_from_queue() - } - - fn play_previous(&self) -> Result<(), ControlPointError> { - { - let mut queue = self - .queue - .lock() - .map_err(|_| ControlPointError::QueueError("Queue mutex poisoned".into()))?; - if !queue.rewind()? { - return Err(ControlPointError::QueueError("No previous track".into())); - } - } - - self.play_from_queue() - } - fn play_from_index(&self, index: usize) -> Result<(), ControlPointError> { // For OpenHome, we need to convert index to track_id let track_id = { diff --git a/pmocontrol/src/music_renderer/upnp_renderer.rs b/pmocontrol/src/music_renderer/upnp_renderer.rs index 408038f5..09555461 100644 --- a/pmocontrol/src/music_renderer/upnp_renderer.rs +++ b/pmocontrol/src/music_renderer/upnp_renderer.rs @@ -6,7 +6,9 @@ use crate::music_renderer::capabilities::{ HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, TransportControl, VolumeControl, }; -use crate::music_renderer::musicrenderer::{build_didl_lite_metadata, MusicRendererBackend}; +use crate::music_renderer::musicrenderer::{ + build_didl_lite_metadata, parse_didl_duration, MusicRendererBackend, +}; use crate::music_renderer::HasQueue; use crate::music_renderer::RendererFromMediaRendererInfo; use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot}; @@ -203,100 +205,6 @@ impl QueueTransportControl for UpnpRenderer { Ok(()) } - fn play_from_queue(&self) -> Result<(), ControlPointError> { - let mut queue = self.queue.lock().unwrap(); - - let current_index = match queue.current_index()? { - Some(idx) => idx, - None => { - if queue.len()? > 0 { - queue.set_index(Some(0))?; - 0 - } else { - return Err(ControlPointError::QueueError("Queue is empty".into())); - } - } - }; - - let item = queue - .get_item(current_index)? - .ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?; - - drop(queue); - - let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri); - *self.continuous_stream.lock().unwrap() = is_stream; - - let queue_state = { - let queue = self.queue.lock().unwrap(); - let idx = queue.current_index().unwrap_or(None); - let len = queue.len().unwrap_or(0); - let uri = item.uri.clone(); - let title = item.metadata.as_ref().and_then(|m| m.title.clone()); - (idx, len, uri, title) - }; - tracing::debug!( - "UpnpRenderer play_from_queue: index={:?}/{}, uri={}, title={:?}, continuous_stream={}", - queue_state.0, - queue_state.1, - queue_state.2, - queue_state.3, - is_stream - ); - - self.play_item(&item) - } - - fn play_next(&self) -> Result<(), ControlPointError> { - let current_idx = { - let queue = self.queue.lock().unwrap(); - let idx = queue.current_index().unwrap_or(None); - let len = queue.len().unwrap_or(0); - tracing::debug!( - current_index = ?idx, - queue_len = len, - "play_next: attempting to advance" - ); - idx - }; - { - let mut queue = self.queue.lock().unwrap(); - if !queue.advance()? { - return Err(ControlPointError::QueueError("No next track".into())); - } - let new_idx = queue.current_index().unwrap_or(None); - tracing::debug!( - previous_index = ?current_idx, - new_index = ?new_idx, - "play_next: advanced" - ); - } - - self.play_from_queue() - } - - fn play_previous(&self) -> Result<(), ControlPointError> { - { - let mut queue = self.queue.lock().unwrap(); - if !queue.rewind()? { - return Err(ControlPointError::QueueError("No previous track".into())); - } - } - - self.play_from_queue() - } - - fn play_from_index(&self, index: usize) -> Result<(), ControlPointError> { - { - let mut queue = self.queue.lock().unwrap(); - queue.set_index(Some(index))?; - } - // CORRECTIF: Quand on change l'index manuellement (shuffle, sélection d'un titre) - // on logue pour être sûr que c'est bien appelé - tracing::debug!(index = index, "✅ SHUFFLE / SEEK: play_from_index appelé"); - - self.play_from_queue() - } } impl HasQueue for UpnpRenderer { @@ -310,36 +218,6 @@ impl HasContinuousStream for UpnpRenderer { &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) - let res_start = didl - .find("")) - .or_else(|| didl.find(" - // Il doit être avant la fermeture du tag (avant '>') - if let Some(tag_close) = after_res.find('>') { - let tag_attrs = &after_res[..tag_close]; - - if let Some(duration_start) = tag_attrs.find("duration=\"") { - let duration_offset = duration_start + "duration=\"".len(); - if let Some(duration_end) = tag_attrs[duration_offset..].find('"') { - let duration = &tag_attrs[duration_offset..duration_offset + duration_end]; - return Some(duration.to_string()); - } - } - } - - tracing::warn!("No duration attribute found in DIDL element"); - None -} - /// Implémentation UPnP AV de `TransportControl` pour [`UpnpRenderer`]. /// /// Cette impl se base sur AVTransport (InstanceID = 0).