use thiserror::Error;
use transmux::SegmentIndexBox;
use super::AddressError;
use super::plan::{DynamicContext, PlanInput, SegmentPlan};
use crate::mpd::urls::{BaseCandidate, representation_candidates};
use crate::selection::RepRef;
use crate::timing::availability::ato_seconds_to_us;
use crate::timing::period::{PeriodTiming, duration_us, period_timings};
use broadcast_dash::inherit::{InheritError, SegmentInfo, resolve_segment_info};
use broadcast_dash::{Mpd, PresentationType};
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum PlanError {
#[error("no such Representation")]
NoSuchRepresentation,
#[error(transparent)]
Inherit(#[from] InheritError),
#[error(transparent)]
Address(#[from] AddressError),
#[error("the Period has no PeriodStart")]
PeriodUnresolved,
#[error("dynamic MPD without availabilityStartTime")]
MissingAvailabilityStartTime,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct PlanOptions<'a> {
pub use_availability_time_offset: bool,
pub horizon_utc_us: Option<i128>,
pub index: Option<(&'a SegmentIndexBox, u64)>,
}
impl<'a> PlanOptions<'a> {
#[must_use]
pub fn with_index(mut self, sidx: &'a SegmentIndexBox, anchor: u64) -> Self {
self.index = Some((sidx, anchor));
self
}
#[must_use]
pub fn with_horizon_utc_us(mut self, horizon_utc_us: i128) -> Self {
self.horizon_utc_us = Some(horizon_utc_us);
self
}
#[must_use]
pub fn with_availability_time_offset(mut self, apply: bool) -> Self {
self.use_availability_time_offset = apply;
self
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PlannedRepresentation {
pub plan: SegmentPlan,
pub info: SegmentInfo,
pub candidates: Vec<BaseCandidate>,
pub period: PeriodTiming,
}
fn info_tsb(info: &SegmentInfo) -> Option<std::time::Duration> {
match info {
SegmentInfo::Template(t) => t.multi.base.time_shift_buffer_depth,
SegmentInfo::List(l) => l.multi.base.time_shift_buffer_depth,
SegmentInfo::Base(b) => b.time_shift_buffer_depth,
_ => None,
}
}
fn info_ato(info: &SegmentInfo) -> Option<f64> {
match info {
SegmentInfo::Template(t) => t.multi.base.availability_time_offset,
SegmentInfo::List(l) => l.multi.base.availability_time_offset,
SegmentInfo::Base(b) => b.availability_time_offset,
_ => None,
}
}
pub fn plan_representation(
mpd: &Mpd,
at: RepRef,
opts: &PlanOptions<'_>,
) -> Result<PlannedRepresentation, PlanError> {
let (period, set, rep) = at.resolve(mpd).ok_or(PlanError::NoSuchRepresentation)?;
let info = resolve_segment_info(period, set, rep)?;
let timing = *period_timings(mpd)
.get(at.period)
.ok_or(PlanError::NoSuchRepresentation)?;
let start = timing.start_us.ok_or(PlanError::PeriodUnresolved)?;
let candidates = representation_candidates(mpd, period, set, rep);
let mut input = PlanInput::new(&rep.id, rep.bandwidth, &info);
input.period_start_us = start;
input.period_end_us = timing.end_us;
input.index = opts.index;
if mpd.kind == PresentationType::Dynamic {
let ast = mpd
.availability_start_time_us
.ok_or(PlanError::MissingAvailabilityStartTime)?;
let tsb = info_tsb(&info)
.or_else(|| rep.base_urls.iter().find_map(|b| b.time_shift_buffer_depth))
.or(mpd.time_shift_buffer_depth)
.map(duration_us);
let mut ctx = DynamicContext::new(i128::from(ast), tsb);
if opts.use_availability_time_offset {
let base_ato = candidates
.first()
.map_or(0.0, |c| c.availability_time_offset_s);
ctx.availability_time_offset_us =
ato_seconds_to_us(base_ato + info_ato(&info).unwrap_or(0.0));
}
ctx.horizon_utc_us = opts.horizon_utc_us;
input.dynamic = Some(ctx);
}
let plan = SegmentPlan::build(&input)?;
Ok(PlannedRepresentation {
plan,
info,
candidates,
period: timing,
})
}