debuggage des stream

This commit is contained in:
2025-11-14 10:43:53 +01:00
parent de84cbafbb
commit 1c2d30cbe9
44 changed files with 2018 additions and 717 deletions

View File

@@ -84,10 +84,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Block Information:");
println!(" Event ID: {}", block.event);
println!(" Songs: {}", block.song_count());
println!(
" Duration: {:.1} minutes",
block.length as f64 / 60000.0
);
println!(" Duration: {:.1} minutes", block.length as f64 / 60000.0);
println!();
// Afficher la liste des pistes

View File

@@ -46,7 +46,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.add_directive("pmoaudio_ext=debug".parse()?)
.add_directive("pmoplaylist=debug".parse()?)
.add_directive("pmoparadise=debug".parse()?)
.add_directive("pmoaudiocache=debug".parse()?)
.add_directive("pmoaudiocache=debug".parse()?),
)
.init();
@@ -89,7 +89,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialiser les caches et le gestionnaire de playlist
// ═══════════════════════════════════════════════════════════════════════════
let base_dir = std::env::var("PMO_CONFIG_DIR").unwrap_or_else(|_| "/tmp/pmomusic_test".to_string());
let base_dir =
std::env::var("PMO_CONFIG_DIR").unwrap_or_else(|_| "/tmp/pmomusic_test".to_string());
std::fs::create_dir_all(&base_dir)?;
tracing::info!("Initializing caches in: {}", base_dir);
@@ -130,8 +131,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing::info!("Creating playlist: {}", playlist_id);
// Créer une playlist éphémère (non persistante) pour cet exemple
let writer = playlist_manager.get_write_handle(playlist_id.clone()).await?;
writer.set_title(format!("Radio Paradise - Channel {}", channel_id)).await?;
let writer = playlist_manager
.get_write_handle(playlist_id.clone())
.await?;
writer
.set_title(format!("Radio Paradise - Channel {}", channel_id))
.await?;
writer.flush().await?; // Vider la playlist si elle existait
tracing::debug!("Playlist created and flushed");
@@ -178,7 +183,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Créer la source Radio Paradise
let mut download_source = RadioParadiseStreamSource::new(client);
download_source.push_block_id(block.event);
tracing::debug!("RadioParadiseStreamSource created with block {}", block.event);
tracing::debug!(
"RadioParadiseStreamSource created with block {}",
block.event
);
// Créer le sink de cache FLAC
let mut cache_sink = FlacCacheSink::new(audio_cache.clone(), cover_cache.clone());

View File

