use std::collections::BTreeSet;
use std::time::Duration;
use broadcast_common::xml::{Elem, Namespace, read_tree};
use crate::descriptors::XLINK_RESOLVE_TO_ZERO;
use crate::duration::parse_iso8601_duration;
use crate::error::{Limit, MpdError, Unsupported};
use crate::limits::Limits;
use crate::model::*;
use crate::utc_timing::UtcScheme;
use crate::xs_datetime::parse_xs_datetime;
const PROFILE_SEPARATOR: char = ',';
pub(crate) const INFINITY_TOKEN: &str = "INF";
pub(crate) const NEGATIVE_INFINITY_TOKEN: &str = "-INF";
const MAX_START_WITH_SAP: u8 = 6;
const NS_MPD_ROOT: &str = "MPD";
pub const NS_MPD: &str = "urn:mpeg:dash:schema:mpd:2011";
pub const NS_DVB: &str = "urn:dvb:dash:dash-extensions:2014-1";
pub const NS_CENC: &str = "urn:mpeg:cenc:2013";
pub const NS_XLINK: &str = "http://www.w3.org/1999/xlink";
pub const NS_URLPARAM: &str = "urn:mpeg:dash:schema:urlparam:2014";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Ns {
None,
Mpd,
Dvb,
Cenc,
Xlink,
UrlParam,
Other,
}
impl Namespace for Ns {
const TARGET: Self = Self::Mpd;
const NONE: Self = Self::None;
fn classify(uri: &str) -> Self {
match uri {
NS_MPD => Self::Mpd,
NS_DVB => Self::Dvb,
NS_CENC => Self::Cenc,
NS_XLINK => Self::Xlink,
NS_URLPARAM => Self::UrlParam,
_ => Self::Other,
}
}
}
fn invalid(what: &'static str, detail: impl Into<String>) -> MpdError {
MpdError::Invalid {
what,
detail: detail.into(),
}
}
fn opt_str(e: &Elem<Ns>, name: &str) -> Option<String> {
e.attr(name).map(str::to_owned)
}
fn opt_parse<T: core::str::FromStr>(
e: &Elem<Ns>,
name: &'static str,
) -> Result<Option<T>, MpdError> {
match e.attr(name) {
None => Ok(None),
Some(v) => v
.trim()
.parse::<T>()
.map(Some)
.map_err(|_| invalid(name, v)),
}
}
fn opt_u64(e: &Elem<Ns>, name: &'static str) -> Result<Option<u64>, MpdError> {
opt_parse(e, name)
}
fn opt_u32(e: &Elem<Ns>, name: &'static str) -> Result<Option<u32>, MpdError> {
opt_parse(e, name)
}
fn opt_i64(e: &Elem<Ns>, name: &'static str) -> Result<Option<i64>, MpdError> {
opt_parse(e, name)
}
fn positive_u64(e: &Elem<Ns>, name: &'static str) -> Result<Option<u64>, MpdError> {
match opt_u64(e, name)? {
Some(0) => Err(invalid(name, "must be greater than zero")),
other => Ok(other),
}
}
fn parse_bool(name: &'static str, v: &str) -> Result<bool, MpdError> {
match v.trim() {
"true" | "1" => Ok(true),
"false" | "0" => Ok(false),
other => Err(invalid(name, other)),
}
}
fn opt_bool(e: &Elem<Ns>, name: &'static str) -> Result<Option<bool>, MpdError> {
e.attr(name).map(|v| parse_bool(name, v)).transpose()
}
fn opt_f64(e: &Elem<Ns>, name: &'static str) -> Result<Option<f64>, MpdError> {
let Some(v) = e.attr(name) else {
return Ok(None);
};
let v = v.trim();
if v == INFINITY_TOKEN {
return Ok(Some(f64::INFINITY));
}
if v == NEGATIVE_INFINITY_TOKEN {
return Ok(Some(f64::NEG_INFINITY));
}
match v.parse::<f64>() {
Ok(x) if x.is_finite() => Ok(Some(x)),
_ => Err(invalid(name, v)),
}
}
fn opt_duration(e: &Elem<Ns>, name: &'static str) -> Result<Option<Duration>, MpdError> {
e.attr(name)
.map(|v| parse_iso8601_duration(v).map_err(|_| invalid(name, v)))
.transpose()
}
fn opt_time(e: &Elem<Ns>, name: &'static str) -> Result<Option<i64>, MpdError> {
e.attr(name)
.map(|v| {
parse_xs_datetime(v)
.map(|t| t.unix_us)
.map_err(|_| invalid(name, v))
})
.transpose()
}
pub(crate) fn parse_range(name: &'static str, v: &str) -> Result<ByteRange, MpdError> {
let v = v.trim();
let (first, last) = v.split_once('-').ok_or_else(|| invalid(name, v))?;
let first = first.parse::<u64>().map_err(|_| invalid(name, v))?;
let last = if last.is_empty() {
None
} else {
Some(last.parse::<u64>().map_err(|_| invalid(name, v))?)
};
if let Some(l) = last
&& l < first
{
return Err(invalid(name, v));
}
Ok(ByteRange { first, last })
}
fn opt_range(e: &Elem<Ns>, name: &'static str) -> Result<Option<ByteRange>, MpdError> {
e.attr(name).map(|v| parse_range(name, v)).transpose()
}
fn xlink(e: &Elem<Ns>) -> Result<Option<Xlink>, MpdError> {
let Some(href) = e.attr_in(Ns::Xlink, "href") else {
return Ok(None);
};
let actuate = match e.attr_in(Ns::Xlink, "actuate") {
None | Some("onRequest") => Actuate::OnRequest,
Some("onLoad") => Actuate::OnLoad,
Some(other) => return Err(invalid("xlink:actuate", other)),
};
Ok(Some(Xlink {
href: href.to_owned(),
actuate,
}))
}
fn is_resolve_to_zero(x: &Option<Xlink>) -> bool {
x.as_ref().is_some_and(|x| x.href == XLINK_RESOLVE_TO_ZERO)
}
struct Ctx<'a> {
limits: &'a Limits,
features: MpdFeatures,
}
fn check_count(count: usize, max: usize, limit: Limit) -> Result<(), MpdError> {
if count > max {
Err(MpdError::TooLarge { limit, max })
} else {
Ok(())
}
}
fn url_query_info(q: &Elem<Ns>) -> Result<UrlQueryInfo, MpdError> {
Ok(UrlQueryInfo {
use_mpd_url_query: opt_bool(q, "useMPDUrlQuery")?.unwrap_or(false),
query_string: opt_str(q, "queryString"),
query_template: opt_str(q, "queryTemplate"),
})
}
fn descriptor(e: &Elem<Ns>) -> Result<Descriptor, MpdError> {
let scheme_id_uri = e
.attr("schemeIdUri")
.ok_or_else(|| invalid("schemeIdUri", format!("missing on {}", e.name)))?
.to_owned();
let url_query = e
.children
.iter()
.find(|c| c.ns == Ns::UrlParam && c.name == "UrlQueryInfo")
.map(url_query_info)
.transpose()?;
let pssh = e
.children
.iter()
.find(|c| c.ns == Ns::Cenc && c.name == "pssh")
.map(|c| c.text.trim().to_owned());
let _ = url_query.as_ref();
Ok(Descriptor {
scheme_id_uri,
value: opt_str(e, "value"),
id: opt_str(e, "id"),
default_kid: e.attr_in(Ns::Cenc, "default_KID").map(str::to_owned),
pssh,
url_query,
})
}
fn descriptors(e: &Elem<Ns>, name: &str) -> Result<Vec<Descriptor>, MpdError> {
e.children_named(name).map(descriptor).collect()
}
fn base_urls(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<Vec<BaseUrl>, MpdError> {
let mut out = Vec::new();
for b in e.children_named("BaseURL") {
check_count(out.len() + 1, ctx.limits.max_base_urls, Limit::BaseUrls)?;
let ato = opt_f64(b, "availabilityTimeOffset")?;
let complete = opt_bool(b, "availabilityTimeComplete")?;
if ato.is_some() || complete == Some(false) {
ctx.features.low_latency = true;
}
out.push(BaseUrl {
url: b.text.trim().to_owned(),
service_location: opt_str(b, "serviceLocation"),
priority: dvb_u32(b, "priority")?,
weight: dvb_u32(b, "weight")?,
availability_time_offset: ato,
availability_time_complete: complete,
time_shift_buffer_depth: opt_duration(b, "timeShiftBufferDepth")?,
});
}
Ok(out)
}
fn dvb_u32(e: &Elem<Ns>, name: &'static str) -> Result<Option<u32>, MpdError> {
e.attr_in(Ns::Dvb, name)
.map(|v| v.trim().parse::<u32>().map_err(|_| invalid(name, v)))
.transpose()
}
fn url_type(e: &Elem<Ns>) -> Result<UrlType, MpdError> {
Ok(UrlType {
source_url: opt_str(e, "sourceURL"),
range: opt_range(e, "range")?,
})
}
fn base_info(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<BaseInfo, MpdError> {
let ato = opt_f64(e, "availabilityTimeOffset")?;
let complete = opt_bool(e, "availabilityTimeComplete")?;
if ato.is_some() || complete == Some(false) {
ctx.features.low_latency = true;
}
Ok(BaseInfo {
timescale: positive_u64(e, "timescale")?,
presentation_time_offset: opt_u64(e, "presentationTimeOffset")?,
ept_delta: opt_i64(e, "eptDelta")?,
pd_delta: opt_i64(e, "pdDelta")?,
presentation_duration: opt_u64(e, "presentationDuration")?,
time_shift_buffer_depth: opt_duration(e, "timeShiftBufferDepth")?,
index_range: opt_range(e, "indexRange")?,
index_range_exact: opt_bool(e, "indexRangeExact")?,
availability_time_offset: ato,
availability_time_complete: complete,
initialization: e.child("Initialization").map(url_type).transpose()?,
representation_index: e.child("RepresentationIndex").map(url_type).transpose()?,
})
}
fn timeline(e: &Elem<Ns>, ctx: &Ctx<'_>) -> Result<SegmentTimeline, MpdError> {
let mut out = SegmentTimeline::default();
if e.has_child("Pattern") {
out.unsupported = Some(Unsupported::SegmentSequence);
}
for s in e.children_named("S") {
check_count(
out.entries.len() + 1,
ctx.limits.max_timeline_entries,
Limit::TimelineEntries,
)?;
let d = opt_u64(s, "d")?.ok_or_else(|| invalid("S@d", "missing"))?;
if d == 0 {
return Err(invalid("S@d", "must be greater than zero"));
}
let k = opt_u64(s, "k")?;
if k.is_some_and(|k| k > 1) {
out.unsupported = Some(Unsupported::SegmentSequence);
}
out.entries.push(TimelineEntry {
t: opt_u64(s, "t")?,
n: opt_u64(s, "n")?,
d,
r: opt_i64(s, "r")?.unwrap_or(0),
k,
});
}
Ok(out)
}
fn multi_info(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<MultiInfo, MpdError> {
Ok(MultiInfo {
base: base_info(e, ctx)?,
duration: positive_u64(e, "duration")?,
start_number: opt_u64(e, "startNumber")?,
end_number: opt_u64(e, "endNumber")?,
timeline: e
.child("SegmentTimeline")
.map(|t| timeline(t, ctx))
.transpose()?,
bitstream_switching: e.child("BitstreamSwitching").map(url_type).transpose()?,
})
}
fn segment_layer(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<SegmentLayer, MpdError> {
let mut layer = SegmentLayer::default();
let count = ["SegmentBase", "SegmentList", "SegmentTemplate"]
.iter()
.map(|n| e.children_named(n).count())
.sum::<usize>();
if count > 1 {
return Err(invalid(
"Segment information",
format!(
"more than one of SegmentBase/SegmentList/SegmentTemplate in {}",
e.name
),
));
}
if let Some(b) = e.child("SegmentBase") {
layer.base = Some(SegmentBase {
info: base_info(b, ctx)?,
});
}
if let Some(l) = e.child("SegmentList") {
let mut urls = Vec::new();
for u in l.children_named("SegmentURL") {
check_count(
urls.len() + 1,
ctx.limits.max_segment_urls,
Limit::SegmentUrls,
)?;
urls.push(SegmentUrl {
media: opt_str(u, "media"),
media_range: opt_range(u, "mediaRange")?,
index: opt_str(u, "index"),
index_range: opt_range(u, "indexRange")?,
});
}
layer.list = Some(SegmentList {
multi: multi_info(l, ctx)?,
xlink: xlink(l)?,
urls,
});
}
if let Some(t) = e.child("SegmentTemplate") {
layer.template = Some(SegmentTemplate {
multi: multi_info(t, ctx)?,
media: opt_str(t, "media"),
index: opt_str(t, "index"),
initialization: opt_str(t, "initialization"),
bitstream_switching: opt_str(t, "bitstreamSwitching"),
});
}
Ok(layer)
}
fn common(e: &Elem<Ns>) -> Result<CommonAttributes, MpdError> {
Ok(CommonAttributes {
profiles: opt_str(e, "profiles"),
width: opt_u32(e, "width")?,
height: opt_u32(e, "height")?,
sar: opt_str(e, "sar"),
frame_rate: opt_str(e, "frameRate"),
audio_sampling_rate: opt_str(e, "audioSamplingRate"),
mime_type: opt_str(e, "mimeType"),
codecs: opt_str(e, "codecs"),
max_playout_rate: opt_f64(e, "maxPlayoutRate")?,
coding_dependency: opt_bool(e, "codingDependency")?,
})
}
fn representation(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<Representation, MpdError> {
let id = e
.attr("id")
.ok_or_else(|| invalid("Representation@id", "missing"))?
.to_owned();
let bandwidth = opt_u64(e, "bandwidth")?
.ok_or_else(|| invalid("Representation@bandwidth", format!("missing on {id}")))?;
Ok(Representation {
id,
bandwidth,
quality_ranking: opt_u32(e, "qualityRanking")?,
dependency_ids: e
.attr("dependencyId")
.map(|v| v.split_ascii_whitespace().map(str::to_owned).collect())
.unwrap_or_default(),
common: common(e)?,
base_urls: base_urls(e, ctx)?,
segment: segment_layer(e, ctx)?,
content_protections: descriptors(e, "ContentProtection")?,
essential_properties: descriptors(e, "EssentialProperty")?,
supplemental_properties: descriptors(e, "SupplementalProperty")?,
audio_channel_configurations: descriptors(e, "AudioChannelConfiguration")?,
inband_event_streams: descriptors(e, "InbandEventStream")?,
producer_reference_times: producer_reference_times(e)?,
resyncs: resyncs(e)?,
})
}
fn conditional(name: &'static str, e: &Elem<Ns>) -> Result<bool, MpdError> {
match e.attr(name) {
None => Ok(false),
Some(v) => match v.trim() {
"true" => Ok(true),
"false" => Ok(false),
digits => digits
.parse::<u64>()
.map(|n| n != 0)
.map_err(|_| invalid(name, digits)),
},
}
}
fn adaptation_set(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<Option<AdaptationSet>, MpdError> {
let link = xlink(e)?;
if is_resolve_to_zero(&link) {
ctx.features.xlink_resolve_to_zero = true;
return Ok(None);
}
let start_with_sap = sap_type(e, "startWithSAP")?;
let mut representations = Vec::new();
for r in e.children_named("Representation") {
check_count(
representations.len() + 1,
ctx.limits.max_representations,
Limit::Representations,
)?;
representations.push(representation(r, ctx)?);
}
Ok(Some(AdaptationSet {
id: opt_u64(e, "id")?,
group: opt_u64(e, "group")?,
lang: opt_str(e, "lang"),
content_type: opt_str(e, "contentType"),
par: opt_str(e, "par"),
segment_alignment: conditional("segmentAlignment", e)?,
start_with_sap,
bitstream_switching: opt_bool(e, "bitstreamSwitching")?,
common: common(e)?,
xlink: link,
roles: descriptors(e, "Role")?,
accessibilities: descriptors(e, "Accessibility")?,
labels: labels(e, "Label")?,
base_urls: base_urls(e, ctx)?,
segment: segment_layer(e, ctx)?,
content_protections: descriptors(e, "ContentProtection")?,
essential_properties: descriptors(e, "EssentialProperty")?,
supplemental_properties: descriptors(e, "SupplementalProperty")?,
audio_channel_configurations: descriptors(e, "AudioChannelConfiguration")?,
inband_event_streams: descriptors(e, "InbandEventStream")?,
producer_reference_times: producer_reference_times(e)?,
resyncs: resyncs(e)?,
representations,
}))
}
fn period(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<Option<Period>, MpdError> {
let link = xlink(e)?;
if is_resolve_to_zero(&link) {
ctx.features.xlink_resolve_to_zero = true;
return Ok(None);
}
let mut sets = Vec::new();
for a in e.children_named("AdaptationSet") {
check_count(
sets.len() + 1,
ctx.limits.max_adaptation_sets,
Limit::AdaptationSets,
)?;
if let Some(set) = adaptation_set(a, ctx)? {
sets.push(set);
}
}
let mut event_streams = Vec::new();
for s in e.children_named("EventStream") {
event_streams.push(event_stream(s, ctx)?);
}
Ok(Some(Period {
id: opt_str(e, "id"),
start: opt_duration(e, "start")?,
duration: opt_duration(e, "duration")?,
bitstream_switching: opt_bool(e, "bitstreamSwitching")?.unwrap_or(false),
xlink: link,
base_urls: base_urls(e, ctx)?,
segment: segment_layer(e, ctx)?,
asset_identifier: e.child("AssetIdentifier").map(descriptor).transpose()?,
event_streams,
imported_mpd: e.child("ImportedMPD").map(imported_mpd).transpose()?,
essential_properties: descriptors(e, "EssentialProperty")?,
supplemental_properties: descriptors(e, "SupplementalProperty")?,
service_descriptions: e
.children_named("ServiceDescription")
.map(service_description)
.collect::<Result<Vec<_>, _>>()?,
adaptation_sets: sets,
preselections: e
.children_named("Preselection")
.map(preselection)
.collect::<Result<Vec<_>, _>>()?,
}))
}
fn imported_mpd(e: &Elem<Ns>) -> Result<ImportedMpd, MpdError> {
Ok(ImportedMpd {
url: e.text.trim().to_owned(),
earliest_resolution_time_offset: opt_f64(e, "earliestResolutionTimeOffset")?,
})
}
fn service_description(e: &Elem<Ns>) -> Result<ServiceDescription, MpdError> {
let latency = e
.child("Latency")
.map(|l| -> Result<Latency, MpdError> {
Ok(Latency {
target: opt_u64(l, "target")?,
min: opt_u64(l, "min")?,
max: opt_u64(l, "max")?,
reference_id: opt_u32(l, "referenceId")?,
})
})
.transpose()?;
let playback_rate = e
.child("PlaybackRate")
.map(|p| -> Result<PlaybackRate, MpdError> {
Ok(PlaybackRate {
min: opt_str(p, "min"),
max: opt_str(p, "max"),
})
})
.transpose()?;
Ok(ServiceDescription {
id: opt_u64(e, "id")?,
latency,
playback_rate,
})
}
fn utc_timing(e: &Elem<Ns>) -> Result<UtcTiming, MpdError> {
let scheme_id_uri = e
.attr("schemeIdUri")
.ok_or_else(|| invalid("schemeIdUri", "missing on UTCTiming"))?
.to_owned();
Ok(UtcTiming {
scheme: UtcScheme::from_scheme_id_uri(&scheme_id_uri),
scheme_id_uri,
value: e.attr("value").unwrap_or_default().to_owned(),
id: opt_str(e, "id"),
})
}
fn sap_type(e: &Elem<Ns>, name: &'static str) -> Result<Option<u8>, MpdError> {
opt_u32(e, name)?
.map(|v| {
u8::try_from(v)
.ok()
.filter(|v| *v <= MAX_START_WITH_SAP)
.ok_or_else(|| invalid(name, "out of range"))
})
.transpose()
}
fn event(e: &Elem<Ns>) -> Result<StreamEvent, MpdError> {
let status = match e.attr("status") {
None => None,
Some("none") => Some(EventStatus::None),
Some("update") => Some(EventStatus::Update),
Some("repeat") => Some(EventStatus::Repeat),
Some(other) => return Err(invalid("Event@status", other)),
};
let unsupported = e
.children
.iter()
.any(|c| {
c.ns != Ns::Mpd
|| !matches!(
c.name.as_str(),
"SupplementalProperty" | "EssentialProperty" | "ServiceDescription"
)
})
.then_some(Unsupported::EventXmlPayload);
Ok(StreamEvent {
presentation_time: opt_u64(e, "presentationTime")?.unwrap_or(0),
duration: opt_u64(e, "duration")?,
id: opt_u64(e, "id")?,
status,
message_data: opt_str(e, "messageData"),
text: e.text.trim().to_owned(),
supplemental_properties: descriptors(e, "SupplementalProperty")?,
essential_properties: descriptors(e, "EssentialProperty")?,
service_descriptions: e
.children_named("ServiceDescription")
.map(service_description)
.collect::<Result<Vec<_>, _>>()?,
unsupported,
})
}
fn event_stream(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<EventStream, MpdError> {
Ok(EventStream {
xlink: xlink(e)?,
scheme_id_uri: e
.attr("schemeIdUri")
.ok_or_else(|| invalid("schemeIdUri", "missing on EventStream"))?
.to_owned(),
value: opt_str(e, "value"),
timescale: opt_u32(e, "timescale")?,
presentation_time_offset: opt_u64(e, "presentationTimeOffset")?,
events: e
.children_named("Event")
.map(event)
.collect::<Result<Vec<_>, _>>()?,
base_urls: base_urls(e, ctx)?,
request_params: e
.children_named("RequestParam")
.map(request_param)
.collect::<Result<Vec<_>, _>>()?,
essential_properties: descriptors(e, "EssentialProperty")?,
supplemental_properties: descriptors(e, "SupplementalProperty")?,
})
}
fn request_param(e: &Elem<Ns>) -> Result<RequestParam, MpdError> {
Ok(RequestParam {
query: url_query_info(e)?,
xlink: xlink(e)?,
include_in_requests: opt_str(e, "includeInRequests"),
header_param_source: opt_str(e, "headerParamSource"),
same_origin_only: opt_bool(e, "sameOriginOnly")?,
header: opt_str(e, "header"),
})
}
fn producer_reference_time(e: &Elem<Ns>) -> Result<ProducerReferenceTime, MpdError> {
let kind = match e.attr("type") {
None | Some("encoder") => ProducerReferenceKind::Encoder,
Some("captured") => ProducerReferenceKind::Captured,
Some("application") => ProducerReferenceKind::Application,
Some(other) => return Err(invalid("ProducerReferenceTime@type", other)),
};
Ok(ProducerReferenceTime {
id: opt_u32(e, "id")?.ok_or_else(|| invalid("ProducerReferenceTime@id", "missing"))?,
inband: opt_bool(e, "inband")?.unwrap_or(false),
kind,
application_scheme: opt_str(e, "applicationScheme"),
wall_clock_time: e
.attr("wallClockTime")
.ok_or_else(|| invalid("ProducerReferenceTime@wallClockTime", "missing"))?
.to_owned(),
presentation_time: opt_u64(e, "presentationTime")?
.ok_or_else(|| invalid("ProducerReferenceTime@presentationTime", "missing"))?,
utc_timing: e.child("UTCTiming").map(utc_timing).transpose()?,
})
}
fn producer_reference_times(e: &Elem<Ns>) -> Result<Vec<ProducerReferenceTime>, MpdError> {
e.children_named("ProducerReferenceTime")
.map(producer_reference_time)
.collect()
}
fn resyncs(e: &Elem<Ns>) -> Result<Vec<Resync>, MpdError> {
e.children_named("Resync")
.map(|r| {
Ok(Resync {
kind: sap_type(r, "type")?.unwrap_or(0),
dt: opt_u32(r, "dT")?,
d_imax: opt_f64(r, "dImax")?,
d_imin: opt_f64(r, "dImin")?,
marker: opt_bool(r, "marker")?.unwrap_or(false),
})
})
.collect()
}
fn preselection(e: &Elem<Ns>) -> Result<Preselection, MpdError> {
let order = match e.attr("order") {
None => None,
Some("undefined") => Some(PreselectionOrder::Undefined),
Some("time-ordered") => Some(PreselectionOrder::TimeOrdered),
Some("fully-ordered") => Some(PreselectionOrder::FullyOrdered),
Some(other) => return Err(invalid("Preselection@order", other)),
};
let unsupported = e
.children
.iter()
.any(|c| {
c.ns == Ns::Mpd
&& matches!(
c.name.as_str(),
"Switching"
| "RandomAccess"
| "ContentPopularityRate"
| "SegmentSequenceProperties"
)
})
.then_some(Unsupported::RepresentationBaseChild);
Ok(Preselection {
id: opt_str(e, "id"),
preselection_components: e
.attr("preselectionComponents")
.ok_or_else(|| invalid("Preselection@preselectionComponents", "missing"))?
.split_ascii_whitespace()
.map(str::to_owned)
.collect(),
lang: opt_str(e, "lang"),
order,
common: common(e)?,
audio_channel_configurations: descriptors(e, "AudioChannelConfiguration")?,
essential_properties: descriptors(e, "EssentialProperty")?,
supplemental_properties: descriptors(e, "SupplementalProperty")?,
accessibilities: descriptors(e, "Accessibility")?,
roles: descriptors(e, "Role")?,
ratings: descriptors(e, "Rating")?,
viewpoints: descriptors(e, "Viewpoint")?,
tag: opt_str(e, "tag"),
selection_priority: opt_u32(e, "selectionPriority")?,
start_with_sap: sap_type(e, "startWithSAP")?,
scan_type: opt_str(e, "scanType"),
segment_profiles: opt_str(e, "segmentProfiles"),
container_profiles: opt_str(e, "containerProfiles"),
maximum_sap_period: opt_f64(e, "maximumSAPPeriod")?,
frame_packings: descriptors(e, "FramePacking")?,
content_protections: descriptors(e, "ContentProtection")?,
output_protection: e.child("OutputProtection").map(descriptor).transpose()?,
inband_event_streams: descriptors(e, "InbandEventStream")?,
group_labels: labels(e, "GroupLabel")?,
labels: labels(e, "Label")?,
producer_reference_times: producer_reference_times(e)?,
resyncs: resyncs(e)?,
unsupported,
})
}
fn labels(e: &Elem<Ns>, name: &str) -> Result<Vec<Label>, MpdError> {
e.children_named(name)
.map(|l| {
Ok(Label {
id: opt_u32(l, "id")?.unwrap_or_default(),
lang: opt_str(l, "lang"),
text: l.text.trim().to_owned(),
})
})
.collect()
}
fn patch_location(e: &Elem<Ns>) -> Result<PatchLocation, MpdError> {
Ok(PatchLocation {
url: e.text.trim().to_owned(),
service_location: opt_str(e, "serviceLocation"),
ttl: opt_f64(e, "ttl")?,
})
}
fn content_steering(e: &Elem<Ns>) -> Result<ContentSteering, MpdError> {
Ok(ContentSteering {
url: e.text.trim().to_owned(),
default_service_location: opt_str(e, "defaultServiceLocation"),
query_before_start: opt_bool(e, "queryBeforeStart")?.unwrap_or(false),
client_requirement: opt_bool(e, "clientRequirement")?,
})
}
fn scan_features(e: &Elem<Ns>, f: &mut MpdFeatures) {
if e.ns == Ns::Mpd {
match e.name.as_str() {
"InbandEventStream" => f.inband_event_stream = true,
"ServiceDescription" => f.service_description = true,
_ => {}
}
}
for c in &e.children {
scan_features(c, f);
}
}
impl Mpd {
pub fn parse(bytes: &[u8]) -> Result<Self, MpdError> {
Self::parse_with(bytes, &Limits::default())
}
pub fn parse_with(bytes: &[u8], limits: &Limits) -> Result<Self, MpdError> {
let root = read_tree::<Ns>(bytes, NS_MPD_ROOT, &limits.xml_limits())?;
let mut ctx = Ctx {
limits,
features: MpdFeatures::default(),
};
let kind = match root.attr("type") {
None | Some("static") => PresentationType::Static,
Some("dynamic") => PresentationType::Dynamic,
Some("list") => PresentationType::List,
Some(other) => return Err(invalid("MPD@type", other)),
};
let mut periods = Vec::new();
for p in root.children_named("Period") {
check_count(periods.len() + 1, limits.max_periods, Limit::Periods)?;
if let Some(period) = period(p, &mut ctx)? {
periods.push(period);
}
}
let mut period_ids = BTreeSet::new();
for id in periods.iter().filter_map(|p| p.id.as_deref()) {
if !period_ids.insert(id) {
return Err(invalid(
"Period@id",
format!("`{id}` names more than one Period"),
));
}
}
let utc_timings = root
.children_named("UTCTiming")
.map(utc_timing)
.collect::<Result<Vec<_>, _>>()?;
let base = base_urls(&root, &mut ctx)?;
let mut features = ctx.features;
scan_features(&root, &mut features);
Ok(Self {
id: opt_str(&root, "id"),
profiles: root
.attr("profiles")
.map(|p| {
p.split(PROFILE_SEPARATOR)
.map(|s| s.trim().to_owned())
.filter(|s| !s.is_empty())
.collect()
})
.unwrap_or_default(),
kind,
availability_start_time_us: opt_time(&root, "availabilityStartTime")?,
availability_end_time_us: opt_time(&root, "availabilityEndTime")?,
publish_time_us: opt_time(&root, "publishTime")?,
media_presentation_duration: opt_duration(&root, "mediaPresentationDuration")?,
minimum_update_period: opt_duration(&root, "minimumUpdatePeriod")?,
min_buffer_time: opt_duration(&root, "minBufferTime")?,
time_shift_buffer_depth: opt_duration(&root, "timeShiftBufferDepth")?,
suggested_presentation_delay: opt_duration(&root, "suggestedPresentationDelay")?,
max_segment_duration: opt_duration(&root, "maxSegmentDuration")?,
max_subsegment_duration: opt_duration(&root, "maxSubsegmentDuration")?,
base_urls: base,
locations: root
.children_named("Location")
.map(|l| l.text.trim().to_owned())
.collect(),
patch_locations: root
.children_named("PatchLocation")
.map(patch_location)
.collect::<Result<Vec<_>, _>>()?,
service_descriptions: root
.children_named("ServiceDescription")
.map(service_description)
.collect::<Result<Vec<_>, _>>()?,
utc_timings,
essential_properties: descriptors(&root, "EssentialProperty")?,
supplemental_properties: descriptors(&root, "SupplementalProperty")?,
content_steering: root
.child("ContentSteering")
.map(content_steering)
.transpose()?,
periods,
features,
})
}
}