From a3f03702df8f948fed7a90a82e2f5dcf706f0320 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Sat, 24 Feb 2024 00:03:32 +0200 Subject: [PATCH] Audio playback mvp --- Cargo.toml | 10 ++++++ src/client/app.rs | 15 +++++++-- src/client/ui.rs | 2 +- src/lib.rs | 2 -- src/protocol.rs | 2 ++ src/server.rs | 46 +++++++++++++++++--------- src/server/playback.rs | 73 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 src/server/playback.rs diff --git a/Cargo.toml b/Cargo.toml index 2551913..8a8876f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,14 @@ crc32fast = "1.3.2" crossterm = "0.27.0" interprocess = "1.2.1" ratatui = "0.23.0" +rodio = {version = "0.17.3", features = [ + "claxon", + "flac", + "hound", + "lewton", + "mp3", + "symphonia-all", + "vorbis", + "wav", +]} serde = "1.0.196" diff --git a/src/client/app.rs b/src/client/app.rs index 451c678..5d27ae9 100644 --- a/src/client/app.rs +++ b/src/client/app.rs @@ -51,11 +51,20 @@ impl App { self.push_message(Message::new(MessageType::StateFetch, None)); } + pub fn play(&mut self) { + self.push_message(Message::new(MessageType::PlayTrack, None)); + } + + pub fn toggle_pause(&mut self) { + self.push_message(Message::new(MessageType::TogglePause, None)); + } + pub fn on_key(&mut self, key: char) { match key { 'S' => self.toggle_shuffle(), 'X' => self.toggle_next(), 'R' => self.toggle_repeat(), + ' ' => self.toggle_pause(), 'q' => self.should_quit = true, _ => (), } @@ -69,11 +78,13 @@ impl App { pub fn on_down(&mut self) {} - pub fn on_enter(&mut self) {} + pub fn on_enter(&mut self) { + self.play(); + } pub fn on_tab(&mut self) {} - pub fn on_tick(&mut self, duration: Duration) { + pub fn on_tick(&mut self, _duration: Duration) { self.push_message(Message::new(MessageType::StateFetch, None)); } } diff --git a/src/client/ui.rs b/src/client/ui.rs index 4e2c087..8c4107b 100644 --- a/src/client/ui.rs +++ b/src/client/ui.rs @@ -47,7 +47,7 @@ fn draw_no_connection(f: &mut Frame) { ); } -fn draw_playlist(f: &mut Frame, state: &ServerState, area: Rect) { +fn draw_playlist(f: &mut Frame, _state: &ServerState, area: Rect) { let playlist = List::new(vec![]) .block( Block::default() diff --git a/src/lib.rs b/src/lib.rs index a238e6c..ef3fd87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,6 @@ pub mod protocol; #[derive(Serialize, Deserialize, Debug, Default)] pub struct ServerState { pub playlist_params: PlaylistParams, - pub directory_playlist: Option, - pub queue_playlist: QueuePlaylist, } pub type PlaylistElement = PathBuf; diff --git a/src/protocol.rs b/src/protocol.rs index 09a6796..84b1677 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -35,6 +35,8 @@ pub enum MessageType { ToggleSuffle, ToggleNext, ToggleRepeat, + PlayTrack, + TogglePause, } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/server.rs b/src/server.rs index a3e9314..da52579 100644 --- a/src/server.rs +++ b/src/server.rs @@ -13,16 +13,22 @@ use std::{ use crate::CliArgs; -pub mod audio_backend; +use self::playback::{playback_manager, Playback}; + +pub mod audio_backend; +pub mod playback; -#[derive(Debug)] pub struct Server { pub state: ServerState, + pub playback: Playback, } impl Server { pub fn from_state(state: ServerState) -> Self { - Self { state } + Self { + state, + playback: Default::default(), + } } } @@ -31,14 +37,22 @@ pub fn run(args: CliArgs) -> Result<(), ServerError> { let exit_code = handle_error(err); std::process::exit(exit_code); } - serve(ServerState { + + let server = Arc::new(Mutex::new(Server::from_state(ServerState { playlist_params: PlaylistParams { shuffle: args.shuffle, next: args.next, repeat: args.repeat, }, - ..Default::default() - }) + }))); + + let server_clone = server.clone(); + std::thread::Builder::new() + .name("playback_manager".into()) + .spawn(move || playback_manager(server_clone)) + .unwrap(); + + serve(server) } pub fn kill() { @@ -65,13 +79,12 @@ pub fn run_in_background() { } } -fn serve(state: ServerState) -> Result<(), ServerError> { +fn serve(server: Arc>) -> Result<(), ServerError> { let socket_path = os::get_socket_path()?; if socket_path.exists() { fs::remove_file(&socket_path).map_err(|err| ServerError::Io(err))?; } let socket = LocalSocketListener::bind(socket_path).map_err(|err| ServerError::Io(err))?; - let server = Arc::new(Mutex::new(Server::from_state(state))); println!("Waiting for connections..."); let mut session_counter = 0; for message in socket.incoming() { @@ -153,22 +166,23 @@ fn handle_error(err: ServerError) -> i32 { fn route_request(request: &Message, server: &mut Server) -> Result { match request.message_type { - MessageType::StateFetch => { - return Message::state_response(&server.state); - } + MessageType::StateFetch => {} MessageType::ToggleNext => { server.state.playlist_params.next = !server.state.playlist_params.next; - return Message::state_response(&server.state); } MessageType::ToggleSuffle => { server.state.playlist_params.shuffle = !server.state.playlist_params.shuffle; - return Message::state_response(&server.state); } MessageType::ToggleRepeat => { server.state.playlist_params.repeat = !server.state.playlist_params.repeat; - return Message::state_response(&server.state); } - _ => {} + MessageType::PlayTrack => { + server.playback.play_test(); + } + MessageType::TogglePause => { + server.playback.pause_test(); + } + _ => return Ok(Message::new(MessageType::NotImplementedAck, None)), } - Ok(Message::new(MessageType::NotImplementedAck, None)) + return Message::state_response(&server.state); } diff --git a/src/server/playback.rs b/src/server/playback.rs new file mode 100644 index 0000000..b00992c --- /dev/null +++ b/src/server/playback.rs @@ -0,0 +1,73 @@ +use std::{ + fs::File, + io::BufReader, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use rodio::{Decoder, OutputStream, Sink}; + +use super::Server; + +// HACK: hard-coded path to track +static TRACK: &str = ""; + +enum PlaybackCommand { + Play(PathBuf), + TogglePause, +} + +pub struct Playback { + command_queue: Vec, +} + +impl Default for Playback { + fn default() -> Self { + Self { + command_queue: vec![], + } + } +} + +impl Playback { + pub fn play_test(&mut self) { + self.command_queue.push(PlaybackCommand::Play(TRACK.into())) + } + + pub fn pause_test(&mut self) { + self.command_queue.push(PlaybackCommand::TogglePause) + } +} + +pub fn playback_manager(server: Arc>) { + let (_stream, stream_handle) = OutputStream::try_default().unwrap(); + let sink: Sink = Sink::try_new(&stream_handle).unwrap(); + loop { + for command in server.lock().unwrap().playback.command_queue.drain(..) { + match command { + PlaybackCommand::Play(track) => { + let file = BufReader::new(File::open(&track).unwrap()); + let source = match Decoder::new(file) { + Ok(source) => source, + Err(err) => { + eprintln!("Decode error:\n\ttrack: {track:?}\n\tinfo: {err:?}"); + continue; + } + }; + sink.clear(); + sink.append(source); + sink.play(); + } + PlaybackCommand::TogglePause => { + if !sink.empty() { + if sink.is_paused() { + sink.play() + } else { + sink.pause() + } + } + } + } + } + } +}