lastest correction on webapp
This commit is contained in:
@@ -28,7 +28,9 @@
|
||||
//!
|
||||
//! if let Some(song) = &now_playing.current_song {
|
||||
//! println!("Now Playing: {} - {}", song.artist, song.title);
|
||||
//! println!("Album: {}", song.album);
|
||||
//! if let Some(album) = &song.album {
|
||||
//! println!("Album: {}", album);
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Get all songs in the current block
|
||||
@@ -189,33 +191,24 @@
|
||||
//! # #[cfg(feature = "cache")]
|
||||
//! # {
|
||||
//! use pmoparadise::{RadioParadiseClient, RadioParadiseSource};
|
||||
//! use pmocovers::Cache as CoverCache;
|
||||
//! use pmoaudiocache::AudioCache;
|
||||
//! use std::sync::Arc;
|
||||
//!
|
||||
//! #[tokio::main]
|
||||
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
//! // Create caches
|
||||
//! let cover_cache = Arc::new(CoverCache::new("./cache/covers", 500)?);
|
||||
//! let audio_cache = Arc::new(AudioCache::new("./cache/audio", 100)?);
|
||||
//! let cover_cache = Arc::new(pmocovers::cache::new_cache("./cache/covers", 500)?);
|
||||
//! let audio_cache = Arc::new(pmoaudiocache::cache::new_cache("./cache/audio", 100)?);
|
||||
//!
|
||||
//! // Create client and source with caching
|
||||
//! let client = RadioParadiseClient::new().await?;
|
||||
//! let source = RadioParadiseSource::new_with_cache(
|
||||
//! client.clone(),
|
||||
//! "http://localhost:8080",
|
||||
//! let source = RadioParadiseSource::new(
|
||||
//! client,
|
||||
//! 50,
|
||||
//! Some(cover_cache),
|
||||
//! Some(audio_cache),
|
||||
//! cover_cache,
|
||||
//! audio_cache,
|
||||
//! );
|
||||
//!
|
||||
//! // Add songs - they will be automatically cached
|
||||
//! let now_playing = client.now_playing().await?;
|
||||
//! if let Some(song) = &now_playing.current_song {
|
||||
//! let block = Arc::new(now_playing.block.clone());
|
||||
//! source.add_song(block, song, 0).await?;
|
||||
//! // Cover and audio are now cached!
|
||||
//! }
|
||||
//! println!("Source ready: {}", source.name());
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Data models for Radio Paradise API responses
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use serde_json::Number;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Deserialize a string or number into a u64
|
||||
@@ -34,17 +35,31 @@ where
|
||||
#[serde(untagged)]
|
||||
enum StringOrNumber {
|
||||
String(String),
|
||||
Float(f64),
|
||||
Int(u64),
|
||||
Number(Number),
|
||||
}
|
||||
|
||||
fn to_milliseconds(value: f64) -> u64 {
|
||||
if value >= 100_000.0 {
|
||||
value.round() as u64
|
||||
} else {
|
||||
(value * 1000.0).round() as u64
|
||||
}
|
||||
}
|
||||
|
||||
match StringOrNumber::deserialize(deserializer)? {
|
||||
StringOrNumber::String(s) => {
|
||||
let seconds = s.parse::<f64>().map_err(D::Error::custom)?;
|
||||
Ok((seconds * 1000.0) as u64)
|
||||
let value = s.parse::<f64>().map_err(D::Error::custom)?;
|
||||
Ok(to_milliseconds(value))
|
||||
}
|
||||
StringOrNumber::Number(n) => {
|
||||
if let Some(int_value) = n.as_u64() {
|
||||
Ok(to_milliseconds(int_value as f64))
|
||||
} else if let Some(float_value) = n.as_f64() {
|
||||
Ok(to_milliseconds(float_value))
|
||||
} else {
|
||||
Err(D::Error::custom("Invalid number for block length"))
|
||||
}
|
||||
}
|
||||
StringOrNumber::Float(f) => Ok((f * 1000.0) as u64),
|
||||
StringOrNumber::Int(i) => Ok(i),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,4 +450,60 @@ mod tests {
|
||||
assert_eq!(idx, 1);
|
||||
assert_eq!(song.title, "Giant Steps");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_length_from_seconds_string() {
|
||||
let json = serde_json::json!({
|
||||
"event": 1,
|
||||
"end_event": 2,
|
||||
"length": "1715.54",
|
||||
"url": "https://example.com/block.flac",
|
||||
"song": {}
|
||||
});
|
||||
|
||||
let block: Block = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(block.length, 1_715_540);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_length_from_seconds_integer() {
|
||||
let json = serde_json::json!({
|
||||
"event": 1,
|
||||
"end_event": 2,
|
||||
"length": 1800,
|
||||
"url": "https://example.com/block.flac",
|
||||
"song": {}
|
||||
});
|
||||
|
||||
let block: Block = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(block.length, 1_800_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_length_from_milliseconds_integer() {
|
||||
let json = serde_json::json!({
|
||||
"event": 1,
|
||||
"end_event": 2,
|
||||
"length": 900_000,
|
||||
"url": "https://example.com/block.flac",
|
||||
"song": {}
|
||||
});
|
||||
|
||||
let block: Block = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(block.length, 900_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_length_from_milliseconds_float() {
|
||||
let json = serde_json::json!({
|
||||
"event": 1,
|
||||
"end_event": 2,
|
||||
"length": 900_000.0,
|
||||
"url": "https://example.com/block.flac",
|
||||
"song": {}
|
||||
});
|
||||
|
||||
let block: Block = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(block.length, 900_000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,6 +217,10 @@ impl From<NowPlaying> for NowPlayingResponse {
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/now-playing",
|
||||
params(
|
||||
("channel" = Option<u8>, Query, description = "Channel ID (0-3)"),
|
||||
("bitrate" = Option<u8>, Query, description = "Bitrate ID (0-4)")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Morceau en cours", body = NowPlayingResponse),
|
||||
(status = 500, description = "Erreur serveur")
|
||||
@@ -240,6 +244,10 @@ async fn get_now_playing(
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/block/current",
|
||||
params(
|
||||
("channel" = Option<u8>, Query, description = "Channel ID (0-3)"),
|
||||
("bitrate" = Option<u8>, Query, description = "Bitrate ID (0-4)")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Block actuel", body = BlockResponse),
|
||||
(status = 500, description = "Erreur serveur")
|
||||
@@ -264,7 +272,9 @@ async fn get_current_block(
|
||||
get,
|
||||
path = "/block/{event_id}",
|
||||
params(
|
||||
("event_id" = u64, Path, description = "Event ID du block")
|
||||
("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)")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Block demandé", body = BlockResponse),
|
||||
|
||||
@@ -114,13 +114,26 @@ fn parse_track_identifier(track_id: &str) -> Option<(u8, u64, usize)> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// use pmoparadise::{RadioParadiseSource, RadioParadiseClient};
|
||||
/// use pmoparadise::{RadioParadiseClient, RadioParadiseSource};
|
||||
/// use pmosource::MusicSource;
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = RadioParadiseClient::new().await?;
|
||||
/// let source = RadioParadiseSource::new(client, "http://localhost:8080", 50);
|
||||
///
|
||||
/// let base_dir = std::env::temp_dir().join("pmoparadise_doc_source");
|
||||
/// let cover_dir = base_dir.join("covers");
|
||||
/// let audio_dir = base_dir.join("audio");
|
||||
/// std::fs::create_dir_all(&cover_dir)?;
|
||||
/// std::fs::create_dir_all(&audio_dir)?;
|
||||
///
|
||||
/// let cover_dir_str = cover_dir.to_string_lossy().into_owned();
|
||||
/// let audio_dir_str = audio_dir.to_string_lossy().into_owned();
|
||||
/// let cover_cache = Arc::new(pmocovers::cache::new_cache(&cover_dir_str, 32)?);
|
||||
/// let audio_cache = Arc::new(pmoaudiocache::cache::new_cache(&audio_dir_str, 32)?);
|
||||
///
|
||||
/// let source = RadioParadiseSource::new(client, 50, cover_cache, audio_cache);
|
||||
///
|
||||
/// println!("Source: {}", source.name());
|
||||
/// println!("Supports FIFO: {}", source.supports_fifo());
|
||||
@@ -431,14 +444,13 @@ impl RadioParadiseSource {
|
||||
.map_err(|e| MusicSourceError::CacheError(e.to_string()))?;
|
||||
|
||||
let audio_source_uri = format!("{}#{}", block.url, song_index);
|
||||
let reader = Cursor::new(flac_data.clone());
|
||||
let data_len = flac_data.len() as u64;
|
||||
let reader = Cursor::new(flac_data);
|
||||
let audio_pk: String = channel
|
||||
.cache_manager
|
||||
.cache_audio_from_reader(&audio_source_uri, reader, Some(flac_data.len() as u64))
|
||||
.cache_audio_from_reader(&audio_source_uri, reader, Some(data_len))
|
||||
.await?;
|
||||
|
||||
channel.cache_manager.wait_audio_ready(&audio_pk).await?;
|
||||
|
||||
let cached_cover_pk = if let Some(ref image_base) = block.image_base {
|
||||
if let Some(ref cover) = song.cover {
|
||||
let image_url = format!("{}{}", image_base, cover);
|
||||
@@ -461,6 +473,19 @@ impl RadioParadiseSource {
|
||||
None
|
||||
};
|
||||
|
||||
let metadata_cover_pk = cached_cover_pk.clone();
|
||||
channel
|
||||
.cache_manager
|
||||
.update_metadata(
|
||||
track_id.clone(),
|
||||
pmosource::TrackMetadata {
|
||||
original_uri: block.url.clone(),
|
||||
cached_audio_pk: Some(audio_pk.clone()),
|
||||
cached_cover_pk: metadata_cover_pk,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
let playback_url = channel.cache_manager.resolve_uri(&track_id).await?;
|
||||
|
||||
let mut track = Track::new(track_id.clone(), song.title.clone(), playback_url);
|
||||
@@ -487,19 +512,33 @@ impl RadioParadiseSource {
|
||||
}
|
||||
}
|
||||
|
||||
channel
|
||||
.cache_manager
|
||||
.update_metadata(
|
||||
track_id.clone(),
|
||||
pmosource::TrackMetadata {
|
||||
original_uri: block.url.clone(),
|
||||
cached_audio_pk: Some(audio_pk.clone()),
|
||||
cached_cover_pk,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
channel.playlist.append_track(track).await;
|
||||
|
||||
let channel_for_wait = channel.clone();
|
||||
let track_id_for_wait = track_id.clone();
|
||||
let audio_pk_for_wait = audio_pk.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = channel_for_wait
|
||||
.cache_manager
|
||||
.wait_audio_ready(&audio_pk_for_wait)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
"Failed to finalize audio {} on channel {}: {}",
|
||||
track_id_for_wait,
|
||||
channel_for_wait.descriptor.name,
|
||||
e
|
||||
);
|
||||
channel_for_wait
|
||||
.cache_manager
|
||||
.remove_track(&track_id_for_wait)
|
||||
.await;
|
||||
channel_for_wait
|
||||
.playlist
|
||||
.remove_by_id(&track_id_for_wait)
|
||||
.await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
|
||||
Reference in New Issue
Block a user