implemente pmoparadise
This commit is contained in:
110
pmoparadise/examples/extract_track.rs
Normal file
110
pmoparadise/examples/extract_track.rs
Normal 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);
|
||||
}
|
||||
102
pmoparadise/examples/now_playing.rs
Normal file
102
pmoparadise/examples/now_playing.rs
Normal 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(())
|
||||
}
|
||||
91
pmoparadise/examples/stream_block.rs
Normal file
91
pmoparadise/examples/stream_block.rs
Normal 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(¤t_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(¤t_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(())
|
||||
}
|
||||
67
pmoparadise/examples/upnp_mediaserver.rs
Normal file
67
pmoparadise/examples/upnp_mediaserver.rs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user