Refactor WebRenderer to use server-side streaming with OGG-FLAC sink
This commit refactors the WebRenderer to use a server-side streaming architecture with OGG-FLAC sink instead of the previous WebSocket-based approach. The changes include: - Replaced WebSocket communication with HTTP streaming using DirectOggFlacSink - Implemented a new pipeline architecture with dedicated handlers for UPnP commands - Added new modules for registration, registry, and streaming - Updated the renderer to work with a pipeline control system - Removed old WebSocket session management - Added support for HTTP streaming with gapless playback - Updated dependencies and features for the new architecture The WebRenderer now acts as a MediaRenderer UPnP device that serves audio streams via HTTP endpoints, with commands relayed to the audio pipeline through a new control system.
This commit is contained in:
@@ -126,6 +126,7 @@ pub use nodes::{
|
||||
resampling_node::ResamplingNode,
|
||||
timer_buffer_node::TimerBufferNode,
|
||||
timer_node::TimerNode,
|
||||
position_tracker_node::{PositionHandle, PositionTrackerNode},
|
||||
AudioError, AudioNode, TypedAudioNode,
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ pub mod http_source;
|
||||
pub mod resampling_node;
|
||||
pub mod timer_buffer_node;
|
||||
pub mod timer_node;
|
||||
pub mod position_tracker_node;
|
||||
|
||||
// Modules temporairement désactivés
|
||||
/*
|
||||
|
||||
127
pmoaudio/src/nodes/position_tracker_node.rs
Normal file
127
pmoaudio/src/nodes/position_tracker_node.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
//! PositionTrackerNode — nœud transparent de suivi de position.
|
||||
//!
|
||||
//! Laisse passer tous les segments sans modification, et maintient
|
||||
//! un compteur de position courant basé sur le `timestamp_sec` des chunks.
|
||||
//!
|
||||
//! Placé après un `TimerBufferNode`, il reflète la position de l'audio
|
||||
//! effectivement sorti du buffer, pas celui encore en attente.
|
||||
|
||||
use crate::{
|
||||
nodes::AudioError,
|
||||
pipeline::{send_to_children, AudioPipelineNode, Node, NodeLogic},
|
||||
type_constraints::TypeRequirement,
|
||||
AudioSegment, _AudioSegment,
|
||||
};
|
||||
use std::sync::{Arc, atomic::{AtomicU64, Ordering}};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
// ─── Handle public ────────────────────────────────────────────────────────────
|
||||
|
||||
/// Handle partageable pour lire la position courante.
|
||||
#[derive(Clone)]
|
||||
pub struct PositionHandle {
|
||||
/// Position encodée en microsecondes dans un AtomicU64 (pas de lock nécessaire).
|
||||
position_us: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
impl PositionHandle {
|
||||
/// Retourne la position courante en secondes.
|
||||
pub fn current_position_sec(&self) -> f64 {
|
||||
self.position_us.load(Ordering::Relaxed) as f64 / 1_000_000.0
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Logique du nœud ─────────────────────────────────────────────────────────
|
||||
|
||||
struct PositionTrackerLogic {
|
||||
position_us: Arc<AtomicU64>,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl NodeLogic for PositionTrackerLogic {
|
||||
async fn process(
|
||||
&mut self,
|
||||
input: Option<mpsc::Receiver<Arc<AudioSegment>>>,
|
||||
output: Vec<mpsc::Sender<Arc<AudioSegment>>>,
|
||||
stop_token: CancellationToken,
|
||||
) -> Result<(), AudioError> {
|
||||
let mut input = input.ok_or_else(|| {
|
||||
AudioError::ProcessingError("PositionTrackerNode requires an input".into())
|
||||
})?;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = stop_token.cancelled() => break,
|
||||
segment = input.recv() => {
|
||||
match segment {
|
||||
None => break,
|
||||
Some(seg) => {
|
||||
// Mettre à jour la position sur les chunks audio uniquement
|
||||
if let _AudioSegment::Chunk(_) = &seg.segment {
|
||||
let us = (seg.timestamp_sec * 1_000_000.0) as u64;
|
||||
self.position_us.store(us, Ordering::Relaxed);
|
||||
}
|
||||
// Passer le segment sans modification
|
||||
send_to_children("PositionTrackerNode", &output, seg).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn cleanup(
|
||||
&mut self,
|
||||
_reason: crate::pipeline::StopReason,
|
||||
) -> Result<(), AudioError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Nœud public ─────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct PositionTrackerNode {
|
||||
inner: Node<PositionTrackerLogic>,
|
||||
}
|
||||
|
||||
impl PositionTrackerNode {
|
||||
pub fn new() -> (Self, PositionHandle) {
|
||||
let position_us = Arc::new(AtomicU64::new(0));
|
||||
let logic = PositionTrackerLogic { position_us: position_us.clone() };
|
||||
let handle = PositionHandle { position_us };
|
||||
let node = Self { inner: Node::new_with_input(logic, 16) };
|
||||
(node, handle)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl AudioPipelineNode for PositionTrackerNode {
|
||||
fn get_tx(&self) -> Option<mpsc::Sender<Arc<AudioSegment>>> {
|
||||
self.inner.get_tx()
|
||||
}
|
||||
|
||||
fn register(&mut self, child: Box<dyn AudioPipelineNode>) {
|
||||
self.inner.register(child);
|
||||
}
|
||||
|
||||
async fn run(self: Box<Self>, stop_token: CancellationToken) -> Result<(), AudioError> {
|
||||
Box::new(self.inner).run(stop_token).await
|
||||
}
|
||||
|
||||
fn start(self: Box<Self>) -> crate::pipeline::PipelineHandle {
|
||||
Box::new(self.inner).start()
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::TypedAudioNode for PositionTrackerNode {
|
||||
fn input_type(&self) -> Option<TypeRequirement> {
|
||||
None // Accepte tout
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Option<TypeRequirement> {
|
||||
None // Passe tout
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user