implemente pmoparadise

This commit is contained in:
2025-10-12 19:52:41 +02:00
parent b8154a4837
commit 664be97ea6
28 changed files with 5266 additions and 5 deletions

View File

@@ -0,0 +1,110 @@
//! Example: Extract individual tracks from a FLAC block (requires `per-track` feature)
//!
//! This example demonstrates:
//! - Per-track extraction from FLAC blocks
//! - Exporting tracks to WAV files
//! - Alternative player-based seeking (recommended)
//!
//! **Warning**: This approach downloads and decodes entire blocks.
//! For most use cases, player-based seeking is more efficient.
//!
//! Run with: cargo run --example extract_track --features per-track
#[cfg(feature = "per-track")]
use pmoparadise::{RadioParadiseClient, Result};
#[cfg(feature = "per-track")]
use std::path::Path;
#[cfg(feature = "per-track")]
#[tokio::main]
async fn main() -> Result<()> {
// Initialize logging
#[cfg(feature = "logging")]
tracing_subscriber::fmt::init();
println!("Radio Paradise - Per-Track Extraction Demo");
println!("===========================================\n");
println!("WARNING: This feature downloads entire blocks (50-100MB)");
println!(" and performs CPU-intensive FLAC decoding.");
println!(" For most use cases, player-based seeking is better.\n");
// Create client
let client = RadioParadiseClient::new().await?;
// Get current block
let block = client.get_block(None).await?;
println!("Block Information:");
println!(" Event: {}", block.event);
println!(" Songs: {}", block.song_count());
println!(" URL: {}\n", block.url);
// Display all tracks
println!("Available Tracks:");
for (index, song) in block.songs_ordered() {
println!(" {}. {} - {} ({:.1}s)",
index,
song.artist,
song.title,
song.duration as f64 / 1000.0);
}
println!();
// Extract first track
let track_index = 0;
if let Some((_, song)) = block.songs_ordered().first() {
println!("Extracting Track {}:", track_index);
println!(" Artist: {}", song.artist);
println!(" Title: {}", song.title);
println!(" Album: {}\n", song.album);
println!("Downloading and decoding... (this may take a while)");
// Open track stream
let mut track_stream = client.open_track_stream(&block, track_index).await?;
println!("Track Metadata:");
println!(" Sample Rate: {} Hz", track_stream.metadata.sample_rate);
println!(" Channels: {}", track_stream.metadata.channels);
println!(" Bits Per Sample: {}", track_stream.metadata.bits_per_sample);
println!(" Total Samples: {}", track_stream.metadata.total_samples);
println!();
// Export to WAV
let output_path = Path::new("track.wav");
println!("Exporting to {:?}...", output_path);
track_stream.export_wav(output_path)?;
println!("✓ Export complete!\n");
}
// Show alternative: player-based seeking
println!("RECOMMENDED ALTERNATIVE: Player-Based Seeking");
println!("=============================================\n");
for (index, song) in block.songs_ordered().into_iter().take(3) {
let (start, duration) = client.track_position_seconds(&block, index)?;
println!("Track {}: {} - {}", index, song.artist, song.title);
println!(" mpv command:");
println!(" mpv --start={:.3} --length={:.3} '{}'", start, duration, block.url);
println!(" ffmpeg command (extract to file):");
println!(" ffmpeg -ss {:.3} -t {:.3} -i '{}' -c copy track_{}.flac",
start, duration, block.url, index);
println!();
}
println!("These methods are much more efficient as they:");
println!(" - Don't download the entire block");
println!(" - Use the player's optimized seeking");
println!(" - Start playback immediately");
println!(" - Preserve original quality (with -c copy)");
Ok(())
}
#[cfg(not(feature = "per-track"))]
fn main() {
eprintln!("ERROR: This example requires the 'per-track' feature.");
eprintln!("Run with: cargo run --example extract_track --features per-track");
std::process::exit(1);
}

View File

