diff --git a/Cargo.lock b/Cargo.lock index 3506ff8b..ff45d399 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "PMOMusic" -version = "0.3.19" +version = "0.3.20" dependencies = [ "axum 0.8.7", "console-subscriber", diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index e1e52e3f..78631881 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "PMOMusic" -version = "0.3.19" +version = "0.3.20" edition = "2024" [dependencies] diff --git a/pmocontrol/src/music_renderer/arylic_tcp.rs b/pmocontrol/src/music_renderer/arylic_tcp.rs index a361e494..38a9ac45 100644 --- a/pmocontrol/src/music_renderer/arylic_tcp.rs +++ b/pmocontrol/src/music_renderer/arylic_tcp.rs @@ -139,6 +139,27 @@ impl ArylicTcpRenderer { pub fn is_continuous_stream(&self) -> bool { *self.continuous_stream.lock().unwrap() } + + /// Create an ArylicTcpRenderer with a shared queue (for HybridUpnpArylic) + pub fn with_shared_queue( + info: &RendererInfo, + shared_queue: Arc>, + ) -> Result { + let host = extract_linkplay_host(info.location()).ok_or_else(|| { + ControlPointError::ArilycTcpError(format!( + "Renderer {} has no valid LOCATION host", + info.udn() + )) + })?; + + Ok(Self { + host, + port: ARYLIC_TCP_PORT, + timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECS), + queue: shared_queue, + continuous_stream: Arc::new(Mutex::new(false)), + }) + } } impl TransportControl for ArylicTcpRenderer { @@ -222,8 +243,61 @@ impl PlaybackStatus for ArylicTcpRenderer { impl PlaybackPosition for ArylicTcpRenderer { fn playback_position(&self) -> Result { - let info = self.fetch_playback_info()?; - Ok(info.position_info()) + let info = match self.fetch_playback_info() { + Ok(info) => { + tracing::debug!("ArylicTcp fetch_playback_info returned: {:?}", info); + info + } + Err(e) => { + tracing::warn!("ArylicTcp fetch_playback_info failed: {}", e); + return Err(e); + } + }; + + let mut position_info = info.position_info(); + tracing::debug!( + "ArylicTcp position_info: track_duration={:?}, rel_time={:?}, track_metadata={:?}, track_uri={:?}", + position_info.track_duration, + position_info.rel_time, + position_info + .track_metadata + .as_ref() + .map(|s| &s[..s.len().min(100)]), + position_info.track_uri + ); + + // Récupérer les métadonnées depuis la queue (avec protection contre diminution de durée) + // Normalement current_index est toujours Some() si la queue n'est pas vide (règle métier) + let mut queue_guard = self.queue.lock().unwrap(); + let queue_item = queue_guard.peek_current().ok().flatten(); + + if let Some((current_item, _)) = queue_item { + // Build DIDL metadata XML from cached/protected TrackMetadata + if let Some(ref metadata) = current_item.metadata { + tracing::debug!( + "ArylicTcp playback_position: using queue metadata - title={:?}, artist={:?}, duration={:?}, is_stream={}", + metadata.title, + metadata.artist, + metadata.duration, + metadata.is_continuous_stream + ); + position_info.track_metadata = Some( + crate::music_renderer::musicrenderer::build_didl_lite_metadata( + metadata, + ¤t_item.uri, + ¤t_item.protocol_info, + ), + ); + } else { + tracing::warn!("ArylicTcp playback_position: queue item has no metadata"); + } + position_info.track_uri = Some(current_item.uri.clone()); + } else { + tracing::warn!("ArylicTcp playback_position: no current queue item"); + } + drop(queue_guard); + + Ok(position_info) } } diff --git a/pmocontrol/src/music_renderer/linkplay_renderer.rs b/pmocontrol/src/music_renderer/linkplay_renderer.rs index bdc798b0..dc0e3b49 100644 --- a/pmocontrol/src/music_renderer/linkplay_renderer.rs +++ b/pmocontrol/src/music_renderer/linkplay_renderer.rs @@ -155,7 +155,27 @@ impl PlaybackStatus for LinkPlayRenderer { impl PlaybackPosition for LinkPlayRenderer { fn playback_position(&self) -> Result { - Ok(self.fetch_status()?.position_info()) + let mut position_info = self.fetch_status()?.position_info(); + + // Use queue metadata instead of direct status metadata to benefit from duration protection + let mut queue_guard = self.queue.lock().unwrap(); + let queue_item = queue_guard.peek_current().ok().flatten(); + + if let Some((current_item, _)) = queue_item { + if let Some(ref metadata) = current_item.metadata { + position_info.track_metadata = Some( + crate::music_renderer::musicrenderer::build_didl_lite_metadata( + metadata, + ¤t_item.uri, + ¤t_item.protocol_info, + ), + ); + } + position_info.track_uri = Some(current_item.uri.clone()); + } + drop(queue_guard); + + Ok(position_info) } } diff --git a/pmocontrol/src/music_renderer/musicrenderer.rs b/pmocontrol/src/music_renderer/musicrenderer.rs index b9df8cca..08f31751 100644 --- a/pmocontrol/src/music_renderer/musicrenderer.rs +++ b/pmocontrol/src/music_renderer/musicrenderer.rs @@ -1685,8 +1685,10 @@ impl RendererFromMediaRendererInfo for MusicRendererBackend { if has_arylic && has_avtransport { let upnp_backend = UpnpRenderer::build_from_renderer_info(info)?; if let MusicRendererBackend::Upnp(upnp) = upnp_backend { - match ArylicTcpRenderer::build_from_renderer_info(info) { - Ok(MusicRendererBackend::ArylicTcp(arylic)) => { + // Share the UPnP queue with Arylic so metadata is consistent + let shared_queue = upnp.queue().clone(); + match ArylicTcpRenderer::with_shared_queue(info, shared_queue) { + Ok(arylic) => { return Ok(MusicRendererBackend::HybridUpnpArylic { upnp, arylic }); } Err(err) => { diff --git a/version.txt b/version.txt index 9ec8136c..f9a4b5f9 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.19 +0.3.20