use std::path::{Path, PathBuf};
use ff_format::AudioFrame;
use futures::stream::{self, Stream};
use crate::async_decoder::AsyncDecoder;
use crate::audio::builder::AudioDecoder;
use crate::error::DecodeError;
pub struct AsyncAudioDecoder {
inner: AsyncDecoder<AudioDecoder>,
}
impl AsyncAudioDecoder {
pub async fn open(path: impl AsRef<Path> + Send + 'static) -> Result<Self, DecodeError> {
let path: PathBuf = path.as_ref().to_path_buf();
let decoder = tokio::task::spawn_blocking(move || AudioDecoder::open(&path).build())
.await
.map_err(|e| DecodeError::Ffmpeg {
code: 0,
message: format!("spawn_blocking panicked: {e}"),
})??;
Ok(Self {
inner: AsyncDecoder::new(decoder),
})
}
pub async fn decode_frame(&mut self) -> Result<Option<AudioFrame>, DecodeError> {
self.inner.with(AudioDecoder::decode_one).await
}
pub fn into_stream(self) -> impl Stream<Item = Result<AudioFrame, DecodeError>> + Send {
stream::unfold(self, |mut decoder| async move {
match decoder.decode_frame().await {
Ok(Some(frame)) => Some((Ok(frame), decoder)),
Ok(None) => None,
Err(e) => Some((Err(e), decoder)),
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn _assert_send() {
fn is_send<T: Send>() {}
is_send::<AsyncAudioDecoder>();
}
#[tokio::test]
async fn async_audio_decoder_should_fail_on_missing_file() {
let result = AsyncAudioDecoder::open("/nonexistent/path/audio.mp3").await;
assert!(
matches!(result, Err(DecodeError::FileNotFound { .. })),
"expected FileNotFound"
);
}
}