@@ -0,0 +1,102 @@
//! Example: Display currently playing song and block information
//!
//! This example demonstrates:
//! - Creating a Radio Paradise client
//! - Fetching the current block
//! - Displaying song metadata
//! - Generating cover image URLs
//!
//! Run with: cargo run --example now_playing
use pmoparadise::{RadioParadiseClient, Result};
#[tokio::main]
async fn main() -> Result<()> {
// Initialize logging (optional)
#[cfg(feature = "logging")]
tracing_subscriber::fmt::init();
println!("Radio Paradise - Now Playing");
println!("=============================\n");
// Create client with default settings (FLAC quality, channel 0)
let client = RadioParadiseClient::new().await?;
// Get what's currently playing
let now_playing = client.now_playing().await?;
let block = &now_playing.block;
// Display block information
println!("Block Information:");
println!(" Event ID: {}", block.event);
println!(" Next Event: {}", block.end_event);
println!(" Duration: {:.1} minutes", block.length as f64 / 60000.0);
println!(" Songs in block: {}", block.song_count());
println!(" Stream URL: {}\n", block.url);
// Display current song (if available)
if let Some(song) = &now_playing.current_song {
println!("Now Playing:");
println!(" Title: {}", song.title);
println!(" Artist: {}", song.artist);
println!(" Album: {}", song.album);
if let Some(year) = song.year {
println!(" Year: {}", year);
}
if let Some(rating) = song.rating {
println!(" Rating: {:.1}/10", rating);
}
println!(" Duration: {}:{:02}",
song.duration / 60000,
(song.duration % 60000) / 1000);
// Display cover URL
if let Some(cover) = &song.cover {
if let Some(cover_url) = block.cover_url(cover) {
println!(" Cover: {}", cover_url);
}
}
println!();
}
// Display all songs in the block
println!("All Songs in This Block:");
println!("------------------------");
for (index, song) in block.songs_ordered() {
let start_sec = song.elapsed / 1000;
let duration_sec = song.duration / 1000;
println!(
"{}. [{:02}:{:02}] {} - {} ({:02}:{:02})",
index + 1,
start_sec / 60,
start_sec % 60,
song.artist,
song.title,
duration_sec / 60,
duration_sec % 60
);
println!(" Album: {}", song.album);
if let Some(year) = song.year {
print!(" Year: {}", year);
}
if let Some(rating) = song.rating {
print!(" Rating: {:.1}/10", rating);
}
println!("\n");
}
// Show how to get the next block
println!("Fetching Next Block...");
let next_block = client.get_block(Some(block.end_event)).await?;
println!(" Next block event: {}", next_block.event);
println!(" Songs in next block: {}", next_block.song_count());
if let Some((_, first_song)) = next_block.songs_ordered().first() {
println!(" First song: {} - {}", first_song.artist, first_song.title);
}
Ok(())
}

View File

