refactor: creating an AudioHandler instead of directly giving the thread, allowing to add custom event handlers while playback.

This commit is contained in:
Gabor Körber 2024-01-22 13:46:12 +01:00
parent a8f8eb4bc1
commit 2a0cd38189
3 changed files with 105 additions and 16 deletions

View File

@ -11,7 +11,7 @@ use std::env;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use dotenvy::dotenv; use dotenvy::dotenv;
use vbplay::{AudioThread, DeviceSelection, SoundDecoder}; use vbplay::{example_handler, AudioThread, DeviceSelection, PlaybackAction, SoundDecoder};
mod soundclips; mod soundclips;
mod state; mod state;
@ -28,11 +28,13 @@ async fn main() {
jinja.set_loader(path_loader("templates")); jinja.set_loader(path_loader("templates"));
let template_engine = Engine::from(jinja); let template_engine = Engine::from(jinja);
let audio = vbplay::audio_thread( let mut audio = vbplay::AudioHandler::new(
DeviceSelection::FindByPattern(device_pattern), DeviceSelection::FindByPattern(device_pattern),
SoundDecoder::Detect, SoundDecoder::Detect,
); );
let audio: Arc<Mutex<AudioThread>> = 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 { let app_state = AppState {
engine: template_engine, engine: template_engine,
@ -88,6 +90,7 @@ async fn play_handler(
player player
.lock() .lock()
.unwrap() .unwrap()
.listener
.send(vbplay::Command::PlayWhilePressing( .send(vbplay::Command::PlayWhilePressing(
full_file_name.into(), full_file_name.into(),
"".to_owned(), // placeholder. "".to_owned(), // placeholder.
@ -99,7 +102,12 @@ async fn play_handler(
async fn stop_handler(state: State<AppState>) -> String { async fn stop_handler(state: State<AppState>) -> String {
let player = state.player.clone(); let player = state.player.clone();
player.lock().unwrap().send(vbplay::Command::Stop).unwrap(); player
.lock()
.unwrap()
.listener
.send(vbplay::Command::Stop)
.unwrap();
"".to_owned() "".to_owned()
} }

View File

@ -1,5 +1,5 @@
use crate::soundclips::SoundClip; use crate::soundclips::SoundClip;
use crate::vbplay::AudioThread; use crate::vbplay::AudioHandler;
use axum::extract::FromRef; use axum::extract::FromRef;
use axum_template::engine::Engine; use axum_template::engine::Engine;
use minijinja::Environment; use minijinja::Environment;
@ -11,8 +11,8 @@ pub type TemplateEngine = Engine<Environment<'static>>;
pub struct AppState { pub struct AppState {
pub engine: TemplateEngine, pub engine: TemplateEngine,
pub clips: Vec<SoundClip>, pub clips: Vec<SoundClip>,
pub player: Arc<Mutex<AudioThread>>, pub player: Arc<Mutex<AudioHandler>>,
pub playback: Option<Arc<Mutex<AudioThread>>>, pub playback: Option<Arc<Mutex<AudioHandler>>>,
} }
impl FromRef<AppState> for TemplateEngine { impl FromRef<AppState> for TemplateEngine {

View File

@ -71,19 +71,100 @@ fn select_device_by_id(index: usize) -> Option<Device> {
pub type AudioThread = Sender<Command>; pub type AudioThread = Sender<Command>;
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<Box<dyn PlaybackAction + Send>>,
}
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<Mutex<PlaybackActionEvents>>,
}
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<dyn PlaybackAction + Send>) {
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<Mutex<PlaybackActionEvents>>,
) -> AudioThread {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || { thread::spawn(move || {
let device = match select_device { let device = match select_device {
DeviceSelection::SelectFirst => select_device_by_id(0).unwrap(), DeviceSelection::SelectFirst => {
DeviceSelection::SelectById(id) => select_device_by_id(id).unwrap(), select_device_by_id(0).expect("No Audio devices found.")
DeviceSelection::FindByPattern(pattern) => {
select_device_by_pattern(pattern.as_ref()).unwrap()
} }
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 (_stream, stream_handle) = rodio::OutputStream::try_from_device(&device).unwrap();
let new_sink = rodio::Sink::try_new(&stream_handle).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) { if !is_button_thread_active.load(Ordering::SeqCst) {
let sink_mutex_clone = sink_mutex.clone(); let sink_mutex_clone = sink_mutex.clone();
let is_active_clone = is_button_thread_active.clone(); let is_active_clone = is_button_thread_active.clone();
let events_clone = events.clone();
thread::spawn(move || { thread::spawn(move || {
let mut enigo = Enigo::new(); events_clone.lock().unwrap().dispatch_before_playback();
enigo.mouse_down(MouseButton::Forward);
is_active_clone.store(true, Ordering::SeqCst); is_active_clone.store(true, Ordering::SeqCst);
while sink_mutex_clone.lock().map_or(false, |sink| !sink.empty()) { while sink_mutex_clone.lock().map_or(false, |sink| !sink.empty()) {
thread::sleep(std::time::Duration::from_millis(100)); 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); is_active_clone.store(false, Ordering::SeqCst);
}); });
} }