soundboard/src/vbplay.rs

291 lines
9.4 KiB
Rust

use cpal::traits::{DeviceTrait, HostTrait};
use cpal::{Device, Host};
use log::{debug, error, info};
use minimp3::{Decoder, Error};
use regex::Regex;
use std::fs::File;
use std::io::Read;
use std::io::{BufReader, Cursor};
use std::sync::mpsc::{self, Sender};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
};
use std::thread;
#[derive(Clone)]
pub enum SoundDecoder {
Detect,
Mp3Mini,
Rodio,
}
pub enum Command {
Exit,
Play(String),
PlayWhilePressing(String, String),
Stop,
}
// todo: implement a device selection with this:
pub enum DeviceSelection {
SelectFirst,
SelectById(usize),
FindByPattern(String),
}
fn list_devices(host: &Host) -> Vec<String> {
let devices = host.output_devices().unwrap();
devices.map(|device| device.name().unwrap()).collect()
}
fn find_device_index_regex(devices: Vec<String>, pattern: &str) -> Option<usize> {
let re = Regex::new(pattern).unwrap();
devices.iter().position(|device| re.is_match(device))
}
fn select_device(host: &Host, i: usize) -> Option<Device> {
host.output_devices().unwrap().nth(i)
}
fn select_device_by_pattern(pattern: &str) -> Option<Device> {
let host = cpal::default_host();
let devices = list_devices(&host);
info!("Devices: {:?}", devices);
if let Some(index) = find_device_index_regex(devices, pattern) {
info!(
"Selecting Device from pattern: \"{}\" at index {:?}",
pattern, index
);
return select_device(&host, index);
}
return None;
}
fn select_device_by_id(index: usize) -> Option<Device> {
let host = cpal::default_host();
return select_device(&host, index);
}
pub type AudioThread = Sender<Command>;
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 fn exit(&mut self) {
self.events.lock().unwrap().actions.clear();
self.listener
.send(Command::Exit)
.expect("Error sending Exit Command.");
}
}
pub mod example_handler {
use enigo::*;
pub struct PressMouseForward {
enigo: Enigo,
}
impl PressMouseForward {
pub fn new() -> Self {
let enigo_settings = Settings::default();
let enigo = Enigo::new(&enigo_settings).unwrap();
PressMouseForward { enigo: enigo }
}
}
impl super::PlaybackAction for PressMouseForward {
fn before_playback(&mut self) {
self.enigo
.button(Button::Forward, Direction::Press)
.unwrap();
}
fn after_playback(&mut self) {
self.enigo
.button(Button::Forward, Direction::Release)
.unwrap();
}
}
}
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).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. 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();
let sink_mutex = Arc::new(Mutex::new(new_sink));
let is_button_thread_active = Arc::new(AtomicBool::new(false));
for command in rx {
match command {
Command::Exit => {
eprintln!("Exiting Soundloop.");
if let Ok(sink) = sink_mutex.lock() {
sink.stop();
}
drop(stream_handle);
break;
}
Command::Play(file_name) => {
if let Ok(sink) = sink_mutex.lock() {
play_file(&sink, file_name, &select_decoder);
} else {
error!("Mutex Lock Failure while trying to play sound.")
}
}
Command::PlayWhilePressing(file_name, _press_button) => {
if let Ok(sink) = sink_mutex.lock() {
play_file(&sink, file_name, &select_decoder);
} else {
error!("Mutex Lock Failure while trying to play sound with callbacks.");
}
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 || {
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));
}
events_clone.lock().unwrap().dispatch_after_playback();
is_active_clone.store(false, Ordering::SeqCst);
});
}
}
Command::Stop => {
if let Ok(sink) = sink_mutex.lock() {
sink.stop();
} else {
error!("Mutex Lock Failure while trying to stop sound.");
}
}
}
}
eprintln!("Exiting audio_thread");
});
tx
}
fn detect_decoder(_file_name: &str, sound_decoder: &SoundDecoder) -> SoundDecoder {
// TODO: File detection via ending or whitelisting?
// This function MUST NOT return SoundDecoder::Detect
match sound_decoder {
SoundDecoder::Detect => SoundDecoder::Mp3Mini,
other => other.clone(),
}
}
fn play_file(sink: &rodio::Sink, file_name: String, sound_decoder: &SoundDecoder) {
match detect_decoder(&file_name, sound_decoder) {
SoundDecoder::Mp3Mini => {
// MP3 Mini provides low latency playback
let mut sound_file = File::open(file_name).unwrap();
let mut file_data = Vec::new();
sound_file.read_to_end(&mut file_data).unwrap();
// Iterate over the MP3 frames and play them
let mut decoder = Decoder::new(Cursor::new(file_data));
loop {
match decoder.next_frame() {
Ok(frame) => {
let source = rodio::buffer::SamplesBuffer::new(
2,
frame.sample_rate.try_into().unwrap(),
&*frame.data,
);
sink.append(source);
}
Err(Error::Eof) => {
debug!("EOF");
break;
}
Err(e) => {
error!("{:?}", e);
break;
}
}
}
}
SoundDecoder::Rodio => {
// Rodio currently supports Ogg, Mp3, WAV and Flac
let file = File::open(&file_name).unwrap();
let source = rodio::Decoder::new(BufReader::new(file)).expect("Failed to open Decoder");
sink.append(source);
//sink.sleep_until_end();
}
SoundDecoder::Detect => error!("This should never happen"),
}
}