Skip to main content

wallr_core/video/
playback.rs

1use crate::video::{
2    DecoderInfo, FrameScheduler, HwAccel, VideoDecoder, VideoError, VideoFrame, VideoMetadata,
3};
4use std::path::Path;
5use std::sync::Mutex;
6use std::time::{Duration, Instant};
7
8pub struct VideoPlayback {
9    decoder: Mutex<Option<VideoDecoder>>,
10    scheduler: Mutex<Option<FrameScheduler>>,
11    pending_frame: Mutex<Option<VideoFrame>>,
12    current_frame: Mutex<Option<VideoFrame>>,
13}
14
15impl VideoPlayback {
16    pub fn new() -> Self {
17        Self {
18            decoder: Mutex::new(None),
19            scheduler: Mutex::new(None),
20            pending_frame: Mutex::new(None),
21            current_frame: Mutex::new(None),
22        }
23    }
24
25    pub fn start(
26        &self,
27        path: &Path,
28        hw_accel: HwAccel,
29    ) -> Result<VideoMetadata, crate::video::error::VideoError> {
30        self.stop();
31        let decoder = VideoDecoder::new(path, hw_accel)?;
32        let metadata = decoder.metadata().clone();
33        let scheduler = FrameScheduler::new(metadata.duration);
34        *self.lock_decoder() = Some(decoder);
35        *self.lock_scheduler() = Some(scheduler);
36        tracing::info!(
37            "Video started: {}x{} @ {:.2} fps, {:?}",
38            metadata.width,
39            metadata.height,
40            metadata.fps,
41            metadata.duration
42        );
43        Ok(metadata)
44    }
45
46    pub fn stop(&self) {
47        *self.lock_decoder() = None;
48        *self.lock_scheduler() = None;
49        *self.lock_pending() = None;
50        *self.lock_current() = None;
51    }
52
53    pub fn pause(&self) {
54        if let Some(s) = self.lock_scheduler().as_mut() {
55            s.pause();
56        }
57        if let Some(d) = self.lock_decoder().as_ref() {
58            d.pause();
59        }
60    }
61
62    pub fn resume(&self) {
63        if let Some(s) = self.lock_scheduler().as_mut() {
64            s.resume();
65        }
66        if let Some(d) = self.lock_decoder().as_ref() {
67            d.resume();
68        }
69    }
70
71    pub fn seek(&self, timestamp: Duration) -> Result<(), crate::video::error::VideoError> {
72        let mut decoder = self.lock_decoder();
73        let mut scheduler = self.lock_scheduler();
74        let mut pending = self.lock_pending();
75        let mut current = self.lock_current();
76        let scheduler = scheduler.as_mut().ok_or_else(|| {
77            VideoError::SeekFailed(timestamp, anyhow::anyhow!("no video is playing"))
78        })?;
79        scheduler.seek(timestamp)?;
80        if let Some(d) = decoder.as_mut() {
81            d.seek(timestamp);
82            d.drain();
83        }
84        *pending = None;
85        *current = None;
86        tracing::info!("Video seeked to {:?}", timestamp);
87        Ok(())
88    }
89
90    pub fn next_frame(&self) -> Option<VideoFrame> {
91        let mut decoder = self.lock_decoder();
92        let mut scheduler = self.lock_scheduler();
93        let mut pending = self.lock_pending();
94        let (Some(decoder), Some(scheduler)) = (decoder.as_mut(), scheduler.as_mut()) else {
95            return None;
96        };
97
98        let frame = take_due_frame(scheduler, &mut pending, || decoder.next_frame());
99        if let Some(frame) = &frame {
100            *self.lock_current() = Some(frame.clone());
101        }
102        frame
103    }
104
105    pub fn wait_first_frame(&self, timeout: Duration) -> Option<VideoFrame> {
106        let deadline = Instant::now() + timeout;
107        loop {
108            if let Some(frame) = self.next_frame() {
109                return Some(frame);
110            }
111            if Instant::now() >= deadline {
112                tracing::warn!("Timed out waiting for first video frame");
113                return None;
114            }
115            std::thread::sleep(Duration::from_millis(5));
116        }
117    }
118
119    pub fn current_frame(&self) -> Option<VideoFrame> {
120        self.lock_current().clone()
121    }
122
123    pub fn metadata(&self) -> Option<VideoMetadata> {
124        self.lock_decoder().as_ref().map(|d| d.metadata().clone())
125    }
126
127    pub fn decoder_info(&self) -> Option<DecoderInfo> {
128        self.lock_decoder().as_ref().map(|d| d.decoder_info())
129    }
130
131    pub fn hw_accel_in_use(&self) -> HwAccel {
132        self.lock_decoder()
133            .as_ref()
134            .map(VideoDecoder::hw_accel_in_use)
135            .unwrap_or(HwAccel::Software)
136    }
137
138    pub fn position(&self) -> Option<Duration> {
139        self.lock_scheduler().as_ref().map(|s| s.current_position())
140    }
141
142    pub fn is_paused(&self) -> bool {
143        self.lock_scheduler()
144            .as_ref()
145            .map(|s| s.is_paused())
146            .unwrap_or(false)
147    }
148
149    pub fn is_playing(&self) -> bool {
150        self.lock_decoder().is_some()
151    }
152
153    fn lock_decoder(&self) -> std::sync::MutexGuard<'_, Option<VideoDecoder>> {
154        self.decoder.lock().unwrap_or_else(|p| p.into_inner())
155    }
156
157    fn lock_scheduler(&self) -> std::sync::MutexGuard<'_, Option<FrameScheduler>> {
158        self.scheduler.lock().unwrap_or_else(|p| p.into_inner())
159    }
160
161    fn lock_pending(&self) -> std::sync::MutexGuard<'_, Option<VideoFrame>> {
162        self.pending_frame.lock().unwrap_or_else(|p| p.into_inner())
163    }
164
165    fn lock_current(&self) -> std::sync::MutexGuard<'_, Option<VideoFrame>> {
166        self.current_frame.lock().unwrap_or_else(|p| p.into_inner())
167    }
168}
169
170fn take_due_frame(
171    scheduler: &mut FrameScheduler,
172    pending: &mut Option<VideoFrame>,
173    mut next_frame: impl FnMut() -> Option<VideoFrame>,
174) -> Option<VideoFrame> {
175    let mut selected = None;
176
177    if let Some(frame) = pending.take() {
178        if !scheduler.should_display(frame.pts) {
179            *pending = Some(frame);
180            return None;
181        }
182        if scheduler.should_upload(frame.pts) {
183            selected = Some(frame);
184        }
185    }
186
187    while let Some(frame) = next_frame() {
188        if !scheduler.should_display(frame.pts) {
189            *pending = Some(frame);
190            break;
191        }
192        if scheduler.should_upload(frame.pts) {
193            selected = Some(frame);
194        }
195    }
196
197    selected
198}
199
200impl Default for VideoPlayback {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use std::collections::VecDeque;
210
211    fn frame(pts_ms: u64) -> VideoFrame {
212        VideoFrame {
213            data: vec![pts_ms as u8],
214            width: 1,
215            height: 1,
216            pts: Duration::from_millis(pts_ms),
217            index: pts_ms,
218        }
219    }
220
221    #[test]
222    fn retains_future_frame_until_due() {
223        let mut scheduler = FrameScheduler::new(Duration::from_secs(1));
224        let mut pending = None;
225        let mut frames = VecDeque::from([frame(0), frame(100)]);
226
227        let first = take_due_frame(&mut scheduler, &mut pending, || frames.pop_front()).unwrap();
228        assert_eq!(first.pts, Duration::ZERO);
229        assert_eq!(pending.as_ref().unwrap().pts, Duration::from_millis(100));
230
231        scheduler.seek(Duration::from_millis(110)).unwrap();
232        let second = take_due_frame(&mut scheduler, &mut pending, || None).unwrap();
233        assert_eq!(second.pts, Duration::from_millis(100));
234        assert!(pending.is_none());
235    }
236
237    #[test]
238    fn selects_latest_due_frame() {
239        let mut scheduler = FrameScheduler::new(Duration::from_secs(1));
240        scheduler.seek(Duration::from_millis(50)).unwrap();
241        let mut pending = None;
242        let mut frames = VecDeque::from([frame(0), frame(16), frame(32), frame(100)]);
243
244        let selected = take_due_frame(&mut scheduler, &mut pending, || frames.pop_front()).unwrap();
245        assert_eq!(selected.pts, Duration::from_millis(32));
246        assert_eq!(pending.as_ref().unwrap().pts, Duration::from_millis(100));
247    }
248}