retire le support des codec non flac de radio paradise

This commit is contained in:
2025-10-26 07:22:33 +01:00
parent e17722a99c
commit 9e4a8410e1
6 changed files with 13 additions and 203 deletions

View File

@@ -1,7 +1,7 @@
//! HTTP client for Radio Paradise API
use crate::error::{Error, Result};
use crate::models::{Bitrate, Block, EventId, NowPlaying};
use crate::models::{Block, EventId, NowPlaying};
use reqwest::Client;
use std::time::Duration;
use url::Url;
@@ -50,7 +50,6 @@ pub struct RadioParadiseClient {
pub(crate) client: Client,
api_base: String,
block_base: String,
bitrate: Bitrate,
channel: u8,
pub(crate) request_timeout: Duration,
pub(crate) block_timeout: Duration,
@@ -60,7 +59,7 @@ pub struct RadioParadiseClient {
impl RadioParadiseClient {
/// Create a new client with default settings
///
/// Uses FLAC quality (bitrate 4) and channel 0 (main mix)
/// Uses FLAC quality and channel 0 (main mix)
pub async fn new() -> Result<Self> {
Self::builder().build().await
}
@@ -78,7 +77,6 @@ impl RadioParadiseClient {
client,
api_base: DEFAULT_API_BASE.to_string(),
block_base: DEFAULT_BLOCK_BASE.to_string(),
bitrate: Bitrate::default(),
channel: 0,
request_timeout: Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECS),
block_timeout: Duration::from_secs(DEFAULT_BLOCK_TIMEOUT_SECS),
@@ -86,11 +84,6 @@ impl RadioParadiseClient {
}
}
/// Get the current bitrate setting
pub fn bitrate(&self) -> Bitrate {
self.bitrate
}
/// Get the current channel (0 = main mix)
pub fn channel(&self) -> u8 {
self.channel
@@ -109,13 +102,6 @@ impl RadioParadiseClient {
cloned
}
/// Clone the client with a different bitrate while preserving other settings.
pub fn clone_with_bitrate(&self, bitrate: Bitrate) -> Self {
let mut cloned = self.clone();
cloned.bitrate = bitrate;
cloned.next_block_url = None;
cloned
}
/// Get a block by event ID
///
@@ -147,7 +133,7 @@ impl RadioParadiseClient {
let mut url = Url::parse(&format!("{}/get_block", self.api_base))?;
url.query_pairs_mut()
.append_pair("bitrate", &self.bitrate.as_u8().to_string())
.append_pair("bitrate", "4") // FLAC lossless
.append_pair("info", "true")
.append_pair("channel", &self.channel.to_string());
@@ -251,7 +237,6 @@ pub struct ClientBuilder {
client: Option<Client>,
api_base: String,
block_base: String,
bitrate: Bitrate,
channel: u8,
request_timeout: Duration,
block_timeout: Duration,
@@ -265,7 +250,6 @@ impl Default for ClientBuilder {
client: None,
api_base: DEFAULT_API_BASE.to_string(),
block_base: DEFAULT_BLOCK_BASE.to_string(),
bitrate: Bitrate::default(),
channel: 0,
request_timeout: Duration::from_secs(DEFAULT_REQUEST_TIMEOUT_SECS),
block_timeout: Duration::from_secs(DEFAULT_BLOCK_TIMEOUT_SECS),
@@ -299,19 +283,6 @@ impl ClientBuilder {
self
}
/// Set the bitrate/quality level
///
/// # Example
///
/// ```
/// # use pmoparadise::{RadioParadiseClient, Bitrate};
/// let builder = RadioParadiseClient::builder()
/// .bitrate(Bitrate::Aac320);
/// ```
pub fn bitrate(mut self, bitrate: Bitrate) -> Self {
self.bitrate = bitrate;
self
}
/// Set the channel (0 = main mix, 1 = mellow, 2 = rock, 3 = world/etc)
pub fn channel(mut self, channel: u8) -> Self {
@@ -371,7 +342,6 @@ impl ClientBuilder {
client,
api_base: self.api_base,
block_base,
bitrate: self.bitrate,
channel: self.channel,
request_timeout: self.request_timeout,
block_timeout: self.block_timeout,
@@ -388,7 +358,6 @@ mod tests {
fn test_builder_defaults() {
let builder = ClientBuilder::default();
assert_eq!(builder.api_base, DEFAULT_API_BASE);
assert_eq!(builder.bitrate, Bitrate::Flac);
assert_eq!(builder.channel, 0);
}

View File

@@ -7,8 +7,8 @@
//! ## 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
//! - **Block Streaming**: Stream continuous FLAC blocks with automatic prefetching
//! - **FLAC Quality**: Lossless CD quality or better
//! - **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
@@ -49,7 +49,7 @@
//! ## 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.
//! FLAC file containing multiple songs with metadata indicating timing offsets.
//!
//! ```no_run
//! use pmoparadise::RadioParadiseClient;
@@ -72,24 +72,6 @@
//! }
//! ```
//!
//! ## 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<dyn std::error::Error>> {
//! 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.
@@ -135,7 +117,7 @@
//!
//! Radio Paradise streams use a block-based format:
//!
//! - Each block is a single audio file (FLAC or AAC)
//! - Each block is a single FLAC audio file
//! - 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/<start>-<end>.flac`
@@ -256,7 +238,7 @@ pub mod pmoserver_ext;
// 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 models::{Block, DurationMs, EventId, NowPlaying, Song};
pub use source::RadioParadiseSource;
pub use stream::BlockStream;

View File

@@ -31,12 +31,10 @@
//! # #[cfg(feature = "mediaserver")]
//! # {
//! use pmoparadise::mediaserver::RadioParadiseMediaServer;
//! use pmoparadise::Bitrate;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let server = RadioParadiseMediaServer::new()
//! .with_bitrate(Bitrate::Flac)
//! let server = RadioParadiseMediaServer::builder()
//! .with_friendly_name("Radio Paradise FLAC")
//! .build()
//! .await?;

View File

@@ -1,7 +1,6 @@
//! Radio Paradise UPnP Media Server implementation
use crate::error::{Error, Result};
use crate::models::Bitrate;
use crate::RadioParadiseClient;
use pmoserver::Server;
use pmoupnp::devices::Device;
@@ -55,7 +54,6 @@ pub struct MediaServerBuilder {
friendly_name: String,
manufacturer: String,
model_name: String,
bitrate: Bitrate,
channel: u8,
port: u16,
}
@@ -66,7 +64,6 @@ impl Default for MediaServerBuilder {
friendly_name: "Radio Paradise Media Server".to_string(),
manufacturer: "PMOMusic".to_string(),
model_name: "Radio Paradise Adapter".to_string(),
bitrate: Bitrate::Flac,
channel: 0,
port: 8080,
}
@@ -97,12 +94,6 @@ impl MediaServerBuilder {
self
}
/// Set the bitrate/quality level
pub fn with_bitrate(mut self, bitrate: Bitrate) -> Self {
self.bitrate = bitrate;
self
}
/// Set the Radio Paradise channel (0=main, 1=mellow, 2=rock, 3=world)
pub fn with_channel(mut self, channel: u8) -> Self {
self.channel = channel;
@@ -119,7 +110,6 @@ impl MediaServerBuilder {
pub async fn build(self) -> Result<RadioParadiseMediaServer> {
// Create Radio Paradise client
let client = RadioParadiseClient::builder()
.bitrate(self.bitrate)
.channel(self.channel)
.build()
.await?;
@@ -180,7 +170,6 @@ mod tests {
fn test_builder_defaults() {
let builder = MediaServerBuilder::default();
assert_eq!(builder.friendly_name, "Radio Paradise Media Server");
assert_eq!(builder.bitrate, Bitrate::Flac);
assert_eq!(builder.channel, 0);
assert_eq!(builder.port, 8080);
}
@@ -189,12 +178,10 @@ mod tests {
fn test_builder_customization() {
let builder = MediaServerBuilder::new()
.with_friendly_name("Custom Server")
.with_bitrate(Bitrate::Aac320)
.with_channel(1)
.with_port(9090);
assert_eq!(builder.friendly_name, "Custom Server");
assert_eq!(builder.bitrate, Bitrate::Aac320);
assert_eq!(builder.channel, 1);
assert_eq!(builder.port, 9090);
}

View File

@@ -122,65 +122,6 @@ where
}
}
/// Bitrate quality levels for Radio Paradise streams
///
/// Radio Paradise offers 5 quality levels:
/// - 0: 128 kbps MP3
/// - 1: AAC 64 kbps
/// - 2: AAC 128 kbps
/// - 3: AAC 320 kbps
/// - 4: FLAC lossless (CD quality or better)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[repr(u8)]
pub enum Bitrate {
/// 128 kbps MP3
Mp3_128 = 0,
/// AAC 64 kbps
Aac64 = 1,
/// AAC 128 kbps
Aac128 = 2,
/// AAC 320 kbps
Aac320 = 3,
/// FLAC lossless
Flac = 4,
}
impl Bitrate {
/// Convert from u8 value
pub fn from_u8(value: u8) -> Result<Self, crate::error::Error> {
match value {
0 => Ok(Self::Mp3_128),
1 => Ok(Self::Aac64),
2 => Ok(Self::Aac128),
3 => Ok(Self::Aac320),
4 => Ok(Self::Flac),
_ => Err(crate::error::Error::InvalidBitrate(value)),
}
}
/// Convert to u8 value
pub fn as_u8(self) -> u8 {
self as u8
}
/// Get human-readable description
pub fn description(&self) -> &'static str {
match self {
Self::Mp3_128 => "MP3 128 kbps",
Self::Aac64 => "AAC 64 kbps",
Self::Aac128 => "AAC 128 kbps",
Self::Aac320 => "AAC 320 kbps",
Self::Flac => "FLAC Lossless",
}
}
}
impl Default for Bitrate {
fn default() -> Self {
Self::Flac
}
}
/// Duration in milliseconds
pub type DurationMs = u64;
@@ -376,13 +317,6 @@ impl NowPlaying {
mod tests {
use super::*;
#[test]
fn test_bitrate_conversion() {
assert_eq!(Bitrate::from_u8(0).unwrap(), Bitrate::Mp3_128);
assert_eq!(Bitrate::from_u8(4).unwrap(), Bitrate::Flac);
assert!(Bitrate::from_u8(5).is_err());
}
#[test]
fn test_song_timing() {
let song = Song {

View File

@@ -4,7 +4,7 @@
//! à un serveur pmoserver.
use crate::paradise::{max_channel_id, ParadiseChannel, PlaylistEntry, ALL_CHANNELS};
use crate::{models::Bitrate, Block, NowPlaying, RadioParadiseClient, RadioParadiseSource};
use crate::{Block, NowPlaying, RadioParadiseClient, RadioParadiseSource};
use axum::{
body::Body,
extract::{Path, Query, State},
@@ -34,7 +34,6 @@ pub struct RadioParadiseState {
#[serde(default)]
struct ParadiseQuery {
channel: Option<u8>,
bitrate: Option<u8>,
}
#[derive(Debug, Default, Deserialize)]
@@ -106,14 +105,6 @@ impl RadioParadiseState {
client = client.clone_with_channel(channel);
}
if let Some(bitrate_id) = params.bitrate {
let bitrate = Bitrate::from_u8(bitrate_id).map_err(|e| {
tracing::warn!("Invalid Radio Paradise bitrate requested: {}", e);
StatusCode::BAD_REQUEST
})?;
client = client.clone_with_bitrate(bitrate);
}
Ok(client)
}
@@ -288,8 +279,7 @@ impl From<NowPlaying> for NowPlayingResponse {
get,
path = "/now-playing",
params(
("channel" = Option<u8>, Query, description = "Channel ID (0-3)"),
("bitrate" = Option<u8>, Query, description = "Bitrate ID (0-4)")
("channel" = Option<u8>, Query, description = "Channel ID (0-3)")
),
responses(
(status = 200, description = "Morceau en cours", body = NowPlayingResponse),
@@ -315,8 +305,7 @@ async fn get_now_playing(
get,
path = "/block/current",
params(
("channel" = Option<u8>, Query, description = "Channel ID (0-3)"),
("bitrate" = Option<u8>, Query, description = "Bitrate ID (0-4)")
("channel" = Option<u8>, Query, description = "Channel ID (0-3)")
),
responses(
(status = 200, description = "Block actuel", body = BlockResponse),
@@ -343,8 +332,7 @@ async fn get_current_block(
path = "/block/{event_id}",
params(
("event_id" = u64, Path, description = "Event ID du block"),
("channel" = Option<u8>, Query, description = "Channel ID (0-3)"),
("bitrate" = Option<u8>, Query, description = "Bitrate ID (0-4)")
("channel" = Option<u8>, Query, description = "Channel ID (0-3)")
),
responses(
(status = 200, description = "Block demandé", body = BlockResponse),
@@ -384,15 +372,6 @@ async fn get_channels() -> Json<Vec<ChannelInfo>> {
Json(channels)
}
/// Information sur un bitrate
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct BitrateInfo {
/// ID du bitrate (0-4)
pub id: u8,
/// Nom/description
pub name: String,
}
/// Statut opérationnel d'un canal Radio Paradise
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ChannelStatusResponse {
@@ -532,42 +511,6 @@ pub struct ChannelHistoryResponse {
pub entries: Vec<ChannelHistoryEntry>,
}
/// GET /bitrates - Liste les bitrates disponibles
#[utoipa::path(
get,
path = "/bitrates",
responses(
(status = 200, description = "Liste des bitrates disponibles", body = Vec<BitrateInfo>)
),
tag = "Radio Paradise"
)]
async fn get_bitrates() -> Json<Vec<BitrateInfo>> {
let bitrates = vec![
BitrateInfo {
id: 0,
name: "MP3 128 kbps".to_string(),
},
BitrateInfo {
id: 1,
name: "AAC 64 kbps".to_string(),
},
BitrateInfo {
id: 2,
name: "AAC 128 kbps".to_string(),
},
BitrateInfo {
id: 3,
name: "AAC 320 kbps".to_string(),
},
BitrateInfo {
id: 4,
name: "FLAC Lossless".to_string(),
},
];
Json(bitrates)
}
/// GET /channels/{channel_id}/status - Statut détaillé d'un canal
#[utoipa::path(
get,
@@ -840,7 +783,6 @@ async fn stream_channel(
get_channel_status,
get_channel_playlist,
get_channel_history,
get_bitrates,
stream_channel
),
components(schemas(
@@ -848,7 +790,6 @@ async fn stream_channel(
BlockResponse,
SongInfo,
ChannelInfo,
BitrateInfo,
ChannelStatusResponse,
ChannelPlaylistEntry,
ChannelPlaylistResponse,
@@ -872,7 +813,6 @@ pub fn create_api_router(state: RadioParadiseState) -> Router {
.route("/channels/{channel_id}/status", get(get_channel_status))
.route("/channels/{channel_id}/playlist", get(get_channel_playlist))
.route("/channels/{channel_id}/history", get(get_channel_history))
.route("/bitrates", get(get_bitrates))
.route("/stream", get(stream_channel))
.with_state(state)
}