use std::path::{Path, PathBuf};
use ff_format::VideoFrame;
use futures::stream::{self, Stream};
use crate::async_decoder::AsyncDecoder;
use crate::error::DecodeError;
use crate::video::builder::VideoDecoder;
pub struct AsyncVideoDecoder {
inner: AsyncDecoder<VideoDecoder>,
}
impl AsyncVideoDecoder {
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 || VideoDecoder::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<VideoFrame>, DecodeError> {
self.inner.with(VideoDecoder::decode_one).await
}
pub fn into_stream(self) -> impl Stream<Item = Result<VideoFrame, 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::<AsyncVideoDecoder>();
}
#[tokio::test]
async fn async_video_decoder_should_fail_on_missing_file() {
let result = AsyncVideoDecoder::open("/nonexistent/path/video.mp4").await;
assert!(
matches!(result, Err(DecodeError::FileNotFound { .. })),
"expected FileNotFound"
);
}
}