Skip to main content

dash_runtime/address/
representation.rs

1//! One call from an MPD position to a ready [`SegmentPlan`]: resolve the
2//! inherited Segment information, derive the Period's place on the timeline,
3//! gather the BaseURL candidates and build the plan.
4
5use thiserror::Error;
6use transmux::SegmentIndexBox;
7
8use super::AddressError;
9use super::plan::{DynamicContext, PlanInput, SegmentPlan};
10use crate::mpd::inherit::{InheritError, SegmentInfo, resolve_segment_info};
11use crate::mpd::model::{Mpd, PresentationType};
12use crate::mpd::urls::{BaseCandidate, representation_candidates};
13use crate::selection::RepRef;
14use crate::timing::availability::ato_seconds_to_us;
15use crate::timing::period::{PeriodTiming, duration_us, period_timings};
16
17/// Why a Representation could not be planned.
18#[derive(Debug, Clone, PartialEq, Eq, Error)]
19#[non_exhaustive]
20pub enum PlanError {
21    /// `RepRef` names nothing in this MPD.
22    #[error("no such Representation")]
23    NoSuchRepresentation,
24    /// Segment information inheritance failed.
25    #[error(transparent)]
26    Inherit(#[from] InheritError),
27    /// Addressing failed.
28    #[error(transparent)]
29    Address(#[from] AddressError),
30    /// The Period has no PeriodStart yet (Early Available Period).
31    #[error("the Period has no PeriodStart")]
32    PeriodUnresolved,
33    /// A dynamic MPD without `@availabilityStartTime`.
34    #[error("dynamic MPD without availabilityStartTime")]
35    MissingAvailabilityStartTime,
36}
37
38/// Knobs of [`plan_representation`].
39#[derive(Debug, Clone, Default)]
40#[non_exhaustive]
41pub struct PlanOptions<'a> {
42    /// Apply `@availabilityTimeOffset` (early requests; LL-DASH).
43    pub use_availability_time_offset: bool,
44    /// `FetchTime + minimumUpdatePeriod` in UTC microseconds, for a trailing
45    /// negative `@r` of a Period with no known end.
46    pub horizon_utc_us: Option<i128>,
47    /// The parsed `sidx` and its anchor, for a SegmentBase Representation.
48    pub index: Option<(&'a SegmentIndexBox, u64)>,
49}
50
51impl<'a> PlanOptions<'a> {
52    /// Supply the parsed `sidx` and its anchor offset.
53    #[must_use]
54    pub fn with_index(mut self, sidx: &'a SegmentIndexBox, anchor: u64) -> Self {
55        self.index = Some((sidx, anchor));
56        self
57    }
58
59    /// Set the horizon of a trailing negative `@r`.
60    #[must_use]
61    pub fn with_horizon_utc_us(mut self, horizon_utc_us: i128) -> Self {
62        self.horizon_utc_us = Some(horizon_utc_us);
63        self
64    }
65
66    /// Opt into applying `@availabilityTimeOffset`.
67    #[must_use]
68    pub fn with_availability_time_offset(mut self, apply: bool) -> Self {
69        self.use_availability_time_offset = apply;
70        self
71    }
72}
73
74/// A planned Representation.
75#[derive(Debug, Clone)]
76#[non_exhaustive]
77pub struct PlannedRepresentation {
78    /// The segments.
79    pub plan: SegmentPlan,
80    /// The resolved Segment information.
81    pub info: SegmentInfo,
82    /// BaseURL candidates, best first.
83    pub candidates: Vec<BaseCandidate>,
84    /// The Period's timing.
85    pub period: PeriodTiming,
86}
87
88fn info_tsb(info: &SegmentInfo) -> Option<std::time::Duration> {
89    match info {
90        SegmentInfo::Template(t) => t.multi.base.time_shift_buffer_depth,
91        SegmentInfo::List(l) => l.multi.base.time_shift_buffer_depth,
92        SegmentInfo::Base(b) => b.time_shift_buffer_depth,
93    }
94}
95
96fn info_ato(info: &SegmentInfo) -> Option<f64> {
97    match info {
98        SegmentInfo::Template(t) => t.multi.base.availability_time_offset,
99        SegmentInfo::List(l) => l.multi.base.availability_time_offset,
100        SegmentInfo::Base(b) => b.availability_time_offset,
101    }
102}
103
104/// Plan the Representation at `at`.
105///
106/// # Errors
107/// [`PlanError`]; the caller drops the Representation (or, for
108/// `AddressError::IndexRequired`, calls again with `opts.index`).
109pub fn plan_representation(
110    mpd: &Mpd,
111    at: RepRef,
112    opts: &PlanOptions<'_>,
113) -> Result<PlannedRepresentation, PlanError> {
114    let (period, set, rep) = at.resolve(mpd).ok_or(PlanError::NoSuchRepresentation)?;
115    let info = resolve_segment_info(period, set, rep)?;
116    let timing = *period_timings(mpd)
117        .get(at.period)
118        .ok_or(PlanError::NoSuchRepresentation)?;
119    let start = timing.start_us.ok_or(PlanError::PeriodUnresolved)?;
120    let candidates = representation_candidates(mpd, period, set, rep);
121    let mut input = PlanInput::new(&rep.id, rep.bandwidth, &info);
122    input.period_start_us = start;
123    input.period_end_us = timing.end_us;
124    input.index = opts.index;
125    if mpd.kind == PresentationType::Dynamic {
126        let ast = mpd
127            .availability_start_time_us
128            .ok_or(PlanError::MissingAvailabilityStartTime)?;
129        let tsb = info_tsb(&info)
130            .or_else(|| rep.base_urls.iter().find_map(|b| b.time_shift_buffer_depth))
131            .or(mpd.time_shift_buffer_depth)
132            .map(duration_us);
133        let mut ctx = DynamicContext::new(i128::from(ast), tsb);
134        if opts.use_availability_time_offset {
135            let base_ato = candidates
136                .first()
137                .map_or(0.0, |c| c.availability_time_offset_s);
138            ctx.availability_time_offset_us =
139                ato_seconds_to_us(base_ato + info_ato(&info).unwrap_or(0.0));
140        }
141        ctx.horizon_utc_us = opts.horizon_utc_us;
142        input.dynamic = Some(ctx);
143    }
144    let plan = SegmentPlan::build(&input)?;
145    Ok(PlannedRepresentation {
146        plan,
147        info,
148        candidates,
149        period: timing,
150    })
151}