Mise en place de la synchronisation de la playlist du point de contrôle avec celle du serveur de média.

This commit is contained in:
2025-12-02 14:41:53 +01:00
parent aeb9332387
commit f99ab2a41e
9 changed files with 19861 additions and 59 deletions

View File

@@ -9,9 +9,9 @@ use std::time::Duration;
use anyhow::{Context, Result};
use pmocontrol::{
ControlPoint, DeviceRegistryRead, MediaBrowser, MediaEntry, MediaResource, MediaServerInfo,
MusicRenderer, MusicServer, PlaybackItem, PlaybackPosition, PlaybackPositionInfo, RendererInfo,
RendererProtocol,
ControlPoint, DeviceRegistryRead, MediaBrowser, MediaEntry, MediaResource, MediaServerEvent,
MediaServerInfo, MusicRenderer, MusicServer, PlaybackItem, PlaybackPosition,
PlaybackPositionInfo, RendererInfo, RendererProtocol,
};
const DEFAULT_TIMEOUT_SECS: u64 = 5;
@@ -94,8 +94,10 @@ fn main() -> Result<()> {
.context("Failed to browse ContentDirectory root")?;
println!("Root returned {} entries", root_entries.len());
let playback_items = collect_playable_items(&server, &root_entries, config.max_tracks)
.context("Failed to derive playable items from ContentDirectory root/children")?;
// Try to find a playlist container first
let (playback_items, bound_container_id) =
collect_playable_items_with_binding(&server, &root_entries, config.max_tracks)
.context("Failed to derive playable items from ContentDirectory root/children")?;
if playback_items.is_empty() {
println!("No playable tracks were found on the selected server.");
@@ -107,6 +109,15 @@ fn main() -> Result<()> {
playback_items.len()
);
if let Some(ref container_id) = bound_container_id {
println!(
"Found playlist container '{}' to bind queue to",
container_id
);
} else {
println!("No playlist container found; queue will not be bound to server");
}
let mut planned_queue: VecDeque<PlaybackItem>;
let renderer_id = renderer.id.clone();
control_point
@@ -116,6 +127,19 @@ fn main() -> Result<()> {
.enqueue_items(&renderer_id, playback_items)
.context("Failed to enqueue playback items")?;
// Attach queue to playlist container if we found one
if let Some(container_id) = bound_container_id {
control_point.attach_queue_to_playlist(
&renderer_id,
server_info.id.clone(),
container_id.clone(),
);
println!(
"✓ Queue attached to playlist container {} on server {}",
container_id, server_info.friendly_name
);
}
let snapshot = control_point
.get_queue_snapshot(&renderer_id)
.context("Failed to snapshot queue after enqueue")?;
@@ -140,9 +164,51 @@ fn main() -> Result<()> {
"Monitoring queue auto-advance for {} seconds (poll every {}s)…",
MONITOR_DURATION_SECS, MONITOR_POLL_SECS
);
// Subscribe to media server events to observe playlist updates
let media_event_rx = control_point.subscribe_media_server_events();
let poll_count = MONITOR_DURATION_SECS / MONITOR_POLL_SECS;
for tick in 0..poll_count {
thread::sleep(Duration::from_secs(MONITOR_POLL_SECS));
// Drain any MediaServerEvent that arrived since last poll
loop {
match media_event_rx.try_recv() {
Ok(MediaServerEvent::GlobalUpdated {
server_id,
system_update_id,
}) => {
println!(
" 📢 MediaServer {} global update (SystemUpdateID={:?})",
server_id.0, system_update_id
);
}
Ok(MediaServerEvent::ContainersUpdated {
server_id,
container_ids,
}) => {
println!(
" 📢 MediaServer {} containers updated: {:?}",
server_id.0, container_ids
);
// Check if our bound container was updated
if let Some((bound_server, bound_container, _)) =
control_point.current_queue_playlist_binding(&renderer_id)
{
if bound_server == server_id && container_ids.contains(&bound_container) {
println!(
" 🔄 Bound playlist container '{}' was updated, queue will refresh automatically",
bound_container
);
}
}
}
Err(_) => break, // No more events, continue with normal monitoring
}
}
let snapshot = control_point
.get_queue_snapshot(&renderer_id)
.context("Queue snapshot failed during monitoring loop")?;
@@ -302,11 +368,60 @@ fn is_pmomusic_renderer(info: &RendererInfo) -> bool {
.contains("pmomusic audio renderer")
}
fn collect_playable_items(
fn collect_playable_items_with_binding(
server: &MusicServer,
entries: &[MediaEntry],
max_tracks: usize,
) -> Result<Vec<PlaybackItem>> {
) -> Result<(Vec<PlaybackItem>, Option<String>)> {
// First, try to find a playlist container
let playlist_container = entries.iter().find(|entry| {
entry.is_container
&& entry
.class
.to_ascii_lowercase()
.contains("object.container.playlistcontainer")
});
if let Some(playlist) = playlist_container {
println!(
"Found playlist container: '{}' (id: {}, class: {})",
playlist.title, playlist.id, playlist.class
);
// Browse the playlist container
match server.browse_children(&playlist.id, 0, max_tracks as u32) {
Ok(children) => {
let mut items = Vec::new();
for entry in &children {
if let Some(item) = playback_item_from_entry(server, entry) {
items.push(item);
if items.len() >= max_tracks {
break;
}
}
}
if !items.is_empty() {
return Ok((items, Some(playlist.id.clone())));
}
println!(
"Playlist container '{}' is empty, falling back to general browse",
playlist.title
);
}
Err(err) => {
println!(
"Failed to browse playlist container '{}': {}, falling back",
playlist.title, err
);
}
}
} else {
println!("No playlist container found in root entries, using fallback");
}
// Fallback: collect from any container/item
let mut items = Vec::new();
for entry in entries {
gather_items_from_entry(server, entry, max_tracks, 0, &mut items)?;
@@ -314,7 +429,7 @@ fn collect_playable_items(
break;
}
}
Ok(items)
Ok((items, None))
}
fn gather_items_from_entry(