184 lines
5.9 KiB
Rust
184 lines
5.9 KiB
Rust
/// Test example to debug playlist attachment issues
|
||
///
|
||
/// This example tests each step of attaching a playlist to an OpenHome renderer:
|
||
/// 1. Browse the MediaServer for playlist items
|
||
/// 2. Insert each item into the OpenHome renderer's playlist
|
||
/// 3. Verify the operation succeeded
|
||
///
|
||
/// Usage:
|
||
/// cargo run --example test_attach_playlist
|
||
use anyhow::{Context, Result};
|
||
use pmocontrol::{
|
||
media_server::{MediaBrowser, MediaEntry, MediaServer, ServerId},
|
||
openhome_client::OhPlaylistClient,
|
||
};
|
||
use tracing::{debug, error, info, warn};
|
||
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
|
||
|
||
#[tokio::main]
|
||
async fn main() -> Result<()> {
|
||
// Initialize logging
|
||
tracing_subscriber::registry()
|
||
.with(fmt::layer())
|
||
.with(
|
||
EnvFilter::try_from_default_env()
|
||
.unwrap_or_else(|_| EnvFilter::new("info,pmocontrol=debug")),
|
||
)
|
||
.init();
|
||
|
||
info!("🧪 Starting playlist attachment test");
|
||
|
||
// Configuration - adjust these for your setup
|
||
let media_server_url = "http://192.168.0.138:8080";
|
||
let media_server_id = "uuid:17fe2ea6-8908-4e30-bc52-b28ea4cab3e4";
|
||
let playlist_container_id = "radio-paradise:channel:mellow:liveplaylist";
|
||
|
||
let renderer_playlist_url = "http://192.168.0.200:49152/Playlist";
|
||
let renderer_service_type = "urn:av-openhome-org:service:Playlist:1";
|
||
|
||
info!("Configuration:");
|
||
info!(" MediaServer: {}", media_server_url);
|
||
info!(" Playlist: {}", playlist_container_id);
|
||
info!(" Renderer: {}", renderer_playlist_url);
|
||
info!("");
|
||
|
||
// Step 1: Create MediaServer client
|
||
info!("📡 Step 1: Connecting to MediaServer");
|
||
let content_directory_url = format!(
|
||
"{}/device/{}/service/ContentDirectory/control",
|
||
media_server_url,
|
||
media_server_id.replace("uuid:", "")
|
||
);
|
||
|
||
let server = MediaServer::new(
|
||
ServerId(media_server_id.to_string()),
|
||
"PMOMusic Test".to_string(),
|
||
content_directory_url,
|
||
);
|
||
|
||
debug!("MediaServer client created");
|
||
|
||
// Step 2: Browse the playlist container
|
||
info!("📂 Step 2: Browsing playlist container");
|
||
info!(" Container ID: {}", playlist_container_id);
|
||
|
||
let entries = match server.browse_children(playlist_container_id, 0, 10) {
|
||
Ok(entries) => {
|
||
info!("✅ Browse succeeded: {} items found", entries.len());
|
||
entries
|
||
}
|
||
Err(e) => {
|
||
error!("❌ Browse failed: {}", e);
|
||
error!(" This is where the error occurs!");
|
||
return Err(e);
|
||
}
|
||
};
|
||
|
||
if entries.is_empty() {
|
||
warn!("⚠️ Playlist is empty, nothing to insert");
|
||
return Ok(());
|
||
}
|
||
|
||
// Display the first few items
|
||
info!("📋 First items in playlist:");
|
||
for (idx, entry) in entries.iter().take(3).enumerate() {
|
||
info!(
|
||
" [{}] {} - {}",
|
||
idx,
|
||
entry.title,
|
||
entry.artist.as_deref().unwrap_or("Unknown")
|
||
);
|
||
if let Some(res) = entry.resources.first() {
|
||
debug!(" URI: {}", res.uri);
|
||
debug!(" protocolInfo: {}", res.protocol_info);
|
||
}
|
||
}
|
||
info!("");
|
||
|
||
// Step 3: Connect to OpenHome renderer
|
||
info!("🎵 Step 3: Connecting to OpenHome renderer");
|
||
let oh_client = OhPlaylistClient::new(
|
||
renderer_playlist_url.to_string(),
|
||
renderer_service_type.to_string(),
|
||
);
|
||
debug!("OpenHome client created");
|
||
|
||
// Step 4: Clear existing playlist
|
||
info!("🗑️ Step 4: Clearing existing playlist");
|
||
match oh_client.delete_all() {
|
||
Ok(_) => info!("✅ Playlist cleared"),
|
||
Err(e) => {
|
||
warn!("⚠️ Could not clear playlist: {}", e);
|
||
}
|
||
}
|
||
info!("");
|
||
|
||
// Step 5: Insert items one by one
|
||
info!("➕ Step 5: Inserting items into renderer");
|
||
let mut after_id = 0u32;
|
||
let mut inserted_count = 0;
|
||
|
||
for (idx, entry) in entries.iter().enumerate() {
|
||
if entry.is_container {
|
||
debug!("Skipping container: {}", entry.title);
|
||
continue;
|
||
}
|
||
|
||
let resource = match entry.resources.iter().find(|r| r.is_audio()) {
|
||
Some(r) => r,
|
||
None => {
|
||
warn!("No audio resource found for: {}", entry.title);
|
||
continue;
|
||
}
|
||
};
|
||
|
||
info!(" [{}] Inserting: {}", idx, entry.title);
|
||
debug!(" URI: {}", resource.uri);
|
||
debug!(" protocolInfo: {}", resource.protocol_info);
|
||
|
||
// Build simple DIDL-Lite metadata
|
||
let didl_metadata = format!(
|
||
r#"<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"><item id="{}" parentID="-1" restricted="1"><dc:title>{}</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><res protocolInfo="{}">{}</res></item></DIDL-Lite>"#,
|
||
entry.id,
|
||
xmlescape(&entry.title),
|
||
resource.protocol_info,
|
||
xmlescape(&resource.uri)
|
||
);
|
||
|
||
trace!("DIDL metadata: {}", didl_metadata);
|
||
|
||
match oh_client.insert(after_id, &resource.uri, &didl_metadata) {
|
||
Ok(new_id) => {
|
||
debug!(" ✅ Inserted with ID: {}", new_id);
|
||
after_id = new_id;
|
||
inserted_count += 1;
|
||
}
|
||
Err(e) => {
|
||
error!(" ❌ Insert failed: {}", e);
|
||
error!(" This is the UPnP 501 error location!");
|
||
|
||
// Continue with next item instead of failing
|
||
warn!(" Continuing with next item...");
|
||
}
|
||
}
|
||
}
|
||
|
||
info!("");
|
||
info!(
|
||
"✅ Test completed: {}/{} items inserted successfully",
|
||
inserted_count,
|
||
entries.len()
|
||
);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Simple XML escaping
|
||
fn xmlescape(s: &str) -> String {
|
||
s.replace('&', "&")
|
||
.replace('<', "<")
|
||
.replace('>', ">")
|
||
.replace('"', """)
|
||
.replace('\'', "'")
|
||
}
|