Skip to main content

pax_runtime_api/
animation.rs

1//! Animation primitives: interpolation, easing curves, transition queues, and timelines. See also: properties.ease_to and properties.ease_to_later.
2
3use super::*;
4
5/// A duration used by animation and transition systems.
6///
7/// `Frames` preserves Pax's historical frame-count semantics, while
8/// `Milliseconds` and `Seconds` advance from the chassis-provided monotonic
9/// wall clock.
10#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
11#[serde(crate = "crate::serde")]
12pub enum Duration {
13    /// Duration measured in runtime frames.
14    Frames(Numeric),
15    /// Duration measured in milliseconds.
16    Milliseconds(Numeric),
17    /// Duration measured in seconds.
18    Seconds(Numeric),
19}
20
21impl Default for Duration {
22    fn default() -> Self {
23        Self::Frames(Numeric::I64(0))
24    }
25}
26
27impl Display for Duration {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        match self {
30            Duration::Frames(value) => write!(f, "{}f", value),
31            Duration::Milliseconds(value) => write!(f, "{}ms", value),
32            Duration::Seconds(value) => write!(f, "{}s", value),
33        }
34    }
35}
36
37impl Interpolatable for Duration {
38    fn interpolate(&self, other: &Self, t: f64) -> Self {
39        match (self, other) {
40            (Duration::Frames(start), Duration::Frames(end)) => {
41                Duration::Frames(start.interpolate(end, t))
42            }
43            (Duration::Milliseconds(start), Duration::Milliseconds(end)) => {
44                Duration::Milliseconds(start.interpolate(end, t))
45            }
46            (Duration::Seconds(start), Duration::Seconds(end)) => {
47                Duration::Seconds(start.interpolate(end, t))
48            }
49            _ => {
50                let start_ms = self.as_milliseconds_f64();
51                let end_ms = other.as_milliseconds_f64();
52                Duration::Milliseconds(Numeric::F64(start_ms + (end_ms - start_ms) * t))
53            }
54        }
55    }
56}
57
58impl Duration {
59    pub const NOMINAL_FRAME_MILLIS: f64 = 1000.0 / 60.0;
60
61    /// Raw value in this duration's own unit.
62    pub fn raw_value(&self) -> f64 {
63        match self {
64            Duration::Frames(value) | Duration::Milliseconds(value) | Duration::Seconds(value) => {
65                value.to_float()
66            }
67        }
68    }
69
70    /// Returns true when the duration is expressed in frame units.
71    pub fn is_frame_based(&self) -> bool {
72        matches!(self, Duration::Frames(_))
73    }
74
75    /// Converts the duration to milliseconds, using 60fps as the nominal
76    /// frame duration when converting frame units for mixed-unit authoring.
77    pub fn as_milliseconds_f64(&self) -> f64 {
78        match self {
79            Duration::Frames(value) => value.to_float() * Self::NOMINAL_FRAME_MILLIS,
80            Duration::Milliseconds(value) => value.to_float(),
81            Duration::Seconds(value) => value.to_float() * 1000.0,
82        }
83    }
84
85    /// Converts the duration to frames, using 60fps as the nominal conversion
86    /// rate when converting wall-clock units for mixed-unit authoring.
87    pub fn as_frames_f64(&self) -> f64 {
88        match self {
89            Duration::Frames(value) => value.to_float(),
90            Duration::Milliseconds(value) => value.to_float() / Self::NOMINAL_FRAME_MILLIS,
91            Duration::Seconds(value) => value.to_float() * 1000.0 / Self::NOMINAL_FRAME_MILLIS,
92        }
93    }
94}
95
96macro_rules! impl_duration_from_number {
97    ($($type:ty),* $(,)?) => {
98        $(
99            impl From<$type> for Duration {
100                fn from(value: $type) -> Self {
101                    Duration::Frames(Numeric::from(value))
102                }
103            }
104        )*
105    };
106}
107
108impl_duration_from_number!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, f32, f64);
109
110// Represents an atomic segment of an imperative transition timeline.
111//
112// This segment is defined with length `duration`, is interpolated according to `curve`, and settles at
113// `ending_value` at the end of its interval.
114pub struct TransitionQueueEntry<T> {
115    // Length of this transition segment.
116    pub duration: Duration,
117    // Easing curve applied over the segment.
118    pub curve: EasingCurve,
119    // Value reached when the segment completes.
120    pub ending_value: T,
121}
122
123#[cfg(debug_assertions)]
124impl<T> std::fmt::Debug for TransitionQueueEntry<T> {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.debug_struct("TransitionQueueEntry")
127            .field("duration", &self.duration)
128            // .field("ending_value", &self.ending_value)
129            .finish()
130    }
131}
132
133impl<T: Interpolatable> Interpolatable for Option<T> {
134    fn interpolate(&self, other: &Self, t: f64) -> Self {
135        match &self {
136            Self::Some(s) => match other {
137                Self::Some(o) => Some(s.interpolate(o, t)),
138                _ => None,
139            },
140            Self::None => None,
141        }
142    }
143}
144
145// Manages transition queues for property interpolation, e.g. for `ease_to_later`.
146pub struct TransitionManager<T> {
147    queue: VecDeque<TransitionQueueEntry<T>>,
148    /// The value we are currently transitioning from
149    transition_checkpoint_value: T,
150    /// The frame clock value when the current transition started.
151    origin_frames_elapsed: u64,
152    /// The millisecond clock value when the current transition started.
153    origin_millis_elapsed: u64,
154}
155
156#[cfg(debug_assertions)]
157impl<T> std::fmt::Debug for TransitionManager<T> {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        f.debug_struct("TransitionManager")
160            .field("queue", &self.queue)
161            // .field("value", &self.transition_checkpoint_value)
162            .finish()
163    }
164}
165
166impl<T: Interpolatable> TransitionManager<T> {
167    // Creates an empty transition queue starting from `value`.
168    pub fn new(value: T, current_frames: u64, current_millis: u64) -> Self {
169        Self {
170            queue: VecDeque::new(),
171            transition_checkpoint_value: value,
172            origin_frames_elapsed: current_frames,
173            origin_millis_elapsed: current_millis,
174        }
175    }
176
177    // Appends one transition segment to the queue.
178    pub fn push_transition(&mut self, transition: TransitionQueueEntry<T>) {
179        self.queue.push_back(transition);
180    }
181
182    // Clears queued transitions after first checkpointing the current eased value.
183    pub fn reset_transitions(&mut self, current_frames: u64, current_millis: u64) {
184        let eased_value = self.compute_eased_value(current_frames, current_millis);
185        self.transition_checkpoint_value =
186            eased_value.unwrap_or(self.transition_checkpoint_value.clone());
187        self.queue.clear();
188        self.origin_frames_elapsed = current_frames;
189        self.origin_millis_elapsed = current_millis;
190    }
191
192    // Computes the current eased value and advances completed transition segments.
193    pub fn compute_eased_value(&mut self, frames_elapsed: u64, millis_elapsed: u64) -> Option<T> {
194        let global_frames = frames_elapsed;
195        let global_millis = millis_elapsed;
196        let mut completed_transition = false;
197
198        // Fast-forward transitions that have already completed, including
199        // zero-duration transitions that should take effect immediately.
200        while let Some(current_transition) = self.queue.front() {
201            let elapsed = transition_elapsed(
202                current_transition.duration,
203                global_frames,
204                global_millis,
205                self.origin_frames_elapsed,
206                self.origin_millis_elapsed,
207            );
208            let duration = transition_duration_units(current_transition.duration);
209            if elapsed < duration {
210                break;
211            }
212            let curr = self.queue.pop_front()?;
213            advance_transition_origin(
214                curr.duration,
215                &mut self.origin_frames_elapsed,
216                &mut self.origin_millis_elapsed,
217            );
218            self.transition_checkpoint_value = curr.ending_value;
219            completed_transition = true;
220        }
221
222        let current_transition = match self.queue.front() {
223            Some(current_transition) => current_transition,
224            None => {
225                return if completed_transition {
226                    Some(self.transition_checkpoint_value.clone())
227                } else {
228                    None
229                };
230            }
231        };
232
233        let elapsed = transition_elapsed(
234            current_transition.duration,
235            global_frames,
236            global_millis,
237            self.origin_frames_elapsed,
238            self.origin_millis_elapsed,
239        );
240        let duration = transition_duration_units(current_transition.duration);
241        let progress = if duration <= f64::EPSILON {
242            1.0
243        } else {
244            (elapsed / duration).clamp(0.0, 1.0)
245        };
246        let interpolated_val = current_transition.curve.interpolate(
247            &self.transition_checkpoint_value,
248            &current_transition.ending_value,
249            progress,
250        );
251        Some(interpolated_val)
252    }
253}
254
255fn transition_duration_units(duration: Duration) -> f64 {
256    match duration {
257        Duration::Frames(value) | Duration::Milliseconds(value) => value.to_float().max(0.0),
258        Duration::Seconds(value) => (value.to_float() * 1000.0).max(0.0),
259    }
260}
261
262fn transition_elapsed(
263    duration: Duration,
264    global_frames: u64,
265    global_millis: u64,
266    origin_frames: u64,
267    origin_millis: u64,
268) -> f64 {
269    match duration {
270        Duration::Frames(_) => global_frames.saturating_sub(origin_frames) as f64,
271        Duration::Milliseconds(_) | Duration::Seconds(_) => {
272            global_millis.saturating_sub(origin_millis) as f64
273        }
274    }
275}
276
277fn advance_transition_origin(duration: Duration, origin_frames: &mut u64, origin_millis: &mut u64) {
278    match duration {
279        Duration::Frames(value) => {
280            *origin_frames = origin_frames.saturating_add(value.to_float().max(0.0).round() as u64)
281        }
282        Duration::Milliseconds(value) => {
283            *origin_millis = origin_millis.saturating_add(value.to_float().max(0.0).round() as u64)
284        }
285        Duration::Seconds(value) => {
286            *origin_millis =
287                origin_millis.saturating_add((value.to_float().max(0.0) * 1000.0).round() as u64)
288        }
289    }
290}
291
292/// Pre-built easing curves for use in transitions, as well as a `Custom` variant that can be used to
293/// specify an arbitrary easing curve via a function `f: f64 -> f64` mapping a time on the unit interval to a multiplier on the unit interval.
294pub enum EasingCurve {
295    /// A linear easing curve, where the interpolated value changes at a constant rate over time.
296    Linear,
297    /// A hold easing curve, where the interpolated value remains constant until the end of the transition, at which point it jumps to the final value.
298    Hold,
299    /// A quadratic easing curve where the interpolated value starts slow and accelerates towards the end of the transition.
300    InQuad,
301    /// A quadratic easing curve where the interpolated value starts fast and decelerates towards the end of the transition.
302    OutQuad,
303    /// A quadratic easing curve where the interpolated value starts slow, accelerates towards the middle of the transition, and then decelerates towards the end of the transition.
304    InOutQuad,
305    /// A back easing curve where the interpolated value starts by briefly moving in the opposite direction before accelerating towards the final value.
306    InBack,
307    /// A back easing curve where the interpolated value overshoots the final value before settling back to it.
308    OutBack,
309    /// A back easing curve where the interpolated value starts by briefly moving in the opposite direction, then accelerates towards the final value, overshooting it before settling back to it.
310    InOutBack,
311    /// A custom easing curve defined by a user-provided function that maps a time on the unit interval to a multiplier on the unit interval.
312    Custom(Box<dyn Fn(f64) -> f64>),
313}
314
315struct EasingEvaluators {}
316impl EasingEvaluators {
317    fn linear(t: f64) -> f64 {
318        t
319    }
320    #[allow(dead_code)]
321    fn none(t: f64) -> f64 {
322        if t == 1.0 {
323            1.0
324        } else {
325            0.0
326        }
327    }
328    fn in_quad(t: f64) -> f64 {
329        t * t
330    }
331    fn out_quad(t: f64) -> f64 {
332        1.0 - (1.0 - t) * (1.0 - t)
333    }
334    fn in_out_quad(t: f64) -> f64 {
335        if t < 0.5 {
336            2.0 * t * t
337        } else {
338            1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
339        }
340    }
341    fn in_back(t: f64) -> f64 {
342        const C1: f64 = 1.70158;
343        const C3: f64 = C1 + 1.00;
344        C3 * t * t * t - C1 * t * t
345    }
346    fn out_back(t: f64) -> f64 {
347        const C1: f64 = 1.70158;
348        const C3: f64 = C1 + 1.00;
349        1.0 + C3 * (t - 1.0).powi(3) + C1 * (t - 1.0).powi(2)
350    }
351
352    fn in_out_back(t: f64) -> f64 {
353        const C1: f64 = 1.70158;
354        const C2: f64 = C1 * 1.525;
355        if t < 0.5 {
356            ((2.0 * t).powi(2) * ((C2 + 1.0) * 2.0 * t - C2)) / 2.0
357        } else {
358            ((2.0 * t - 2.0).powi(2) * ((C2 + 1.0) * (t * 2.0 - 2.0) + C2) + 2.0) / 2.0
359        }
360    }
361}
362
363impl EasingCurve {
364    /// Interpolates between `v0` and `v1` using `t` as time on the unit interval.
365    pub fn interpolate<T: Interpolatable>(&self, v0: &T, v1: &T, t: f64) -> T /*vt*/ {
366        let multiplier = match self {
367            EasingCurve::Linear => EasingEvaluators::linear(t),
368            EasingCurve::Hold => EasingEvaluators::none(t),
369            EasingCurve::InQuad => EasingEvaluators::in_quad(t),
370            EasingCurve::OutQuad => EasingEvaluators::out_quad(t),
371            EasingCurve::InOutQuad => EasingEvaluators::in_out_quad(t),
372            EasingCurve::InBack => EasingEvaluators::in_back(t),
373            EasingCurve::OutBack => EasingEvaluators::out_back(t),
374            EasingCurve::InOutBack => EasingEvaluators::in_out_back(t),
375            EasingCurve::Custom(evaluator) => (*evaluator)(t),
376        };
377
378        v0.interpolate(v1, multiplier)
379    }
380}
381
382impl<I: Clone + 'static> ImplToFromPaxAny for std::ops::Range<I> {}
383impl<T: 'static> ImplToFromPaxAny for Rc<T> {}
384impl<T: Clone + 'static> ImplToFromPaxAny for Weak<T> {}
385impl<T: Clone + 'static> ImplToFromPaxAny for Option<T> {}
386
387impl<T1: Clone + 'static, T2: Clone + 'static> ImplToFromPaxAny for (T1, T2) {}
388
389/// Marks a value as able to participate in Pax property transitions.
390pub trait Interpolatable
391where
392    Self: Sized + Clone,
393{
394    /// Interpolates this value toward `other` at time `t` on the unit interval.
395    ///
396    /// The default implementation acts like a `None` ease: the first value is
397    /// simply retained until the transition completes.
398    /// Vectors interpolate element by element when lengths match and switch
399    /// to the destination immediately when their lengths differ. Integer
400    /// interpolation uses floating-point arithmetic and truncates the result.
401    fn interpolate(&self, _other: &Self, _t: f64) -> Self {
402        self.clone()
403    }
404}
405
406impl<I: Interpolatable> Interpolatable for std::ops::Range<I> {
407    fn interpolate(&self, _other: &Self, _t: f64) -> Self {
408        self.start.interpolate(&_other.start, _t)..self.end.interpolate(&_other.end, _t)
409    }
410}
411impl Interpolatable for () {}
412
413impl<T: ?Sized + Clone> Interpolatable for HashSet<T> {}
414impl<T: ?Sized + Clone> Interpolatable for VecDeque<T> {}
415impl<T: ?Sized> Interpolatable for Rc<T> {}
416impl<T: Interpolatable> Interpolatable for Weak<T> {}
417impl<T1: Interpolatable, T2: Interpolatable> Interpolatable for (T1, T2) {}
418impl<I: Interpolatable> Interpolatable for Vec<I> {
419    fn interpolate(&self, other: &Self, t: f64) -> Self {
420        if self.len() != other.len() {
421            return other.clone();
422        }
423
424        self.iter()
425            .enumerate()
426            .map(|(i, elem)| elem.interpolate(other.get(i).unwrap(), t))
427            .collect()
428    }
429}
430
431impl Interpolatable for kurbo::BezPath {}
432
433impl Interpolatable for Instant {}
434
435impl Interpolatable for char {}
436
437impl Interpolatable for f64 {
438    fn interpolate(&self, other: &f64, t: f64) -> f64 {
439        self + (*other - self) * t
440    }
441}
442
443#[cfg(test)]
444mod tests {
445    use super::{
446        Duration, EasingCurve, Interpolatable, Numeric, Rotation, TransitionManager,
447        TransitionQueueEntry,
448    };
449
450    #[test]
451    fn zero_duration_transition_applies_immediately() {
452        let mut tm = TransitionManager::new(0.0, 0, 0);
453        tm.push_transition(TransitionQueueEntry {
454            duration: Duration::Frames(0.into()),
455            curve: EasingCurve::Linear,
456            ending_value: 10.0,
457        });
458
459        assert_eq!(tm.compute_eased_value(0, 0), Some(10.0));
460        assert_eq!(tm.compute_eased_value(1, 16), None);
461    }
462
463    #[test]
464    fn completed_transition_returns_final_value_when_polled_late() {
465        let mut tm = TransitionManager::new(0.0, 0, 0);
466        tm.push_transition(TransitionQueueEntry {
467            duration: Duration::Frames(10.into()),
468            curve: EasingCurve::Linear,
469            ending_value: 10.0,
470        });
471
472        assert_eq!(tm.compute_eased_value(11, 183), Some(10.0));
473        assert_eq!(tm.compute_eased_value(12, 200), None);
474    }
475
476    #[test]
477    fn zero_duration_transition_chains_into_next_transition() {
478        let mut tm = TransitionManager::new(0.0, 0, 0);
479        tm.push_transition(TransitionQueueEntry {
480            duration: Duration::Frames(0.into()),
481            curve: EasingCurve::Linear,
482            ending_value: 10.0,
483        });
484        tm.push_transition(TransitionQueueEntry {
485            duration: Duration::Frames(10.into()),
486            curve: EasingCurve::Linear,
487            ending_value: 20.0,
488        });
489
490        assert_eq!(tm.compute_eased_value(0, 0), Some(10.0));
491        assert_eq!(tm.compute_eased_value(5, 83), Some(15.0));
492        assert_eq!(tm.compute_eased_value(10, 167), Some(20.0));
493        assert_eq!(tm.compute_eased_value(11, 183), None);
494    }
495
496    #[test]
497    fn millisecond_transition_uses_wall_clock() {
498        let mut tm = TransitionManager::new(0.0, 0, 0);
499        tm.push_transition(TransitionQueueEntry {
500            duration: Duration::Milliseconds(100.into()),
501            curve: EasingCurve::Linear,
502            ending_value: 10.0,
503        });
504
505        assert_eq!(tm.compute_eased_value(1, 50), Some(5.0));
506        assert_eq!(tm.compute_eased_value(2, 100), Some(10.0));
507    }
508
509    #[test]
510    fn rotation_interpolates_linearly_in_degrees() {
511        let start = Rotation::Degrees(Numeric::F64(-4.0));
512        let end = Rotation::Degrees(Numeric::F64(8.0));
513
514        let midpoint = start.interpolate(&end, 0.5);
515        assert!((midpoint.get_as_degrees() - 2.0).abs() < 0.0001);
516    }
517
518    #[test]
519    fn rotation_percent_normalizes_to_unit_interval() {
520        let rotation = Rotation::Percent(Numeric::F64(100.0));
521        assert!((rotation.to_float_0_1() - 1.0).abs() < f64::EPSILON);
522    }
523
524    #[test]
525    fn in_out_quad_eases_symmetrically() {
526        let start = 0.0f64;
527        let end = 10.0f64;
528
529        let first_quarter = EasingCurve::InOutQuad.interpolate(&start, &end, 0.25);
530        let midpoint = EasingCurve::InOutQuad.interpolate(&start, &end, 0.5);
531        let third_quarter = EasingCurve::InOutQuad.interpolate(&start, &end, 0.75);
532
533        assert!((first_quarter - 1.25).abs() < 0.0001);
534        assert!((midpoint - 5.0).abs() < 0.0001);
535        assert!((third_quarter - 8.75).abs() < 0.0001);
536    }
537
538    #[test]
539    fn option_interpolates_inner_value_when_both_sides_are_present() {
540        let start = Some(0.0f64);
541        let end = Some(10.0f64);
542
543        assert_eq!(start.interpolate(&end, 0.0), Some(0.0));
544        assert_eq!(start.interpolate(&end, 0.5), Some(5.0));
545        assert_eq!(start.interpolate(&end, 1.0), Some(10.0));
546    }
547
548    #[test]
549    fn integers_interpolate_in_both_directions_without_overflow() {
550        assert_eq!(100_u64.interpolate(&0, 0.5), 50);
551        assert_eq!(100_u8.interpolate(&0, 0.5), 50);
552        assert_eq!(i64::MIN.interpolate(&i64::MAX, 0.5), 0);
553        assert_eq!(0_u32.interpolate(&100, 0.5), 50);
554    }
555
556    #[test]
557    fn changed_vector_topology_switches_discretely() {
558        assert_eq!(vec![0.0].interpolate(&vec![2.0, 3.0], 0.5), vec![2.0, 3.0]);
559        assert_eq!(
560            vec![0.0, 2.0].interpolate(&vec![2.0, 4.0], 0.5),
561            vec![1.0, 3.0]
562        );
563    }
564}
565
566impl Interpolatable for bool {
567    fn interpolate(&self, _other: &bool, _t: f64) -> bool {
568        *self
569    }
570}
571
572impl Interpolatable for usize {
573    fn interpolate(&self, other: &usize, t: f64) -> usize {
574        (*self as f64 + (*other as f64 - *self as f64) * t) as usize
575    }
576}
577
578impl Interpolatable for isize {
579    fn interpolate(&self, other: &isize, t: f64) -> isize {
580        (*self as f64 + (*other as f64 - *self as f64) * t) as isize
581    }
582}
583
584impl Interpolatable for i64 {
585    fn interpolate(&self, other: &i64, t: f64) -> i64 {
586        (*self as f64 + (*other as f64 - *self as f64) * t) as i64
587    }
588}
589
590impl Interpolatable for i128 {
591    fn interpolate(&self, other: &i128, t: f64) -> i128 {
592        (*self as f64 + (*other as f64 - *self as f64) * t) as i128
593    }
594}
595
596impl Interpolatable for u128 {
597    fn interpolate(&self, other: &u128, t: f64) -> u128 {
598        (*self as f64 + (*other as f64 - *self as f64) * t) as u128
599    }
600}
601
602impl Interpolatable for u64 {
603    fn interpolate(&self, other: &u64, t: f64) -> u64 {
604        (*self as f64 + (*other as f64 - *self as f64) * t) as u64
605    }
606}
607
608impl Interpolatable for u8 {
609    fn interpolate(&self, other: &u8, t: f64) -> u8 {
610        (*self as f64 + (*other as f64 - *self as f64) * t) as u8
611    }
612}
613
614impl Interpolatable for u16 {
615    fn interpolate(&self, other: &u16, t: f64) -> u16 {
616        (*self as f64 + (*other as f64 - *self as f64) * t) as u16
617    }
618}
619
620impl Interpolatable for u32 {
621    fn interpolate(&self, other: &u32, t: f64) -> u32 {
622        (*self as f64 + (*other as f64 - *self as f64) * t) as u32
623    }
624}
625
626impl Interpolatable for i8 {
627    fn interpolate(&self, other: &i8, t: f64) -> i8 {
628        (*self as f64 + (*other as f64 - *self as f64) * t) as i8
629    }
630}
631
632impl Interpolatable for i16 {
633    fn interpolate(&self, other: &i16, t: f64) -> i16 {
634        (*self as f64 + (*other as f64 - *self as f64) * t) as i16
635    }
636}
637
638impl Interpolatable for i32 {
639    fn interpolate(&self, other: &i32, t: f64) -> i32 {
640        (*self as f64 + (*other as f64 - *self as f64) * t) as i32
641    }
642}
643
644impl Interpolatable for String {}
645
646/// Minimal timeline state used by animation-oriented controls.
647pub struct Timeline {
648    /// Current playhead frame.
649    pub playhead_position: usize,
650    /// Total number of frames.
651    pub frame_count: usize,
652    /// Whether the timeline is currently advancing.
653    pub is_playing: bool,
654}