230 lines
7.8 KiB
Rust
230 lines
7.8 KiB
Rust
// pmocontrol/examples/renderer_demo.rs
|
|
|
|
use anyhow::{anyhow, Result};
|
|
use pmocontrol::{ControlPoint, Renderer};
|
|
use std::env;
|
|
use std::io::{self, Write};
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
fn main() -> Result<()> {
|
|
// -------------------------------------------------------------------------
|
|
// CLI arguments
|
|
//
|
|
// Usage:
|
|
// renderer_demo
|
|
// -> URI par défaut, renderer index 0
|
|
//
|
|
// renderer_demo 1
|
|
// -> URI par défaut, renderer index 1
|
|
//
|
|
// renderer_demo https://.../track.flac
|
|
// -> cette URI, renderer index 0
|
|
//
|
|
// renderer_demo https://.../track.flac 1
|
|
// -> cette URI, renderer index 1
|
|
// -------------------------------------------------------------------------
|
|
let default_uri =
|
|
"https://audio-fb.radioparadise.com/chan/1/x/1117/4/g/1117-3.flac".to_string();
|
|
let mut uri = default_uri.clone();
|
|
let mut renderer_index: usize = 0;
|
|
|
|
let args: Vec<String> = env::args().collect();
|
|
if args.len() >= 2 {
|
|
let first = &args[1];
|
|
if let Ok(idx) = first.parse::<usize>() {
|
|
// cas: renderer_demo 1 [URI]
|
|
renderer_index = idx;
|
|
if args.len() >= 3 {
|
|
uri = args[2].clone();
|
|
}
|
|
} else {
|
|
// cas: renderer_demo URI [INDEX]
|
|
uri = first.clone();
|
|
if args.len() >= 3 {
|
|
if let Ok(idx) = args[2].parse::<usize>() {
|
|
renderer_index = idx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("Using URI : {}", uri);
|
|
println!("Requested renderer index : {}", renderer_index);
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 1. Démarrer le control point et laisser la découverte tourner un peu
|
|
// -------------------------------------------------------------------------
|
|
let cp = ControlPoint::spawn(5)?;
|
|
thread::sleep(Duration::from_secs(5));
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 2. Récupérer la liste des renderers (handles haut niveau)
|
|
// -------------------------------------------------------------------------
|
|
let mut renderers: Vec<Renderer> = cp.list_renderer_handles();
|
|
|
|
// Filtre : on ignore le renderer PMOMusic interne en développement
|
|
renderers.retain(|r| {
|
|
!r.friendly_name()
|
|
.to_ascii_lowercase()
|
|
.contains("pmomusic audio renderer")
|
|
});
|
|
|
|
if renderers.is_empty() {
|
|
println!("No valid UPnP MediaRenderer discovered.");
|
|
return Ok(());
|
|
}
|
|
|
|
println!("\nDiscovered MediaRenderers:");
|
|
for (idx, r) in renderers.iter().enumerate() {
|
|
println!(
|
|
" [{}] {} | model={} | manufacturer={} | has_avt={} | has_rc={}",
|
|
idx,
|
|
r.friendly_name(),
|
|
r.info.model_name,
|
|
r.info.manufacturer,
|
|
r.has_avtransport(),
|
|
r.has_rendering_control(),
|
|
);
|
|
}
|
|
|
|
if renderer_index >= renderers.len() {
|
|
return Err(anyhow!(
|
|
"Renderer index {} out of range (0..={})",
|
|
renderer_index,
|
|
renderers.len().saturating_sub(1)
|
|
));
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 3. Sélection du renderer
|
|
// -------------------------------------------------------------------------
|
|
let renderer = &renderers[renderer_index];
|
|
|
|
println!("\nSelected renderer (index {}):", renderer_index);
|
|
println!(" Name : {}", renderer.friendly_name());
|
|
println!(" Model : {}", renderer.info.model_name);
|
|
println!(" Manufacturer: {}", renderer.info.manufacturer);
|
|
println!(" UDN : {}", renderer.info.udn);
|
|
println!(" Location : {}", renderer.info.location);
|
|
println!(" has_avt : {}", renderer.has_avtransport());
|
|
println!(" has_rc : {}", renderer.has_rendering_control());
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 4. Si RenderingControl dispo : tester get/set volume + mute
|
|
// (set_* remet juste la même valeur pour ne rien changer en pratique)
|
|
// -------------------------------------------------------------------------
|
|
if renderer.has_rendering_control() {
|
|
println!("\n[RenderingControl]");
|
|
match renderer.get_master_volume() {
|
|
Ok(vol) => {
|
|
println!(" Current master volume: {}", vol);
|
|
if let Err(e) = renderer.set_master_volume(vol) {
|
|
println!(" set_master_volume({}) failed: {}", vol, e);
|
|
} else {
|
|
println!(" set_master_volume({}) OK (no-op)", vol);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!(" get_master_volume() failed: {}", e);
|
|
}
|
|
}
|
|
|
|
match renderer.get_master_mute() {
|
|
Ok(muted) => {
|
|
println!(" Current master mute : {}", muted);
|
|
if let Err(e) = renderer.set_master_mute(muted) {
|
|
println!(" set_master_mute({}) failed: {}", muted, e);
|
|
} else {
|
|
println!(" set_master_mute({}) OK (no-op)", muted);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!(" get_master_mute() failed: {}", e);
|
|
}
|
|
}
|
|
} else {
|
|
println!("\n[RenderingControl]");
|
|
println!(" Renderer has no RenderingControl service.");
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// 5. Si AVTransport dispo : tester play_uri / seek / pause / stop
|
|
// -------------------------------------------------------------------------
|
|
if !renderer.has_avtransport() {
|
|
println!("\n[AVTransport]");
|
|
println!(" Renderer has no AVTransport service, skipping playback tests.");
|
|
return Ok(());
|
|
}
|
|
|
|
println!("\n[AVTransport]");
|
|
|
|
// Helper pour afficher l'état courant
|
|
let dump_info = |label: &str| -> Result<()> {
|
|
let info = renderer
|
|
.avtransport()
|
|
.and_then(|_| renderer.avtransport().unwrap().get_transport_info(0))?;
|
|
println!("\n [{}]", label);
|
|
println!(" State : {}", info.current_transport_state);
|
|
println!(" Status : {}", info.current_transport_status);
|
|
println!(" Speed : {}", info.current_speed);
|
|
Ok(())
|
|
};
|
|
|
|
// Set + Play
|
|
println!("\n Calling play_uri(...)");
|
|
renderer.play_uri(&uri, "")?;
|
|
println!(" play_uri: OK");
|
|
thread::sleep(Duration::from_secs(8));
|
|
let _ = dump_info("After play_uri");
|
|
|
|
// Seek (si supporté)
|
|
println!("\n Calling seek_rel_time(\"00:01:00\")...");
|
|
match renderer.seek_rel_time("00:01:00") {
|
|
Ok(()) => {
|
|
println!(" seek_rel_time: OK");
|
|
thread::sleep(Duration::from_secs(3));
|
|
let _ = dump_info("After seek_rel_time");
|
|
}
|
|
Err(e) => {
|
|
println!(" seek_rel_time failed: {}", e);
|
|
}
|
|
}
|
|
|
|
// Pause (ENTER pour laisser jouer)
|
|
print!("\nPress ENTER to Pause...");
|
|
io::stdout().flush().ok();
|
|
let _ = io::stdin().read_line(&mut String::new());
|
|
|
|
println!("\n Calling pause()...");
|
|
match renderer.pause() {
|
|
Ok(()) => {
|
|
println!(" pause: OK");
|
|
thread::sleep(Duration::from_secs(2));
|
|
let _ = dump_info("After pause");
|
|
}
|
|
Err(e) => {
|
|
println!(" pause failed: {}", e);
|
|
}
|
|
}
|
|
|
|
// Stop
|
|
print!("\nPress ENTER to Stop...");
|
|
io::stdout().flush().ok();
|
|
let _ = io::stdin().read_line(&mut String::new());
|
|
|
|
println!("\n Calling stop()...");
|
|
match renderer.stop() {
|
|
Ok(()) => {
|
|
println!(" stop: OK");
|
|
let _ = dump_info("After stop");
|
|
}
|
|
Err(e) => {
|
|
println!(" stop failed: {}", e);
|
|
}
|
|
}
|
|
|
|
println!("\nDone.");
|
|
Ok(())
|
|
}
|