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:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user