//! # pmoparadise - Radio Paradise Client for Rust //! //! `pmoparadise` is an idiomatic Rust client library for accessing Radio Paradise's //! streaming API. It provides metadata retrieval, block streaming, and optional //! per-track extraction from FLAC blocks. //! //! ## Features //! //! - **Metadata Access**: Get current and historical block metadata with song information //! - **Block Streaming**: Stream continuous FLAC/AAC blocks with automatic prefetching //! - **Multiple Quality Levels**: Support for MP3, AAC (64/128/320 kbps), and FLAC //! - **Per-Track Extraction** (optional): Extract individual tracks from FLAC blocks //! - **Async/Await**: Built on tokio for efficient async I/O //! - **Type-Safe**: Strongly typed API with comprehensive error handling //! //! ## Quick Start //! //! ```no_run //! use pmoparadise::RadioParadiseClient; //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! // Create a client //! let client = RadioParadiseClient::new().await?; //! //! // Get what's currently playing //! let now_playing = client.now_playing().await?; //! //! if let Some(song) = &now_playing.current_song { //! println!("Now Playing: {} - {}", song.artist, song.title); //! println!("Album: {}", song.album); //! } //! //! // Get all songs in the current block //! for (index, song) in now_playing.block.songs_ordered() { //! println!(" {}. {} - {} ({}s)", //! index, //! song.artist, //! song.title, //! song.duration / 1000); //! } //! //! Ok(()) //! } //! ``` //! //! ## Streaming Blocks //! //! Radio Paradise broadcasts music in continuous "blocks" - each block is a single //! FLAC or AAC file containing multiple songs with metadata indicating timing offsets. //! //! ```no_run //! use pmoparadise::RadioParadiseClient; //! use futures::StreamExt; //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! let client = RadioParadiseClient::new().await?; //! let block = client.get_block(None).await?; //! //! // Stream the block //! let mut stream = client.stream_block_from_metadata(&block).await?; //! //! while let Some(chunk) = stream.next().await { //! let bytes = chunk?; //! // Feed to audio player, write to file, etc. //! } //! //! Ok(()) //! } //! ``` //! //! ## Quality Levels //! //! Radio Paradise offers multiple quality levels via the [`Bitrate`] enum: //! //! ```no_run //! use pmoparadise::{RadioParadiseClient, Bitrate}; //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! let client = RadioParadiseClient::builder() //! .bitrate(Bitrate::Aac320) //! .build() //! .await?; //! //! Ok(()) //! } //! ``` //! //! ## Per-Track Extraction (Feature: `per-track`) //! //! **Important**: This is an advanced feature with significant tradeoffs. //! See the [`track`] module documentation for details. //! //! Most applications should stream blocks and use player-based seeking instead. //! //! ```no_run //! # #[cfg(feature = "per-track")] //! # { //! use pmoparadise::RadioParadiseClient; //! use std::path::Path; //! //! #[tokio::main] //! async fn main() -> Result<(), Box> { //! let client = RadioParadiseClient::new().await?; //! let block = client.get_block(None).await?; //! //! // Extract first track to WAV //! let mut track = client.open_track_stream(&block, 0).await?; //! track.export_wav(Path::new("track.wav"))?; //! //! // Or get position for player-based seeking (recommended) //! let (start, duration) = client.track_position_seconds(&block, 0)?; //! println!("Play with: mpv --start={} --length={} {}", start, duration, block.url); //! //! Ok(()) //! } //! # } //! ``` //! //! ## Architecture //! //! The API is organized into several modules: //! //! - [`client`]: Main HTTP client for API access //! - [`models`]: Data structures for blocks, songs, and metadata //! - [`stream`]: Block streaming functionality //! - [`track`]: Per-track extraction (feature-gated) //! - [`error`]: Error types and result aliases //! //! ## Radio Paradise Block Format //! //! Radio Paradise streams use a block-based format: //! //! - Each block is a single audio file (FLAC or AAC) //! - Blocks contain multiple songs (typically 10-15 minutes total) //! - Metadata includes timing offsets (`song[i].elapsed` in ms) for each song //! - Block URLs follow the pattern: `https://apps.radioparadise.com/blocks/chan/0/4/-.flac` //! - The `end_event` of one block is the `event` of the next, enabling seamless transitions //! //! ## Best Practices //! //! ### For Continuous Playback //! //! 1. Get current block with `get_block(None)` //! 2. Stream block with `stream_block_from_metadata()` //! 3. Use `prefetch_next()` to prepare the next block //! 4. When current block ends, stream the next block seamlessly //! //! ### For Per-Song Seeking //! //! **Recommended approach** (efficient): //! ```bash //! # Use your audio player's seek capability //! mpv --start=123.5 --length=234.0 //! ``` //! //! **Alternative** (resource-intensive, requires `per-track` feature): //! - Download and decode block //! - Extract specific track to PCM/WAV //! //! ## Error Handling //! //! All operations return `Result` with detailed error types: //! //! ```no_run //! use pmoparadise::{RadioParadiseClient, Error}; //! //! #[tokio::main] //! async fn main() { //! let client = RadioParadiseClient::new().await.unwrap(); //! //! match client.get_block(Some(99999999)).await { //! Ok(block) => println!("Got block: {}", block.event), //! Err(Error::Http(e)) => eprintln!("Network error: {}", e), //! Err(Error::Json(e)) => eprintln!("Parse error: {}", e), //! Err(e) => eprintln!("Other error: {}", e), //! } //! } //! ``` //! //! ## Cargo Features //! //! - `default = ["metadata-only"]`: Standard metadata and streaming (no FLAC decoding) //! - `per-track`: Enable FLAC decoding and per-track extraction (adds `claxon`, `hound`, `tempfile`) //! - `logging`: Enable tracing logs for debugging //! - `mediaserver`: Enable UPnP/DLNA Media Server (adds `pmoupnp`, `pmoserver`, `pmodidl`) //! //! ## See Also //! //! - [Radio Paradise](https://radioparadise.com) - Official website //! - [Radio Paradise API](https://api.radioparadise.com) - API documentation pub mod client; pub mod error; pub mod models; pub mod stream; #[cfg(feature = "per-track")] pub mod track; #[cfg(feature = "mediaserver")] pub mod mediaserver; // Re-exports for convenience pub use client::{ClientBuilder, RadioParadiseClient}; pub use error::{Error, Result}; pub use models::{Bitrate, Block, DurationMs, EventId, NowPlaying, Song}; pub use stream::BlockStream; #[cfg(feature = "per-track")] pub use track::{TrackMetadata, TrackStream}; #[cfg(feature = "mediaserver")] pub use mediaserver::{RadioParadiseMediaServer, MediaServerBuilder}; // Version information pub const VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg(test)] mod tests { use super::*; #[test] fn test_version() { assert!(!VERSION.is_empty()); } }