Add a ArylicTCPRenderer
This commit is contained in:
119
pmocontrol/examples/arylic_tcp_demo.rs
Normal file
119
pmocontrol/examples/arylic_tcp_demo.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use std::io;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use pmocontrol::{
|
||||
ControlPoint, DeviceRegistryRead, MusicRenderer, PlaybackPosition, PlaybackPositionInfo,
|
||||
PlaybackState, PlaybackStatus, TransportControl, VolumeControl,
|
||||
};
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let _ = tracing_subscriber::fmt::try_init();
|
||||
println!("Starting PMOMusic Arylic TCP 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={} | Arylic TCP={} | LinkPlay HTTP={}",
|
||||
idx,
|
||||
info.friendly_name,
|
||||
info.model_name,
|
||||
info.capabilities.has_arylic_tcp,
|
||||
info.capabilities.has_linkplay_http
|
||||
);
|
||||
}
|
||||
|
||||
let arylic_renderers: Vec<MusicRenderer> = {
|
||||
let reg = registry.read().unwrap();
|
||||
renderers
|
||||
.iter()
|
||||
.filter(|info| info.capabilities.has_arylic_tcp)
|
||||
.filter_map(|info| MusicRenderer::from_registry_info(info.clone(), ®))
|
||||
.collect()
|
||||
};
|
||||
|
||||
if arylic_renderers.is_empty() {
|
||||
println!("\nNo Arylic TCP-capable renderers detected.");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
println!("\nArylic TCP renderer status:");
|
||||
for renderer in arylic_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!(" attempting pause/play test...");
|
||||
if let Err(err) = renderer.pause() {
|
||||
println!(" pause error: {}", err);
|
||||
} else {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
println!(" pause OK");
|
||||
}
|
||||
|
||||
if let Err(err) = renderer.play() {
|
||||
println!(" play error: {}", err);
|
||||
} else {
|
||||
println!(" play OK");
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -152,17 +152,12 @@ fn format_playback_state(state: &PlaybackState) -> String {
|
||||
|
||||
/// Affichage lisible d'un PlaybackPositionInfo.
|
||||
fn format_position(pos: &PlaybackPositionInfo) -> String {
|
||||
let track = pos.track.map(|t| t.to_string()).unwrap_or_else(|| "-".to_string());
|
||||
let rel = pos
|
||||
.rel_time
|
||||
.as_deref()
|
||||
.unwrap_or("-")
|
||||
.to_string();
|
||||
let dur = pos
|
||||
.track_duration
|
||||
.as_deref()
|
||||
.unwrap_or("-")
|
||||
.to_string();
|
||||
let track = pos
|
||||
.track
|
||||
.map(|t| t.to_string())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
let rel = pos.rel_time.as_deref().unwrap_or("-").to_string();
|
||||
let dur = pos.track_duration.as_deref().unwrap_or("-").to_string();
|
||||
|
||||
format!("track={} rel_time={} duration={}", track, rel, dur)
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
// Default URI if none is provided on the CLI.
|
||||
const DEFAULT_TEST_URI: &str =
|
||||
"https://audio-fb.radioparadise.com/chan/1/x/1117/4/g/1117-3.flac";
|
||||
const DEFAULT_TEST_URI: &str = "https://audio-fb.radioparadise.com/chan/1/x/1117/4/g/1117-3.flac";
|
||||
|
||||
// Extra wait after play_uri() so slow renderers (e.g. Arylic H50) have time
|
||||
// to prefetch and actually start playback.
|
||||
@@ -103,48 +102,42 @@ fn main() -> Result<()> {
|
||||
if let Some(upnp) = renderer.as_upnp() {
|
||||
println!(
|
||||
" [UPnP] AVTransport control URL : {}",
|
||||
upnp
|
||||
.info
|
||||
upnp.info
|
||||
.avtransport_control_url
|
||||
.as_deref()
|
||||
.unwrap_or("<none>")
|
||||
);
|
||||
println!(
|
||||
" [UPnP] AVTransport service type: {}",
|
||||
upnp
|
||||
.info
|
||||
upnp.info
|
||||
.avtransport_service_type
|
||||
.as_deref()
|
||||
.unwrap_or("<none>")
|
||||
);
|
||||
println!(
|
||||
" [UPnP] RenderingControl control URL : {}",
|
||||
upnp
|
||||
.info
|
||||
upnp.info
|
||||
.rendering_control_control_url
|
||||
.as_deref()
|
||||
.unwrap_or("<none>")
|
||||
);
|
||||
println!(
|
||||
" [UPnP] RenderingControl service type: {}",
|
||||
upnp
|
||||
.info
|
||||
upnp.info
|
||||
.rendering_control_service_type
|
||||
.as_deref()
|
||||
.unwrap_or("<none>")
|
||||
);
|
||||
println!(
|
||||
" [UPnP] ConnectionManager control URL : {}",
|
||||
upnp
|
||||
.info
|
||||
upnp.info
|
||||
.connection_manager_control_url
|
||||
.as_deref()
|
||||
.unwrap_or("<none>")
|
||||
);
|
||||
println!(
|
||||
" [UPnP] ConnectionManager service type: {}",
|
||||
upnp
|
||||
.info
|
||||
upnp.info
|
||||
.connection_manager_service_type
|
||||
.as_deref()
|
||||
.unwrap_or("<none>")
|
||||
@@ -223,6 +216,7 @@ fn print_capabilities(prefix: &str, caps: &RendererCapabilities, proto: &Rendere
|
||||
println!("{prefix} RendControl : {}", caps.has_rendering_control);
|
||||
println!("{prefix} ConnManager : {}", caps.has_connection_manager);
|
||||
println!("{prefix} LinkPlay HTTP : {}", caps.has_linkplay_http);
|
||||
println!("{prefix} Arylic TCP : {}", caps.has_arylic_tcp);
|
||||
println!("{prefix} OH Playlist : {}", caps.has_oh_playlist);
|
||||
println!("{prefix} OH Volume : {}", caps.has_oh_volume);
|
||||
println!("{prefix} OH Info : {}", caps.has_oh_info);
|
||||
@@ -234,6 +228,8 @@ fn print_backend(prefix: &str, renderer: &MusicRenderer) {
|
||||
let backend = match renderer {
|
||||
MusicRenderer::Upnp(_) => "UpnpRenderer (UPnP AV / DLNA)",
|
||||
MusicRenderer::LinkPlay(_) => "LinkPlayRenderer (LinkPlay HTTP)",
|
||||
MusicRenderer::ArylicTcp(_) => "ArylicTcpRenderer (ARylic TCP Protocol)",
|
||||
MusicRenderer::HybridUpnpArylic{..} => "Hybrid UpnpArylicRenderer (UPnP AV / DLNA + ARylic TCP Protocol)",
|
||||
};
|
||||
println!("{prefix}Backend : {backend}");
|
||||
}
|
||||
@@ -293,12 +289,7 @@ fn dump_renderer_state(renderer: &MusicRenderer, label: &str) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn progress_monitor(
|
||||
renderer: &MusicRenderer,
|
||||
label: &str,
|
||||
iterations: usize,
|
||||
interval_secs: u64,
|
||||
) {
|
||||
fn progress_monitor(renderer: &MusicRenderer, label: &str, iterations: usize, interval_secs: u64) {
|
||||
println!(
|
||||
"\n[{label}] polling playback state/position {} times (every {} s)...",
|
||||
iterations, interval_secs
|
||||
@@ -343,14 +334,16 @@ fn volume_demo(renderer: &MusicRenderer) -> Result<()> {
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
// Small volume bump if possible
|
||||
let new_volume = original.saturating_add(5).min(u16::MAX);
|
||||
let new_volume = original.saturating_add(10).min(u16::MAX);
|
||||
println!(" Bumping volume to : {}", new_volume);
|
||||
renderer.set_volume(new_volume)?;
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
println!(" Volume after bump : {}", renderer.volume()?);
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
|
||||
println!(" Restoring original volume: {}", original);
|
||||
println!(" Volume after reset : {}", renderer.volume()?);
|
||||
renderer.set_volume(original)?;
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use anyhow::{Result, anyhow};
|
||||
use pmocontrol::{ControlPoint, DeviceRegistryRead, RendererInfo};
|
||||
use std::env;
|
||||
use std::io::{self, Write};
|
||||
@@ -66,11 +66,7 @@ fn main() -> Result<()> {
|
||||
for (idx, r) in renderers.iter().enumerate() {
|
||||
println!(
|
||||
" [{}] {} | model={} | udn={} | location={}",
|
||||
idx,
|
||||
r.friendly_name,
|
||||
r.model_name,
|
||||
r.udn,
|
||||
r.location,
|
||||
idx, r.friendly_name, r.model_name, r.udn, r.location,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user