1use std::{
16 sync::{Arc, Mutex},
17 time::Duration,
18};
19
20use thiserror::Error as ThisError;
21
22use crate::clock::Clock;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum PlaybackMaster {
27 Unavailable,
29 Wall,
31 AudioPriming,
33 Audio,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, ThisError)]
38pub enum PlaybackClockError {
44 #[error("this pipeline already has an audio playback-clock master")]
46 AudioMasterAlreadyRegistered,
47
48 #[error("the audio playback-clock registration is stale")]
50 StaleAudioMaster,
51}
52
53pub struct PlaybackClock {
62 wall_clock: Arc<Clock>,
63 state: Mutex<State>,
64}
65
66#[derive(Clone, Copy)]
67enum State {
68 Unavailable {
69 next_registration: u64,
70 },
71 Wall {
72 anchor_ns: i64,
73 anchor_elapsed: Duration,
74 next_registration: u64,
75 },
76 AudioPriming {
77 registration: u64,
78 held_ns: Option<i64>,
79 next_registration: u64,
80 },
81 #[allow(dead_code)]
87 Audio {
88 registration: u64,
89 position_ns: i64,
90 sampled_elapsed: Duration,
91 submitted_until_ns: i64,
92 running: bool,
93 next_registration: u64,
94 },
95 #[allow(dead_code)]
96 AudioFallback {
97 registration: u64,
98 anchor_ns: i64,
99 anchor_elapsed: Duration,
100 next_registration: u64,
101 },
102}
103
104impl PlaybackClock {
105 pub(crate) fn new(wall_clock: Arc<Clock>) -> Self {
106 Self {
107 wall_clock,
108 state: Mutex::new(State::Unavailable {
109 next_registration: 1,
110 }),
111 }
112 }
113
114 pub fn master(&self) -> PlaybackMaster {
119 match *self.state.lock().unwrap() {
120 State::Unavailable { .. } => PlaybackMaster::Unavailable,
121 State::Wall { .. } | State::AudioFallback { .. } => PlaybackMaster::Wall,
122 State::AudioPriming { .. } => PlaybackMaster::AudioPriming,
123 State::Audio { .. } => PlaybackMaster::Audio,
124 }
125 }
126
127 #[cfg(test)]
128 pub(crate) fn position_ns(&self) -> Option<i64> {
129 let state = self.state.lock().unwrap();
130 position_at(*state, self.wall_clock.elapsed())
131 }
132
133 pub(crate) fn interrupt_epoch(&self) -> u64 {
134 self.wall_clock.interrupt_epoch()
135 }
136
137 #[cfg(test)]
140 pub(crate) fn ensure_wall_origin(&self, media_ns: i64) -> Option<i64> {
141 let mut state = self.state.lock().unwrap();
142 if let State::Unavailable { next_registration } = *state {
143 self.wall_clock.start();
144 let elapsed = self.wall_clock.elapsed();
145 *state = State::Wall {
146 anchor_ns: media_ns,
147 anchor_elapsed: elapsed,
148 next_registration,
149 };
150 }
151 position_at(*state, self.wall_clock.elapsed())
152 }
153
154 pub(crate) fn video_snapshot(&self, media_ns: i64) -> (PlaybackMaster, Option<i64>) {
155 let mut state = self.state.lock().unwrap();
156 if let State::Unavailable { next_registration } = *state {
157 self.wall_clock.start();
158 let elapsed = self.wall_clock.elapsed();
159 *state = State::Wall {
160 anchor_ns: media_ns,
161 anchor_elapsed: elapsed,
162 next_registration,
163 };
164 }
165 let elapsed = self.wall_clock.elapsed();
166 let master = match *state {
167 State::Unavailable { .. } => PlaybackMaster::Unavailable,
168 State::Wall { .. } | State::AudioFallback { .. } => PlaybackMaster::Wall,
169 State::AudioPriming { .. } => PlaybackMaster::AudioPriming,
170 State::Audio { .. } => PlaybackMaster::Audio,
171 };
172 (master, position_at(*state, elapsed))
173 }
174
175 #[allow(dead_code)]
178 pub(crate) fn register_audio_master(
179 self: &Arc<Self>,
180 ) -> Result<AudioMasterRegistration, PlaybackClockError> {
181 let mut state = self.state.lock().unwrap();
182 let elapsed = self.wall_clock.elapsed();
183 let (held_ns, registration, next_registration) = match *state {
184 State::Unavailable { next_registration } => {
185 (None, next_registration, next_registration.wrapping_add(1))
186 }
187 State::Wall {
188 next_registration, ..
189 } => (
190 position_at(*state, elapsed),
191 next_registration,
192 next_registration.wrapping_add(1),
193 ),
194 State::AudioPriming { .. } | State::Audio { .. } | State::AudioFallback { .. } => {
195 return Err(PlaybackClockError::AudioMasterAlreadyRegistered);
196 }
197 };
198 *state = State::AudioPriming {
199 registration,
200 held_ns,
201 next_registration,
202 };
203 Ok(AudioMasterRegistration {
204 clock: self.clone(),
205 registration,
206 })
207 }
208
209 pub(crate) fn reset_for_seek(&self) {
213 let mut state = self.state.lock().unwrap();
214 *state = match *state {
215 State::Unavailable { next_registration }
216 | State::Wall {
217 next_registration, ..
218 } => State::Unavailable { next_registration },
219 State::AudioPriming {
220 registration,
221 next_registration,
222 ..
223 }
224 | State::Audio {
225 registration,
226 next_registration,
227 ..
228 }
229 | State::AudioFallback {
230 registration,
231 next_registration,
232 ..
233 } => State::AudioPriming {
234 registration,
235 held_ns: None,
236 next_registration,
237 },
238 };
239 }
240
241 #[allow(dead_code)]
242 fn release_audio_master(&self, registration: u64) {
243 let mut state = self.state.lock().unwrap();
244 let elapsed = self.wall_clock.elapsed();
245 let (matches, next_registration) = match *state {
246 State::AudioPriming {
247 registration: current,
248 next_registration,
249 ..
250 }
251 | State::Audio {
252 registration: current,
253 next_registration,
254 ..
255 }
256 | State::AudioFallback {
257 registration: current,
258 next_registration,
259 ..
260 } => (current == registration, next_registration),
261 State::Unavailable { .. } | State::Wall { .. } => return,
262 };
263 if !matches {
264 return;
265 }
266 *state = match position_at(*state, elapsed) {
267 Some(anchor_ns) => State::Wall {
268 anchor_ns,
269 anchor_elapsed: elapsed,
270 next_registration,
271 },
272 None => State::Unavailable { next_registration },
273 };
274 }
275}
276
277#[allow(dead_code)]
289pub(crate) struct AudioMasterRegistration {
290 clock: Arc<PlaybackClock>,
291 registration: u64,
292}
293
294#[allow(dead_code)]
295impl AudioMasterRegistration {
296 pub(crate) fn priming_target_ns(&self) -> Result<Option<i64>, PlaybackClockError> {
297 match *self.clock.state.lock().unwrap() {
298 State::AudioPriming {
299 registration,
300 held_ns,
301 ..
302 } if registration == self.registration => Ok(held_ns),
303 State::Audio { registration, .. } if registration == self.registration => Ok(None),
304 State::AudioFallback { registration, .. } if registration == self.registration => {
305 Ok(None)
306 }
307 _ => Err(PlaybackClockError::StaleAudioMaster),
308 }
309 }
310
311 pub(crate) fn publish(
312 &self,
313 position_ns: i64,
314 submitted_until_ns: i64,
315 running: bool,
316 ) -> Result<(), PlaybackClockError> {
317 let mut state = self.clock.state.lock().unwrap();
318 self.clock.wall_clock.start();
319 let elapsed = self.clock.wall_clock.elapsed();
320 let (held_ns, next_registration) = match *state {
321 State::AudioPriming {
322 registration,
323 held_ns,
324 next_registration,
325 } if registration == self.registration => (held_ns, next_registration),
326 State::Audio {
327 registration,
328 next_registration,
329 ..
330 } if registration == self.registration => (None, next_registration),
331 State::AudioFallback {
332 registration,
333 next_registration,
334 ..
335 } if registration == self.registration => (None, next_registration),
336 _ => return Err(PlaybackClockError::StaleAudioMaster),
337 };
338
339 let position_ns = held_ns.map_or(position_ns, |held| position_ns.max(held));
341 let submitted_until_ns = submitted_until_ns.max(position_ns);
342 *state = State::Audio {
343 registration: self.registration,
344 position_ns,
345 sampled_elapsed: elapsed,
346 submitted_until_ns,
347 running,
348 next_registration,
349 };
350 Ok(())
351 }
352
353 pub(crate) fn finish(&self, position_ns: i64) -> Result<(), PlaybackClockError> {
357 let mut state = self.clock.state.lock().unwrap();
358 let elapsed = self.clock.wall_clock.elapsed();
359 let next_registration = match *state {
360 State::AudioPriming {
361 registration,
362 next_registration,
363 ..
364 }
365 | State::Audio {
366 registration,
367 next_registration,
368 ..
369 } if registration == self.registration => next_registration,
370 _ => return Err(PlaybackClockError::StaleAudioMaster),
371 };
372 *state = State::AudioFallback {
373 registration: self.registration,
374 anchor_ns: position_ns,
375 anchor_elapsed: elapsed,
376 next_registration,
377 };
378 Ok(())
379 }
380
381 pub(crate) fn reset_for_seek(&self) -> Result<(), PlaybackClockError> {
382 let mut state = self.clock.state.lock().unwrap();
383 let next_registration = match *state {
384 State::AudioPriming {
385 registration,
386 next_registration,
387 ..
388 }
389 | State::Audio {
390 registration,
391 next_registration,
392 ..
393 }
394 | State::AudioFallback {
395 registration,
396 next_registration,
397 ..
398 } if registration == self.registration => next_registration,
399 _ => return Err(PlaybackClockError::StaleAudioMaster),
400 };
401 *state = State::AudioPriming {
402 registration: self.registration,
403 held_ns: None,
404 next_registration,
405 };
406 Ok(())
407 }
408}
409
410impl Drop for AudioMasterRegistration {
411 fn drop(&mut self) {
412 self.clock.release_audio_master(self.registration);
413 }
414}
415
416fn position_at(state: State, elapsed: Duration) -> Option<i64> {
417 match state {
418 State::Unavailable { .. } => None,
419 State::Wall {
420 anchor_ns,
421 anchor_elapsed,
422 ..
423 } => Some(add_duration(
424 anchor_ns,
425 elapsed.saturating_sub(anchor_elapsed),
426 )),
427 State::AudioPriming { held_ns, .. } => held_ns,
428 State::Audio {
429 position_ns,
430 sampled_elapsed,
431 submitted_until_ns,
432 running,
433 ..
434 } => {
435 let projected = if running {
436 add_duration(position_ns, elapsed.saturating_sub(sampled_elapsed))
437 } else {
438 position_ns
439 };
440 Some(projected.min(submitted_until_ns))
441 }
442 State::AudioFallback {
443 anchor_ns,
444 anchor_elapsed,
445 ..
446 } => Some(add_duration(
447 anchor_ns,
448 elapsed.saturating_sub(anchor_elapsed),
449 )),
450 }
451}
452
453fn add_duration(value_ns: i64, duration: Duration) -> i64 {
454 let delta = duration.as_nanos().min(i64::MAX as u128) as i64;
455 value_ns.saturating_add(delta)
456}
457
458#[cfg(test)]
459mod tests {
460 use std::{thread, time::Duration};
461
462 use super::*;
463
464 #[test]
465 fn wall_origin_advances_and_freezes_with_pipeline_clock() {
466 let wall = Arc::new(Clock::new());
467 let playback = PlaybackClock::new(wall.clone());
468 assert!(playback.ensure_wall_origin(1_000).unwrap() >= 1_000);
469 thread::sleep(Duration::from_millis(20));
470 assert!(playback.position_ns().unwrap() >= 10_000_000);
471
472 wall.pause();
473 let paused = playback.position_ns().unwrap();
474 thread::sleep(Duration::from_millis(20));
475 assert_eq!(playback.position_ns(), Some(paused));
476 }
477
478 #[test]
479 fn audio_handoff_never_moves_backwards_and_release_continues_on_wall() {
480 let wall = Arc::new(Clock::new());
481 let playback = Arc::new(PlaybackClock::new(wall));
482 playback.ensure_wall_origin(50_000_000);
483 let audio = playback.register_audio_master().unwrap();
484 let held = audio.priming_target_ns().unwrap().unwrap();
485
486 audio
487 .publish(held - 10_000_000, held + 100_000_000, true)
488 .unwrap();
489 assert!(playback.position_ns().unwrap() >= held);
490 drop(audio);
491 let released = playback.position_ns().unwrap();
492 thread::sleep(Duration::from_millis(10));
493 assert!(playback.position_ns().unwrap() >= released);
494 assert_eq!(playback.master(), PlaybackMaster::Wall);
495 }
496
497 #[test]
498 fn only_one_audio_master_can_publish_and_seek_retains_its_generation() {
499 let wall = Arc::new(Clock::new());
500 let playback = Arc::new(PlaybackClock::new(wall));
501 let audio = playback.register_audio_master().unwrap();
502 assert!(matches!(
503 playback.register_audio_master(),
504 Err(PlaybackClockError::AudioMasterAlreadyRegistered)
505 ));
506
507 playback.reset_for_seek();
508 audio.publish(2_000, 3_000, true).unwrap();
509 assert_eq!(playback.master(), PlaybackMaster::Audio);
510 }
511
512 #[test]
513 fn audio_projection_is_capped_at_submitted_media() {
514 let wall = Arc::new(Clock::new());
515 let playback = Arc::new(PlaybackClock::new(wall));
516 let audio = playback.register_audio_master().unwrap();
517 audio.publish(10, 1_000_000, true).unwrap();
518 thread::sleep(Duration::from_millis(5));
519 assert_eq!(playback.position_ns(), Some(1_000_000));
520 }
521
522 #[test]
523 fn finished_audio_continues_on_wall_and_can_reset_for_seek() {
524 let wall = Arc::new(Clock::new());
525 let playback = Arc::new(PlaybackClock::new(wall));
526 let audio = playback.register_audio_master().unwrap();
527 audio.publish(1_000, 2_000, false).unwrap();
528 audio.finish(2_000).unwrap();
529 assert_eq!(playback.master(), PlaybackMaster::Wall);
530 thread::sleep(Duration::from_millis(5));
531 assert!(playback.position_ns().unwrap() > 2_000);
532
533 audio.reset_for_seek().unwrap();
534 assert_eq!(playback.master(), PlaybackMaster::AudioPriming);
535 assert_eq!(playback.position_ns(), None);
536 }
537}