use crate::video::{
DecoderInfo, FrameScheduler, HwAccel, VideoDecoder, VideoError, VideoFrame, VideoMetadata,
};
use std::path::Path;
use std::sync::Mutex;
use std::time::{Duration, Instant};
pub struct VideoPlayback {
decoder: Mutex<Option<VideoDecoder>>,
scheduler: Mutex<Option<FrameScheduler>>,
pending_frame: Mutex<Option<VideoFrame>>,
current_frame: Mutex<Option<VideoFrame>>,
}
impl VideoPlayback {
pub fn new() -> Self {
Self {
decoder: Mutex::new(None),
scheduler: Mutex::new(None),
pending_frame: Mutex::new(None),
current_frame: Mutex::new(None),
}
}
pub fn start(
&self,
path: &Path,
hw_accel: HwAccel,
) -> Result<VideoMetadata, crate::video::error::VideoError> {
self.stop();
let decoder = VideoDecoder::new(path, hw_accel)?;
let metadata = decoder.metadata().clone();
let scheduler = FrameScheduler::new(metadata.duration);
*self.lock_decoder() = Some(decoder);
*self.lock_scheduler() = Some(scheduler);
tracing::info!(
"Video started: {}x{} @ {:.2} fps, {:?}",
metadata.width,
metadata.height,
metadata.fps,
metadata.duration
);
Ok(metadata)
}
pub fn stop(&self) {
*self.lock_decoder() = None;
*self.lock_scheduler() = None;
*self.lock_pending() = None;
*self.lock_current() = None;
}
pub fn pause(&self) {
if let Some(s) = self.lock_scheduler().as_mut() {
s.pause();
}
if let Some(d) = self.lock_decoder().as_ref() {
d.pause();
}
}
pub fn resume(&self) {
if let Some(s) = self.lock_scheduler().as_mut() {
s.resume();
}
if let Some(d) = self.lock_decoder().as_ref() {
d.resume();
}
}
pub fn seek(&self, timestamp: Duration) -> Result<(), crate::video::error::VideoError> {
let mut decoder = self.lock_decoder();
let mut scheduler = self.lock_scheduler();
let mut pending = self.lock_pending();
let mut current = self.lock_current();
let scheduler = scheduler.as_mut().ok_or_else(|| {
VideoError::SeekFailed(timestamp, anyhow::anyhow!("no video is playing"))
})?;
scheduler.seek(timestamp)?;
if let Some(d) = decoder.as_mut() {
d.seek(timestamp);
d.drain();
}
*pending = None;
*current = None;
tracing::info!("Video seeked to {:?}", timestamp);
Ok(())
}
pub fn next_frame(&self) -> Option<VideoFrame> {
let mut decoder = self.lock_decoder();
let mut scheduler = self.lock_scheduler();
let mut pending = self.lock_pending();
let (Some(decoder), Some(scheduler)) = (decoder.as_mut(), scheduler.as_mut()) else {
return None;
};
let frame = take_due_frame(scheduler, &mut pending, || decoder.next_frame());
if let Some(frame) = &frame {
*self.lock_current() = Some(frame.clone());
}
frame
}
pub fn wait_first_frame(&self, timeout: Duration) -> Option<VideoFrame> {
let deadline = Instant::now() + timeout;
loop {
if let Some(frame) = self.next_frame() {
return Some(frame);
}
if Instant::now() >= deadline {
tracing::warn!("Timed out waiting for first video frame");
return None;
}
std::thread::sleep(Duration::from_millis(5));
}
}
pub fn current_frame(&self) -> Option<VideoFrame> {
self.lock_current().clone()
}
pub fn metadata(&self) -> Option<VideoMetadata> {
self.lock_decoder().as_ref().map(|d| d.metadata().clone())
}
pub fn decoder_info(&self) -> Option<DecoderInfo> {
self.lock_decoder().as_ref().map(|d| d.decoder_info())
}
pub fn hw_accel_in_use(&self) -> HwAccel {
self.lock_decoder()
.as_ref()
.map(VideoDecoder::hw_accel_in_use)
.unwrap_or(HwAccel::Software)
}
pub fn position(&self) -> Option<Duration> {
self.lock_scheduler().as_ref().map(|s| s.current_position())
}
pub fn is_paused(&self) -> bool {
self.lock_scheduler()
.as_ref()
.map(|s| s.is_paused())
.unwrap_or(false)
}
pub fn is_playing(&self) -> bool {
self.lock_decoder().is_some()
}
fn lock_decoder(&self) -> std::sync::MutexGuard<'_, Option<VideoDecoder>> {
self.decoder.lock().unwrap_or_else(|p| p.into_inner())
}
fn lock_scheduler(&self) -> std::sync::MutexGuard<'_, Option<FrameScheduler>> {
self.scheduler.lock().unwrap_or_else(|p| p.into_inner())
}
fn lock_pending(&self) -> std::sync::MutexGuard<'_, Option<VideoFrame>> {
self.pending_frame.lock().unwrap_or_else(|p| p.into_inner())
}
fn lock_current(&self) -> std::sync::MutexGuard<'_, Option<VideoFrame>> {
self.current_frame.lock().unwrap_or_else(|p| p.into_inner())
}
}
fn take_due_frame(
scheduler: &mut FrameScheduler,
pending: &mut Option<VideoFrame>,
mut next_frame: impl FnMut() -> Option<VideoFrame>,
) -> Option<VideoFrame> {
let mut selected = None;
if let Some(frame) = pending.take() {
if !scheduler.should_display(frame.pts) {
*pending = Some(frame);
return None;
}
if scheduler.should_upload(frame.pts) {
selected = Some(frame);
}
}
while let Some(frame) = next_frame() {
if !scheduler.should_display(frame.pts) {
*pending = Some(frame);
break;
}
if scheduler.should_upload(frame.pts) {
selected = Some(frame);
}
}
selected
}
impl Default for VideoPlayback {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::VecDeque;
fn frame(pts_ms: u64) -> VideoFrame {
VideoFrame {
data: vec![pts_ms as u8],
width: 1,
height: 1,
pts: Duration::from_millis(pts_ms),
index: pts_ms,
}
}
#[test]
fn retains_future_frame_until_due() {
let mut scheduler = FrameScheduler::new(Duration::from_secs(1));
let mut pending = None;
let mut frames = VecDeque::from([frame(0), frame(100)]);
let first = take_due_frame(&mut scheduler, &mut pending, || frames.pop_front()).unwrap();
assert_eq!(first.pts, Duration::ZERO);
assert_eq!(pending.as_ref().unwrap().pts, Duration::from_millis(100));
scheduler.seek(Duration::from_millis(110)).unwrap();
let second = take_due_frame(&mut scheduler, &mut pending, || None).unwrap();
assert_eq!(second.pts, Duration::from_millis(100));
assert!(pending.is_none());
}
#[test]
fn selects_latest_due_frame() {
let mut scheduler = FrameScheduler::new(Duration::from_secs(1));
scheduler.seek(Duration::from_millis(50)).unwrap();
let mut pending = None;
let mut frames = VecDeque::from([frame(0), frame(16), frame(32), frame(100)]);
let selected = take_due_frame(&mut scheduler, &mut pending, || frames.pop_front()).unwrap();
assert_eq!(selected.pts, Duration::from_millis(32));
assert_eq!(pending.as_ref().unwrap().pts, Duration::from_millis(100));
}
}