@@ -0,0 +1,91 @@
//! Example: Stream a Radio Paradise block with prefetching
//!
//! This example demonstrates:
//! - Streaming block audio data
//! - Writing to a file or piping to a player
//! - Prefetching the next block for gapless playback
//! - Continuous playback loop
//!
//! Run with: cargo run --example stream_block
//!
//! To play directly with mpv:
//! cargo run --example stream_block | mpv --no-cache --demuxer=+lavf -
use futures::StreamExt;
use pmoparadise::{RadioParadiseClient, Result};
use std::io::Write;
#[tokio::main]
async fn main() -> Result<()> {
// Initialize logging (optional)
#[cfg(feature = "logging")]
tracing_subscriber::fmt::init();
eprintln!("Radio Paradise - Block Streaming Demo");
eprintln!("======================================\n");
// Create client
let mut client = RadioParadiseClient::builder()
.bitrate(pmoparadise::Bitrate::Flac)
.build()
.await?;
eprintln!("Client configured for FLAC streaming\n");
// Get current block
let current_block = client.get_block(None).await?;
eprintln!("Current Block:");
eprintln!(" Event: {}", current_block.event);
eprintln!(" Songs: {}", current_block.song_count());
eprintln!(" Duration: {:.1} minutes", current_block.length as f64 / 60000.0);
eprintln!(" URL: {}\n", current_block.url);
// Display tracklist
eprintln!("Tracklist:");
for (index, song) in current_block.songs_ordered() {
eprintln!(" {}. {} - {}", index + 1, song.artist, song.title);
}
eprintln!();
// Prefetch next block in advance
eprintln!("Prefetching next block...");
client.prefetch_next(&current_block).await?;
eprintln!("Next block prefetched: {}\n", client.next_block_url().unwrap());
// Stream the block
eprintln!("Streaming block... (writing to stdout)");
eprintln!("Tip: Pipe to a player like: cargo run --example stream_block | mpv -\n");
let mut stream = client.stream_block_from_metadata(&current_block).await?;
let mut total_bytes = 0u64;
let mut stdout = std::io::stdout();
while let Some(chunk_result) = stream.next().await {
let chunk = chunk_result?;
total_bytes += chunk.len() as u64;
// Write to stdout (can be piped to a player)
stdout.write_all(&chunk)?;
stdout.flush()?;
// Progress indicator (to stderr so it doesn't interfere with piped audio)
if total_bytes % (1024 * 1024) == 0 {
eprintln!(" Downloaded: {:.1} MB", total_bytes as f64 / 1024.0 / 1024.0);
}
}
eprintln!("\nBlock streaming complete!");
eprintln!("Total downloaded: {:.2} MB", total_bytes as f64 / 1024.0 / 1024.0);
// In a real application, you would now:
// 1. Get the next block using prefetched metadata
// 2. Stream it seamlessly
// 3. Prefetch the following block
// 4. Repeat for continuous playback
eprintln!("\nFor continuous playback, you would now stream the next block:");
eprintln!(" Event: {}", current_block.end_event);
Ok(())
}

View File

@@ -0,0 +1,67 @@
//! Example: Run a UPnP/DLNA Media Server for Radio Paradise
//!
//! This example demonstrates:
//! - Creating a UPnP Media Server
//! - Exposing Radio Paradise blocks and songs
//! - SSDP discovery and announcements
//! - ContentDirectory and ConnectionManager services
//!
//! Run with: cargo run --example upnp_mediaserver --features mediaserver
//!
//! The server will be discoverable by DLNA/UPnP clients on your network.
#[cfg(feature = "mediaserver")]
use pmoparadise::mediaserver::RadioParadiseMediaServer;
#[cfg(feature = "mediaserver")]
use pmoparadise::Bitrate;
#[cfg(feature = "mediaserver")]
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logging
#[cfg(feature = "logging")]
tracing_subscriber::fmt::init();
println!("Radio Paradise UPnP Media Server");
println!("=================================\n");
// Create the media server
println!("Creating media server...");
let server = RadioParadiseMediaServer::builder()
.with_friendly_name("Radio Paradise FLAC")
.with_manufacturer("PMOMusic")
.with_model_name("Radio Paradise Adapter v0.1")
.with_bitrate(Bitrate::Flac)
.with_channel(0) // Main mix
.with_port(8080)
.build()
.await?;
println!("Media Server created!");
println!(" UDN: {}", server.udn());
println!(" Port: 8080");
println!(" Quality: FLAC Lossless");
println!(" Channel: Main Mix (0)");
println!();
println!("Server is now discoverable on your network.");
println!("Look for 'Radio Paradise FLAC' in your DLNA/UPnP clients.");
println!();
println!("ContentDirectory service available at:");
println!(" http://localhost:8080/upnp/device/{}/service/ContentDirectory", server.udn());
println!();
println!("Press Ctrl+C to stop the server.");
println!();
// Run the server
server.run().await?;
Ok(())
}
#[cfg(not(feature = "mediaserver"))]
fn main() {
eprintln!("ERROR: This example requires the 'mediaserver' feature.");
eprintln!("Run with: cargo run --example upnp_mediaserver --features mediaserver");
std::process::exit(1);
}