ranim/cmd/preview/
playback.rs1use web_time::Instant;
15
16use ranim_core::SceneEvaluator;
17
18use super::audio::{AudioPlayer, MixedAudio};
19
20enum PlaybackClock {
22 Wall {
23 started_at: Instant,
24 base_sec: f64,
25 speed: f64,
26 },
27 Audio {
28 player: AudioPlayer,
29 },
30}
31
32impl PlaybackClock {
33 fn pos_secs(&self) -> f64 {
34 match self {
35 PlaybackClock::Wall {
36 started_at,
37 base_sec,
38 speed,
39 } => base_sec + started_at.elapsed().as_secs_f64() * speed,
40 PlaybackClock::Audio { player } => player.pos_secs(),
41 }
42 }
43}
44
45pub(crate) struct PlaybackEngine {
48 clock: Option<PlaybackClock>,
49 player: Option<AudioPlayer>,
50}
51
52impl PlaybackEngine {
53 pub fn new(evaluator: &SceneEvaluator) -> Self {
55 Self {
56 clock: None,
57 player: Self::build_player(evaluator),
58 }
59 }
60
61 fn build_player(evaluator: &SceneEvaluator) -> Option<AudioPlayer> {
62 if !evaluator.has_audio() {
63 return None;
64 }
65 let total_secs = evaluator.total_secs();
66 match AudioPlayer::try_new(MixedAudio::new(evaluator, total_secs)) {
67 Some(player) => Some(player),
68 None => {
69 tracing::warn!("no audio output device available; previewing without sound");
70 None
71 }
72 }
73 }
74
75 pub fn is_playing(&self) -> bool {
77 self.clock.is_some()
78 }
79
80 pub fn play(&mut self, from: f64, total: f64, speed: f64) -> f64 {
83 let start = if from >= total { 0.0 } else { from };
84 self.clock = Some(match self.player.take() {
85 Some(mut player) => {
86 player.play_from(start, speed);
87 PlaybackClock::Audio { player }
88 }
89 None => PlaybackClock::Wall {
90 started_at: Instant::now(),
91 base_sec: start,
92 speed,
93 },
94 });
95 start
96 }
97
98 pub fn pause(&mut self) -> f64 {
100 match self.clock.take() {
101 Some(PlaybackClock::Wall {
102 started_at,
103 base_sec,
104 speed,
105 }) => base_sec + started_at.elapsed().as_secs_f64() * speed,
106 Some(PlaybackClock::Audio { mut player }) => {
107 player.pause();
108 let pos = player.pos_secs();
109 self.player = Some(player);
110 pos
111 }
112 None => 0.0,
113 }
114 }
115
116 pub fn pos_secs(&self) -> Option<f64> {
118 self.clock.as_ref().map(PlaybackClock::pos_secs)
119 }
120
121 pub fn scrub_to(&mut self, sec: f64) {
124 match self.clock.as_mut() {
125 Some(PlaybackClock::Wall {
126 started_at,
127 base_sec,
128 ..
129 }) => {
130 *base_sec = sec;
131 *started_at = Instant::now();
132 }
133 Some(PlaybackClock::Audio { player }) => player.seek_to(sec),
134 None => {
135 if let Some(player) = &mut self.player {
136 player.scrub(sec);
137 }
138 }
139 }
140 }
141
142 pub fn tick_scrub(&mut self) -> bool {
146 if self.clock.is_none()
147 && let Some(player) = &mut self.player
148 {
149 player.end_stale_scrub();
150 return player.scrub_voice_active();
151 }
152 false
153 }
154
155 pub fn set_speed(&mut self, speed: f64) {
157 match self.clock.as_mut() {
158 Some(PlaybackClock::Wall {
159 started_at,
160 base_sec,
161 speed: old_speed,
162 }) => {
163 let pos = *base_sec + started_at.elapsed().as_secs_f64() * *old_speed;
164 *base_sec = pos;
165 *started_at = Instant::now();
166 *old_speed = speed;
167 }
168 Some(PlaybackClock::Audio { player }) => player.set_speed(speed),
169 None => {}
170 }
171 }
172}