Replace HTTP timeout with idle mode + END_OF_BLOCKS_SIGNAL

MAJOR ARCHITECTURAL IMPROVEMENT:

Instead of using arbitrary timeouts that don't solve the real problem,
implement proper idle mode and explicit end-of-stream signaling.

Changes:

1. **Remove block_id timeout completely**
   - No more BLOCK_ID_TIMEOUT_SECS
   - Source enters idle mode when queue is empty
   - Waits indefinitely for new block_ids (poll every 100ms)
   - Only exits on cancellation or END_OF_BLOCKS_SIGNAL

2. **Introduce END_OF_BLOCKS_SIGNAL (EventId::MAX)**
   - Special block_id value to signal "no more blocks"
   - Source terminates cleanly after processing current block
   - Allows proper shutdown without cancellation
   - Exported from pmoparadise crate for public use

3. **Update HTTP timeout to 24 hours**
   - Effectively infinite timeout for block downloads
   - HTTP stream stays open as long as needed
   - Closed by pipeline termination, not arbitrary timeout

4. **Update stream_block example**
   - Push END_OF_BLOCKS_SIGNAL after the single block
   - Demonstrates clean termination after one block
   - Documents pattern for continuous vs. bounded streaming

Benefits:
- No arbitrary timeouts that might truncate valid streams
- Clean separation: cancellation (external) vs. completion (internal)
- Supports both continuous radio and bounded playlists
- Proper idle mode for on-demand streaming applications

Usage pattern:
```rust
// Single block then stop
source.push_block_id(block_id);
source.push_block_id(END_OF_BLOCKS_SIGNAL);

// Continuous streaming
source.push_block_id(block1);
source.push_block_id(block2);
// ... keep pushing or wait in idle mode

// Graceful shutdown
source.push_block_id(END_OF_BLOCKS_SIGNAL);
```
This commit is contained in:
Claude
2025-11-12 11:32:40 +00:00
parent e44bef2021
commit dbb809261a
3 changed files with 42 additions and 33 deletions

View File

@@ -4,6 +4,9 @@
//! using the StreamingFlacSink over HTTP via pmoserver. Perfect for
//! testing with VLC or other media players that support HTTP streaming.
//!
//! The example streams ONE block then terminates cleanly using END_OF_BLOCKS_SIGNAL.
//! For continuous streaming, push multiple block_ids without the END signal.
//!
//! Architecture:
//! ```text
//! RadioParadiseStreamSource → TimerNode → StreamingFlacSink
@@ -38,7 +41,7 @@ use axum::{
use pmoaudio::{AudioPipelineNode, TimerNode};
use pmoaudio_ext::{StreamingFlacSink, StreamingOggFlacSink};
use pmoflac::EncoderOptions;
use pmoparadise::{RadioParadiseClient, RadioParadiseStreamSource};
use pmoparadise::{RadioParadiseClient, RadioParadiseStreamSource, END_OF_BLOCKS_SIGNAL};
use pmoserver::{ServerBuilder, init_logging};
use std::env;
use std::sync::Arc;
@@ -206,7 +209,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut source_flac = RadioParadiseStreamSource::new(client.clone());
source_flac.push_block_id(block.event);
tracing::debug!("RadioParadiseStreamSource (FLAC) created with block {}", block.event);
source_flac.push_block_id(END_OF_BLOCKS_SIGNAL); // Signal: no more blocks after this one
tracing::debug!("RadioParadiseStreamSource (FLAC) created with block {} + END signal", block.event);
// Calculate channel size to match max_lead_time
// With 50ms chunks and 3.0s lead time: 3.0 / 0.05 = 60 chunks
@@ -232,7 +236,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut source_ogg = RadioParadiseStreamSource::new(client);
source_ogg.push_block_id(block.event);
tracing::debug!("RadioParadiseStreamSource (OGG) created with block {}", block.event);
source_ogg.push_block_id(END_OF_BLOCKS_SIGNAL); // Signal: no more blocks after this one
tracing::debug!("RadioParadiseStreamSource (OGG) created with block {} + END signal", block.event);
let mut timer_ogg = TimerNode::with_channel_size(max_lead_time, channel_size);
tracing::debug!("TimerNode (OGG) created with {:.1}s max lead time, {} chunk buffer", max_lead_time, channel_size);