use pipewire::{context::Context, main_loop::MainLoop}; use serde::Deserialize; use std::net::{Ipv6Addr, SocketAddrV6}; use tokio::task::spawn_blocking; use warp::{serve, Filter}; mod audio_control; mod app; use app::App; #[derive(Deserialize)] struct PlayTrackParams { track_name: String, } async fn server_main(state: App) { let localhost: Ipv6Addr = "::1".parse().unwrap(); let server_addr = SocketAddrV6::new(localhost, 3001, 0, 0); let root = warp::path!().map(|| "ok".to_string()); let list_output_devices = warp::path!("output_devices").map({ let state = state.clone(); move || { let devices = state.audio_devices(); serde_json::to_string(&devices).unwrap() } }); let list_tracks = warp::path!("tracks").map({ let state = state.clone(); move || serde_json::to_string(&state.tracks()).unwrap() }); let enable_track = warp::put() .and(warp::path!("playing")) .and(warp::body::json()) .map({ let state = state.clone(); move |params: PlayTrackParams| { state.enable_track(¶ms.track_name); "".to_owned() } }); let disable_track = warp::delete() .and(warp::path!("playing")) .and(warp::body::json()) .map({ let state = state.clone(); move |params: PlayTrackParams| { state.disable_track(¶ms.track_name); "".to_owned() } }); let play_all = warp::put().and(warp::path!("playing")).map({ let state = state.clone(); move || { state.play(); "".to_owned() } }); let stop_all = warp::delete().and(warp::path!("playing")).map({ let state = state.clone(); move || { state.stop(); "".to_owned() } }); let now_playing = warp::path!("playing").map({ let state = state.clone(); move || serde_json::to_string(&state.playing()).unwrap() }); let routes = root .or(list_output_devices) .or(list_tracks) .or(enable_track) .or(disable_track) .or(play_all) .or(stop_all) .or(now_playing); serve(routes).run(server_addr).await; } fn handle_add_audio_device(state: App, props: &pipewire::spa::utils::dict::DictRef) { if props.get("media.class") == Some("Audio/Sink") { if let Some(device_name) = props.get("node.description") { state.add_audio(device_name.to_owned()); } } } fn pipewire_loop(state: App) -> Result<(), Box<dyn std::error::Error>> { let mainloop = MainLoop::new(None)?; let context = Context::new(&mainloop)?; let core = context.connect(None)?; let registry = core.get_registry()?; let _listener = registry .add_listener_local() .global({ let state = state.clone(); move |global_data| { if let Some(props) = global_data.props { handle_add_audio_device(state.clone(), props); } } }) .register(); mainloop.run(); Ok(()) } fn pipewire_main(state: App) { pipewire_loop(state).expect("pipewire should not error"); } #[tokio::main] async fn main() { gstreamer::init(); let state = App::default(); spawn_blocking({ let state = state.clone(); move || pipewire_main(state) }); server_main(state.clone()).await; }