Skip to main content

yog_registry/videos/
clock.rs

1//! Playing a video: where it is in time, and which packets to decode to
2//! show a frame — the same for the game and the editors, whatever decodes.
3
4use std::ops::Range;
5
6use super::VideoDef;
7
8/// The packets to decode, in order, to show a frame.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct Plan {
11    /// The decoder forgets what it has first (they start at a keyframe).
12    pub reset: bool,
13    /// The frames whose packets go in; the last is the one shown.
14    pub frames: Range<u32>,
15}
16
17impl VideoDef {
18    /// How to get from showing `shown` (none yet) to showing `target`:
19    /// onward from `shown` when no keyframe is closer, else from the
20    /// keyframe before `target`.
21    #[must_use]
22    pub fn plan(&self, shown: Option<u32>, target: u32) -> Plan {
23        let target = target.min(self.frames().saturating_sub(1));
24        let key = self.keyframe_before(target);
25        match shown {
26            Some(s) if s == target => Plan {
27                reset: false,
28                frames: target..target,
29            },
30            Some(s) if s < target && key <= s => Plan {
31                reset: false,
32                frames: s + 1..target + 1,
33            },
34            _ => Plan {
35                reset: true,
36                frames: key..target + 1,
37            },
38        }
39    }
40}
41
42/// A video's clock: where it is, whether it runs.
43#[derive(Debug, Clone, Default, PartialEq)]
44pub struct Clock {
45    /// Seconds from its start.
46    pub at: f64,
47    pub playing: bool,
48}
49
50impl Clock {
51    /// `dt` seconds later, for `def`: wrapped round when it loops, else
52    /// stopped at its end. The frame it's on.
53    pub fn advance(&mut self, def: &VideoDef, dt: f64) -> u32 {
54        if self.playing {
55            self.at += dt;
56        }
57        let length = def.seconds();
58        if self.at >= length {
59            if def.looping && length > 0.0 {
60                self.at %= length;
61            } else {
62                self.at = length;
63                self.playing = false;
64            }
65        }
66        self.frame(def)
67    }
68
69    /// Goes to `seconds` from the start (kept inside the video).
70    pub fn seek(&mut self, def: &VideoDef, seconds: f64) {
71        self.at = seconds.clamp(0.0, def.seconds());
72    }
73
74    /// The frame it's on.
75    #[must_use]
76    pub fn frame(&self, def: &VideoDef) -> u32 {
77        let last = def.frames().saturating_sub(1);
78        ((self.at / def.frame_seconds()) as u32).min(last)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    /// 10 frames at 10 a second, keyframes at 0 and 5.
87    fn def(looping: bool) -> VideoDef {
88        VideoDef {
89            id: "m:v".into(),
90            width: 2,
91            height: 2,
92            fps_num: 10,
93            fps_den: 1,
94            sizes: vec![1; 10],
95            packets: vec![0; 10],
96            keyframes: vec![0, 5],
97            sound: None,
98            volume: 1.0,
99            looping,
100        }
101    }
102
103    #[test]
104    fn a_plan_goes_on_or_starts_at_the_keyframe_before() {
105        let v = def(false);
106        let plan = |shown, target| v.plan(shown, target);
107        assert_eq!(
108            plan(None, 3),
109            Plan {
110                reset: true,
111                frames: 0..4
112            }
113        );
114        assert_eq!(
115            plan(Some(1), 3),
116            Plan {
117                reset: false,
118                frames: 2..4
119            }
120        );
121        assert_eq!(plan(Some(3), 3).frames, 3..3);
122        assert_eq!(
123            plan(Some(3), 7),
124            Plan {
125                reset: true,
126                frames: 5..8
127            }
128        );
129        assert_eq!(
130            plan(Some(7), 2),
131            Plan {
132                reset: true,
133                frames: 0..3
134            }
135        );
136        assert_eq!(plan(None, 99).frames, 5..10);
137    }
138
139    #[test]
140    fn a_clock_loops_or_stops_at_the_end() {
141        let v = def(true);
142        let mut c = Clock {
143            at: 0.0,
144            playing: true,
145        };
146        assert_eq!(c.advance(&v, 0.35), 3);
147        assert_eq!(c.advance(&v, 0.8), 1, "wrapped round");
148        let v = def(false);
149        let mut c = Clock {
150            at: 0.9,
151            playing: true,
152        };
153        assert_eq!(c.advance(&v, 5.0), 9);
154        assert!(!c.playing);
155        c.seek(&v, -1.0);
156        assert_eq!(c.frame(&v), 0);
157    }
158}