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 remaining(&self, media_ns: i64) -> Duration {
179 let mut state = self.state.lock().unwrap();
180 if let State::Unavailable { next_registration } = *state {
181 self.wall_clock.start();
182 let elapsed = self.wall_clock.elapsed();
183 *state = State::Wall {
184 anchor_ns: media_ns,
185 anchor_elapsed: elapsed,
186 next_registration,
187 };
188 }
189 let Some(position) = position_at(*state, self.wall_clock.elapsed()) else {
190 return Duration::ZERO;
191 };
192 let ahead = media_ns.saturating_sub(position);
193 if ahead <= 0 {
194 return Duration::ZERO;
195 }
196 Duration::from_nanos(ahead as u64)
197 }
198
199 pub(crate) fn re_anchor(&self, media_ns: i64) {
210 let mut state = self.state.lock().unwrap();
211 let next_registration = match *state {
212 State::Unavailable {
213 next_registration, ..
214 }
215 | State::Wall {
216 next_registration, ..
217 } => next_registration,
218 State::AudioPriming { .. } | State::Audio { .. } | State::AudioFallback { .. } => {
219 return;
220 }
221 };
222 self.wall_clock.start();
223 *state = State::Wall {
224 anchor_ns: media_ns,
225 anchor_elapsed: self.wall_clock.elapsed(),
226 next_registration,
227 };
228 }
229
230 pub(crate) fn video_snapshot(&self, media_ns: i64) -> (PlaybackMaster, Option<i64>) {
231 let mut state = self.state.lock().unwrap();
232 if let State::Unavailable { next_registration } = *state {
233 self.wall_clock.start();
234 let elapsed = self.wall_clock.elapsed();
235 *state = State::Wall {
236 anchor_ns: media_ns,
237 anchor_elapsed: elapsed,
238 next_registration,
239 };
240 }
241 let elapsed = self.wall_clock.elapsed();
242 let master = match *state {
243 State::Unavailable { .. } => PlaybackMaster::Unavailable,
244 State::Wall { .. } | State::AudioFallback { .. } => PlaybackMaster::Wall,
245 State::AudioPriming { .. } => PlaybackMaster::AudioPriming,
246 State::Audio { .. } => PlaybackMaster::Audio,
247 };
248 (master, position_at(*state, elapsed))
249 }
250
251 #[allow(dead_code)]
254 pub(crate) fn register_audio_master(
255 self: &Arc<Self>,
256 ) -> Result<AudioMasterRegistration, PlaybackClockError> {
257 let mut state = self.state.lock().unwrap();
258 let elapsed = self.wall_clock.elapsed();
259 let (held_ns, registration, next_registration) = match *state {
260 State::Unavailable { next_registration } => {
261 (None, next_registration, next_registration.wrapping_add(1))
262 }
263 State::Wall {
264 next_registration, ..
265 } => (
266 position_at(*state, elapsed),
267 next_registration,
268 next_registration.wrapping_add(1),
269 ),
270 State::AudioPriming { .. } | State::Audio { .. } | State::AudioFallback { .. } => {
271 return Err(PlaybackClockError::AudioMasterAlreadyRegistered);
272 }
273 };
274 *state = State::AudioPriming {
275 registration,
276 held_ns,
277 next_registration,
278 };
279 Ok(AudioMasterRegistration {
280 clock: self.clone(),
281 registration,
282 })
283 }
284
285 pub(crate) fn reset_for_seek(&self) {
289 let mut state = self.state.lock().unwrap();
290 *state = match *state {
291 State::Unavailable { next_registration }
292 | State::Wall {
293 next_registration, ..
294 } => State::Unavailable { next_registration },
295 State::AudioPriming {
296 registration,
297 next_registration,
298 ..
299 }
300 | State::Audio {
301 registration,
302 next_registration,
303 ..
304 }
305 | State::AudioFallback {
306 registration,
307 next_registration,
308 ..
309 } => State::AudioPriming {
310 registration,
311 held_ns: None,
312 next_registration,
313 },
314 };
315 }
316
317 #[allow(dead_code)]
318 fn release_audio_master(&self, registration: u64) {
319 let mut state = self.state.lock().unwrap();
320 let elapsed = self.wall_clock.elapsed();
321 let (matches, next_registration) = match *state {
322 State::AudioPriming {
323 registration: current,
324 next_registration,
325 ..
326 }
327 | State::Audio {
328 registration: current,
329 next_registration,
330 ..
331 }
332 | State::AudioFallback {
333 registration: current,
334 next_registration,
335 ..
336 } => (current == registration, next_registration),
337 State::Unavailable { .. } | State::Wall { .. } => return,
338 };
339 if !matches {
340 return;
341 }
342 *state = match position_at(*state, elapsed) {
343 Some(anchor_ns) => State::Wall {
344 anchor_ns,
345 anchor_elapsed: elapsed,
346 next_registration,
347 },
348 None => State::Unavailable { next_registration },
349 };
350 }
351}
352
353#[allow(dead_code)]
365pub(crate) struct AudioMasterRegistration {
366 clock: Arc<PlaybackClock>,
367 registration: u64,
368}
369
370#[allow(dead_code)]
371impl AudioMasterRegistration {
372 pub(crate) fn priming_target_ns(&self) -> Result<Option<i64>, PlaybackClockError> {
373 match *self.clock.state.lock().unwrap() {
374 State::AudioPriming {
375 registration,
376 held_ns,
377 ..
378 } if registration == self.registration => Ok(held_ns),
379 State::Audio { registration, .. } if registration == self.registration => Ok(None),
380 State::AudioFallback { registration, .. } if registration == self.registration => {
381 Ok(None)
382 }
383 _ => Err(PlaybackClockError::StaleAudioMaster),
384 }
385 }
386
387 pub(crate) fn publish(
388 &self,
389 position_ns: i64,
390 submitted_until_ns: i64,
391 running: bool,
392 ) -> Result<(), PlaybackClockError> {
393 let mut state = self.clock.state.lock().unwrap();
394 self.clock.wall_clock.start();
395 let elapsed = self.clock.wall_clock.elapsed();
396 let (held_ns, next_registration) = match *state {
397 State::AudioPriming {
398 registration,
399 held_ns,
400 next_registration,
401 } if registration == self.registration => (held_ns, next_registration),
402 State::Audio {
403 registration,
404 next_registration,
405 ..
406 } if registration == self.registration => (None, next_registration),
407 State::AudioFallback {
408 registration,
409 next_registration,
410 ..
411 } if registration == self.registration => (None, next_registration),
412 _ => return Err(PlaybackClockError::StaleAudioMaster),
413 };
414
415 let position_ns = held_ns.map_or(position_ns, |held| position_ns.max(held));
417 let submitted_until_ns = submitted_until_ns.max(position_ns);
418 *state = State::Audio {
419 registration: self.registration,
420 position_ns,
421 sampled_elapsed: elapsed,
422 submitted_until_ns,
423 running,
424 next_registration,
425 };
426 Ok(())
427 }
428
429 pub(crate) fn finish(&self, position_ns: i64) -> Result<(), PlaybackClockError> {
433 let mut state = self.clock.state.lock().unwrap();
434 let elapsed = self.clock.wall_clock.elapsed();
435 let next_registration = match *state {
436 State::AudioPriming {
437 registration,
438 next_registration,
439 ..
440 }
441 | State::Audio {
442 registration,
443 next_registration,
444 ..
445 } if registration == self.registration => next_registration,
446 _ => return Err(PlaybackClockError::StaleAudioMaster),
447 };
448 *state = State::AudioFallback {
449 registration: self.registration,
450 anchor_ns: position_ns,
451 anchor_elapsed: elapsed,
452 next_registration,
453 };
454 Ok(())
455 }
456
457 pub(crate) fn reset_for_seek(&self) -> Result<(), PlaybackClockError> {
458 let mut state = self.clock.state.lock().unwrap();
459 let next_registration = match *state {
460 State::AudioPriming {
461 registration,
462 next_registration,
463 ..
464 }
465 | State::Audio {
466 registration,
467 next_registration,
468 ..
469 }
470 | State::AudioFallback {
471 registration,
472 next_registration,
473 ..
474 } if registration == self.registration => next_registration,
475 _ => return Err(PlaybackClockError::StaleAudioMaster),
476 };
477 *state = State::AudioPriming {
478 registration: self.registration,
479 held_ns: None,
480 next_registration,
481 };
482 Ok(())
483 }
484}
485
486impl Drop for AudioMasterRegistration {
487 fn drop(&mut self) {
488 self.clock.release_audio_master(self.registration);
489 }
490}
491
492fn position_at(state: State, elapsed: Duration) -> Option<i64> {
493 match state {
494 State::Unavailable { .. } => None,
495 State::Wall {
496 anchor_ns,
497 anchor_elapsed,
498 ..
499 } => Some(add_duration(
500 anchor_ns,
501 elapsed.saturating_sub(anchor_elapsed),
502 )),
503 State::AudioPriming { held_ns, .. } => held_ns,
504 State::Audio {
505 position_ns,
506 sampled_elapsed,
507 submitted_until_ns,
508 running,
509 ..
510 } => {
511 let projected = if running {
512 add_duration(position_ns, elapsed.saturating_sub(sampled_elapsed))
513 } else {
514 position_ns
515 };
516 Some(projected.min(submitted_until_ns))
517 }
518 State::AudioFallback {
519 anchor_ns,
520 anchor_elapsed,
521 ..
522 } => Some(add_duration(
523 anchor_ns,
524 elapsed.saturating_sub(anchor_elapsed),
525 )),
526 }
527}
528
529fn add_duration(value_ns: i64, duration: Duration) -> i64 {
530 let delta = duration.as_nanos().min(i64::MAX as u128) as i64;
531 value_ns.saturating_add(delta)
532}
533
534#[cfg(test)]
535mod tests {
536 use std::{thread, time::Duration};
537
538 use super::*;
539
540 #[test]
541 fn wall_origin_advances_and_freezes_with_pipeline_clock() {
542 let wall = Arc::new(Clock::new());
543 let playback = PlaybackClock::new(wall.clone());
544 assert!(playback.ensure_wall_origin(1_000).unwrap() >= 1_000);
545 thread::sleep(Duration::from_millis(20));
546 assert!(playback.position_ns().unwrap() >= 10_000_000);
547
548 wall.pause();
549 let paused = playback.position_ns().unwrap();
550 thread::sleep(Duration::from_millis(20));
551 assert_eq!(playback.position_ns(), Some(paused));
552 }
553
554 #[test]
555 fn audio_handoff_never_moves_backwards_and_release_continues_on_wall() {
556 let wall = Arc::new(Clock::new());
557 let playback = Arc::new(PlaybackClock::new(wall));
558 playback.ensure_wall_origin(50_000_000);
559 let audio = playback.register_audio_master().unwrap();
560 let held = audio.priming_target_ns().unwrap().unwrap();
561
562 audio
563 .publish(held - 10_000_000, held + 100_000_000, true)
564 .unwrap();
565 assert!(playback.position_ns().unwrap() >= held);
566 drop(audio);
567 let released = playback.position_ns().unwrap();
568 thread::sleep(Duration::from_millis(10));
569 assert!(playback.position_ns().unwrap() >= released);
570 assert_eq!(playback.master(), PlaybackMaster::Wall);
571 }
572
573 #[test]
574 fn only_one_audio_master_can_publish_and_seek_retains_its_generation() {
575 let wall = Arc::new(Clock::new());
576 let playback = Arc::new(PlaybackClock::new(wall));
577 let audio = playback.register_audio_master().unwrap();
578 assert!(matches!(
579 playback.register_audio_master(),
580 Err(PlaybackClockError::AudioMasterAlreadyRegistered)
581 ));
582
583 playback.reset_for_seek();
584 audio.publish(2_000, 3_000, true).unwrap();
585 assert_eq!(playback.master(), PlaybackMaster::Audio);
586 }
587
588 #[test]
589 fn audio_projection_is_capped_at_submitted_media() {
590 let wall = Arc::new(Clock::new());
591 let playback = Arc::new(PlaybackClock::new(wall));
592 let audio = playback.register_audio_master().unwrap();
593 audio.publish(10, 1_000_000, true).unwrap();
594 thread::sleep(Duration::from_millis(5));
595 assert_eq!(playback.position_ns(), Some(1_000_000));
596 }
597
598 #[test]
599 fn finished_audio_continues_on_wall_and_can_reset_for_seek() {
600 let wall = Arc::new(Clock::new());
601 let playback = Arc::new(PlaybackClock::new(wall));
602 let audio = playback.register_audio_master().unwrap();
603 audio.publish(1_000, 2_000, false).unwrap();
604 audio.finish(2_000).unwrap();
605 assert_eq!(playback.master(), PlaybackMaster::Wall);
606 thread::sleep(Duration::from_millis(5));
607 assert!(playback.position_ns().unwrap() > 2_000);
608
609 audio.reset_for_seek().unwrap();
610 assert_eq!(playback.master(), PlaybackMaster::AudioPriming);
611 assert_eq!(playback.position_ns(), None);
612 }
613}