From a51d7c551ea032a3ce9121fcce350a9db2902bbd Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sat, 22 Nov 2025 04:59:29 +0100 Subject: [PATCH] Prblen enxt chanson en flac --- pmoaudio-ext/src/sinks/broadcast_pacing.rs | 3 +- pmoaudio-ext/src/sinks/mod.rs | 2 +- pmoaudio-ext/src/sinks/streaming_flac_sink.rs | 104 ++++++++++++------ .../src/sinks/streaming_icyflac_sink.rs | 3 +- .../src/sinks/streaming_ogg_flac_sink.rs | 95 ++++++++++------ .../src/sinks/streaming_sink_common.rs | 103 ++++++++++++++++- pmoaudio-ext/src/sinks/timed_broadcast.rs | 4 +- pmoparadise/examples/single_channel_server.rs | 9 +- pmoparadise/src/stream_channel.rs | 17 ++- pmoparadise/src/stream_channel_old.rs | 18 ++- 10 files changed, 272 insertions(+), 86 deletions(-) diff --git a/pmoaudio-ext/src/sinks/broadcast_pacing.rs b/pmoaudio-ext/src/sinks/broadcast_pacing.rs index 32666c6b..dcf28bf2 100644 --- a/pmoaudio-ext/src/sinks/broadcast_pacing.rs +++ b/pmoaudio-ext/src/sinks/broadcast_pacing.rs @@ -6,13 +6,14 @@ //! - Paces broadcast to match audio playback rate use std::time::Instant; -use tracing::{trace, warn}; +use tracing::trace; /// Error returned when a frame should be skipped (too late) #[derive(Debug)] pub struct SkipFrame; /// Manages broadcast pacing with TopZeroSync detection +#[allow(dead_code)] pub struct BroadcastPacer { /// Start time (reset on TopZeroSync) start_time: Instant, diff --git a/pmoaudio-ext/src/sinks/mod.rs b/pmoaudio-ext/src/sinks/mod.rs index 2d517aa6..0aa776ca 100755 --- a/pmoaudio-ext/src/sinks/mod.rs +++ b/pmoaudio-ext/src/sinks/mod.rs @@ -42,4 +42,4 @@ mod streaming_ogg_flac_sink; pub use streaming_ogg_flac_sink::{OggFlacClientStream, OggFlacStreamHandle, StreamingOggFlacSink}; #[cfg(feature = "http-stream")] -pub use streaming_sink_common::MetadataSnapshot; +pub use streaming_sink_common::{MetadataSnapshot, StreamingSinkOptions}; diff --git a/pmoaudio-ext/src/sinks/streaming_flac_sink.rs b/pmoaudio-ext/src/sinks/streaming_flac_sink.rs index 9d132adb..234b211f 100644 --- a/pmoaudio-ext/src/sinks/streaming_flac_sink.rs +++ b/pmoaudio-ext/src/sinks/streaming_flac_sink.rs @@ -54,7 +54,6 @@ //! } //! ``` -use std::collections::VecDeque; use std::io; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; @@ -65,7 +64,7 @@ use std::time::Duration; use super::{ broadcast_pacing::BroadcastPacer, flac_frame_utils, - timed_broadcast::{self, SendError, TryRecvError}, + timed_broadcast::{self, SendError}, }; use async_trait::async_trait; use bytes::Bytes; @@ -77,12 +76,13 @@ use pmoflac::{EncoderOptions, FlacEncodedStream}; use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf}; use tokio::sync::{mpsc, RwLock}; use tokio_util::sync::CancellationToken; -use tracing::{debug, error, info, trace, warn}; +use tracing::{debug, error, trace, warn}; use crate::byte_stream_reader::{PcmChunk}; use crate::chunk_to_pcm::chunk_to_pcm_bytes; use crate::sinks::streaming_sink_common::{ MetadataSnapshot, SharedClientStream, SharedSinkContext, SharedStreamHandleInner, + StreamingSinkOptions, }; use crate::sinks::timed_broadcast::{ calculate_broadcast_capacity, DEFAULT_BROADCAST_MAX_LEAD_TIME, @@ -214,8 +214,8 @@ impl NodeLogic for StreamingFlacSinkLogic { self.ctx.first_chunk_timestamp_checked = true; if seg.timestamp_sec.abs() > 1e-6 { warn!( - "StreamingFlacSink: first chunk timestamp is {:.6}s (expected 0.0)", - seg.timestamp_sec + "StreamingFlacSink: first chunk timestamp is {:.3}ms (expected 0.0)", + seg.timestamp_sec * 1000.0 ); } else { trace!("StreamingFlacSink: first chunk timestamp verified at 0.0s"); @@ -318,39 +318,53 @@ impl NodeLogic for StreamingFlacSinkLogic { debug!("StreamingFlacSink: SyncMarker::TrackBoundary {:?}",metadata.read().await.get_duration().await); - // Only restart encoder if it's already initialized (not the first track) - if self.ctx.sample_rate.is_some() && self.ctx.encoder_state.is_some() { - // Restart encoder to emit new header and reset timestamps - if let Err(e) = self - .ctx - .restart_encoder_for_new_track( - |flac_stream, - broadcast, - header, - current_timestamp, - current_duration, - max_lead, - sample_rate, - timestamp_offset_sec| { - broadcast_flac_stream( - flac_stream, - broadcast, - header, - current_timestamp, - current_duration, - max_lead, - sample_rate, - timestamp_offset_sec, - ) - }, - ) - .await + if self.ctx.restart_encoder_on_track_boundary { + // Only restart encoder if it's already initialized (not the first track) + if self.ctx.sample_rate.is_some() + && self.ctx.encoder_state.is_some() { - error!("Failed to restart encoder for new track: {}", e); - break; + // Restart encoder to emit new header and reset timestamps + if let Err(e) = self + .ctx + .restart_encoder_for_new_track( + |flac_stream, + broadcast, + header, + current_timestamp, + current_duration, + max_lead, + sample_rate, + timestamp_offset_sec| { + broadcast_flac_stream( + flac_stream, + broadcast, + header, + current_timestamp, + current_duration, + max_lead, + sample_rate, + timestamp_offset_sec, + ) + }, + ) + .await + { + error!( + "Failed to restart encoder for new track: {}", + e + ); + break; + } + } else { + trace!("Skipping encoder restart for first track (encoder not yet initialized)"); } } else { - trace!("Skipping encoder restart for first track (encoder not yet initialized)"); + // For raw FLAC streaming we keep a single continuous encoder. + // Restarting would insert a new STREAMINFO header mid-stream and many + // clients treat that as end-of-file. + trace!( + "StreamingFlacSink: keeping encoder alive across track boundary" + ); } // Update metadata for the new track @@ -422,6 +436,21 @@ impl StreamingFlacSink { encoder_options: EncoderOptions, bits_per_sample: u8, broadcast_max_lead_time: f64, + ) -> (Self, StreamHandle) { + Self::with_options( + encoder_options, + bits_per_sample, + broadcast_max_lead_time, + StreamingSinkOptions::flac_defaults(), + ) + } + + /// Create a sink with a custom broadcast pacing limit and options. + pub fn with_options( + encoder_options: EncoderOptions, + bits_per_sample: u8, + broadcast_max_lead_time: f64, + options: StreamingSinkOptions, ) -> (Self, StreamHandle) { // Validate bit depth if ![16, 24, 32].contains(&bits_per_sample) { @@ -465,6 +494,11 @@ impl StreamingFlacSink { ctx: SharedSinkContext { encoder_options, bits_per_sample, + enable_total_samples: options.enable_total_samples, + restart_encoder_on_track_boundary: options.restart_encoder_on_track_boundary, + default_title: options.default_title.clone(), + default_artist: options.default_artist.clone(), + use_only_default_metadata: options.use_only_default_metadata, pcm_tx: Some(pcm_tx), pcm_rx: Some(pcm_rx), metadata, diff --git a/pmoaudio-ext/src/sinks/streaming_icyflac_sink.rs b/pmoaudio-ext/src/sinks/streaming_icyflac_sink.rs index 2d1aaccf..e68e8377 100644 --- a/pmoaudio-ext/src/sinks/streaming_icyflac_sink.rs +++ b/pmoaudio-ext/src/sinks/streaming_icyflac_sink.rs @@ -6,7 +6,7 @@ use crate::{MetadataSnapshot, sinks::{flac_frame_utils::FlacStreamState, streami use bytes::Bytes; use std::io; -use tracing::{debug, error, info, trace, warn}; +use tracing::{debug, warn}; /// ICY-wrapped FLAC client stream (implements AsyncRead). /// @@ -97,6 +97,7 @@ impl IcyClientStream { } /// Get metadata block if it needs to be inserted. + #[allow(dead_code)] async fn get_metadata_if_changed(&mut self) -> Option { let meta = self.metadata.read().await; if meta.version > self.current_metadata_version { diff --git a/pmoaudio-ext/src/sinks/streaming_ogg_flac_sink.rs b/pmoaudio-ext/src/sinks/streaming_ogg_flac_sink.rs index 50cbc6dc..7a306daa 100644 --- a/pmoaudio-ext/src/sinks/streaming_ogg_flac_sink.rs +++ b/pmoaudio-ext/src/sinks/streaming_ogg_flac_sink.rs @@ -46,7 +46,6 @@ //! - Pages are broadcast immediately to connected clients //! - TrackBoundary only triggers encoder flush (no data accumulation) -use std::collections::VecDeque; use std::io; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; @@ -56,7 +55,7 @@ use std::task::{Context, Poll}; use super::{ broadcast_pacing::BroadcastPacer, flac_frame_utils, - timed_broadcast::{self, SendError, TryRecvError}, + timed_broadcast::{self, SendError}, }; use async_trait::async_trait; use bytes::Bytes; @@ -68,13 +67,14 @@ use pmoflac::{EncoderOptions, FlacEncodedStream}; use tokio::io::{AsyncRead, AsyncReadExt, ReadBuf}; use tokio::sync::{mpsc, RwLock}; use tokio_util::sync::CancellationToken; -use tracing::{debug, error, info, trace, warn}; +use tracing::{debug, error, trace, warn}; use crate::byte_stream_reader::{PcmChunk}; use crate::chunk_to_pcm::chunk_to_pcm_bytes; use crate::sinks::flac_frame_utils::{extract_sample_rate_from_streaminfo, read_flac_header}; use crate::sinks::streaming_sink_common::{ MetadataSnapshot, SharedClientStream, SharedSinkContext, SharedStreamHandleInner, + StreamingSinkOptions, }; use crate::sinks::timed_broadcast::{ calculate_broadcast_capacity, DEFAULT_BROADCAST_MAX_LEAD_TIME, @@ -210,7 +210,7 @@ impl NodeLogic for StreamingOggFlacSinkLogic { current_timestamp, current_duration, max_lead, - sample_rate, + _sample_rate, timestamp_offset_sec| { broadcast_ogg_flac_stream( flac_stream, @@ -273,38 +273,44 @@ impl NodeLogic for StreamingOggFlacSinkLogic { error!("Failed to prepare encoder options for new track: {}", e); } - // Only restart encoder if it's already initialized (not the first track) - if self.ctx.sample_rate.is_some() && self.ctx.encoder_state.is_some() { - // Restart encoder to emit new OGG stream header and reset timestamps - if let Err(e) = self - .ctx - .restart_encoder_for_new_track( - |flac_stream, - broadcast, - header, - current_timestamp, - current_duration, - max_lead, - sample_rate, - timestamp_offset_sec| { - broadcast_ogg_flac_stream( - flac_stream, - broadcast, - header, - current_timestamp, - current_duration, - max_lead, - timestamp_offset_sec, - ) - }, - ) - .await + if self.ctx.restart_encoder_on_track_boundary { + // Only restart encoder if it's already initialized (not the first track) + if self.ctx.sample_rate.is_some() + && self.ctx.encoder_state.is_some() { - error!("Failed to restart OGG encoder for new track: {}", e); - break; + // Restart encoder to emit new OGG stream header and reset timestamps + if let Err(e) = self + .ctx + .restart_encoder_for_new_track( + |flac_stream, + broadcast, + header, + current_timestamp, + current_duration, + max_lead, + _sample_rate, + timestamp_offset_sec| { + broadcast_ogg_flac_stream( + flac_stream, + broadcast, + header, + current_timestamp, + current_duration, + max_lead, + timestamp_offset_sec, + ) + }, + ) + .await + { + error!("Failed to restart OGG encoder for new track: {}", e); + break; + } + } else { + trace!("Skipping OGG encoder restart for first track (encoder not yet initialized)"); } } else { - trace!("Skipping OGG encoder restart for first track (encoder not yet initialized)"); + trace!("StreamingOggFlacSink: restart disabled; continuing encoder across track boundary"); } // Update metadata for the new track @@ -379,6 +385,21 @@ impl StreamingOggFlacSink { encoder_options: EncoderOptions, bits_per_sample: u8, broadcast_max_lead_time: f64, + ) -> (Self, OggFlacStreamHandle) { + Self::with_options( + encoder_options, + bits_per_sample, + broadcast_max_lead_time, + StreamingSinkOptions::ogg_defaults(), + ) + } + + /// Create a sink with a custom broadcast pacing limit and options. + pub fn with_options( + encoder_options: EncoderOptions, + bits_per_sample: u8, + broadcast_max_lead_time: f64, + options: StreamingSinkOptions, ) -> (Self, OggFlacStreamHandle) { // Validate bit depth if ![16, 24, 32].contains(&bits_per_sample) { @@ -420,6 +441,11 @@ impl StreamingOggFlacSink { ctx: SharedSinkContext { encoder_options, bits_per_sample, + enable_total_samples: options.enable_total_samples, + restart_encoder_on_track_boundary: options.restart_encoder_on_track_boundary, + default_title: options.default_title.clone(), + default_artist: options.default_artist.clone(), + use_only_default_metadata: options.use_only_default_metadata, pcm_tx: Some(pcm_tx), pcm_rx: Some(pcm_rx), metadata, @@ -494,9 +520,7 @@ async fn broadcast_ogg_flac_stream( let mut ogg_writer = OggPageWriter::new(stream_serial); let mut total_bytes = 0u64; - let mut header_captured = false; let mut pacer = BroadcastPacer::new(broadcast_max_lead_time, "OGG"); - let mut last_granule_update_time = 0.0f64; // Timing instrumentation for burst detection let mut last_broadcast_time = std::time::Instant::now(); @@ -538,7 +562,6 @@ async fn broadcast_ogg_flac_stream( cached_header.extend_from_slice(&bos_bytes); cached_header.extend_from_slice(&comment_bytes); *header_cache.write().await = Some(Bytes::from(cached_header)); - header_captured = true; trace!( "OGG-FLAC header cached ({} bytes: BOS + Vorbis Comment)", bos_bytes.len() + comment_bytes.len() diff --git a/pmoaudio-ext/src/sinks/streaming_sink_common.rs b/pmoaudio-ext/src/sinks/streaming_sink_common.rs index 3a37f4f8..e8748404 100644 --- a/pmoaudio-ext/src/sinks/streaming_sink_common.rs +++ b/pmoaudio-ext/src/sinks/streaming_sink_common.rs @@ -45,6 +45,63 @@ pub struct MetadataSnapshot { pub version: u64, } +/// Configuration options shared by streaming sinks. +#[derive(Clone, Debug)] +pub struct StreamingSinkOptions { + pub restart_encoder_on_track_boundary: bool, + pub enable_total_samples: bool, + pub default_title: Option, + pub default_artist: Option, + pub use_only_default_metadata: bool, +} + +impl StreamingSinkOptions { + pub fn flac_defaults() -> Self { + Self { + restart_encoder_on_track_boundary: false, + enable_total_samples: false, + default_title: None, + default_artist: None, + use_only_default_metadata: false, + } + } + + pub fn ogg_defaults() -> Self { + Self { + restart_encoder_on_track_boundary: true, + enable_total_samples: true, + default_title: None, + default_artist: None, + use_only_default_metadata: false, + } + } + + pub fn with_restart(mut self, restart: bool) -> Self { + self.restart_encoder_on_track_boundary = restart; + self + } + + pub fn with_total_samples(mut self, enable: bool) -> Self { + self.enable_total_samples = enable; + self + } + + pub fn with_default_title(mut self, title: impl Into>) -> Self { + self.default_title = title.into(); + self + } + + pub fn with_default_artist(mut self, artist: impl Into>) -> Self { + self.default_artist = artist.into(); + self + } + + pub fn with_only_default_metadata(mut self, only_default: bool) -> Self { + self.use_only_default_metadata = only_default; + self + } +} + /// Shared handle state for streaming sinks. pub struct SharedStreamHandleInner { pub broadcast: timed_broadcast::Sender, @@ -202,6 +259,14 @@ pub struct EncoderState { pub struct SharedSinkContext { pub encoder_options: EncoderOptions, pub bits_per_sample: u8, + /// Whether to propagate total_samples into STREAMINFO. + /// For unbounded live streams (raw FLAC), this must stay false to avoid + /// players stopping after they reach the advertised length. + pub enable_total_samples: bool, + pub restart_encoder_on_track_boundary: bool, + pub default_title: Option, + pub default_artist: Option, + pub use_only_default_metadata: bool, pub pcm_tx: Option>, pub pcm_rx: Option>, pub metadata: Arc>, @@ -311,6 +376,15 @@ impl SharedSinkContext { // Always pass the metadata handle to the encoder so Vorbis comments are emitted. self.encoder_options.metadata = Some(metadata_lock.clone()); + // In raw FLAC live streaming we must NOT advertise a total_samples value, + // otherwise players think the stream ends after the first track. + if !self.enable_total_samples { + self.pending_track_duration = None; + self.pending_total_samples = None; + self.encoder_options.total_samples = None; + return Ok(()); + } + // Capture duration (if any) to set total_samples. let duration_opt = { let metadata = metadata_lock.read().await; @@ -339,6 +413,12 @@ impl SharedSinkContext { self.sample_rate ); + if !self.enable_total_samples { + self.encoder_options.total_samples = None; + info!("Encoder metadata: total_samples disabled for live streaming"); + return; + } + if let Some(total) = self.pending_total_samples { self.encoder_options.total_samples = Some(total); info!( @@ -430,8 +510,24 @@ impl SharedSinkContext { let metadata = metadata_lock.read().await; let mut snapshot = self.metadata.write().await; - snapshot.title = metadata.get_title().await.ok().flatten(); - snapshot.artist = metadata.get_artist().await.ok().flatten(); + // Title / artist with default fallback or forced default. + if self.use_only_default_metadata { + snapshot.title = self.default_title.clone(); + snapshot.artist = self.default_artist.clone(); + } else { + snapshot.title = metadata + .get_title() + .await + .ok() + .flatten() + .or_else(|| self.default_title.clone()); + snapshot.artist = metadata + .get_artist() + .await + .ok() + .flatten() + .or_else(|| self.default_artist.clone()); + } snapshot.album = metadata.get_album().await.ok().flatten(); snapshot.duration = metadata.get_duration().await.ok().flatten(); snapshot.cover_url = metadata.get_cover_url().await.ok().flatten(); @@ -443,6 +539,9 @@ impl SharedSinkContext { snapshot.track_number = extra .get("track_number") .and_then(|s| s.parse::().ok()); + } else { + snapshot.genre = None; + snapshot.track_number = None; } snapshot.audio_timestamp_sec = timestamp_sec; diff --git a/pmoaudio-ext/src/sinks/timed_broadcast.rs b/pmoaudio-ext/src/sinks/timed_broadcast.rs index 65662c95..0bfbb764 100644 --- a/pmoaudio-ext/src/sinks/timed_broadcast.rs +++ b/pmoaudio-ext/src/sinks/timed_broadcast.rs @@ -6,7 +6,7 @@ use std::{ collections::VecDeque, - fmt, string, + fmt, sync::{ atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering}, Arc, Mutex, Weak, @@ -15,7 +15,7 @@ use std::{ }; use tokio::sync::Notify; -use tracing::{debug, info, trace, warn}; +use tracing::{info, trace, warn}; /// Tolérance pour détecter un timestamp à zéro (TopZero). const TOP_ZERO_EPSILON: f64 = 1e-9; diff --git a/pmoparadise/examples/single_channel_server.rs b/pmoparadise/examples/single_channel_server.rs index c6de215a..82e2ca2f 100644 --- a/pmoparadise/examples/single_channel_server.rs +++ b/pmoparadise/examples/single_channel_server.rs @@ -20,6 +20,7 @@ use pmoparadise::{ channels::{ChannelDescriptor, ALL_CHANNELS}, ParadiseHistoryBuilder, ParadiseStreamChannel, ParadiseStreamChannelConfig, }; +use pmoaudio_ext::StreamingSinkOptions; use pmoplaylist::register_audio_cache as register_playlist_audio_cache; use std::{fs, net::SocketAddr, sync::Arc}; use tokio::net::TcpListener; @@ -62,10 +63,16 @@ async fn main() -> anyhow::Result<()> { history_builder.collection_prefix = Some(format!("single-channel-{}", descriptor.slug)); let history_opts = history_builder.build_for_channel(&descriptor).await?; + let mut channel_config = ParadiseStreamChannelConfig::default(); + channel_config.flac_options = StreamingSinkOptions::flac_defaults() + .with_default_artist(Some("Radio Paradise".to_string())) + .with_default_title(descriptor.display_name.to_string()) + .with_only_default_metadata(true); + let channel = Arc::new( ParadiseStreamChannel::new( descriptor, - ParadiseStreamChannelConfig::default(), + channel_config, Some(cover_cache), Some(history_opts), ) diff --git a/pmoparadise/src/stream_channel.rs b/pmoparadise/src/stream_channel.rs index 740be535..db552039 100644 --- a/pmoparadise/src/stream_channel.rs +++ b/pmoparadise/src/stream_channel.rs @@ -26,6 +26,7 @@ use pmoaudio::{AudioError, AudioPipelineNode}; use pmoaudio_ext::{ FlacClientStream, IcyClientStream, MetadataSnapshot, OggFlacClientStream, OggFlacStreamHandle, PlaylistSource, StreamHandle, StreamingFlacSink, StreamingOggFlacSink, TrackBoundaryCoverNode, + StreamingSinkOptions, }; use pmoaudiocache::{get_audio_cache, Cache as AudioCache}; use pmocovers::{get_cover_cache, Cache as CoverCache}; @@ -43,12 +44,18 @@ use tracing::{error, info, warn}; pub struct ParadiseStreamChannelConfig { /// Durée maximale (en secondes) d'avance acceptée par le broadcast. pub max_lead_seconds: f64, + /// Options pour le flux FLAC pur. + pub flac_options: StreamingSinkOptions, + /// Options pour le flux OGG-FLAC. + pub ogg_options: StreamingSinkOptions, } impl Default for ParadiseStreamChannelConfig { fn default() -> Self { Self { max_lead_seconds: 1.0, + flac_options: StreamingSinkOptions::flac_defaults(), + ogg_options: StreamingSinkOptions::ogg_defaults(), } } } @@ -136,6 +143,8 @@ impl ParadiseStreamChannelConfig { if let Some(v) = num.as_f64() { Self { max_lead_seconds: v.max(0.1), + flac_options: StreamingSinkOptions::flac_defaults(), + ogg_options: StreamingSinkOptions::ogg_defaults(), } } else { let default = Self::default(); @@ -148,6 +157,8 @@ impl ParadiseStreamChannelConfig { if let Ok(v) = s.parse::() { Self { max_lead_seconds: v.max(0.1), + flac_options: StreamingSinkOptions::flac_defaults(), + ogg_options: StreamingSinkOptions::ogg_defaults(), } } else { let default = Self::default(); @@ -238,15 +249,17 @@ impl ParadiseStreamChannel { }; // 4. Créer les sinks de broadcast (FLAC + OGG) - let (flac_sink, stream_handle) = StreamingFlacSink::with_max_broadcast_lead( + let (flac_sink, stream_handle) = StreamingFlacSink::with_options( EncoderOptions::default(), 16, config.max_lead_seconds, + config.flac_options.clone(), ); - let (ogg_sink, ogg_handle) = StreamingOggFlacSink::with_max_broadcast_lead( + let (ogg_sink, ogg_handle) = StreamingOggFlacSink::with_options( EncoderOptions::default(), 16, config.max_lead_seconds, + config.ogg_options.clone(), ); let mut downstream_children: Vec> = Vec::new(); diff --git a/pmoparadise/src/stream_channel_old.rs b/pmoparadise/src/stream_channel_old.rs index 7e7b49b0..76464b9f 100644 --- a/pmoparadise/src/stream_channel_old.rs +++ b/pmoparadise/src/stream_channel_old.rs @@ -19,7 +19,7 @@ use pmoaudio::{nodes::DEFAULT_CHANNEL_SIZE, AudioPipelineNode}; use pmoaudio_ext::{ FlacCacheSink, FlacClientStream, IcyClientStream, MetadataSnapshot, OggFlacClientStream, OggFlacStreamHandle, PlaylistSource, StreamHandle, StreamingFlacSink, StreamingOggFlacSink, - TrackBoundaryCoverNode, + TrackBoundaryCoverNode, StreamingSinkOptions, }; use pmoaudiocache::Cache as AudioCache; use pmocovers::Cache as CoverCache; @@ -37,12 +37,16 @@ use tracing::{error, info, warn}; pub struct ParadiseStreamChannelConfig { /// Durée maximale (en secondes) d'avance acceptée par le broadcast. pub max_lead_seconds: f64, + pub flac_options: StreamingSinkOptions, + pub ogg_options: StreamingSinkOptions, } impl Default for ParadiseStreamChannelConfig { fn default() -> Self { Self { max_lead_seconds: 1.0, + flac_options: StreamingSinkOptions::flac_defaults(), + ogg_options: StreamingSinkOptions::ogg_defaults(), } } } @@ -189,15 +193,17 @@ impl ParadiseStreamChannel { let mut source = RadioParadiseStreamSource::new(client.clone()); let block_handle = source.block_handle(); - let (flac_sink, stream_handle) = StreamingFlacSink::with_max_broadcast_lead( + let (flac_sink, stream_handle) = StreamingFlacSink::with_options( EncoderOptions::default(), 16, config.max_lead_seconds, + config.flac_options.clone(), ); - let (ogg_sink, ogg_handle) = StreamingOggFlacSink::with_max_broadcast_lead( + let (ogg_sink, ogg_handle) = StreamingOggFlacSink::with_options( EncoderOptions::default(), 16, config.max_lead_seconds, + config.ogg_options.clone(), ); let mut downstream_children: Vec> = Vec::new(); @@ -361,10 +367,11 @@ impl ParadiseStreamChannel { .await .map_err(|e| HistoryStreamError::Playlist(e.to_string()))?; let mut source = PlaylistSource::new(reader, history.audio_cache.clone()); - let (flac_sink, handle) = StreamingFlacSink::with_max_broadcast_lead( + let (flac_sink, handle) = StreamingFlacSink::with_options( EncoderOptions::default(), 16, history.replay_max_lead_seconds, + self.state.config.flac_options.clone(), ); source.register(Box::new(flac_sink)); let stop_token = CancellationToken::new(); @@ -397,10 +404,11 @@ impl ParadiseStreamChannel { .await .map_err(|e| HistoryStreamError::Playlist(e.to_string()))?; let mut source = PlaylistSource::new(reader, history.audio_cache.clone()); - let (ogg_sink, handle) = StreamingOggFlacSink::with_max_broadcast_lead( + let (ogg_sink, handle) = StreamingOggFlacSink::with_options( EncoderOptions::default(), 16, history.replay_max_lead_seconds, + self.state.config.ogg_options.clone(), ); source.register(Box::new(ogg_sink)); let stop_token = CancellationToken::new();