⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/video/
player.rs

1//! Simple video playback (raw frame sequences)
2//!
3//! Provides `RawVideoStream` for sequential frame playback and
4//! `MediaPlayer` for higher-level control including image loading
5//! and display-rect management.
6
7#![allow(dead_code)]
8
9use alloc::vec::Vec;
10
11use spin::Mutex;
12
13use super::{decode, VideoFrame, VideoInfo};
14use crate::error::KernelError;
15
16// ---------------------------------------------------------------------------
17// Playback state
18// ---------------------------------------------------------------------------
19
20/// Current state of a video stream.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub(crate) enum PlaybackState {
23    Stopped,
24    Playing,
25    Paused,
26    Finished,
27}
28
29// ---------------------------------------------------------------------------
30// Raw video stream
31// ---------------------------------------------------------------------------
32
33/// A raw, in-memory video stream made of pre-decoded frames.
34///
35/// Frame advancement is driven by calling `next_frame()` which
36/// uses a simple tick-based timing model (caller provides tick count).
37pub(crate) struct RawVideoStream {
38    frames: Vec<VideoFrame>,
39    current_frame: usize,
40    info: VideoInfo,
41    state: PlaybackState,
42    frames_displayed: u64,
43    /// Tick at which playback started (set by `play()`).
44    start_tick: u64,
45}
46
47impl RawVideoStream {
48    /// Create an empty stream with the given metadata.
49    pub(crate) fn new(info: VideoInfo) -> Self {
50        Self {
51            frames: Vec::new(),
52            current_frame: 0,
53            info,
54            state: PlaybackState::Stopped,
55            frames_displayed: 0,
56            start_tick: 0,
57        }
58    }
59
60    /// Append a decoded frame to the stream.
61    pub(crate) fn add_frame(&mut self, frame: VideoFrame) {
62        self.frames.push(frame);
63    }
64
65    /// Begin (or resume) playback.
66    pub(crate) fn play(&mut self) {
67        match self.state {
68            PlaybackState::Stopped | PlaybackState::Finished => {
69                self.current_frame = 0;
70                self.frames_displayed = 0;
71                self.start_tick = get_tick();
72                self.state = PlaybackState::Playing;
73            }
74            PlaybackState::Paused => {
75                // Adjust start_tick to exclude paused duration
76                self.start_tick = get_tick();
77                self.state = PlaybackState::Playing;
78            }
79            PlaybackState::Playing => {} // already playing
80        }
81    }
82
83    /// Pause playback.
84    pub(crate) fn pause(&mut self) {
85        if self.state == PlaybackState::Playing {
86            self.state = PlaybackState::Paused;
87        }
88    }
89
90    /// Stop playback and reset to the beginning.
91    pub(crate) fn stop(&mut self) {
92        self.state = PlaybackState::Stopped;
93        self.current_frame = 0;
94        self.frames_displayed = 0;
95        self.start_tick = 0;
96    }
97
98    /// Seek to a specific frame index.
99    pub(crate) fn seek(&mut self, frame_index: usize) {
100        if frame_index < self.frames.len() {
101            self.current_frame = frame_index;
102        }
103    }
104
105    /// Advance to the next frame based on frame-rate timing.
106    ///
107    /// Returns a reference to the current frame if one should be displayed,
108    /// or `None` if the stream is not playing or has finished.
109    pub(crate) fn next_frame(&mut self) -> Option<&VideoFrame> {
110        if self.state != PlaybackState::Playing {
111            // If paused or stopped, return the current frame without advancing
112            return if self.state == PlaybackState::Paused {
113                self.frames.get(self.current_frame)
114            } else {
115                None
116            };
117        }
118
119        if self.frames.is_empty() {
120            self.state = PlaybackState::Finished;
121            return None;
122        }
123
124        // Calculate which frame we should be on based on elapsed ticks.
125        // We treat each tick as 1 millisecond.
126        let elapsed_ms = get_tick().saturating_sub(self.start_tick);
127
128        // Frame duration in ms: 1000 * den / num
129        let frame_dur_ms = if self.info.frame_rate_num > 0 {
130            (1000u64 * self.info.frame_rate_den as u64) / self.info.frame_rate_num as u64
131        } else {
132            // Default to ~30 fps if unspecified
133            33
134        };
135
136        let target_frame = if frame_dur_ms > 0 {
137            (elapsed_ms / frame_dur_ms) as usize
138        } else {
139            0
140        };
141
142        if target_frame >= self.frames.len() {
143            self.state = PlaybackState::Finished;
144            self.current_frame = self.frames.len() - 1;
145            return self.frames.last();
146        }
147
148        self.current_frame = target_frame;
149        self.frames_displayed += 1;
150
151        self.frames.get(self.current_frame)
152    }
153
154    /// Current playback position in milliseconds.
155    pub(crate) fn current_position_ms(&self) -> u64 {
156        if self.info.frame_rate_num == 0 || self.frames.is_empty() {
157            return 0;
158        }
159        let frame_dur_ms =
160            (1000u64 * self.info.frame_rate_den as u64) / self.info.frame_rate_num as u64;
161        (self.current_frame as u64) * frame_dur_ms
162    }
163
164    /// Total stream duration in milliseconds.
165    pub(crate) fn duration_ms(&self) -> u64 {
166        if self.info.frame_rate_num == 0 || self.frames.is_empty() {
167            return 0;
168        }
169        let frame_dur_ms =
170            (1000u64 * self.info.frame_rate_den as u64) / self.info.frame_rate_num as u64;
171        (self.frames.len() as u64) * frame_dur_ms
172    }
173
174    /// Whether the stream has reached the end.
175    pub(crate) fn is_finished(&self) -> bool {
176        self.state == PlaybackState::Finished
177    }
178
179    /// Get current playback state.
180    pub(crate) fn state(&self) -> PlaybackState {
181        self.state
182    }
183
184    /// Number of frames in the stream.
185    pub(crate) fn frame_count(&self) -> usize {
186        self.frames.len()
187    }
188
189    /// Get video info.
190    pub(crate) fn info(&self) -> &VideoInfo {
191        &self.info
192    }
193}
194
195// ---------------------------------------------------------------------------
196// Media player
197// ---------------------------------------------------------------------------
198
199/// Higher-level media player that wraps a video stream and a display
200/// rectangle.
201pub(crate) struct MediaPlayer {
202    video_stream: Option<RawVideoStream>,
203    audio_stream_id: Option<u32>,
204    display_x: u32,
205    display_y: u32,
206    display_width: u32,
207    display_height: u32,
208}
209
210impl MediaPlayer {
211    /// Create a new, empty media player.
212    pub(crate) fn new() -> Self {
213        Self {
214            video_stream: None,
215            audio_stream_id: None,
216            display_x: 0,
217            display_y: 0,
218            display_width: 0,
219            display_height: 0,
220        }
221    }
222
223    /// Load a single image (TGA, QOI) and present it as a one-frame video.
224    pub(crate) fn load_image(&mut self, data: &[u8]) -> Result<(), KernelError> {
225        let frame = decode::decode_image(data)?;
226
227        let info = VideoInfo {
228            width: frame.width,
229            height: frame.height,
230            format: frame.format,
231            frame_rate_num: 1,
232            frame_rate_den: 1,
233        };
234
235        let mut stream = RawVideoStream::new(info);
236        stream.add_frame(frame);
237
238        self.video_stream = Some(stream);
239        Ok(())
240    }
241
242    /// Load a pre-decoded frame sequence as a video.
243    pub(crate) fn load_video(
244        &mut self,
245        frames: Vec<VideoFrame>,
246        info: VideoInfo,
247    ) -> Result<(), KernelError> {
248        if frames.is_empty() {
249            return Err(KernelError::InvalidArgument {
250                name: "frames",
251                value: "empty frame list",
252            });
253        }
254
255        let mut stream = RawVideoStream::new(info);
256        for frame in frames {
257            stream.add_frame(frame);
258        }
259
260        self.video_stream = Some(stream);
261        Ok(())
262    }
263
264    /// Start playback.
265    pub(crate) fn play(&mut self) -> Result<(), KernelError> {
266        match self.video_stream.as_mut() {
267            Some(stream) => {
268                stream.play();
269                Ok(())
270            }
271            None => Err(KernelError::InvalidState {
272                expected: "loaded",
273                actual: "no video loaded",
274            }),
275        }
276    }
277
278    /// Pause playback.
279    pub(crate) fn pause(&mut self) {
280        if let Some(stream) = self.video_stream.as_mut() {
281            stream.pause();
282        }
283    }
284
285    /// Stop playback.
286    pub(crate) fn stop(&mut self) {
287        if let Some(stream) = self.video_stream.as_mut() {
288            stream.stop();
289        }
290    }
291
292    /// Get the current frame for rendering.
293    pub(crate) fn render_current_frame(&self) -> Option<&VideoFrame> {
294        self.video_stream
295            .as_ref()
296            .and_then(|stream| stream.frames.get(stream.current_frame))
297    }
298
299    /// Set the display rectangle on screen.
300    pub(crate) fn set_display_rect(&mut self, x: u32, y: u32, width: u32, height: u32) {
301        self.display_x = x;
302        self.display_y = y;
303        self.display_width = width;
304        self.display_height = height;
305    }
306
307    /// Get display rectangle.
308    pub(crate) fn display_rect(&self) -> (u32, u32, u32, u32) {
309        (
310            self.display_x,
311            self.display_y,
312            self.display_width,
313            self.display_height,
314        )
315    }
316
317    /// Check if a video is loaded.
318    pub(crate) fn is_loaded(&self) -> bool {
319        self.video_stream.is_some()
320    }
321
322    /// Get the playback state.
323    pub(crate) fn playback_state(&self) -> Option<PlaybackState> {
324        self.video_stream.as_ref().map(|s| s.state())
325    }
326}
327
328impl Default for MediaPlayer {
329    fn default() -> Self {
330        Self::new()
331    }
332}
333
334// ---------------------------------------------------------------------------
335// Global player instance
336// ---------------------------------------------------------------------------
337
338static PLAYER: Mutex<Option<MediaPlayer>> = Mutex::new(None);
339
340/// Initialize the player subsystem.
341pub(crate) fn init() -> Result<(), KernelError> {
342    let mut guard = PLAYER.lock();
343    if guard.is_none() {
344        *guard = Some(MediaPlayer::new());
345    }
346    Ok(())
347}
348
349/// Execute a closure with the global media player.
350pub(crate) fn with_player<R, F: FnOnce(&mut MediaPlayer) -> R>(f: F) -> Result<R, KernelError> {
351    let mut guard = PLAYER.lock();
352    match guard.as_mut() {
353        Some(player) => Ok(f(player)),
354        None => Err(KernelError::NotInitialized {
355            subsystem: "video player",
356        }),
357    }
358}
359
360// ---------------------------------------------------------------------------
361// Tick source (monotonic milliseconds)
362// ---------------------------------------------------------------------------
363
364/// Simple monotonic tick counter (milliseconds).
365///
366/// In a full implementation this would read the kernel's timer. For now
367/// we use an atomic counter that can be bumped externally or defaults to 0.
368static TICK_COUNTER: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
369
370/// Get the current tick value (milliseconds).
371fn get_tick() -> u64 {
372    TICK_COUNTER.load(core::sync::atomic::Ordering::Relaxed)
373}
374
375/// Advance the tick counter (called by the kernel timer or test harness).
376pub(crate) fn advance_tick(ms: u64) {
377    TICK_COUNTER.fetch_add(ms, core::sync::atomic::Ordering::Relaxed);
378}
379
380/// Set the tick counter to an absolute value.
381pub(crate) fn set_tick(ms: u64) {
382    TICK_COUNTER.store(ms, core::sync::atomic::Ordering::Relaxed);
383}
384
385// ---------------------------------------------------------------------------
386// Tests
387// ---------------------------------------------------------------------------
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392    use crate::graphics::PixelFormat;
393
394    fn make_info(fps: u32) -> VideoInfo {
395        VideoInfo {
396            width: 4,
397            height: 4,
398            format: PixelFormat::Argb8888,
399            frame_rate_num: fps,
400            frame_rate_den: 1,
401        }
402    }
403
404    fn make_frame(r: u8) -> VideoFrame {
405        let mut f = VideoFrame::new(4, 4, PixelFormat::Argb8888);
406        f.set_pixel(0, 0, r, 0, 0, 255);
407        f
408    }
409
410    #[test]
411    fn test_stream_lifecycle() {
412        set_tick(0);
413        let mut stream = RawVideoStream::new(make_info(10)); // 10 fps = 100ms/frame
414        stream.add_frame(make_frame(10));
415        stream.add_frame(make_frame(20));
416        stream.add_frame(make_frame(30));
417
418        assert_eq!(stream.state(), PlaybackState::Stopped);
419        assert_eq!(stream.duration_ms(), 300); // 3 frames * 100ms
420
421        stream.play();
422        assert_eq!(stream.state(), PlaybackState::Playing);
423
424        // At t=0 should show frame 0
425        let f = stream.next_frame().unwrap();
426        assert_eq!(f.get_pixel(0, 0).0, 10);
427
428        // Advance to t=150ms -> frame 1
429        advance_tick(150);
430        let f = stream.next_frame().unwrap();
431        assert_eq!(f.get_pixel(0, 0).0, 20);
432
433        // Pause
434        stream.pause();
435        assert_eq!(stream.state(), PlaybackState::Paused);
436
437        // Should still return current frame when paused
438        let f = stream.next_frame().unwrap();
439        assert_eq!(f.get_pixel(0, 0).0, 20);
440
441        // Stop
442        stream.stop();
443        assert_eq!(stream.state(), PlaybackState::Stopped);
444        assert!(stream.next_frame().is_none());
445
446        // Reset tick for other tests
447        set_tick(0);
448    }
449
450    #[test]
451    fn test_stream_finished() {
452        set_tick(0);
453        let mut stream = RawVideoStream::new(make_info(10));
454        stream.add_frame(make_frame(10));
455
456        stream.play();
457        // Advance well past the single frame
458        advance_tick(500);
459        let _f = stream.next_frame();
460        assert!(stream.is_finished());
461
462        set_tick(0);
463    }
464
465    #[test]
466    fn test_media_player_load_video() {
467        let info = make_info(30);
468        let frames = alloc::vec![make_frame(1), make_frame(2)];
469        let mut player = MediaPlayer::new();
470
471        player.load_video(frames, info).expect("load should work");
472        assert!(player.is_loaded());
473
474        let frame = player.render_current_frame().unwrap();
475        assert_eq!(frame.width, 4);
476    }
477
478    #[test]
479    fn test_media_player_display_rect() {
480        let mut player = MediaPlayer::new();
481        player.set_display_rect(10, 20, 640, 480);
482        assert_eq!(player.display_rect(), (10, 20, 640, 480));
483    }
484
485    #[test]
486    fn test_media_player_play_without_load() {
487        let mut player = MediaPlayer::new();
488        let result = player.play();
489        assert!(result.is_err());
490    }
491
492    #[test]
493    fn test_seek() {
494        let mut stream = RawVideoStream::new(make_info(30));
495        stream.add_frame(make_frame(10));
496        stream.add_frame(make_frame(20));
497        stream.add_frame(make_frame(30));
498
499        stream.seek(2);
500        assert_eq!(stream.current_frame, 2);
501
502        // Out of range: should stay at 2
503        stream.seek(100);
504        assert_eq!(stream.current_frame, 2);
505    }
506}