refactor: creating an AudioHandler instead of directly giving the thread, allowing to add custom event handlers while playback.
This commit is contained in:
parent
a8f8eb4bc1
commit
2a0cd38189
16
src/main.rs
16
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<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 {
|
||||
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<AppState>) -> 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()
|
||||
}
|
||||
|
@ -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<Environment<'static>>;
|
||||
pub struct AppState {
|
||||
pub engine: TemplateEngine,
|
||||
pub clips: Vec<SoundClip>,
|
||||
pub player: Arc<Mutex<AudioThread>>,
|
||||
pub playback: Option<Arc<Mutex<AudioThread>>>,
|
||||
pub player: Arc<Mutex<AudioHandler>>,
|
||||
pub playback: Option<Arc<Mutex<AudioHandler>>>,
|
||||
}
|
||||
|
||||
impl FromRef<AppState> for TemplateEngine {
|
||||
|
@ -71,19 +71,100 @@ fn select_device_by_id(index: usize) -> Option<Device> {
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user