Changement du mécanisme d'attention sur les channels Radio Paradise.
This commit is contained in:
@@ -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)]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user