Fabriquans un object Channel dans RadioRaradise

This commit is contained in:
2025-11-15 14:02:22 +01:00
parent 4e27255305
commit d9ad056933
12 changed files with 712 additions and 72 deletions

View File

@@ -22,14 +22,14 @@
//! Aucune des crates ci-dessus ne dépend de `pmoaudio-ext`, évitant ainsi
//! tout cycle de dépendances.
#[cfg(feature = "cache-sink")]
#[cfg(any(feature = "cache-sink", feature = "http-stream"))]
pub mod sinks;
#[cfg(feature = "playlist")]
pub mod sources;
// Re-exports pour faciliter l'utilisation
#[cfg(feature = "cache-sink")]
#[cfg(any(feature = "cache-sink", feature = "http-stream"))]
pub use sinks::*;
#[cfg(feature = "playlist")]

View File

@@ -76,10 +76,7 @@ impl BroadcastPacer {
if lead_time < 0.0 {
warn!(
"{}: Dropping late frame: audio_ts={:.3}s, elapsed={:.3}s, lag={:.3}s",
self.label,
audio_timestamp,
elapsed,
-lead_time
self.label, audio_timestamp, elapsed, -lead_time
);
return Err(SkipFrame);
}

View File

@@ -57,7 +57,7 @@
use std::collections::VecDeque;
use std::io;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll};
use std::time::Duration;
@@ -177,6 +177,8 @@ pub struct StreamHandle {
/// Cached FLAC header (sent to new subscribers first)
flac_header: Arc<RwLock<Option<Bytes>>>,
auto_stop: Arc<AtomicBool>,
}
impl StreamHandle {
@@ -243,6 +245,11 @@ impl StreamHandle {
pub fn should_stop(&self) -> bool {
self.active_clients.load(Ordering::SeqCst) == 0
}
/// Enable or disable automatic pipeline shutdown when the last client disconnects.
pub fn set_auto_stop(&self, enabled: bool) {
self.auto_stop.store(enabled, Ordering::SeqCst);
}
}
/// State for FLAC stream subscription.
@@ -348,8 +355,12 @@ impl Drop for FlacClientStream {
debug!("FLAC client disconnected (remaining: {})", count - 1);
if count == 1 {
info!("Last client disconnected, signaling pipeline stop");
self.handle.stop_token.cancel();
if self.handle.auto_stop.load(Ordering::SeqCst) {
info!("Last client disconnected, signaling pipeline stop");
self.handle.stop_token.cancel();
} else {
info!("Last client disconnected, keeping pipeline alive");
}
}
}
}
@@ -556,8 +567,12 @@ impl Drop for IcyClientStream {
debug!("ICY client disconnected (remaining: {})", count - 1);
if count == 1 {
info!("Last client disconnected, signaling pipeline stop");
self.handle.stop_token.cancel();
if self.handle.auto_stop.load(Ordering::SeqCst) {
info!("Last client disconnected, signaling pipeline stop");
self.handle.stop_token.cancel();
} else {
info!("Last client disconnected, keeping pipeline alive");
}
}
}
}
@@ -1068,6 +1083,7 @@ impl StreamingFlacSink {
active_clients,
stop_token: stop_token.clone(),
flac_header: flac_header.clone(),
auto_stop: Arc::new(AtomicBool::new(true)),
};
let logic = StreamingFlacSinkLogic {

View File

@@ -49,7 +49,7 @@
use std::collections::VecDeque;
use std::io;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::{Context, Poll};
@@ -114,6 +114,8 @@ pub struct OggFlacStreamHandle {
/// Cached OGG-FLAC header (sent to new subscribers first)
ogg_header: Arc<RwLock<Option<Bytes>>>,
auto_stop: Arc<AtomicBool>,
}
impl OggFlacStreamHandle {
@@ -143,6 +145,10 @@ impl OggFlacStreamHandle {
pub fn active_client_count(&self) -> usize {
self.active_clients.load(Ordering::SeqCst)
}
pub fn set_auto_stop(&self, enabled: bool) {
self.auto_stop.store(enabled, Ordering::SeqCst);
}
}
/// State for OGG-FLAC stream subscription.
@@ -248,8 +254,12 @@ impl Drop for OggFlacClientStream {
debug!("OGG-FLAC client disconnected (remaining: {})", count - 1);
if count == 1 {
info!("Last OGG-FLAC client disconnected, signaling pipeline stop");
self.handle.stop_token.cancel();
if self.handle.auto_stop.load(Ordering::SeqCst) {
info!("Last OGG-FLAC client disconnected, signaling pipeline stop");
self.handle.stop_token.cancel();
} else {
info!("Last OGG-FLAC client disconnected, keeping pipeline alive");
}
}
}
}
@@ -555,6 +565,7 @@ impl StreamingOggFlacSink {
active_clients,
stop_token: stop_token.clone(),
ogg_header: ogg_header.clone(),
auto_stop: Arc::new(AtomicBool::new(true)),
};
let logic = StreamingOggFlacSinkLogic {

View File

@@ -99,7 +99,7 @@ impl<T> State<T> {
}
}
if purged > 0 {
warn!(
tracing::debug!(
"TimedBroadcast: purged {} expired packet(s) (head_seq={})",
purged,
self.head_seq
@@ -167,10 +167,7 @@ impl<T> Inner<T> {
}
fn close(&self) {
if !self
.is_closed
.swap(true, Ordering::SeqCst)
{
if !self.is_closed.swap(true, Ordering::SeqCst) {
if let Ok(mut state) = self.state.lock() {
state.closed = true;
}
@@ -185,10 +182,7 @@ pub fn channel<T>(capacity: usize) -> (Sender<T>, Receiver<T>) {
assert!(capacity > 0, "capacity must be > 0");
let inner = Arc::new(Inner::new(capacity));
let next_seq = {
let state = inner
.state
.lock()
.expect("timed broadcast mutex poisoned");
let state = inner.state.lock().expect("timed broadcast mutex poisoned");
state.next_seq
};
let sender = Sender {
@@ -198,10 +192,7 @@ pub fn channel<T>(capacity: usize) -> (Sender<T>, Receiver<T>) {
next_seq: AtomicU64::new(next_seq),
});
{
let mut state = inner
.state
.lock()
.expect("timed broadcast mutex poisoned");
let mut state = inner.state.lock().expect("timed broadcast mutex poisoned");
state.cursors.push(Arc::downgrade(&cursor));
}
inner.receiver_count.store(1, Ordering::SeqCst);
@@ -257,15 +248,12 @@ impl<T> Sender<T> {
}
if state.buffer.len() < self.inner.capacity {
let audio_offset =
Duration::from_secs_f64(audio_timestamp.max(0.0));
let audio_offset = Duration::from_secs_f64(audio_timestamp.max(0.0));
let expires_at = state.epoch_start + audio_offset;
let entry = Entry {
seq: state.next_seq,
expires_at,
payload: payload
.take()
.expect("payload already consumed"),
payload: payload.take().expect("payload already consumed"),
audio_timestamp,
epoch: state.epoch,
};
@@ -390,19 +378,14 @@ where
let offset = (self.next_seq - state.head_seq) as usize;
if offset < state.buffer.len() {
let entry = state
.buffer
.get(offset)
.expect("invalid buffer offset");
let entry = state.buffer.get(offset).expect("invalid buffer offset");
let packet = TimedPacket {
payload: entry.payload.clone(),
audio_timestamp: entry.audio_timestamp,
epoch: entry.epoch,
};
self.next_seq += 1;
self.cursor
.next_seq
.store(self.next_seq, Ordering::SeqCst);
self.cursor.next_seq.store(self.next_seq, Ordering::SeqCst);
if state.prune_consumed() {
self.inner.space_notify.notify_waiters();
}
@@ -460,9 +443,7 @@ impl<T> Clone for Receiver<T> {
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
self.cursor
.next_seq
.store(self.next_seq, Ordering::SeqCst);
self.cursor.next_seq.store(self.next_seq, Ordering::SeqCst);
if let Ok(mut state) = self.inner.state.lock() {
if state.prune_consumed() {
self.inner.space_notify.notify_waiters();