use std::path::Path;
use std::sync::atomic::{AtomicI64, AtomicU64};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use super::decode_buffer::FrameResult;
use super::player::PreviewPlayer;
use super::sink::FrameSink;
use crate::error::PreviewError;
#[derive(Clone)]
pub struct AsyncPreviewPlayer {
inner: Arc<Mutex<PreviewPlayer>>,
}
impl AsyncPreviewPlayer {
pub async fn open(path: &Path) -> Result<Self, PreviewError> {
let path = path.to_path_buf();
let player = tokio::task::spawn_blocking(move || PreviewPlayer::open(&path))
.await
.map_err(|e| PreviewError::Ffmpeg {
code: 0,
message: format!("tokio task join error: {e}"),
})??;
Ok(Self {
inner: Arc::new(Mutex::new(player)),
})
}
pub fn set_sink(&self, sink: Box<dyn FrameSink>) {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_sink(sink);
}
pub async fn play(&self) {
let inner = Arc::clone(&self.inner);
let _ = tokio::task::spawn_blocking(move || {
inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.play();
})
.await;
}
pub async fn pause(&self) {
let inner = Arc::clone(&self.inner);
let _ = tokio::task::spawn_blocking(move || {
inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.pause();
})
.await;
}
pub async fn stop(&self) {
let inner = Arc::clone(&self.inner);
let _ = tokio::task::spawn_blocking(move || {
inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.stop();
})
.await;
}
pub async fn seek(&self, pts: Duration) -> Result<(), PreviewError> {
let inner = Arc::clone(&self.inner);
tokio::task::spawn_blocking(move || {
inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.seek(pts)
})
.await
.map_err(|e| PreviewError::Ffmpeg {
code: 0,
message: format!("tokio task join error: {e}"),
})?
}
pub async fn seek_coarse(&self, pts: Duration) -> Result<(), PreviewError> {
let inner = Arc::clone(&self.inner);
tokio::task::spawn_blocking(move || {
inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.seek_coarse(pts)
})
.await
.map_err(|e| PreviewError::Ffmpeg {
code: 0,
message: format!("tokio task join error: {e}"),
})?
}
pub async fn pop_frame(&self) -> FrameResult {
let inner = Arc::clone(&self.inner);
tokio::task::spawn_blocking(move || {
inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.pop_frame()
})
.await
.unwrap_or(FrameResult::Eof)
}
pub fn av_offset_handle(&self) -> Arc<AtomicI64> {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.av_offset_handle()
}
pub fn set_rate(&self, rate: f64) {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.set_rate(rate);
}
pub fn rate_handle(&self) -> Arc<AtomicU64> {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.rate_handle()
}
pub fn current_pts(&self) -> Duration {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.current_pts()
}
pub fn duration(&self) -> Option<Duration> {
self.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.duration()
}
pub async fn pop_audio_samples(&self, n: usize) -> Vec<f32> {
let inner = Arc::clone(&self.inner);
tokio::task::spawn_blocking(move || {
inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.pop_audio_samples(n)
})
.await
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_video_path() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../assets/video/gameplay.mp4")
}
#[test]
#[ignore = "requires FFmpeg and assets/video/gameplay.mp4; run with -- --include-ignored"]
fn async_preview_player_should_open_and_pop_frame() {
let path = test_video_path();
match tokio::runtime::Builder::new_current_thread().build() {
Ok(rt) => rt.block_on(async {
let player = match AsyncPreviewPlayer::open(&path).await {
Ok(p) => p,
Err(e) => {
println!("skipping: open failed: {e}");
return;
}
};
player.play().await;
let frame = player.pop_frame().await;
assert!(
matches!(frame, FrameResult::Frame(_) | FrameResult::Seeking(_)),
"pop_frame() must return Frame or Seeking"
);
}),
Err(e) => println!("skipping: failed to build tokio runtime: {e}"),
}
}
}