@@ -9,13 +9,13 @@
//!
//! Architecture:
//! ```text
//! RadioParadiseStreamSource → TimerNode → StreamingFlacSink
//! ↓
//! StreamHandle
//! ↓
//! pmoserver (Axum)
//! ↓
//! VLC / Media Player Client
//! RadioParadiseStreamSource → TimerBufferNode → StreamingFlacSink
//!
//! StreamHandle
//!
//! pmoserver (Axum)
//!
//! VLC / Media Player Client
//! ```
//!
//! Usage:
@@ -38,11 +38,11 @@ use axum::{
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
use pmoaudio::{AudioPipelineNode, TimerNode};
use pmoaudio::{AudioPipelineNode, TimerBufferNode};
use pmoaudio_ext::{StreamingFlacSink, StreamingOggFlacSink};
use pmoflac::EncoderOptions;
use pmoparadise::{RadioParadiseClient, RadioParadiseStreamSource, END_OF_BLOCKS_SIGNAL};
use pmoserver::{ServerBuilder, init_logging};
use pmoserver::{init_logging, ServerBuilder};
use std::env;
use std::sync::Arc;
use tokio_util::io::ReaderStream;
@@ -210,25 +210,40 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut source_flac = RadioParadiseStreamSource::new(client.clone());
source_flac.push_block_id(block.event);
source_flac.push_block_id(END_OF_BLOCKS_SIGNAL); // Signal: no more blocks after this one
tracing::debug!("RadioParadiseStreamSource (FLAC) created with block {} + END signal", block.event);
tracing::debug!(
"RadioParadiseStreamSource (FLAC) created with block {} + END signal",
block.event
);
// Use SMALL channel size to make backpressure more reactive
// Instead of trying to buffer 3s of audio (60 chunks), use a much smaller buffer
// This forces tighter backpressure control
let max_lead_time = 3.0;
let channel_size = 8; // Small buffer for reactive backpressure
tracing::debug!("Using channel size: {} chunks ({:.1}s buffer at 50ms/chunk)", channel_size, channel_size as f64 * 0.05);
let buffer_sec = 10.0;
let max_lead_time = buffer_sec;
let channel_size = 512;
tracing::debug!(
"Using channel size: {} chunks ({:.1}s buffer à 50ms/chunk)",
channel_size,
channel_size as f64 * 0.05
);
let mut timer_flac = TimerNode::with_channel_size(max_lead_time, channel_size);
tracing::debug!("TimerNode (FLAC) created with {:.1}s max lead time, {} chunk buffer", max_lead_time, channel_size);
let mut timer_flac = TimerBufferNode::with_channel_size(buffer_sec, channel_size);
tracing::debug!(
"TimerBufferNode (FLAC) created with {:.1}s buffer, {} chunk queue",
buffer_sec,
channel_size
);
// StreamingFlacSink doesn't take channel_size - it uses bits_per_sample (16, 24, or 32)
let (streaming_sink, stream_handle) = StreamingFlacSink::new(encoder_options.clone(), 16);
let (streaming_sink, stream_handle) =
StreamingFlacSink::with_max_broadcast_lead(encoder_options.clone(), 16, max_lead_time);
tracing::debug!("StreamingFlacSink created");
timer_flac.register(Box::new(streaming_sink));
source_flac.register(Box::new(timer_flac));
tracing::info!("Pipeline 1 connected: RadioParadiseStreamSource → TimerNode → StreamingFlacSink");
tracing::info!(
"Pipeline 1 connected: RadioParadiseStreamSource → TimerBufferNode → StreamingFlacSink"
);
// ─────────────────────────────────────────────────────────────────────────
// Pipeline 2: OGG-FLAC streaming
@@ -237,18 +252,29 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut source_ogg = RadioParadiseStreamSource::new(client);
source_ogg.push_block_id(block.event);
source_ogg.push_block_id(END_OF_BLOCKS_SIGNAL); // Signal: no more blocks after this one
tracing::debug!("RadioParadiseStreamSource (OGG) created with block {} + END signal", block.event);
tracing::debug!(
"RadioParadiseStreamSource (OGG) created with block {} + END signal",
block.event
);
let mut timer_ogg = TimerNode::with_channel_size(max_lead_time, channel_size);
tracing::debug!("TimerNode (OGG) created with {:.1}s max lead time, {} chunk buffer", max_lead_time, channel_size);
let mut timer_ogg = TimerBufferNode::with_channel_size(buffer_sec, channel_size);
tracing::debug!(
"TimerBufferNode (OGG) created with {:.1}s buffer, {} chunk queue",
buffer_sec,
channel_size
);
// StreamingOggFlacSink doesn't take channel_size - it uses bits_per_sample (16, 24, or 32)
let (ogg_sink, ogg_handle) = StreamingOggFlacSink::new(encoder_options, 16);
let (ogg_sink, ogg_handle) =
StreamingOggFlacSink::with_max_broadcast_lead(encoder_options, 16, max_lead_time);
tracing::debug!("StreamingOggFlacSink created");
timer_ogg.register(Box::new(ogg_sink));
source_ogg.register(Box::new(timer_ogg));
tracing::info!("Pipeline 2 connected: RadioParadiseStreamSource → TimerNode → StreamingOggFlacSink");
tracing::info!(
"Pipeline 2 connected: RadioParadiseStreamSource → TimerBufferNode → StreamingOggFlacSink"
);
// ═══════════════════════════════════════════════════════════════════════════
// Setup pmoserver with streaming routes
@@ -256,8 +282,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing::info!("Setting up pmoserver...");
let mut server = ServerBuilder::new("RadioParadiseStreamTest", "http://localhost", 8080)
.build();
let mut server =
ServerBuilder::new("RadioParadiseStreamTest", "http://localhost", 8080).build();
let app_state = Arc::new(AppState {
stream_handle,
@@ -265,12 +291,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
});
// Add streaming routes
server.add_handler_with_state("/test/stream", stream_handler, app_state.clone()).await;
server.add_handler_with_state("/test/stream-icy", stream_icy_handler, app_state.clone()).await;
server.add_handler_with_state("/test/stream-ogg", stream_ogg_handler, app_state.clone()).await;
server
.add_handler_with_state("/test/stream", stream_handler, app_state.clone())
.await;
server
.add_handler_with_state("/test/stream-icy", stream_icy_handler, app_state.clone())
.await;
server
.add_handler_with_state("/test/stream-ogg", stream_ogg_handler, app_state.clone())
.await;
// Add metadata route
server.add_handler_with_state("/test/metadata", metadata_handler, app_state.clone()).await;
server
.add_handler_with_state("/test/metadata", metadata_handler, app_state.clone())
.await;
// Add health check
server.add_handler("/test/health", health_handler).await;