Bump version to 0.3.20 and enhance metadata handling for Arylic and LinkPlay renderers
Update version from 0.3.19 to 0.3.20 - Enhanced metadata handling in ArylicTcpRenderer and LinkPlayRenderer to use queue metadata for consistent duration protection - Added shared queue functionality for HybridUpnpArylic to synchronize metadata between UPnP and Arylic backends - Improved error logging and debug information for playback position fetching - Updated version in Cargo.toml and version.txt
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4,7 +4,7 @@ version = 4
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "PMOMusic"
|
name = "PMOMusic"
|
||||||
version = "0.3.19"
|
version = "0.3.20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum 0.8.7",
|
"axum 0.8.7",
|
||||||
"console-subscriber",
|
"console-subscriber",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "PMOMusic"
|
name = "PMOMusic"
|
||||||
version = "0.3.19"
|
version = "0.3.20"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -139,6 +139,27 @@ impl ArylicTcpRenderer {
|
|||||||
pub fn is_continuous_stream(&self) -> bool {
|
pub fn is_continuous_stream(&self) -> bool {
|
||||||
*self.continuous_stream.lock().unwrap()
|
*self.continuous_stream.lock().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create an ArylicTcpRenderer with a shared queue (for HybridUpnpArylic)
|
||||||
|
pub fn with_shared_queue(
|
||||||
|
info: &RendererInfo,
|
||||||
|
shared_queue: Arc<Mutex<MusicQueue>>,
|
||||||
|
) -> Result<Self, ControlPointError> {
|
||||||
|
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 {
|
impl TransportControl for ArylicTcpRenderer {
|
||||||
@@ -222,8 +243,61 @@ impl PlaybackStatus for ArylicTcpRenderer {
|
|||||||
|
|
||||||
impl PlaybackPosition for ArylicTcpRenderer {
|
impl PlaybackPosition for ArylicTcpRenderer {
|
||||||
fn playback_position(&self) -> Result<PlaybackPositionInfo, ControlPointError> {
|
fn playback_position(&self) -> Result<PlaybackPositionInfo, ControlPointError> {
|
||||||
let info = self.fetch_playback_info()?;
|
let info = match self.fetch_playback_info() {
|
||||||
Ok(info.position_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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -155,7 +155,27 @@ impl PlaybackStatus for LinkPlayRenderer {
|
|||||||
|
|
||||||
impl PlaybackPosition for LinkPlayRenderer {
|
impl PlaybackPosition for LinkPlayRenderer {
|
||||||
fn playback_position(&self) -> Result<PlaybackPositionInfo, ControlPointError> {
|
fn playback_position(&self) -> Result<PlaybackPositionInfo, ControlPointError> {
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1685,8 +1685,10 @@ impl RendererFromMediaRendererInfo for MusicRendererBackend {
|
|||||||
if has_arylic && has_avtransport {
|
if has_arylic && has_avtransport {
|
||||||
let upnp_backend = UpnpRenderer::build_from_renderer_info(info)?;
|
let upnp_backend = UpnpRenderer::build_from_renderer_info(info)?;
|
||||||
if let MusicRendererBackend::Upnp(upnp) = upnp_backend {
|
if let MusicRendererBackend::Upnp(upnp) = upnp_backend {
|
||||||
match ArylicTcpRenderer::build_from_renderer_info(info) {
|
// Share the UPnP queue with Arylic so metadata is consistent
|
||||||
Ok(MusicRendererBackend::ArylicTcp(arylic)) => {
|
let shared_queue = upnp.queue().clone();
|
||||||
|
match ArylicTcpRenderer::with_shared_queue(info, shared_queue) {
|
||||||
|
Ok(arylic) => {
|
||||||
return Ok(MusicRendererBackend::HybridUpnpArylic { upnp, arylic });
|
return Ok(MusicRendererBackend::HybridUpnpArylic { upnp, arylic });
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
0.3.19
|
0.3.20
|
||||||
|
|||||||
Reference in New Issue
Block a user