use std::io::Cursor;
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread::JoinHandle;
use kira::sound::static_sound::StaticSoundData;
pub(crate) struct DecodeResult {
pub key: u64,
pub decoded: Result<StaticSoundData, String>,
}
pub(crate) struct DecodeWorker {
request_tx: Option<Sender<(u64, Vec<u8>)>>,
results: Receiver<DecodeResult>,
thread: Option<JoinHandle<()>>,
}
impl DecodeWorker {
pub(crate) fn spawn() -> Self {
let (request_tx, requests) = channel::<(u64, Vec<u8>)>();
let (result_tx, results) = channel();
let thread = std::thread::Builder::new()
.name("audio-decode".into())
.spawn(move || {
while let Ok((key, bytes)) = requests.recv() {
let decoded = decode(bytes);
if result_tx.send(DecodeResult { key, decoded }).is_err() {
break;
}
}
})
.expect("audio decode worker spawn failed");
Self {
request_tx: Some(request_tx),
results,
thread: Some(thread),
}
}
pub(crate) fn send(&self, key: u64, bytes: Vec<u8>) -> bool {
self.request_tx
.as_ref()
.is_some_and(|tx| tx.send((key, bytes)).is_ok())
}
pub(crate) fn drain(&self) -> Vec<DecodeResult> {
let mut out = Vec::new();
while let Ok(result) = self.results.try_recv() {
out.push(result);
}
out
}
}
impl Drop for DecodeWorker {
fn drop(&mut self) {
self.request_tx = None;
if let Some(thread) = self.thread.take() {
let _ = thread.join();
}
}
}
fn decode(bytes: Vec<u8>) -> Result<StaticSoundData, String> {
StaticSoundData::from_cursor(Cursor::new(bytes)).map_err(|e| e.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::audio::test_wav::pcm_wav_mono;
fn drain_one(worker: &DecodeWorker) -> DecodeResult {
for _ in 0..500 {
if let Some(result) = worker.drain().into_iter().next() {
return result;
}
std::thread::sleep(std::time::Duration::from_millis(2));
}
panic!("decode worker produced no result within a second");
}
#[test]
fn worker_decodes_valid_bytes_off_thread() {
let worker = DecodeWorker::spawn();
assert!(worker.send(11, pcm_wav_mono(64)));
let result = drain_one(&worker);
assert_eq!(result.key, 11);
let data = result.decoded.expect("valid WAV decodes");
assert!(data.num_frames() > 0);
}
#[test]
fn worker_reports_undecodable_bytes_as_errors() {
let worker = DecodeWorker::spawn();
assert!(worker.send(3, b"not an audio file".to_vec()));
let result = drain_one(&worker);
assert_eq!(result.key, 3);
assert!(result.decoded.is_err());
}
#[test]
fn drop_joins_the_worker_cleanly() {
let worker = DecodeWorker::spawn();
worker.send(1, pcm_wav_mono(16));
drop(worker);
}
}