Changement du mécanisme d'attention sur les channels Radio Paradise.

This commit is contained in:
2025-11-29 14:19:16 +01:00
parent cf3f0afde4
commit 0a03f72467
44 changed files with 564 additions and 231 deletions

View File

@@ -1,7 +1,15 @@
use std::io;
use std::{collections::VecDeque, pin::Pin, sync::Arc, task::{Context, Poll}};
use std::{
collections::VecDeque,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use tokio::{io::{AsyncRead, ReadBuf}, sync::{RwLock, mpsc}};
use tokio::{
io::{AsyncRead, ReadBuf},
sync::{mpsc, RwLock},
};
/// PCM chunk with audio data and timestamp for precise pacing.
#[derive(Debug)]

View File

@@ -1,7 +1,10 @@
use pmoaudio::{AudioChunk, AudioError};
/// Convert an AudioChunk to PCM bytes with specified bit depth.
pub(crate) fn chunk_to_pcm_bytes(chunk: &AudioChunk, bits_per_sample: u8) -> Result<Vec<u8>, AudioError> {
pub(crate) fn chunk_to_pcm_bytes(
chunk: &AudioChunk,
bits_per_sample: u8,
) -> Result<Vec<u8>, AudioError> {
match chunk {
AudioChunk::F32(_) | AudioChunk::F64(_) => {
return Err(AudioError::ProcessingError(

View File

@@ -298,28 +298,44 @@ impl NodeLogic for FlacCacheSinkLogic {
if let Some(sr) = transform.sample_rate {
if let Err(e) = meta.set_sample_rate(Some(sr)).await {
tracing::error!("FlacCacheSink: Failed to set sample_rate for pk {}: {:?}", pk, e);
tracing::error!(
"FlacCacheSink: Failed to set sample_rate for pk {}: {:?}",
pk,
e
);
} else {
tracing::debug!("FlacCacheSink: Set sample_rate={} for pk {}", sr, pk);
}
}
if let Some(bps) = transform.bits_per_sample {
if let Err(e) = meta.set_bits_per_sample(Some(bps)).await {
tracing::error!("FlacCacheSink: Failed to set bits_per_sample for pk {}: {:?}", pk, e);
tracing::error!(
"FlacCacheSink: Failed to set bits_per_sample for pk {}: {:?}",
pk,
e
);
} else {
tracing::debug!("FlacCacheSink: Set bits_per_sample={} for pk {}", bps, pk);
}
}
if let Some(ch) = transform.channels {
if let Err(e) = meta.set_channels(Some(ch)).await {
tracing::error!("FlacCacheSink: Failed to set channels for pk {}: {:?}", pk, e);
tracing::error!(
"FlacCacheSink: Failed to set channels for pk {}: {:?}",
pk,
e
);
} else {
tracing::debug!("FlacCacheSink: Set channels={} for pk {}", ch, pk);
}
}
if let Some(ts) = transform.total_samples {
if let Err(e) = meta.set_total_samples(Some(ts)).await {
tracing::error!("FlacCacheSink: Failed to set total_samples for pk {}: {:?}", pk, e);
tracing::error!(
"FlacCacheSink: Failed to set total_samples for pk {}: {:?}",
pk,
e
);
} else {
tracing::debug!("FlacCacheSink: Set total_samples={} for pk {}", ts, pk);
}
@@ -329,10 +345,19 @@ impl NodeLogic for FlacCacheSinkLogic {
if sr > 0 {
use std::time::Duration;
let secs = (ts as f64 / sr as f64).round() as u64;
if let Err(e) = meta.set_duration(Some(Duration::from_secs(secs))).await {
tracing::error!("FlacCacheSink: Failed to set duration for pk {}: {:?}", pk, e);
if let Err(e) = meta.set_duration(Some(Duration::from_secs(secs))).await
{
tracing::error!(
"FlacCacheSink: Failed to set duration for pk {}: {:?}",
pk,
e
);
} else {
tracing::debug!("FlacCacheSink: Set duration={} secs for pk {}", secs, pk);
tracing::debug!(
"FlacCacheSink: Set duration={} secs for pk {}",
secs,
pk
);
}
}
}
@@ -340,7 +365,10 @@ impl NodeLogic for FlacCacheSinkLogic {
drop(meta); // Libérer le lock explicitement
} else {
tracing::warn!("FlacCacheSink: No transform metadata available for pk {}", pk);
tracing::warn!(
"FlacCacheSink: No transform metadata available for pk {}",
pk
);
}
// Phase 2: Prebuffer terminé! Copier les métadonnées et pusher à la playlist

View File

@@ -17,7 +17,6 @@ pub(crate) enum FlacStreamState {
Streaming,
}
/// Validate and parse FLAC block size from frame header
///
/// Returns the number of samples in the frame if the header is valid, or None if:
@@ -376,7 +375,6 @@ pub(crate) fn find_complete_frames_with_samples(data: &[u8]) -> (usize, u64) {
}
}
/// Extract sample rate from STREAMINFO block in FLAC header
pub(crate) fn extract_sample_rate_from_streaminfo(flac_header: &[u8]) -> Result<u32, AudioError> {
// Verify we have at least "fLaC" magic + STREAMINFO block header
@@ -424,7 +422,9 @@ pub(crate) fn extract_sample_rate_from_streaminfo(flac_header: &[u8]) -> Result<
}
/// Read FLAC header (fLaC + all metadata blocks until first frame)
pub(crate) async fn read_flac_header(stream: &mut FlacEncodedStream) -> Result<Vec<u8>, AudioError> {
pub(crate) async fn read_flac_header(
stream: &mut FlacEncodedStream,
) -> Result<Vec<u8>, AudioError> {
let mut header = Vec::new();
let mut buffer = [0u8; 4];
@@ -472,8 +472,6 @@ pub(crate) async fn read_flac_header(stream: &mut FlacEncodedStream) -> Result<V
Ok(header)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -78,7 +78,7 @@ use tokio::sync::{mpsc, RwLock};
use tokio_util::sync::CancellationToken;
use tracing::{debug, error, info, trace, warn};
use crate::byte_stream_reader::{PcmChunk};
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,
@@ -143,7 +143,6 @@ impl StreamHandle {
}
}
pub struct FlacClientStream {
inner: SharedClientStream,
}

View File

@@ -1,8 +1,23 @@
use std::{collections::VecDeque, pin::Pin, sync::Arc, task::{Context, Poll}};
use std::{
collections::VecDeque,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use tokio::{io::{AsyncRead, ReadBuf}, sync::RwLock};
use tokio::{
io::{AsyncRead, ReadBuf},
sync::RwLock,
};
use crate::{MetadataSnapshot, sinks::{flac_frame_utils::FlacStreamState, streaming_sink_common::SharedStreamHandleInner, timed_broadcast::{self, TryRecvError}}};
use crate::{
sinks::{
flac_frame_utils::FlacStreamState,
streaming_sink_common::SharedStreamHandleInner,
timed_broadcast::{self, TryRecvError},
},
MetadataSnapshot,
};
use bytes::Bytes;
use std::io;

View File

@@ -69,7 +69,7 @@ use tokio::sync::{mpsc, RwLock};
use tokio_util::sync::CancellationToken;
use tracing::{debug, error, trace, warn};
use crate::byte_stream_reader::{PcmChunk};
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::{
@@ -421,7 +421,7 @@ impl StreamingOggFlacSink {
"Streaming Sink: using broadcast capacity of {} items (max_lead_time={:.1}s)",
broadcast_capacity, broadcast_max_lead_time
);
let (broadcast, _) = timed_broadcast::channel("Ogg-Flac",broadcast_capacity);
let (broadcast, _) = timed_broadcast::channel("Ogg-Flac", broadcast_capacity);
// OGG-FLAC header cache
let header = Arc::new(RwLock::new(None));

View File

@@ -397,7 +397,9 @@ impl SharedSinkContext {
debug!(
"Encoder metadata: from TrackBoundary - duration={:?}s, total_samples={:?}",
self.pending_track_duration.as_ref().map(|d| d.as_secs_f64()),
self.pending_track_duration
.as_ref()
.map(|d| d.as_secs_f64()),
self.pending_total_samples
);

View File

@@ -181,7 +181,9 @@ impl<T> State<T> {
let entry = oentry.unwrap();
trace!(
"TimedBroadcast[{}]: pruning played packet (@{} epoch={})",
self.name, entry.seq, entry.epoch
self.name,
entry.seq,
entry.epoch
);
self.head_seq += 1;
@@ -311,15 +313,13 @@ impl<T> Sender<T> {
}
// 2. Vérifier si un slot est disponible et insérer
let is_top_zero =
audio_timestamp.abs() < TOP_ZERO_EPSILON
let is_top_zero = audio_timestamp.abs() < TOP_ZERO_EPSILON
&& segment_duration >= TOP_ZERO_EPSILON;
let is_zero_header =
audio_timestamp.abs() < TOP_ZERO_EPSILON
&& segment_duration < TOP_ZERO_EPSILON;
audio_timestamp.abs() < TOP_ZERO_EPSILON && segment_duration < TOP_ZERO_EPSILON;
if state.buffer.len() < self.inner.capacity {
if !state.initialized {
if !is_top_zero && segment_duration >= TOP_ZERO_EPSILON {
if !state.initialized {
if !is_top_zero && segment_duration >= TOP_ZERO_EPSILON {
warn!(
"TimedBroadcast[{}]: First packet has non-zero timestamp {:.1}ms - Duration={:.1}ms, treating as epoch start anyway",
state.name,
@@ -352,13 +352,12 @@ impl<T> Sender<T> {
state.name,
state.epoch,
state.last_segment_end.is_some(),
segment_duration*1000.0
segment_duration * 1000.0
);
}
let expires_at = state.epoch_start
+ Duration::from_secs_f64(audio_timestamp
+ segment_duration);
+ Duration::from_secs_f64(audio_timestamp + segment_duration);
let is_first_packet = state.next_seq == 0;
if !is_first_packet && !is_top_zero && !is_zero_header && expires_at <= now {