Browse and listen to thousands of radio stations across the globe right from your terminal ๐ŸŒŽ ๐Ÿ“ป ๐ŸŽตโœจ
radio rust tokio web-radio command-line-tool tui

play: sink thread receives & processes commands

This adds play, pause & playpause functionality. The media keys
haven't been tested but in theory should work.

Changed files
+72 -5
src
+42 -4
src/app.rs
··· 1 - use crossterm::event::{self, Event, KeyCode, KeyModifiers}; 2 use ratatui::{ 3 prelude::*, 4 widgets::{block::*, *}, ··· 11 thread, 12 time::{Duration, Instant}, 13 }; 14 - use tokio::sync::mpsc::UnboundedReceiver; 15 16 use crate::{ 17 extract::get_currently_playing, 18 input::stream_to_matrix, 19 tui, 20 visualization::{ 21 oscilloscope::Oscilloscope, spectroscope::Spectroscope, vectorscope::Vectorscope, ··· 196 &mut self, 197 terminal: &mut tui::Tui, 198 mut cmd_rx: UnboundedReceiver<State>, 199 id: &str, 200 ) { 201 let new_state = cmd_rx.recv().await.unwrap(); ··· 274 // process all enqueued events 275 let event = event::read().unwrap(); 276 277 - if self.process_events(event.clone()).unwrap() { 278 return; 279 } 280 self.current_display_mut().handle(event); ··· 298 } 299 } 300 301 - fn process_events(&mut self, event: Event) -> Result<bool, io::Error> { 302 let mut quit = false; 303 if let Event::Key(key) = event { 304 if let KeyModifiers::CONTROL = key.modifiers { ··· 357 } 358 } 359 } 360 _ => {} 361 } 362 };
··· 1 + use crossterm::event::{self, Event, KeyCode, KeyModifiers, MediaKeyCode}; 2 use ratatui::{ 3 prelude::*, 4 widgets::{block::*, *}, ··· 11 thread, 12 time::{Duration, Instant}, 13 }; 14 + use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; 15 16 use crate::{ 17 extract::get_currently_playing, 18 input::stream_to_matrix, 19 + play::SinkCommand, 20 tui, 21 visualization::{ 22 oscilloscope::Oscilloscope, spectroscope::Spectroscope, vectorscope::Vectorscope, ··· 197 &mut self, 198 terminal: &mut tui::Tui, 199 mut cmd_rx: UnboundedReceiver<State>, 200 + mut sink_cmd_tx: UnboundedSender<SinkCommand>, 201 id: &str, 202 ) { 203 let new_state = cmd_rx.recv().await.unwrap(); ··· 276 // process all enqueued events 277 let event = event::read().unwrap(); 278 279 + if self 280 + .process_events(event.clone(), &mut sink_cmd_tx) 281 + .unwrap() 282 + { 283 return; 284 } 285 self.current_display_mut().handle(event); ··· 303 } 304 } 305 306 + fn process_events( 307 + &mut self, 308 + event: Event, 309 + sink_cmd_tx: &mut UnboundedSender<SinkCommand>, 310 + ) -> Result<bool, io::Error> { 311 let mut quit = false; 312 if let Event::Key(key) = event { 313 if let KeyModifiers::CONTROL = key.modifiers { ··· 366 } 367 } 368 } 369 + KeyCode::Media(media_key_code) => match media_key_code { 370 + MediaKeyCode::Play => { 371 + sink_cmd_tx 372 + .send(SinkCommand::Play) 373 + .expect("receiver never dropped"); 374 + } 375 + MediaKeyCode::Pause => { 376 + sink_cmd_tx 377 + .send(SinkCommand::Pause) 378 + .expect("receiver never dropped"); 379 + } 380 + MediaKeyCode::PlayPause => { 381 + sink_cmd_tx 382 + .send(SinkCommand::PlayPause) 383 + .expect("receiver never dropped"); 384 + } 385 + MediaKeyCode::Stop => { 386 + quit = true; 387 + } 388 + MediaKeyCode::LowerVolume 389 + | MediaKeyCode::RaiseVolume 390 + | MediaKeyCode::MuteVolume 391 + | MediaKeyCode::TrackNext 392 + | MediaKeyCode::TrackPrevious 393 + | MediaKeyCode::Reverse 394 + | MediaKeyCode::FastForward 395 + | MediaKeyCode::Rewind 396 + | MediaKeyCode::Record => todo!(), 397 + }, 398 _ => {} 399 } 400 };
+30 -1
src/play.rs
··· 34 let now_playing = station.playing.clone().unwrap_or_default(); 35 36 let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel::<State>(); 37 let (frame_tx, frame_rx) = std::sync::mpsc::channel::<minimp3::Frame>(); 38 39 let ui = UiOptions { ··· 112 sink.append(decoder); 113 114 loop { 115 std::thread::sleep(Duration::from_millis(10)); 116 } 117 }); 118 119 let mut terminal = tui::init()?; 120 - app.run(&mut terminal, cmd_rx, &id).await; 121 tui::restore()?; 122 Ok(()) 123 }
··· 34 let now_playing = station.playing.clone().unwrap_or_default(); 35 36 let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel::<State>(); 37 + let (sink_cmd_tx, mut sink_cmd_rx) = tokio::sync::mpsc::unbounded_channel::<SinkCommand>(); 38 let (frame_tx, frame_rx) = std::sync::mpsc::channel::<minimp3::Frame>(); 39 40 let ui = UiOptions { ··· 113 sink.append(decoder); 114 115 loop { 116 + while let Ok(sink_cmd) = sink_cmd_rx.try_recv() { 117 + match sink_cmd { 118 + SinkCommand::Play => { 119 + sink.play(); 120 + } 121 + SinkCommand::Pause => { 122 + sink.pause(); 123 + } 124 + SinkCommand::PlayPause => { 125 + if sink.is_paused() { 126 + sink.play(); 127 + } else { 128 + sink.pause(); 129 + } 130 + } 131 + } 132 + } 133 std::thread::sleep(Duration::from_millis(10)); 134 } 135 }); 136 137 let mut terminal = tui::init()?; 138 + app.run(&mut terminal, cmd_rx, sink_cmd_tx, &id).await; 139 tui::restore()?; 140 Ok(()) 141 } 142 + 143 + /// Command for a sink. 144 + #[derive(Debug, Clone, PartialEq)] 145 + pub enum SinkCommand { 146 + /// Play. 147 + Play, 148 + /// Pause. 149 + Pause, 150 + /// Toggle between play and pause. 151 + PlayPause, 152 + }