1use crate::{EasingType, TimelineData, TimelineKeyframe, Value};
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum PlaybackState {
29 Stopped,
31 Playing,
33 Paused,
35 Finished,
37}
38
39#[derive(Debug, Clone)]
41pub struct TimelinePlayer {
42 timeline: TimelineData,
44 state: PlaybackState,
46 start_time: u64,
48 pause_time: Option<u64>,
50 loop_count: u32,
52}
53
54impl TimelinePlayer {
55 pub fn new(timeline: TimelineData) -> Self {
57 Self {
58 timeline,
59 state: PlaybackState::Stopped,
60 start_time: 0,
61 pause_time: None,
62 loop_count: 0,
63 }
64 }
65
66 pub fn start(&mut self, current_time_us: u64) {
68 self.start_time = current_time_us;
69 self.state = PlaybackState::Playing;
70 self.pause_time = None;
71 self.loop_count = 0;
72 }
73
74 pub fn start_at(&mut self, start_time_us: u64) {
76 self.start_time = start_time_us;
77 self.state = PlaybackState::Playing;
78 self.pause_time = None;
79 self.loop_count = 0;
80 }
81
82 pub fn pause(&mut self, current_time_us: u64) {
84 if self.state == PlaybackState::Playing {
85 self.pause_time = Some(current_time_us);
86 self.state = PlaybackState::Paused;
87 }
88 }
89
90 pub fn resume(&mut self, current_time_us: u64) {
92 if self.state == PlaybackState::Paused {
93 if let Some(pause_time) = self.pause_time {
94 let pause_duration = current_time_us.saturating_sub(pause_time);
96 self.start_time = self.start_time.saturating_add(pause_duration);
97 }
98 self.state = PlaybackState::Playing;
99 self.pause_time = None;
100 }
101 }
102
103 pub fn stop(&mut self) {
105 self.state = PlaybackState::Stopped;
106 self.pause_time = None;
107 }
108
109 pub fn state(&self) -> PlaybackState {
111 self.state
112 }
113
114 pub fn loop_count(&self) -> u32 {
116 self.loop_count
117 }
118
119 pub fn duration(&self) -> u64 {
121 self.timeline.duration()
122 }
123
124 pub fn sample(&mut self, current_time_us: u64) -> Option<Value> {
129 if self.state == PlaybackState::Stopped {
130 return None;
131 }
132
133 if self.timeline.keyframes.is_empty() {
134 return None;
135 }
136
137 let elapsed = if self.state == PlaybackState::Paused {
139 self.pause_time.unwrap_or(current_time_us) - self.start_time
140 } else {
141 current_time_us.saturating_sub(self.start_time)
142 };
143
144 let duration = self.timeline.duration();
145 if duration == 0 {
146 return Some(self.timeline.keyframes[0].value.clone());
147 }
148
149 let position = if self.timeline.loop_ {
151 let new_loop_count = (elapsed / duration) as u32;
152 if new_loop_count > self.loop_count {
153 self.loop_count = new_loop_count;
154 }
155 elapsed % duration
156 } else if elapsed >= duration {
157 self.state = PlaybackState::Finished;
158 return Some(self.timeline.keyframes.last()?.value.clone());
159 } else {
160 elapsed
161 };
162
163 let (prev_kf, next_kf) = self.find_keyframes(position)?;
165
166 let segment_duration = next_kf.time.saturating_sub(prev_kf.time);
168 if segment_duration == 0 {
169 return Some(prev_kf.value.clone());
170 }
171
172 let local_t = (position - prev_kf.time) as f64 / segment_duration as f64;
173 let eased_t = apply_easing(local_t, prev_kf.easing, prev_kf.bezier);
174
175 Some(interpolate_value(&prev_kf.value, &next_kf.value, eased_t))
177 }
178
179 fn find_keyframes(&self, position: u64) -> Option<(&TimelineKeyframe, &TimelineKeyframe)> {
181 let keyframes = &self.timeline.keyframes;
182
183 if keyframes.is_empty() {
184 return None;
185 }
186
187 if keyframes.len() == 1 {
188 return Some((&keyframes[0], &keyframes[0]));
189 }
190
191 let next_idx = keyframes
193 .iter()
194 .position(|kf| kf.time > position)
195 .unwrap_or(keyframes.len());
196
197 if next_idx == 0 {
198 Some((&keyframes[0], &keyframes[0]))
200 } else if next_idx >= keyframes.len() {
201 let last = &keyframes[keyframes.len() - 1];
203 Some((last, last))
204 } else {
205 Some((&keyframes[next_idx - 1], &keyframes[next_idx]))
206 }
207 }
208}
209
210fn apply_easing(t: f64, easing: EasingType, bezier: Option<[f64; 4]>) -> f64 {
212 let t = t.clamp(0.0, 1.0);
213
214 match easing {
215 EasingType::Linear => t,
216 EasingType::EaseIn => t * t,
217 EasingType::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
218 EasingType::EaseInOut => {
219 if t < 0.5 {
220 2.0 * t * t
221 } else {
222 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
223 }
224 }
225 EasingType::Step => {
226 if t < 1.0 {
227 0.0
228 } else {
229 1.0
230 }
231 }
232 EasingType::CubicBezier => {
233 if let Some([x1, y1, x2, y2]) = bezier {
234 cubic_bezier(t, x1, y1, x2, y2)
235 } else {
236 t }
238 }
239 }
240}
241
242fn cubic_bezier(t: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
245 let mut u = t;
250 for _ in 0..8 {
251 let x = bezier_sample(u, x1, x2);
252 let dx = bezier_derivative(u, x1, x2);
253 if dx.abs() < 1e-10 {
254 break;
255 }
256 u -= (x - t) / dx;
257 u = u.clamp(0.0, 1.0);
258 }
259
260 bezier_sample(u, y1, y2)
262}
263
264fn bezier_sample(t: f64, p1: f64, p2: f64) -> f64 {
265 let mt = 1.0 - t;
267 3.0 * mt * mt * t * p1 + 3.0 * mt * t * t * p2 + t * t * t
268}
269
270fn bezier_derivative(t: f64, p1: f64, p2: f64) -> f64 {
271 let mt = 1.0 - t;
273 3.0 * mt * mt * p1 + 6.0 * mt * t * (p2 - p1) + 3.0 * t * t * (1.0 - p2)
274}
275
276fn interpolate_value(a: &Value, b: &Value, t: f64) -> Value {
278 match (a, b) {
279 (Value::Float(a), Value::Float(b)) => Value::Float(a + (b - a) * t),
280 (Value::Int(a), Value::Int(b)) => Value::Int(*a + ((*b - *a) as f64 * t) as i64),
281 (Value::Array(arr_a), Value::Array(arr_b)) if arr_a.len() == arr_b.len() => Value::Array(
282 arr_a
283 .iter()
284 .zip(arr_b.iter())
285 .map(|(a, b)| interpolate_value(a, b, t))
286 .collect(),
287 ),
288 _ => {
290 if t < 0.5 {
291 a.clone()
292 } else {
293 b.clone()
294 }
295 }
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 fn make_simple_timeline() -> TimelineData {
304 TimelineData::new(vec![
305 TimelineKeyframe {
306 time: 0,
307 value: Value::Float(0.0),
308 easing: EasingType::Linear,
309 bezier: None,
310 },
311 TimelineKeyframe {
312 time: 1_000_000, value: Value::Float(100.0),
314 easing: EasingType::Linear,
315 bezier: None,
316 },
317 ])
318 }
319
320 #[test]
321 fn test_timeline_player_creation() {
322 let timeline = make_simple_timeline();
323 let player = TimelinePlayer::new(timeline);
324 assert_eq!(player.state(), PlaybackState::Stopped);
325 }
326
327 #[test]
328 fn test_timeline_player_start() {
329 let timeline = make_simple_timeline();
330 let mut player = TimelinePlayer::new(timeline);
331
332 player.start(0);
333 assert_eq!(player.state(), PlaybackState::Playing);
334 }
335
336 #[test]
337 fn test_timeline_linear_interpolation() {
338 let timeline = make_simple_timeline();
339 let mut player = TimelinePlayer::new(timeline);
340
341 player.start(0);
342
343 let val = player.sample(0).unwrap();
345 assert!(matches!(val, Value::Float(v) if (v - 0.0).abs() < 0.01));
346
347 let val = player.sample(500_000).unwrap();
349 assert!(matches!(val, Value::Float(v) if (v - 50.0).abs() < 0.01));
350
351 let val = player.sample(1_000_000).unwrap();
353 assert!(matches!(val, Value::Float(v) if (v - 100.0).abs() < 0.01));
354 }
355
356 #[test]
357 fn test_timeline_finished_state() {
358 let timeline = make_simple_timeline();
359 let mut player = TimelinePlayer::new(timeline);
360
361 player.start(0);
362
363 let _ = player.sample(2_000_000);
365 assert_eq!(player.state(), PlaybackState::Finished);
366 }
367
368 #[test]
369 fn test_timeline_looping() {
370 let timeline = make_simple_timeline().with_loop(true);
371 let mut player = TimelinePlayer::new(timeline);
372
373 player.start(0);
374
375 let val = player.sample(500_000).unwrap();
377 assert!(matches!(val, Value::Float(v) if (v - 50.0).abs() < 0.01));
378
379 let val = player.sample(1_500_000).unwrap();
381 assert!(matches!(val, Value::Float(v) if (v - 50.0).abs() < 0.01));
382
383 assert_eq!(player.loop_count(), 1);
384 }
385
386 #[test]
387 fn test_timeline_pause_resume() {
388 let timeline = make_simple_timeline();
389 let mut player = TimelinePlayer::new(timeline);
390
391 player.start(0);
392
393 let _ = player.sample(250_000);
395
396 player.pause(250_000);
398 assert_eq!(player.state(), PlaybackState::Paused);
399
400 let val = player.sample(750_000).unwrap();
402 assert!(matches!(val, Value::Float(v) if (v - 25.0).abs() < 0.01));
404
405 player.resume(750_000);
407 assert_eq!(player.state(), PlaybackState::Playing);
408
409 let val = player.sample(1_000_000).unwrap();
411 assert!(matches!(val, Value::Float(v) if (v - 50.0).abs() < 0.01));
413 }
414
415 #[test]
416 fn test_easing_ease_in() {
417 let timeline = TimelineData::new(vec![
418 TimelineKeyframe {
419 time: 0,
420 value: Value::Float(0.0),
421 easing: EasingType::EaseIn,
422 bezier: None,
423 },
424 TimelineKeyframe {
425 time: 1_000_000,
426 value: Value::Float(100.0),
427 easing: EasingType::Linear,
428 bezier: None,
429 },
430 ]);
431 let mut player = TimelinePlayer::new(timeline);
432 player.start(0);
433
434 let val = player.sample(500_000).unwrap();
436 assert!(matches!(val, Value::Float(v) if v < 50.0));
437 }
438
439 #[test]
440 fn test_easing_ease_out() {
441 let timeline = TimelineData::new(vec![
442 TimelineKeyframe {
443 time: 0,
444 value: Value::Float(0.0),
445 easing: EasingType::EaseOut,
446 bezier: None,
447 },
448 TimelineKeyframe {
449 time: 1_000_000,
450 value: Value::Float(100.0),
451 easing: EasingType::Linear,
452 bezier: None,
453 },
454 ]);
455 let mut player = TimelinePlayer::new(timeline);
456 player.start(0);
457
458 let val = player.sample(500_000).unwrap();
460 assert!(matches!(val, Value::Float(v) if v > 50.0));
461 }
462
463 #[test]
464 fn test_array_interpolation() {
465 let timeline = TimelineData::new(vec![
466 TimelineKeyframe {
467 time: 0,
468 value: Value::Array(vec![Value::Float(0.0), Value::Float(0.0)]),
469 easing: EasingType::Linear,
470 bezier: None,
471 },
472 TimelineKeyframe {
473 time: 1_000_000,
474 value: Value::Array(vec![Value::Float(100.0), Value::Float(200.0)]),
475 easing: EasingType::Linear,
476 bezier: None,
477 },
478 ]);
479 let mut player = TimelinePlayer::new(timeline);
480 player.start(0);
481
482 let val = player.sample(500_000).unwrap();
483 if let Value::Array(arr) = val {
484 assert!(matches!(arr[0], Value::Float(v) if (v - 50.0).abs() < 0.01));
485 assert!(matches!(arr[1], Value::Float(v) if (v - 100.0).abs() < 0.01));
486 } else {
487 panic!("Expected array value");
488 }
489 }
490}