Fabriquans un object Channel dans RadioRaradise
This commit is contained in:
157
pmoparadise/examples/serve_channels.rs
Normal file
157
pmoparadise/examples/serve_channels.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
//! Minimal HTTP server exposing all four Radio Paradise channels.
|
||||
//!
|
||||
//! Routes:
|
||||
//! - `/radioparadise/stream/<slug>/flac`
|
||||
//! - `/radioparadise/stream/<slug>/ogg`
|
||||
//! - `/radioparadise/stream/<slug>/icy`
|
||||
//! - `/radioparadise/metadata/<slug>`
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use pmoparadise::{channels::ALL_CHANNELS, stream_channel::ParadiseChannelManager};
|
||||
use pmoserver::{init_logging, ServerBuilder};
|
||||
use tokio_util::io::ReaderStream;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
manager: Arc<ParadiseChannelManager>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let _ = init_logging();
|
||||
|
||||
info!("Initializing Radio Paradise channels...");
|
||||
let manager = Arc::new(ParadiseChannelManager::with_defaults().await?);
|
||||
let app_state = Arc::new(AppState {
|
||||
manager: manager.clone(),
|
||||
});
|
||||
|
||||
let mut server = ServerBuilder::new("RadioParadiseChannels", "http://localhost", 8080).build();
|
||||
|
||||
for descriptor in ALL_CHANNELS.iter() {
|
||||
let slug = descriptor.slug;
|
||||
let flac_path = format!("/radioparadise/stream/{}/flac", slug);
|
||||
let ogg_path = format!("/radioparadise/stream/{}/ogg", slug);
|
||||
let icy_path = format!("/radioparadise/stream/{}/icy", slug);
|
||||
let meta_path = format!("/radioparadise/metadata/{}", slug);
|
||||
let channel_id = descriptor.id;
|
||||
|
||||
server
|
||||
.add_handler_with_state(
|
||||
&flac_path,
|
||||
move |State(state): State<Arc<AppState>>| {
|
||||
let manager = state.manager.clone();
|
||||
async move { stream_flac(manager, channel_id).await }
|
||||
},
|
||||
app_state.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
server
|
||||
.add_handler_with_state(
|
||||
&ogg_path,
|
||||
move |State(state): State<Arc<AppState>>| {
|
||||
let manager = state.manager.clone();
|
||||
async move { stream_ogg(manager, channel_id).await }
|
||||
},
|
||||
app_state.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
server
|
||||
.add_handler_with_state(
|
||||
&icy_path,
|
||||
move |State(state): State<Arc<AppState>>| {
|
||||
let manager = state.manager.clone();
|
||||
async move { stream_icy(manager, channel_id).await }
|
||||
},
|
||||
app_state.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
server
|
||||
.add_handler_with_state(
|
||||
&meta_path,
|
||||
move |State(state): State<Arc<AppState>>| {
|
||||
let manager = state.manager.clone();
|
||||
async move { get_metadata(manager, channel_id).await }
|
||||
},
|
||||
app_state.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
info!("========================================");
|
||||
info!("Radio Paradise streaming server running on http://localhost:8080");
|
||||
info!("Available channels:");
|
||||
for descriptor in ALL_CHANNELS.iter() {
|
||||
info!(
|
||||
" {}: /radioparadise/stream/{}/flac (also /ogg, /icy, metadata)",
|
||||
descriptor.display_name, descriptor.slug
|
||||
);
|
||||
}
|
||||
info!("Press Ctrl+C to stop.");
|
||||
info!("========================================");
|
||||
|
||||
server.start().await;
|
||||
server.wait().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn stream_flac(
|
||||
manager: Arc<ParadiseChannelManager>,
|
||||
channel_id: u8,
|
||||
) -> Result<Response, StatusCode> {
|
||||
let channel = manager.get(channel_id).ok_or(StatusCode::NOT_FOUND)?;
|
||||
let stream = channel.subscribe_flac();
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/flac")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
async fn stream_ogg(
|
||||
manager: Arc<ParadiseChannelManager>,
|
||||
channel_id: u8,
|
||||
) -> Result<Response, StatusCode> {
|
||||
let channel = manager.get(channel_id).ok_or(StatusCode::NOT_FOUND)?;
|
||||
let stream = channel.subscribe_ogg();
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/ogg")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
async fn stream_icy(
|
||||
manager: Arc<ParadiseChannelManager>,
|
||||
channel_id: u8,
|
||||
) -> Result<Response, StatusCode> {
|
||||
let channel = manager.get(channel_id).ok_or(StatusCode::NOT_FOUND)?;
|
||||
let stream = channel.subscribe_icy();
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header("Content-Type", "audio/flac")
|
||||
.header("icy-metaint", "16000")
|
||||
.body(Body::from_stream(ReaderStream::new(stream)))
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
async fn get_metadata(
|
||||
manager: Arc<ParadiseChannelManager>,
|
||||
channel_id: u8,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let channel = manager.get(channel_id).ok_or(StatusCode::NOT_FOUND)?;
|
||||
let metadata = channel.metadata().await;
|
||||
Ok(Json(metadata))
|
||||
}
|
||||
@@ -248,9 +248,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
source.register(Box::new(streaming_sink));
|
||||
source.register(Box::new(ogg_sink));
|
||||
|
||||
tracing::info!(
|
||||
"Pipeline connected: StreamSource → TimerBufferNode → {{FLAC, OGG}} sinks"
|
||||
);
|
||||
tracing::info!("Pipeline connected: StreamSource → TimerBufferNode → {{FLAC, OGG}} sinks");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// Setup pmoserver with streaming routes
|
||||
@@ -267,19 +265,36 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
});
|
||||
|
||||
// Add streaming routes
|
||||
let base = "/radioparadise/test";
|
||||
server
|
||||
.add_handler_with_state("/test/stream", stream_handler, app_state.clone())
|
||||
.add_handler_with_state(
|
||||
&format!("{}/stream", base),
|
||||
stream_handler,
|
||||
app_state.clone(),
|
||||
)
|
||||
.await;
|
||||
server
|
||||
.add_handler_with_state("/test/stream-icy", stream_icy_handler, app_state.clone())
|
||||
.add_handler_with_state(
|
||||
&format!("{}/stream-icy", base),
|
||||
stream_icy_handler,
|
||||
app_state.clone(),
|
||||
)
|
||||
.await;
|
||||
server
|
||||
.add_handler_with_state("/test/stream-ogg", stream_ogg_handler, app_state.clone())
|
||||
.add_handler_with_state(
|
||||
&format!("{}/stream-ogg", base),
|
||||
stream_ogg_handler,
|
||||
app_state.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Add metadata route
|
||||
server
|
||||
.add_handler_with_state("/test/metadata", metadata_handler, app_state.clone())
|
||||
.add_handler_with_state(
|
||||
&format!("{}/metadata", base),
|
||||
metadata_handler,
|
||||
app_state.clone(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Add health check
|
||||
@@ -290,16 +305,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing::info!("Ready to stream!");
|
||||
tracing::info!("");
|
||||
tracing::info!("Pure FLAC stream (for VLC, standard players):");
|
||||
tracing::info!(" vlc http://localhost:8080/test/stream");
|
||||
tracing::info!(" vlc http://localhost:8080{}/stream", base);
|
||||
tracing::info!("");
|
||||
tracing::info!("OGG-FLAC stream (streaming container with metadata support):");
|
||||
tracing::info!(" vlc http://localhost:8080/test/stream-ogg");
|
||||
tracing::info!(" vlc http://localhost:8080{}/stream-ogg", base);
|
||||
tracing::info!("");
|
||||
tracing::info!("FLAC + ICY metadata stream (for ICY-aware clients):");
|
||||
tracing::info!(" http://localhost:8080/test/stream-icy");
|
||||
tracing::info!(" http://localhost:8080{}/stream-icy", base);
|
||||
tracing::info!("");
|
||||
tracing::info!("Metadata endpoint (JSON):");
|
||||
tracing::info!(" curl http://localhost:8080/test/metadata");
|
||||
tracing::info!(" curl http://localhost:8080{}/metadata", base);
|
||||
tracing::info!("========================================");
|
||||
tracing::info!("");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user