Skip to main content

dash_runtime/timing/
period.rs

1//! PeriodStart derivation and Period kinds (ISO/IEC 23009-1 5.3.2.1).
2//!
3//! The five rules are a plain list in the standard, evaluated here per Period
4//! in document order:
5//! 1. `@start` present: PeriodStart = `@start`.
6//! 2. `@start` absent, previous Period has `@duration`: previous start plus
7//!    previous duration.
8//! 3. `@start` absent, first Period of a static MPD: 0.
9//! 4. `@start` absent, first Period or previous without `@duration`, dynamic
10//!    MPD: Early Available Period (no PeriodStart yet).
11//! 5. `@duration` present and (the next Period has `@start` or
12//!    `MPD@minimumUpdatePeriod` exists): Early Terminated Period.
13
14use std::time::Duration;
15
16use broadcast_common::impl_spec_display;
17
18use broadcast_common::units::{MICROS_PER_SEC, NANOS_PER_MICRO};
19use broadcast_dash::{Mpd, PresentationType};
20
21/// Duration to microseconds.
22#[must_use]
23pub fn duration_us(d: Duration) -> i128 {
24    i128::from(d.as_secs()) * (MICROS_PER_SEC as i128)
25        + i128::from(d.subsec_nanos() / (NANOS_PER_MICRO as u32))
26}
27
28/// 5.3.2.1 Period kind.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum PeriodKind {
32    /// Ends where the next Period starts (or the presentation ends).
33    Regular,
34    /// Ends after its own `@duration`.
35    EarlyTerminated,
36    /// Announced before it has a PeriodStart; not part of the presentation.
37    EarlyAvailable,
38}
39
40impl PeriodKind {
41    /// Stable snake_case label.
42    #[must_use]
43    pub fn name(&self) -> &'static str {
44        match self {
45            Self::Regular => "regular",
46            Self::EarlyTerminated => "early_terminated",
47            Self::EarlyAvailable => "early_available",
48        }
49    }
50}
51impl_spec_display!(PeriodKind);
52
53/// A Period's place on the presentation timeline.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55#[non_exhaustive]
56pub struct PeriodTiming {
57    /// PeriodStart in microseconds from the presentation start; `None` until
58    /// one can be derived.
59    pub start_us: Option<i128>,
60    /// The Period end in the same domain, when known.
61    pub end_us: Option<i128>,
62    /// The kind.
63    pub kind: PeriodKind,
64}
65
66/// Derive the timing of every Period of `mpd`, in document order.
67#[must_use]
68pub fn period_timings(mpd: &Mpd) -> Vec<PeriodTiming> {
69    let mut out: Vec<PeriodTiming> = Vec::with_capacity(mpd.periods.len());
70    let starts: Vec<Option<i128>> = {
71        let mut starts: Vec<Option<i128>> = Vec::with_capacity(mpd.periods.len());
72        for (i, p) in mpd.periods.iter().enumerate() {
73            let start = if let Some(s) = p.start {
74                Some(duration_us(s))
75            } else if i == 0 {
76                (mpd.kind == PresentationType::Static).then_some(0)
77            } else {
78                let prev = &mpd.periods[i - 1];
79                match (prev.duration, starts[i - 1]) {
80                    (Some(d), Some(ps)) => Some(ps.saturating_add(duration_us(d))),
81                    _ => None,
82                }
83            };
84            starts.push(start);
85        }
86        starts
87    };
88    for (i, p) in mpd.periods.iter().enumerate() {
89        let start = starts[i];
90        let next_has_start = mpd.periods.get(i + 1).is_some_and(|n| n.start.is_some());
91        let early_terminated =
92            p.duration.is_some() && (next_has_start || mpd.minimum_update_period.is_some());
93        let kind = match start {
94            None if mpd.kind == PresentationType::Dynamic => PeriodKind::EarlyAvailable,
95            _ if early_terminated => PeriodKind::EarlyTerminated,
96            _ => PeriodKind::Regular,
97        };
98        let next_start = starts.get(i + 1).copied().flatten();
99        let end = match (start, p.duration) {
100            (Some(s), Some(d)) => Some(s.saturating_add(duration_us(d))),
101            (Some(_), None) => next_start.or_else(|| {
102                (i + 1 == mpd.periods.len())
103                    .then(|| mpd.media_presentation_duration.map(duration_us))
104                    .flatten()
105            }),
106            (None, _) => None,
107        };
108        out.push(PeriodTiming {
109            start_us: start,
110            end_us: end,
111            kind,
112        });
113    }
114    out
115}