Add StreamType for multi-client support and improved UPnP control

- Introduce `Streamtype` enum (Continuous vs Finite) to distinguish radio streams from finite tracks
- Enrich `TrackBoundary` sync marker with stream type for proper pause behavior per mode (silence vs backpressure)
- Update all sources and sinks to pass `StreamType` when creating track boundaries
  - Radio Paradise, HTTP source → Continuous (infinite)
- Improve UPnP control architecture: pause sends silence for radio, blocks pipeline via backpressure for tracks
- Prepare groundwork for multi-client DSP architecture with shared source and per-DSP pipelines
This commit is contained in:
2026-04-04 14:37:11 +02:00
parent 33b279ff1e
commit 8924552696
16 changed files with 204 additions and 21 deletions

View File

@@ -233,7 +233,7 @@ impl NodeLogic for FlacCacheSinkLogic {
}
}
_AudioSegment::Sync(marker) => match &**marker {
SyncMarker::TrackBoundary { metadata } => {
SyncMarker::TrackBoundary { metadata, .. } => {
// TrackBoundary pendant le prebuffer - track courte (< 512KB)
tracing::warn!(
"FlacCacheSink: TrackBoundary received before prebuffer complete - track shorter than 512KB, closing pump and waiting for ingestion"
@@ -589,7 +589,7 @@ impl NodeLogic for FlacCacheSinkLogic {
// Si pump_closed, ignorer silencieusement le chunk
}
_AudioSegment::Sync(marker) => match &**marker {
SyncMarker::TrackBoundary { metadata } => {
SyncMarker::TrackBoundary { metadata, .. } => {
// Nouveau morceau - fermer le pump si pas déjà fermé
tracing::debug!("FlacCacheSink: TrackBoundary received, closing pump and storing metadata for next track");
// Stocker les métadonnées pour la prochaine track

View File

@@ -307,7 +307,7 @@ impl NodeLogic for StreamingFlacSinkLogic {
_AudioSegment::Sync(marker) => {
match marker.as_ref() {
SyncMarker::TrackBoundary { metadata } => {
SyncMarker::TrackBoundary { metadata, .. } => {
// Prepare encoder options (metadata + duration) for the upcoming track.
if let Err(e) =
self.ctx.prepare_encoder_options_for_track(metadata).await

View File

@@ -88,8 +88,17 @@ impl IcyClientStream {
// Add cover URL if we have a cover_pk
if let Some(pk) = &meta.cover_pk {
#[cfg(feature = "playlist")]
let cover_url = pmocache::covers_absolute_url_for_upnp(pk, None);
metadata_str.push_str(&format!("StreamUrl='{}';", cover_url));
#[cfg(feature = "playlist")]
{
metadata_str.push_str(&format!("StreamUrl='{}';", cover_url));
}
#[cfg(not(feature = "playlist"))]
{
// When playlist feature is not enabled, use relative URL
metadata_str.push_str(&format!("StreamUrl='/covers/image/{}/256';", pk));
}
} else if let Some(url) = &meta.cover_url {
// Fallback to external cover URL if no local pk
metadata_str.push_str(&format!("StreamUrl='{}';", url));

View File

@@ -265,7 +265,7 @@ impl NodeLogic for StreamingOggFlacSinkLogic {
_AudioSegment::Sync(marker) => {
match marker.as_ref() {
SyncMarker::TrackBoundary { metadata } => {
SyncMarker::TrackBoundary { metadata, .. } => {
// Inject per-track metadata and duration into the next FLAC header.
if let Err(e) =
self.ctx.prepare_encoder_options_for_track(metadata).await

View File

@@ -35,6 +35,7 @@ use pmoaudio::{
AudioSegment,
nodes::AudioError,
pipeline::{AudioPipelineNode, Node, NodeLogic, send_to_children},
StreamType,
};
use tokio::sync::{broadcast, mpsc};
use tokio_util::sync::CancellationToken;
@@ -505,7 +506,7 @@ async fn send_track_boundary(
let _ = meta.set_title(Some(u.to_string())).await;
}
let meta_arc = Arc::new(tokio::sync::RwLock::new(meta));
let boundary = AudioSegment::new_track_boundary(0, timestamp_sec, meta_arc);
let boundary = AudioSegment::new_track_boundary(0, timestamp_sec, meta_arc, StreamType::Finite);
send_to_children("PlayerSource", output, boundary).await
}

View File

@@ -113,7 +113,7 @@ use pmoaudio::{
nodes::{AudioError, TypedAudioNode, DEFAULT_CHUNK_DURATION_MS},
pipeline::{send_to_children, AudioPipelineNode, Node, NodeLogic},
type_constraints::TypeRequirement,
AudioSegment,
AudioSegment, StreamType,
};
use pmoaudiocache::Cache as AudioCache;
use pmoflac::decode_audio_stream;
@@ -279,7 +279,7 @@ impl NodeLogic for PlaylistSourceLogic {
let track_start = std::time::Instant::now();
tracing::debug!("PlaylistSourceLogic: emitting TrackBoundary");
let metadata_for_boundary = metadata.clone();
let boundary = AudioSegment::new_track_boundary(0, 0.0, metadata_for_boundary);
let boundary = AudioSegment::new_track_boundary(0, 0.0, metadata_for_boundary, StreamType::Finite);
send_to_children(node_name, &output, boundary).await?;
// Obtenir le chemin du fichier