From 2a0cd38189001a9f8b029d170cd37ad717b80b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabor=20K=C3=B6rber?= Date: Mon, 22 Jan 2024 13:46:12 +0100 Subject: [PATCH] refactor: creating an AudioHandler instead of directly giving the thread, allowing to add custom event handlers while playback. --- src/main.rs | 16 ++++++--- src/state.rs | 6 ++-- src/vbplay.rs | 99 ++++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 105 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2b26262..d655607 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use std::env; use std::sync::{Arc, Mutex}; use dotenvy::dotenv; -use vbplay::{AudioThread, DeviceSelection, SoundDecoder}; +use vbplay::{example_handler, AudioThread, DeviceSelection, PlaybackAction, SoundDecoder}; mod soundclips; mod state; @@ -28,11 +28,13 @@ async fn main() { jinja.set_loader(path_loader("templates")); let template_engine = Engine::from(jinja); - let audio = vbplay::audio_thread( + let mut audio = vbplay::AudioHandler::new( DeviceSelection::FindByPattern(device_pattern), SoundDecoder::Detect, ); - let audio: Arc> = Arc::new(Mutex::new(audio)); + let event_handler = Box::new(example_handler::PressMouseForward::new()); + audio.on_playback(event_handler); + let audio = Arc::new(Mutex::new(audio)); let app_state = AppState { engine: template_engine, @@ -88,6 +90,7 @@ async fn play_handler( player .lock() .unwrap() + .listener .send(vbplay::Command::PlayWhilePressing( full_file_name.into(), "".to_owned(), // placeholder. @@ -99,7 +102,12 @@ async fn play_handler( async fn stop_handler(state: State) -> String { let player = state.player.clone(); - player.lock().unwrap().send(vbplay::Command::Stop).unwrap(); + player + .lock() + .unwrap() + .listener + .send(vbplay::Command::Stop) + .unwrap(); "".to_owned() } diff --git a/src/state.rs b/src/state.rs index b7c064d..8bb16ef 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,5 +1,5 @@ use crate::soundclips::SoundClip; -use crate::vbplay::AudioThread; +use crate::vbplay::AudioHandler; use axum::extract::FromRef; use axum_template::engine::Engine; use minijinja::Environment; @@ -11,8 +11,8 @@ pub type TemplateEngine = Engine>; pub struct AppState { pub engine: TemplateEngine, pub clips: Vec, - pub player: Arc>, - pub playback: Option>>, + pub player: Arc>, + pub playback: Option>>, } impl FromRef for TemplateEngine { diff --git a/src/vbplay.rs b/src/vbplay.rs index 5d153db..6cee62d 100644 --- a/src/vbplay.rs +++ b/src/vbplay.rs @@ -71,19 +71,100 @@ fn select_device_by_id(index: usize) -> Option { pub type AudioThread = Sender; -pub fn audio_thread(select_device: DeviceSelection, select_decoder: SoundDecoder) -> AudioThread { +pub trait PlaybackAction { + fn before_playback(&mut self); + fn after_playback(&mut self); +} + +pub struct PlaybackActionEvents { + actions: Vec>, +} + +impl PlaybackActionEvents { + pub fn new() -> Self { + PlaybackActionEvents { + actions: Vec::new(), + } + } + + pub fn dispatch_before_playback(&mut self) { + for action in self.actions.iter_mut() { + action.before_playback(); + } + } + + pub fn dispatch_after_playback(&mut self) { + for action in self.actions.iter_mut() { + action.after_playback(); + } + } +} + +pub struct AudioHandler { + pub listener: AudioThread, + events: Arc>, +} + +impl AudioHandler { + pub fn new(select_device: DeviceSelection, select_decoder: SoundDecoder) -> Self { + let events = Arc::new(Mutex::new(PlaybackActionEvents::new())); + let tx = audio_thread(select_device, select_decoder, events.clone()); + Self { + listener: tx, + events, + } + } + + pub fn on_playback(&mut self, event: Box) { + self.events.lock().unwrap().actions.push(event); + } +} + +pub mod example_handler { + use enigo::*; + + pub struct PressMouseForward { + enigo: Enigo, + } + + impl PressMouseForward { + pub fn new() -> Self { + let enigo = Enigo::new(); + PressMouseForward { enigo: enigo } + } + } + + impl super::PlaybackAction for PressMouseForward { + fn before_playback(&mut self) { + self.enigo.mouse_down(MouseButton::Forward); + } + + fn after_playback(&mut self) { + self.enigo.mouse_up(MouseButton::Forward); + } + } +} + +pub fn audio_thread( + select_device: DeviceSelection, + select_decoder: SoundDecoder, + events: Arc>, +) -> AudioThread { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let device = match select_device { - DeviceSelection::SelectFirst => select_device_by_id(0).unwrap(), - DeviceSelection::SelectById(id) => select_device_by_id(id).unwrap(), - DeviceSelection::FindByPattern(pattern) => { - select_device_by_pattern(pattern.as_ref()).unwrap() + DeviceSelection::SelectFirst => { + select_device_by_id(0).expect("No Audio devices found.") } + DeviceSelection::SelectById(id) => { + select_device_by_id(id).expect("Audio device for ID not found.") + } + DeviceSelection::FindByPattern(pattern) => select_device_by_pattern(pattern.as_ref()) + .expect("Pattern search for audio device yielded no results."), }; - // Create a rodio Sink connected to our device + // Create a rodio Sink connected to our device. We need to keep stream and stream_handle alive during the thread. let (_stream, stream_handle) = rodio::OutputStream::try_from_device(&device).unwrap(); let new_sink = rodio::Sink::try_new(&stream_handle).unwrap(); @@ -109,16 +190,16 @@ pub fn audio_thread(select_device: DeviceSelection, select_decoder: SoundDecoder if !is_button_thread_active.load(Ordering::SeqCst) { let sink_mutex_clone = sink_mutex.clone(); let is_active_clone = is_button_thread_active.clone(); + let events_clone = events.clone(); thread::spawn(move || { - let mut enigo = Enigo::new(); - enigo.mouse_down(MouseButton::Forward); + events_clone.lock().unwrap().dispatch_before_playback(); is_active_clone.store(true, Ordering::SeqCst); while sink_mutex_clone.lock().map_or(false, |sink| !sink.empty()) { thread::sleep(std::time::Duration::from_millis(100)); } - enigo.mouse_up(MouseButton::Forward); + events_clone.lock().unwrap().dispatch_after_playback(); is_active_clone.store(false, Ordering::SeqCst); }); }