dash_runtime/address/
representation.rs1use thiserror::Error;
6use transmux::SegmentIndexBox;
7
8use super::AddressError;
9use super::plan::{DynamicContext, PlanInput, SegmentPlan};
10use crate::mpd::urls::{BaseCandidate, representation_candidates};
11use crate::selection::RepRef;
12use crate::timing::availability::ato_seconds_to_us;
13use crate::timing::period::{PeriodTiming, duration_us, period_timings};
14use broadcast_dash::inherit::{InheritError, SegmentInfo, resolve_segment_info};
15use broadcast_dash::{Mpd, PresentationType};
16
17#[derive(Debug, Clone, PartialEq, Eq, Error)]
19#[non_exhaustive]
20pub enum PlanError {
21 #[error("no such Representation")]
23 NoSuchRepresentation,
24 #[error(transparent)]
26 Inherit(#[from] InheritError),
27 #[error(transparent)]
29 Address(#[from] AddressError),
30 #[error("the Period has no PeriodStart")]
32 PeriodUnresolved,
33 #[error("dynamic MPD without availabilityStartTime")]
35 MissingAvailabilityStartTime,
36}
37
38#[derive(Debug, Clone, Default)]
40#[non_exhaustive]
41pub struct PlanOptions<'a> {
42 pub use_availability_time_offset: bool,
44 pub horizon_utc_us: Option<i128>,
47 pub index: Option<(&'a SegmentIndexBox, u64)>,
49}
50
51impl<'a> PlanOptions<'a> {
52 #[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 #[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 #[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#[derive(Debug, Clone)]
76#[non_exhaustive]
77pub struct PlannedRepresentation {
78 pub plan: SegmentPlan,
80 pub info: SegmentInfo,
82 pub candidates: Vec<BaseCandidate>,
84 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 _ => None,
94 }
95}
96
97fn info_ato(info: &SegmentInfo) -> Option<f64> {
98 match info {
99 SegmentInfo::Template(t) => t.multi.base.availability_time_offset,
100 SegmentInfo::List(l) => l.multi.base.availability_time_offset,
101 SegmentInfo::Base(b) => b.availability_time_offset,
102 _ => None,
103 }
104}
105
106pub fn plan_representation(
112 mpd: &Mpd,
113 at: RepRef,
114 opts: &PlanOptions<'_>,
115) -> Result<PlannedRepresentation, PlanError> {
116 let (period, set, rep) = at.resolve(mpd).ok_or(PlanError::NoSuchRepresentation)?;
117 let info = resolve_segment_info(period, set, rep)?;
118 let timing = *period_timings(mpd)
119 .get(at.period)
120 .ok_or(PlanError::NoSuchRepresentation)?;
121 let start = timing.start_us.ok_or(PlanError::PeriodUnresolved)?;
122 let candidates = representation_candidates(mpd, period, set, rep);
123 let mut input = PlanInput::new(&rep.id, rep.bandwidth, &info);
124 input.period_start_us = start;
125 input.period_end_us = timing.end_us;
126 input.index = opts.index;
127 if mpd.kind == PresentationType::Dynamic {
128 let ast = mpd
129 .availability_start_time_us
130 .ok_or(PlanError::MissingAvailabilityStartTime)?;
131 let tsb = info_tsb(&info)
132 .or_else(|| rep.base_urls.iter().find_map(|b| b.time_shift_buffer_depth))
133 .or(mpd.time_shift_buffer_depth)
134 .map(duration_us);
135 let mut ctx = DynamicContext::new(i128::from(ast), tsb);
136 if opts.use_availability_time_offset {
137 let base_ato = candidates
138 .first()
139 .map_or(0.0, |c| c.availability_time_offset_s);
140 ctx.availability_time_offset_us =
141 ato_seconds_to_us(base_ato + info_ato(&info).unwrap_or(0.0));
142 }
143 ctx.horizon_utc_us = opts.horizon_utc_us;
144 input.dynamic = Some(ctx);
145 }
146 let plan = SegmentPlan::build(&input)?;
147 Ok(PlannedRepresentation {
148 plan,
149 info,
150 candidates,
151 period: timing,
152 })
153}