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 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()
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user