use futures::StreamExt; use libmpv::{FileState, Mpv}; use std::process::Stdio; use tokio::io::{AsyncBufReadExt, AsyncRead, BufReader}; use tokio::process::Command; use tokio_stream::wrappers::LinesStream; use tubest_common::*; #[tokio::main] async fn main() -> Result<(), Box> { let args: Vec = std::env::args().skip(1).collect(); let mut subprocs: Vec<_> = args .iter() .map(|arg| { Command::new(arg) .stdout(Stdio::piped()) .spawn() .unwrap_or_else(|e| panic!("couldn't spawn {arg}: {e:?}")) }) .collect(); let stdin_box: Box = Box::new(tokio::io::stdin()); let mut combined = futures::stream::select_all(Some(LinesStream::new(BufReader::new(stdin_box).lines()))); for subproc in &mut subprocs { let stdout_box: Box = Box::new(subproc.stdout.take().unwrap()); combined.push(LinesStream::new(BufReader::new(stdout_box).lines())); } let mpv = Mpv::new().expect("couldn't create mpv"); #[allow(deprecated)] let home = std::env::home_dir().unwrap(); mpv.load_config(home.join(".config/mpv/mpv.conf").to_str().unwrap()) .unwrap(); // TODO: handle other cases of combined.next().await appropriately while let Some(Ok(line)) = combined.next().await { // TODO: remove ? and unwrap so stuff is nonfatal let cmd: Action = serde_json::from_str(line.trim())?; do_action(&mpv, cmd).unwrap(); } Ok(()) } fn do_action(mpv: &Mpv, cmd: Action) -> Result<(), libmpv::Error> { match cmd { Action::Media(mcmd) => match mcmd { MediaAction::Enqueue { path, replace } => { let action = if replace { FileState::Replace } else { FileState::AppendPlay }; mpv.playlist_load_files(&[(&path, action, None)])?; } MediaAction::Pause => { mpv.pause()?; } MediaAction::Unpause => { mpv.unpause()?; } MediaAction::FastForward(duration) => { mpv.seek_forward(duration.as_secs_f64())?; } MediaAction::Rewind(duration) => { mpv.seek_backward(duration.as_secs_f64())?; } MediaAction::Next => { mpv.playlist_next_weak()?; } MediaAction::Stop => { mpv.playlist_clear()?; } } } Ok(()) }