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

@@ -3,7 +3,7 @@ use tokio::sync::RwLock;
use pmometadata::TrackMetadata;
use crate::{gain_db_from_linear, AudioChunk, AudioChunkData, BitDepth, SyncMarker};
use crate::{gain_db_from_linear, AudioChunk, AudioChunkData, BitDepth, StreamType, SyncMarker};
pub enum _AudioSegment {
Chunk(Arc<AudioChunk>),
@@ -160,9 +160,11 @@ impl AudioSegment {
order: u64,
timestamp_sec: f64,
metadata: Arc<RwLock<dyn TrackMetadata>>,
stream_type: StreamType,
) -> Arc<Self> {
let marker = Arc::new(SyncMarker::TrackBoundary {
metadata: Arc::clone(&metadata),
stream_type,
});
Arc::new(Self {
order,
@@ -303,7 +305,18 @@ impl AudioSegment {
pub fn as_track_metadata(&self) -> Option<&Arc<RwLock<dyn TrackMetadata>>> {
match &self.segment {
_AudioSegment::Sync(marker) => match &**marker {
SyncMarker::TrackBoundary { metadata } => Some(metadata),
SyncMarker::TrackBoundary { metadata, .. } => Some(metadata),
_ => None,
},
_ => None,
}
}
/// Récupère le stream_type si c'est un TrackBoundary
pub fn stream_type(&self) -> Option<StreamType> {
match &self.segment {
_AudioSegment::Sync(marker) => match &**marker {
SyncMarker::TrackBoundary { stream_type, .. } => Some(*stream_type),
_ => None,
},
_ => None,

View File

@@ -95,7 +95,7 @@ pub mod bit_depth;
pub mod dsp;
pub use audio_segment::{AudioSegment, _AudioSegment};
pub use sync_marker::SyncMarker;
pub use sync_marker::{StreamType, SyncMarker};
pub use audio_chunk::{
gain_db_from_linear, gain_linear_from_db, AudioChunk, AudioChunkData, AudioFloatChunk,

View File

@@ -2,7 +2,7 @@ use crate::{
nodes::{AudioError, TypedAudioNode, DEFAULT_CHUNK_DURATION_MS},
pipeline::{send_to_children, AudioPipelineNode, Node, NodeLogic},
type_constraints::TypeRequirement,
AudioChunk, AudioChunkData, AudioSegment, I24,
AudioChunk, AudioChunkData, AudioSegment, I24, StreamType,
};
use pmoflac::{decode_audio_stream, AudioFileMetadata, StreamInfo};
use pmometadata::{MemoryTrackMetadata, TrackMetadata};
@@ -98,6 +98,7 @@ impl NodeLogic for FileSourceLogic {
0,
0.0,
Arc::new(tokio::sync::RwLock::new(metadata)),
StreamType::Finite,
);
send_to_children(std::any::type_name::<Self>(), &output, track_boundary).await?;
}

View File

@@ -2,7 +2,7 @@ use crate::{
nodes::{AudioError, TypedAudioNode, DEFAULT_CHANNEL_SIZE},
pipeline::{Node, NodeLogic},
type_constraints::TypeRequirement,
AudioChunk, AudioPipelineNode, AudioSegment, SyncMarker,
AudioChunk, AudioPipelineNode, AudioSegment, SyncMarker, StreamType,
};
use pmoflac::{encode_flac_stream, EncoderOptions, PcmFormat};
use std::{
@@ -822,6 +822,7 @@ mod tests {
0,
0.0,
std::sync::Arc::new(tokio::sync::RwLock::new(metadata)),
crate::StreamType::Finite,
);
tx.send(track_boundary).await.unwrap();

View File

@@ -2,7 +2,7 @@ use crate::{
nodes::{AudioError, TypedAudioNode, DEFAULT_CHUNK_DURATION_MS},
pipeline::{send_to_children, Node, NodeLogic},
type_constraints::TypeRequirement,
AudioChunk, AudioChunkData, AudioPipelineNode, AudioSegment, I24,
AudioChunk, AudioChunkData, AudioPipelineNode, AudioSegment, I24, StreamType,
};
use futures_util::StreamExt;
use pmoflac::{decode_audio_stream, StreamInfo};
@@ -178,7 +178,7 @@ impl NodeLogic for HttpSourceLogic {
// Émettre TrackBoundary avec les métadonnées HTTP
let track_boundary =
AudioSegment::new_track_boundary(0, 0.0, Arc::new(tokio::sync::RwLock::new(metadata)));
AudioSegment::new_track_boundary(0, 0.0, Arc::new(tokio::sync::RwLock::new(metadata)), StreamType::Continuous);
send_to_children(std::any::type_name::<Self>(), &output, track_boundary).await?;
// Préparer la lecture des chunks audio

View File

@@ -33,7 +33,7 @@ use crate::{
nodes::{AudioError, TypedAudioNode},
pipeline::{send_to_children, AudioPipelineNode, Node, NodeLogic},
type_constraints::TypeRequirement,
AudioChunk, AudioChunkData, AudioSegment, BitDepth, I24,
AudioChunk, AudioChunkData, AudioSegment, BitDepth, I24, StreamType,
};
use std::sync::Arc;
use tokio::sync::mpsc;
@@ -495,7 +495,7 @@ mod tests {
let metadata = Arc::new(tokio::sync::RwLock::new(
pmometadata::MemoryTrackMetadata::new(),
));
let boundary = AudioSegment::new_track_boundary(0, 0.0, metadata);
let boundary = AudioSegment::new_track_boundary(0, 0.0, metadata, StreamType::Finite);
// Envoyer le boundary
input_tx.send(boundary.clone()).await.unwrap();

View File

@@ -3,9 +3,16 @@ use tokio::sync::RwLock;
use pmometadata::TrackMetadata;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamType {
Continuous,
Finite,
}
pub enum SyncMarker {
TrackBoundary {
metadata: Arc<RwLock<dyn TrackMetadata>>,
stream_type: StreamType,
},
StreamMetadata {
key: String,
@@ -15,5 +22,4 @@ pub enum SyncMarker {
Heartbeat,
EndOfStream,
Error(String),
// autres cas à venir…
}