Add a LinkPlayRenderer

This commit is contained in:
2025-11-30 14:43:29 +01:00
parent f096a8cd8f
commit 0e70a91ff9
9 changed files with 655 additions and 33 deletions

View File

@@ -0,0 +1,101 @@
use std::io;
use std::thread;
use std::time::Duration;
use pmocontrol::{
ControlPoint, DeviceRegistryRead, MusicRenderer, PlaybackPosition, PlaybackPositionInfo,
PlaybackState, PlaybackStatus, VolumeControl,
};
fn main() -> io::Result<()> {
let _ = tracing_subscriber::fmt::try_init();
println!("Starting PMOMusic LinkPlay demo...");
let cp = ControlPoint::spawn(5)?;
println!("Waiting 5 seconds for SSDP discovery...");
thread::sleep(Duration::from_secs(5));
let registry = cp.registry();
let renderers = {
let reg = registry.read().unwrap();
reg.list_renderers()
};
if renderers.is_empty() {
println!("No renderers discovered.");
return Ok(());
}
println!("Discovered renderers:");
for (idx, info) in renderers.iter().enumerate() {
println!(
" [{}] {} | model={} | LinkPlay HTTP={}",
idx, info.friendly_name, info.model_name, info.capabilities.has_linkplay_http
);
}
let linkplay_renderers: Vec<MusicRenderer> = {
let reg = registry.read().unwrap();
renderers
.iter()
.filter(|info| info.capabilities.has_linkplay_http)
.filter_map(|info| MusicRenderer::from_registry_info(info.clone(), &reg))
.collect()
};
if linkplay_renderers.is_empty() {
println!("\nNo LinkPlay-capable renderers detected.");
return Ok(());
}
println!("\nLinkPlay renderer status:");
for renderer in linkplay_renderers {
println!("- {} ({})", renderer.friendly_name(), renderer.id().0);
match renderer.playback_state() {
Ok(state) => println!(" state: {}", format_playback_state(&state)),
Err(err) => println!(" state: error: {}", err),
}
match renderer.playback_position() {
Ok(pos) => println!(" position: {}", format_position(&pos)),
Err(err) => println!(" position: error: {}", err),
}
match renderer.volume() {
Ok(vol) => println!(" volume: {}", vol),
Err(err) => println!(" volume: error: {}", err),
}
match renderer.mute() {
Ok(mute) => println!(" mute: {}", mute),
Err(err) => println!(" mute: error: {}", err),
}
println!();
}
Ok(())
}
fn format_playback_state(state: &PlaybackState) -> String {
match state {
PlaybackState::Stopped => "Stopped".to_string(),
PlaybackState::Playing => "Playing".to_string(),
PlaybackState::Paused => "Paused".to_string(),
PlaybackState::Transitioning => "Transitioning".to_string(),
PlaybackState::NoMedia => "NoMedia".to_string(),
PlaybackState::Unknown(raw) => format!("Unknown({})", raw),
}
}
fn format_position(pos: &PlaybackPositionInfo) -> String {
let rel = pos.rel_time.as_deref().unwrap_or("-");
let dur = pos.track_duration.as_deref().unwrap_or("-");
let track = pos
.track
.map(|t| t.to_string())
.unwrap_or_else(|| "-".to_string());
format!("track={} rel_time={} duration={}", track, rel, dur)
}

View File

@@ -82,6 +82,7 @@ fn main() -> Result<()> {
" [{}] {} | model={} | udn={} | location={}",
idx, info.friendly_name, info.model_name, info.udn, info.location
);
print_backend(" ", r);
print_capabilities(" ", &info.capabilities, &info.protocol);
}
@@ -96,6 +97,7 @@ fn main() -> Result<()> {
println!(" UDN : {}", info.udn);
println!(" Location : {}", info.location);
println!(" Protocol : {:?}", info.protocol);
print_backend(" ", renderer);
print_capabilities(" ", &info.capabilities, &info.protocol);
if let Some(upnp) = renderer.as_upnp() {
@@ -216,15 +218,24 @@ fn main() -> Result<()> {
fn print_capabilities(prefix: &str, caps: &RendererCapabilities, proto: &RendererProtocol) {
println!("{prefix}Capabilities:");
println!("{prefix} Protocol : {:?}", proto);
println!("{prefix} AVTransport : {}", caps.has_avtransport);
println!("{prefix} RendControl : {}", caps.has_rendering_control);
println!("{prefix} ConnManager : {}", caps.has_connection_manager);
println!("{prefix} OH Playlist : {}", caps.has_oh_playlist);
println!("{prefix} OH Volume : {}", caps.has_oh_volume);
println!("{prefix} OH Info : {}", caps.has_oh_info);
println!("{prefix} OH Time : {}", caps.has_oh_time);
println!("{prefix} OH Radio : {}", caps.has_oh_radio);
println!("{prefix} Protocol : {:?}", proto);
println!("{prefix} AVTransport : {}", caps.has_avtransport);
println!("{prefix} RendControl : {}", caps.has_rendering_control);
println!("{prefix} ConnManager : {}", caps.has_connection_manager);
println!("{prefix} LinkPlay HTTP : {}", caps.has_linkplay_http);
println!("{prefix} OH Playlist : {}", caps.has_oh_playlist);
println!("{prefix} OH Volume : {}", caps.has_oh_volume);
println!("{prefix} OH Info : {}", caps.has_oh_info);
println!("{prefix} OH Time : {}", caps.has_oh_time);
println!("{prefix} OH Radio : {}", caps.has_oh_radio);
}
fn print_backend(prefix: &str, renderer: &MusicRenderer) {
let backend = match renderer {
MusicRenderer::Upnp(_) => "UpnpRenderer (UPnP AV / DLNA)",
MusicRenderer::LinkPlay(_) => "LinkPlayRenderer (LinkPlay HTTP)",
};
println!("{prefix}Backend : {backend}");
}
fn dump_renderer_state(renderer: &MusicRenderer, label: &str) -> Result<()> {