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