ranim/cmd/preview/
playback.rs1use web_time::Instant;
15
16use ranim_core::SceneEvaluator;
17
18#[cfg(not(target_family = "wasm"))]
19use super::audio::{AudioPlayer, MixedAudio};
20
21enum PlaybackClock {
23 Wall {
24 started_at: Instant,
25 base_sec: f64,
26 speed: f64,
27 },
28 #[cfg(not(target_family = "wasm"))]
29 Audio { player: AudioPlayer },
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 #[cfg(not(target_family = "wasm"))]
41 PlaybackClock::Audio { player } => player.pos_secs(),
42 }
43 }
44}
45
46pub(crate) struct PlaybackEngine {
49 clock: Option<PlaybackClock>,
50 #[cfg(not(target_family = "wasm"))]
51 player: Option<AudioPlayer>,
52}
53
54impl PlaybackEngine {
55 pub fn new(evaluator: &SceneEvaluator) -> Self {
57 #[cfg(target_family = "wasm")]
58 let _ = evaluator;
59 Self {
60 clock: None,
61 #[cfg(not(target_family = "wasm"))]
62 player: Self::build_player(evaluator),
63 }
64 }
65
66 #[cfg(not(target_family = "wasm"))]
67 fn build_player(evaluator: &SceneEvaluator) -> Option<AudioPlayer> {
68 if !evaluator.has_audio() {
69 return None;
70 }
71 let total_secs = evaluator.total_secs();
72 match AudioPlayer::try_new(MixedAudio::new(evaluator, total_secs)) {
73 Some(player) => Some(player),
74 None => {
75 tracing::warn!("no audio output device available; previewing without sound");
76 None
77 }
78 }
79 }
80
81 pub fn is_playing(&self) -> bool {
83 self.clock.is_some()
84 }
85
86 pub fn play(&mut self, from: f64, total: f64, speed: f64) -> f64 {
89 let start = if from >= total { 0.0 } else { from };
90 self.clock = Some({
91 #[cfg(not(target_family = "wasm"))]
92 match self.player.take() {
93 Some(mut player) => {
94 player.play_from(start, speed);
95 PlaybackClock::Audio { player }
96 }
97 None => PlaybackClock::Wall {
98 started_at: Instant::now(),
99 base_sec: start,
100 speed,
101 },
102 }
103 #[cfg(target_family = "wasm")]
104 PlaybackClock::Wall {
105 started_at: Instant::now(),
106 base_sec: start,
107 speed,
108 }
109 });
110 start
111 }
112
113 pub fn pause(&mut self) -> f64 {
115 match self.clock.take() {
116 Some(PlaybackClock::Wall {
117 started_at,
118 base_sec,
119 speed,
120 }) => base_sec + started_at.elapsed().as_secs_f64() * speed,
121 #[cfg(not(target_family = "wasm"))]
122 Some(PlaybackClock::Audio { mut player }) => {
123 player.pause();
124 let pos = player.pos_secs();
125 self.player = Some(player);
126 pos
127 }
128 None => 0.0,
129 }
130 }
131
132 pub fn pos_secs(&self) -> Option<f64> {
134 self.clock.as_ref().map(PlaybackClock::pos_secs)
135 }
136
137 pub fn scrub_to(&mut self, sec: f64) {
140 match self.clock.as_mut() {
141 Some(PlaybackClock::Wall {
142 started_at,
143 base_sec,
144 ..
145 }) => {
146 *base_sec = sec;
147 *started_at = Instant::now();
148 }
149 #[cfg(not(target_family = "wasm"))]
150 Some(PlaybackClock::Audio { player }) => player.seek_to(sec),
151 #[cfg(not(target_family = "wasm"))]
152 None => {
153 if let Some(player) = &mut self.player {
154 player.scrub(sec);
155 }
156 }
157 #[cfg(target_family = "wasm")]
158 None => {}
159 }
160 }
161
162 pub fn tick_scrub(&mut self) -> bool {
166 #[cfg(not(target_family = "wasm"))]
167 if self.clock.is_none()
168 && let Some(player) = &mut self.player
169 {
170 player.end_stale_scrub();
171 return player.scrub_voice_active();
172 }
173 false
174 }
175
176 pub fn set_speed(&mut self, speed: f64) {
178 match self.clock.as_mut() {
179 Some(PlaybackClock::Wall {
180 started_at,
181 base_sec,
182 speed: old_speed,
183 }) => {
184 let pos = *base_sec + started_at.elapsed().as_secs_f64() * *old_speed;
185 *base_sec = pos;
186 *started_at = Instant::now();
187 *old_speed = speed;
188 }
189 #[cfg(not(target_family = "wasm"))]
190 Some(PlaybackClock::Audio { player }) => player.set_speed(speed),
191 None => {}
192 }
193 }
194
195 pub fn reload_scene(&mut self, evaluator: &SceneEvaluator) {
198 self.clock = None;
199 #[cfg(target_family = "wasm")]
200 let _ = evaluator;
201 #[cfg(not(target_family = "wasm"))]
202 {
203 self.player = Self::build_player(evaluator);
204 }
205 }
206}