Skip to main content

dash_runtime/timing/
live_edge.rs

1//! Live edge, presentation delay and join (ISO/IEC 23009-1 5.3.9.5.3, A.2
2//! step 4; DASH-IF IOP v4.3 4.3.3.2.2 and 4.3.4).
3//!
4//! * `D` = `@suggestedPresentationDelay` when present, else
5//!   `max(4 s, 3 x maxSegmentDuration)` (IOP 4.3.3.2.2: 2-4 segment
6//!   durations and not under 4 s), clamped to `TSB - 2 x maxSegmentDuration`
7//!   when a time-shift buffer exists; an explicit override replaces it.
8//! * Join time (Period-relative) `T_join = utc_now - D - (AST + PeriodStart)`;
9//!   the segment joined is the one containing it on the Representation's own
10//!   timeline (audio and video boundaries differ), clamped to the segments
11//!   that are complete and still inside the time-shift window.
12
13use std::time::Duration;
14
15use crate::address::plan::SegmentPlan;
16use crate::timing::availability::complete_count_constant;
17use crate::timing::period::duration_us;
18
19const MICROS_PER_SECOND: i128 = 1_000_000;
20/// IOP 4.3.3.2.2: the presentation delay is not under four seconds.
21pub const MIN_PRESENTATION_DELAY_US: i128 = 4 * MICROS_PER_SECOND;
22/// IOP 4.3.3.2.2: the delay is a small multiple of the segment duration.
23pub const DELAY_SEGMENT_MULTIPLE: i128 = 3;
24/// Segment durations kept free inside the time-shift buffer.
25pub const TSB_GUARD_SEGMENTS: i128 = 2;
26
27/// The presentation delay `D`, in microseconds.
28#[must_use]
29pub fn presentation_delay_us(
30    suggested: Option<Duration>,
31    max_segment_duration_us: i128,
32    time_shift_buffer_depth_us: Option<i128>,
33    override_us: Option<i128>,
34) -> i128 {
35    if let Some(o) = override_us {
36        return o.max(0);
37    }
38    let mut delay = suggested.map_or_else(
39        || {
40            MIN_PRESENTATION_DELAY_US
41                .max(max_segment_duration_us.saturating_mul(DELAY_SEGMENT_MULTIPLE))
42        },
43        duration_us,
44    );
45    if let Some(tsb) = time_shift_buffer_depth_us {
46        let ceiling =
47            tsb.saturating_sub(max_segment_duration_us.saturating_mul(TSB_GUARD_SEGMENTS));
48        delay = delay.min(ceiling);
49    }
50    delay.max(0)
51}
52
53/// `T_join`, Period-relative microseconds.
54#[must_use]
55pub fn join_time_us(utc_now_us: i128, delay_us: i128, ast_us: i128, period_start_us: i128) -> i128 {
56    utc_now_us
57        .saturating_sub(delay_us)
58        .saturating_sub(ast_us.saturating_add(period_start_us))
59}
60
61/// The plan indices of the segments obtainable now.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63#[non_exhaustive]
64pub struct LiveWindow {
65    /// Oldest segment still inside the time-shift window.
66    pub oldest: u64,
67    /// Newest complete segment.
68    pub newest: u64,
69}
70
71/// Index of the newest complete segment at `utc_now_us`; `None` when none is
72/// complete yet or the plan is static.
73#[must_use]
74pub fn newest_complete(plan: &SegmentPlan, utc_now_us: i128) -> Option<u64> {
75    let (ast, ato) = plan.avail_parts()?;
76    let count = match (plan.len(), plan.constant_duration()) {
77        (None, Some(d)) => complete_count_constant(
78            utc_now_us,
79            ast,
80            plan.period_start_us(),
81            ato,
82            d,
83            plan.timescale(),
84        ),
85        (len, _) => {
86            let bound = len.unwrap_or(u64::MAX / 2);
87            plan.leading_true(bound, |s| {
88                s.available_from_us.is_some_and(|a| a <= utc_now_us)
89            })
90        }
91    };
92    count.checked_sub(1)
93}
94
95/// Index of the oldest segment whose availability has not ended at
96/// `utc_now_us`; `None` when none is complete or all have expired.
97#[must_use]
98pub fn oldest_in_window(plan: &SegmentPlan, utc_now_us: i128) -> Option<u64> {
99    let newest = newest_complete(plan, utc_now_us)?;
100    let expired = plan.leading_true(newest + 1, |s| {
101        s.available_until_us.is_some_and(|u| u < utc_now_us)
102    });
103    (expired <= newest).then_some(expired)
104}
105
106/// The obtainable window at `utc_now_us`.
107#[must_use]
108pub fn window(plan: &SegmentPlan, utc_now_us: i128) -> Option<LiveWindow> {
109    Some(LiveWindow {
110        oldest: oldest_in_window(plan, utc_now_us)?,
111        newest: newest_complete(plan, utc_now_us)?,
112    })
113}
114
115/// The segment to start with: the one containing `T_join`, clamped into the
116/// window. `None` when nothing is obtainable.
117#[must_use]
118pub fn join_index(plan: &SegmentPlan, utc_now_us: i128, delay_us: i128) -> Option<u64> {
119    let w = window(plan, utc_now_us)?;
120    let (ast, _) = plan.avail_parts()?;
121    let t = join_time_us(utc_now_us, delay_us, ast, plan.period_start_us());
122    let at = plan.index_at_or_before(t).unwrap_or(w.oldest);
123    Some(at.clamp(w.oldest, w.newest))
124}