Add separate endpoints for pure FLAC and ICY streams

VLC cannot decode FLAC streams with embedded ICY metadata because
the ICY blocks break the FLAC decoder. Split into two endpoints:

- /test/stream: Pure FLAC (for VLC and standard FLAC players)
- /test/stream-icy: FLAC + ICY metadata (for ICY-aware clients)

This allows:
- VLC to play audio correctly using pure FLAC
- ICY-aware clients to receive metadata updates
- Metadata endpoint remains available for JSON queries

Fixes the "no audio" issue where VLC would connect, receive the
FLAC header with ICY metadata blocks, fail to decode, and disconnect.
This commit is contained in:
Claude
2025-11-11 23:44:09 +00:00
parent 1f5884627c
commit d9bc1cfc03

View File

@@ -22,9 +22,9 @@
//! cargo run --example stream_block --features full -- 0 # Main Mix //! cargo run --example stream_block --features full -- 0 # Main Mix
//! //!
//! Then open in VLC: //! Then open in VLC:
//! vlc http://localhost:8080/test/stream //! vlc http://localhost:8080/test/stream (pure FLAC)
//! vlc http://localhost:8080/test/stream-icy (FLAC + ICY metadata)
//! //!
//! The stream includes ICY metadata for "Now Playing" information.
//! To check current metadata: //! To check current metadata:
//! curl http://localhost:8080/test/metadata //! curl http://localhost:8080/test/metadata
@@ -49,18 +49,34 @@ struct AppState {
stream_handle: pmoaudio_ext::StreamHandle, stream_handle: pmoaudio_ext::StreamHandle,
} }
/// Main HTTP handler for streaming /// Main HTTP handler for streaming (pure FLAC, no ICY metadata)
async fn stream_handler( async fn stream_handler(
State(state): State<Arc<AppState>>, State(state): State<Arc<AppState>>,
_headers: HeaderMap, _headers: HeaderMap,
) -> Result<Response, StatusCode> { ) -> Result<Response, StatusCode> {
tracing::info!("New client connected"); tracing::info!("New client connected (pure FLAC mode)");
// Always use ICY mode for metadata support // Pure FLAC stream without ICY metadata
tracing::info!("Serving FLAC stream with ICY metadata"); let flac_stream = state.stream_handle.subscribe_flac();
Ok(Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "audio/flac")
.header("Cache-Control", "no-cache, no-store")
.body(Body::from_stream(ReaderStream::new(flac_stream)))
.unwrap())
}
/// ICY streaming handler (FLAC with embedded metadata)
async fn stream_icy_handler(
State(state): State<Arc<AppState>>,
_headers: HeaderMap,
) -> Result<Response, StatusCode> {
tracing::info!("New client connected (ICY mode)");
// FLAC stream with ICY metadata
let icy_stream = state.stream_handle.subscribe_icy(); let icy_stream = state.stream_handle.subscribe_icy();
// Build response with ICY headers
Ok(Response::builder() Ok(Response::builder()
.status(StatusCode::OK) .status(StatusCode::OK)
.header("Content-Type", "audio/flac") .header("Content-Type", "audio/flac")
@@ -105,9 +121,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
eprintln!(" 3 - World/Etc Mix (global sounds)"); eprintln!(" 3 - World/Etc Mix (global sounds)");
eprintln!(); eprintln!();
eprintln!("After starting, open in VLC:"); eprintln!("After starting, open in VLC:");
eprintln!(" vlc http://localhost:8080/test/stream"); eprintln!(" vlc http://localhost:8080/test/stream (pure FLAC)");
eprintln!(); eprintln!(" vlc http://localhost:8080/test/stream-icy (FLAC + ICY metadata)");
eprintln!("The stream includes ICY metadata for Now Playing info");
std::process::exit(1); std::process::exit(1);
} }
@@ -192,8 +207,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app_state = Arc::new(AppState { stream_handle }); let app_state = Arc::new(AppState { stream_handle });
// Add streaming route // Add streaming routes
server.add_handler_with_state("/test/stream", stream_handler, app_state.clone()).await; server.add_handler_with_state("/test/stream", stream_handler, app_state.clone()).await;
server.add_handler_with_state("/test/stream-icy", stream_icy_handler, app_state.clone()).await;
// Add metadata route // Add metadata route
server.add_handler_with_state("/test/metadata", metadata_handler, app_state.clone()).await; server.add_handler_with_state("/test/metadata", metadata_handler, app_state.clone()).await;
@@ -205,12 +221,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing::info!("========================================"); tracing::info!("========================================");
tracing::info!("Ready to stream!"); tracing::info!("Ready to stream!");
tracing::info!(""); tracing::info!("");
tracing::info!("Open in VLC:"); tracing::info!("Pure FLAC stream (for VLC, standard players):");
tracing::info!(" vlc http://localhost:8080/test/stream"); tracing::info!(" vlc http://localhost:8080/test/stream");
tracing::info!(""); tracing::info!("");
tracing::info!("Stream includes ICY metadata for Now Playing info"); tracing::info!("FLAC + ICY metadata stream (for ICY-aware clients):");
tracing::info!(" http://localhost:8080/test/stream-icy");
tracing::info!(""); tracing::info!("");
tracing::info!("Metadata endpoint:"); tracing::info!("Metadata endpoint (JSON):");
tracing::info!(" curl http://localhost:8080/test/metadata"); tracing::info!(" curl http://localhost:8080/test/metadata");
tracing::info!("========================================"); tracing::info!("========================================");
tracing::info!(""); tracing::info!("");