Skip to main content

broadcast_dash/
parse.rs

1//! MPD element tree to typed model (ISO/IEC 23009-1 clause 5.3, Annex B).
2
3use std::collections::BTreeSet;
4use std::time::Duration;
5
6use broadcast_common::xml::{Elem, Namespace, read_tree};
7
8use crate::descriptors::XLINK_RESOLVE_TO_ZERO;
9use crate::duration::parse_iso8601_duration;
10use crate::error::{Limit, MpdError, Unsupported};
11use crate::limits::Limits;
12use crate::model::*;
13use crate::utc_timing::UtcScheme;
14use crate::xs_datetime::parse_xs_datetime;
15
16const PROFILE_SEPARATOR: char = ',';
17pub(crate) const INFINITY_TOKEN: &str = "INF";
18pub(crate) const NEGATIVE_INFINITY_TOKEN: &str = "-INF";
19const MAX_START_WITH_SAP: u8 = 6;
20const NS_MPD_ROOT: &str = "MPD";
21
22/// ISO/IEC 23009-1 clause 5.3.1 / Annex B: the MPD schema namespace.
23pub const NS_MPD: &str = "urn:mpeg:dash:schema:mpd:2011";
24/// ETSI TS 103 285 clause 10.8.2: DVB DASH extension namespace.
25pub const NS_DVB: &str = "urn:dvb:dash:dash-extensions:2014-1";
26/// ISO/IEC 23001-7: `cenc` attribute namespace (`cenc:default_KID`).
27pub const NS_CENC: &str = "urn:mpeg:cenc:2013";
28/// XLink namespace of `@xlink:href`/`@xlink:actuate` (5.5.3).
29pub const NS_XLINK: &str = "http://www.w3.org/1999/xlink";
30/// ISO/IEC 23009-1 Annex I.2: URL parameter namespace.
31pub const NS_URLPARAM: &str = "urn:mpeg:dash:schema:urlparam:2014";
32
33/// The namespaces the model reads; anything else is [`Ns::Other`].
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub(crate) enum Ns {
36    /// No namespace.
37    None,
38    /// The MPD schema namespace ([`NS_MPD`]).
39    Mpd,
40    /// The DVB DASH extensions namespace ([`NS_DVB`]).
41    Dvb,
42    /// The `cenc` attribute namespace ([`NS_CENC`]).
43    Cenc,
44    /// The XLink namespace ([`NS_XLINK`]).
45    Xlink,
46    /// The URL parameter namespace ([`NS_URLPARAM`]).
47    UrlParam,
48    /// Any other namespace.
49    Other,
50}
51
52impl Namespace for Ns {
53    const TARGET: Self = Self::Mpd;
54    const NONE: Self = Self::None;
55
56    fn classify(uri: &str) -> Self {
57        match uri {
58            NS_MPD => Self::Mpd,
59            NS_DVB => Self::Dvb,
60            NS_CENC => Self::Cenc,
61            NS_XLINK => Self::Xlink,
62            NS_URLPARAM => Self::UrlParam,
63            _ => Self::Other,
64        }
65    }
66}
67
68fn invalid(what: &'static str, detail: impl Into<String>) -> MpdError {
69    MpdError::Invalid {
70        what,
71        detail: detail.into(),
72    }
73}
74
75fn opt_str(e: &Elem<Ns>, name: &str) -> Option<String> {
76    e.attr(name).map(str::to_owned)
77}
78
79fn opt_parse<T: core::str::FromStr>(
80    e: &Elem<Ns>,
81    name: &'static str,
82) -> Result<Option<T>, MpdError> {
83    match e.attr(name) {
84        None => Ok(None),
85        Some(v) => v
86            .trim()
87            .parse::<T>()
88            .map(Some)
89            .map_err(|_| invalid(name, v)),
90    }
91}
92
93fn opt_u64(e: &Elem<Ns>, name: &'static str) -> Result<Option<u64>, MpdError> {
94    opt_parse(e, name)
95}
96
97fn opt_u32(e: &Elem<Ns>, name: &'static str) -> Result<Option<u32>, MpdError> {
98    opt_parse(e, name)
99}
100
101fn opt_i64(e: &Elem<Ns>, name: &'static str) -> Result<Option<i64>, MpdError> {
102    opt_parse(e, name)
103}
104
105fn positive_u64(e: &Elem<Ns>, name: &'static str) -> Result<Option<u64>, MpdError> {
106    match opt_u64(e, name)? {
107        Some(0) => Err(invalid(name, "must be greater than zero")),
108        other => Ok(other),
109    }
110}
111
112fn parse_bool(name: &'static str, v: &str) -> Result<bool, MpdError> {
113    match v.trim() {
114        "true" | "1" => Ok(true),
115        "false" | "0" => Ok(false),
116        other => Err(invalid(name, other)),
117    }
118}
119
120fn opt_bool(e: &Elem<Ns>, name: &'static str) -> Result<Option<bool>, MpdError> {
121    e.attr(name).map(|v| parse_bool(name, v)).transpose()
122}
123
124fn opt_f64(e: &Elem<Ns>, name: &'static str) -> Result<Option<f64>, MpdError> {
125    let Some(v) = e.attr(name) else {
126        return Ok(None);
127    };
128    let v = v.trim();
129    if v == INFINITY_TOKEN {
130        return Ok(Some(f64::INFINITY));
131    }
132    if v == NEGATIVE_INFINITY_TOKEN {
133        return Ok(Some(f64::NEG_INFINITY));
134    }
135    match v.parse::<f64>() {
136        Ok(x) if x.is_finite() => Ok(Some(x)),
137        _ => Err(invalid(name, v)),
138    }
139}
140
141fn opt_duration(e: &Elem<Ns>, name: &'static str) -> Result<Option<Duration>, MpdError> {
142    e.attr(name)
143        .map(|v| parse_iso8601_duration(v).map_err(|_| invalid(name, v)))
144        .transpose()
145}
146
147fn opt_time(e: &Elem<Ns>, name: &'static str) -> Result<Option<i64>, MpdError> {
148    e.attr(name)
149        .map(|v| {
150            parse_xs_datetime(v)
151                .map(|t| t.unix_us)
152                .map_err(|_| invalid(name, v))
153        })
154        .transpose()
155}
156
157pub(crate) fn parse_range(name: &'static str, v: &str) -> Result<ByteRange, MpdError> {
158    let v = v.trim();
159    let (first, last) = v.split_once('-').ok_or_else(|| invalid(name, v))?;
160    let first = first.parse::<u64>().map_err(|_| invalid(name, v))?;
161    let last = if last.is_empty() {
162        None
163    } else {
164        Some(last.parse::<u64>().map_err(|_| invalid(name, v))?)
165    };
166    if let Some(l) = last
167        && l < first
168    {
169        return Err(invalid(name, v));
170    }
171    Ok(ByteRange { first, last })
172}
173
174fn opt_range(e: &Elem<Ns>, name: &'static str) -> Result<Option<ByteRange>, MpdError> {
175    e.attr(name).map(|v| parse_range(name, v)).transpose()
176}
177
178fn xlink(e: &Elem<Ns>) -> Result<Option<Xlink>, MpdError> {
179    let Some(href) = e.attr_in(Ns::Xlink, "href") else {
180        return Ok(None);
181    };
182    let actuate = match e.attr_in(Ns::Xlink, "actuate") {
183        None | Some("onRequest") => Actuate::OnRequest,
184        Some("onLoad") => Actuate::OnLoad,
185        Some(other) => return Err(invalid("xlink:actuate", other)),
186    };
187    Ok(Some(Xlink {
188        href: href.to_owned(),
189        actuate,
190    }))
191}
192
193fn is_resolve_to_zero(x: &Option<Xlink>) -> bool {
194    x.as_ref().is_some_and(|x| x.href == XLINK_RESOLVE_TO_ZERO)
195}
196
197struct Ctx<'a> {
198    limits: &'a Limits,
199    features: MpdFeatures,
200}
201
202fn check_count(count: usize, max: usize, limit: Limit) -> Result<(), MpdError> {
203    if count > max {
204        Err(MpdError::TooLarge { limit, max })
205    } else {
206        Ok(())
207    }
208}
209
210fn url_query_info(q: &Elem<Ns>) -> Result<UrlQueryInfo, MpdError> {
211    Ok(UrlQueryInfo {
212        use_mpd_url_query: opt_bool(q, "useMPDUrlQuery")?.unwrap_or(false),
213        query_string: opt_str(q, "queryString"),
214        query_template: opt_str(q, "queryTemplate"),
215    })
216}
217
218fn descriptor(e: &Elem<Ns>) -> Result<Descriptor, MpdError> {
219    let scheme_id_uri = e
220        .attr("schemeIdUri")
221        .ok_or_else(|| invalid("schemeIdUri", format!("missing on {}", e.name)))?
222        .to_owned();
223    let url_query = e
224        .children
225        .iter()
226        .find(|c| c.ns == Ns::UrlParam && c.name == "UrlQueryInfo")
227        .map(url_query_info)
228        .transpose()?;
229    let pssh = e
230        .children
231        .iter()
232        .find(|c| c.ns == Ns::Cenc && c.name == "pssh")
233        .map(|c| c.text.trim().to_owned());
234    let _ = url_query.as_ref();
235    Ok(Descriptor {
236        scheme_id_uri,
237        value: opt_str(e, "value"),
238        id: opt_str(e, "id"),
239        default_kid: e.attr_in(Ns::Cenc, "default_KID").map(str::to_owned),
240        pssh,
241        url_query,
242    })
243}
244
245fn descriptors(e: &Elem<Ns>, name: &str) -> Result<Vec<Descriptor>, MpdError> {
246    e.children_named(name).map(descriptor).collect()
247}
248
249fn base_urls(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<Vec<BaseUrl>, MpdError> {
250    let mut out = Vec::new();
251    for b in e.children_named("BaseURL") {
252        check_count(out.len() + 1, ctx.limits.max_base_urls, Limit::BaseUrls)?;
253        let ato = opt_f64(b, "availabilityTimeOffset")?;
254        let complete = opt_bool(b, "availabilityTimeComplete")?;
255        if ato.is_some() || complete == Some(false) {
256            ctx.features.low_latency = true;
257        }
258        out.push(BaseUrl {
259            url: b.text.trim().to_owned(),
260            service_location: opt_str(b, "serviceLocation"),
261            priority: dvb_u32(b, "priority")?,
262            weight: dvb_u32(b, "weight")?,
263            availability_time_offset: ato,
264            availability_time_complete: complete,
265            time_shift_buffer_depth: opt_duration(b, "timeShiftBufferDepth")?,
266        });
267    }
268    Ok(out)
269}
270
271fn dvb_u32(e: &Elem<Ns>, name: &'static str) -> Result<Option<u32>, MpdError> {
272    e.attr_in(Ns::Dvb, name)
273        .map(|v| v.trim().parse::<u32>().map_err(|_| invalid(name, v)))
274        .transpose()
275}
276
277fn url_type(e: &Elem<Ns>) -> Result<UrlType, MpdError> {
278    Ok(UrlType {
279        source_url: opt_str(e, "sourceURL"),
280        range: opt_range(e, "range")?,
281    })
282}
283
284fn base_info(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<BaseInfo, MpdError> {
285    let ato = opt_f64(e, "availabilityTimeOffset")?;
286    let complete = opt_bool(e, "availabilityTimeComplete")?;
287    if ato.is_some() || complete == Some(false) {
288        ctx.features.low_latency = true;
289    }
290    Ok(BaseInfo {
291        timescale: positive_u64(e, "timescale")?,
292        presentation_time_offset: opt_u64(e, "presentationTimeOffset")?,
293        ept_delta: opt_i64(e, "eptDelta")?,
294        pd_delta: opt_i64(e, "pdDelta")?,
295        presentation_duration: opt_u64(e, "presentationDuration")?,
296        time_shift_buffer_depth: opt_duration(e, "timeShiftBufferDepth")?,
297        index_range: opt_range(e, "indexRange")?,
298        index_range_exact: opt_bool(e, "indexRangeExact")?,
299        availability_time_offset: ato,
300        availability_time_complete: complete,
301        initialization: e.child("Initialization").map(url_type).transpose()?,
302        representation_index: e.child("RepresentationIndex").map(url_type).transpose()?,
303    })
304}
305
306fn timeline(e: &Elem<Ns>, ctx: &Ctx<'_>) -> Result<SegmentTimeline, MpdError> {
307    let mut out = SegmentTimeline::default();
308    if e.has_child("Pattern") {
309        out.unsupported = Some(Unsupported::SegmentSequence);
310    }
311    for s in e.children_named("S") {
312        check_count(
313            out.entries.len() + 1,
314            ctx.limits.max_timeline_entries,
315            Limit::TimelineEntries,
316        )?;
317        let d = opt_u64(s, "d")?.ok_or_else(|| invalid("S@d", "missing"))?;
318        if d == 0 {
319            return Err(invalid("S@d", "must be greater than zero"));
320        }
321        let k = opt_u64(s, "k")?;
322        if k.is_some_and(|k| k > 1) {
323            out.unsupported = Some(Unsupported::SegmentSequence);
324        }
325        out.entries.push(TimelineEntry {
326            t: opt_u64(s, "t")?,
327            n: opt_u64(s, "n")?,
328            d,
329            r: opt_i64(s, "r")?.unwrap_or(0),
330            k,
331        });
332    }
333    Ok(out)
334}
335
336fn multi_info(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<MultiInfo, MpdError> {
337    Ok(MultiInfo {
338        base: base_info(e, ctx)?,
339        duration: positive_u64(e, "duration")?,
340        start_number: opt_u64(e, "startNumber")?,
341        end_number: opt_u64(e, "endNumber")?,
342        timeline: e
343            .child("SegmentTimeline")
344            .map(|t| timeline(t, ctx))
345            .transpose()?,
346        bitstream_switching: e.child("BitstreamSwitching").map(url_type).transpose()?,
347    })
348}
349
350fn segment_layer(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<SegmentLayer, MpdError> {
351    let mut layer = SegmentLayer::default();
352    let count = ["SegmentBase", "SegmentList", "SegmentTemplate"]
353        .iter()
354        .map(|n| e.children_named(n).count())
355        .sum::<usize>();
356    if count > 1 {
357        return Err(invalid(
358            "Segment information",
359            format!(
360                "more than one of SegmentBase/SegmentList/SegmentTemplate in {}",
361                e.name
362            ),
363        ));
364    }
365    if let Some(b) = e.child("SegmentBase") {
366        layer.base = Some(SegmentBase {
367            info: base_info(b, ctx)?,
368        });
369    }
370    if let Some(l) = e.child("SegmentList") {
371        let mut urls = Vec::new();
372        for u in l.children_named("SegmentURL") {
373            check_count(
374                urls.len() + 1,
375                ctx.limits.max_segment_urls,
376                Limit::SegmentUrls,
377            )?;
378            urls.push(SegmentUrl {
379                media: opt_str(u, "media"),
380                media_range: opt_range(u, "mediaRange")?,
381                index: opt_str(u, "index"),
382                index_range: opt_range(u, "indexRange")?,
383            });
384        }
385        layer.list = Some(SegmentList {
386            multi: multi_info(l, ctx)?,
387            xlink: xlink(l)?,
388            urls,
389        });
390    }
391    if let Some(t) = e.child("SegmentTemplate") {
392        layer.template = Some(SegmentTemplate {
393            multi: multi_info(t, ctx)?,
394            media: opt_str(t, "media"),
395            index: opt_str(t, "index"),
396            initialization: opt_str(t, "initialization"),
397            bitstream_switching: opt_str(t, "bitstreamSwitching"),
398        });
399    }
400    Ok(layer)
401}
402
403fn common(e: &Elem<Ns>) -> Result<CommonAttributes, MpdError> {
404    Ok(CommonAttributes {
405        profiles: opt_str(e, "profiles"),
406        width: opt_u32(e, "width")?,
407        height: opt_u32(e, "height")?,
408        sar: opt_str(e, "sar"),
409        frame_rate: opt_str(e, "frameRate"),
410        audio_sampling_rate: opt_str(e, "audioSamplingRate"),
411        mime_type: opt_str(e, "mimeType"),
412        codecs: opt_str(e, "codecs"),
413        max_playout_rate: opt_f64(e, "maxPlayoutRate")?,
414        coding_dependency: opt_bool(e, "codingDependency")?,
415    })
416}
417
418fn representation(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<Representation, MpdError> {
419    let id = e
420        .attr("id")
421        .ok_or_else(|| invalid("Representation@id", "missing"))?
422        .to_owned();
423    let bandwidth = opt_u64(e, "bandwidth")?
424        .ok_or_else(|| invalid("Representation@bandwidth", format!("missing on {id}")))?;
425    Ok(Representation {
426        id,
427        bandwidth,
428        quality_ranking: opt_u32(e, "qualityRanking")?,
429        dependency_ids: e
430            .attr("dependencyId")
431            .map(|v| v.split_ascii_whitespace().map(str::to_owned).collect())
432            .unwrap_or_default(),
433        common: common(e)?,
434        base_urls: base_urls(e, ctx)?,
435        segment: segment_layer(e, ctx)?,
436        content_protections: descriptors(e, "ContentProtection")?,
437        essential_properties: descriptors(e, "EssentialProperty")?,
438        supplemental_properties: descriptors(e, "SupplementalProperty")?,
439        audio_channel_configurations: descriptors(e, "AudioChannelConfiguration")?,
440        inband_event_streams: descriptors(e, "InbandEventStream")?,
441        producer_reference_times: producer_reference_times(e)?,
442        resyncs: resyncs(e)?,
443    })
444}
445
446fn conditional(name: &'static str, e: &Elem<Ns>) -> Result<bool, MpdError> {
447    match e.attr(name) {
448        None => Ok(false),
449        Some(v) => match v.trim() {
450            "true" => Ok(true),
451            "false" => Ok(false),
452            digits => digits
453                .parse::<u64>()
454                .map(|n| n != 0)
455                .map_err(|_| invalid(name, digits)),
456        },
457    }
458}
459
460fn adaptation_set(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<Option<AdaptationSet>, MpdError> {
461    let link = xlink(e)?;
462    if is_resolve_to_zero(&link) {
463        ctx.features.xlink_resolve_to_zero = true;
464        return Ok(None);
465    }
466    let start_with_sap = sap_type(e, "startWithSAP")?;
467    let mut representations = Vec::new();
468    for r in e.children_named("Representation") {
469        check_count(
470            representations.len() + 1,
471            ctx.limits.max_representations,
472            Limit::Representations,
473        )?;
474        representations.push(representation(r, ctx)?);
475    }
476    Ok(Some(AdaptationSet {
477        id: opt_u64(e, "id")?,
478        group: opt_u64(e, "group")?,
479        lang: opt_str(e, "lang"),
480        content_type: opt_str(e, "contentType"),
481        par: opt_str(e, "par"),
482        segment_alignment: conditional("segmentAlignment", e)?,
483        start_with_sap,
484        bitstream_switching: opt_bool(e, "bitstreamSwitching")?,
485        common: common(e)?,
486        xlink: link,
487        roles: descriptors(e, "Role")?,
488        accessibilities: descriptors(e, "Accessibility")?,
489        labels: labels(e, "Label")?,
490        base_urls: base_urls(e, ctx)?,
491        segment: segment_layer(e, ctx)?,
492        content_protections: descriptors(e, "ContentProtection")?,
493        essential_properties: descriptors(e, "EssentialProperty")?,
494        supplemental_properties: descriptors(e, "SupplementalProperty")?,
495        audio_channel_configurations: descriptors(e, "AudioChannelConfiguration")?,
496        inband_event_streams: descriptors(e, "InbandEventStream")?,
497        producer_reference_times: producer_reference_times(e)?,
498        resyncs: resyncs(e)?,
499        representations,
500    }))
501}
502
503fn period(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<Option<Period>, MpdError> {
504    let link = xlink(e)?;
505    if is_resolve_to_zero(&link) {
506        ctx.features.xlink_resolve_to_zero = true;
507        return Ok(None);
508    }
509    let mut sets = Vec::new();
510    for a in e.children_named("AdaptationSet") {
511        check_count(
512            sets.len() + 1,
513            ctx.limits.max_adaptation_sets,
514            Limit::AdaptationSets,
515        )?;
516        if let Some(set) = adaptation_set(a, ctx)? {
517            sets.push(set);
518        }
519    }
520    let mut event_streams = Vec::new();
521    for s in e.children_named("EventStream") {
522        event_streams.push(event_stream(s, ctx)?);
523    }
524    Ok(Some(Period {
525        id: opt_str(e, "id"),
526        start: opt_duration(e, "start")?,
527        duration: opt_duration(e, "duration")?,
528        bitstream_switching: opt_bool(e, "bitstreamSwitching")?.unwrap_or(false),
529        xlink: link,
530        base_urls: base_urls(e, ctx)?,
531        segment: segment_layer(e, ctx)?,
532        asset_identifier: e.child("AssetIdentifier").map(descriptor).transpose()?,
533        event_streams,
534        imported_mpd: e.child("ImportedMPD").map(imported_mpd).transpose()?,
535        essential_properties: descriptors(e, "EssentialProperty")?,
536        supplemental_properties: descriptors(e, "SupplementalProperty")?,
537        service_descriptions: e
538            .children_named("ServiceDescription")
539            .map(service_description)
540            .collect::<Result<Vec<_>, _>>()?,
541        adaptation_sets: sets,
542        preselections: e
543            .children_named("Preselection")
544            .map(preselection)
545            .collect::<Result<Vec<_>, _>>()?,
546    }))
547}
548
549/// `ImportedMPD` (ISO/IEC 23009-1 ed.6 `ImportedMpdType`).
550fn imported_mpd(e: &Elem<Ns>) -> Result<ImportedMpd, MpdError> {
551    Ok(ImportedMpd {
552        url: e.text.trim().to_owned(),
553        earliest_resolution_time_offset: opt_f64(e, "earliestResolutionTimeOffset")?,
554    })
555}
556
557fn service_description(e: &Elem<Ns>) -> Result<ServiceDescription, MpdError> {
558    let latency = e
559        .child("Latency")
560        .map(|l| -> Result<Latency, MpdError> {
561            Ok(Latency {
562                target: opt_u64(l, "target")?,
563                min: opt_u64(l, "min")?,
564                max: opt_u64(l, "max")?,
565                reference_id: opt_u32(l, "referenceId")?,
566            })
567        })
568        .transpose()?;
569    let playback_rate = e
570        .child("PlaybackRate")
571        .map(|p| -> Result<PlaybackRate, MpdError> {
572            Ok(PlaybackRate {
573                min: opt_str(p, "min"),
574                max: opt_str(p, "max"),
575            })
576        })
577        .transpose()?;
578    Ok(ServiceDescription {
579        id: opt_u64(e, "id")?,
580        latency,
581        playback_rate,
582    })
583}
584
585fn utc_timing(e: &Elem<Ns>) -> Result<UtcTiming, MpdError> {
586    let scheme_id_uri = e
587        .attr("schemeIdUri")
588        .ok_or_else(|| invalid("schemeIdUri", "missing on UTCTiming"))?
589        .to_owned();
590    Ok(UtcTiming {
591        scheme: UtcScheme::from_scheme_id_uri(&scheme_id_uri),
592        scheme_id_uri,
593        value: e.attr("value").unwrap_or_default().to_owned(),
594        id: opt_str(e, "id"),
595    })
596}
597
598fn sap_type(e: &Elem<Ns>, name: &'static str) -> Result<Option<u8>, MpdError> {
599    opt_u32(e, name)?
600        .map(|v| {
601            u8::try_from(v)
602                .ok()
603                .filter(|v| *v <= MAX_START_WITH_SAP)
604                .ok_or_else(|| invalid(name, "out of range"))
605        })
606        .transpose()
607}
608
609fn event(e: &Elem<Ns>) -> Result<StreamEvent, MpdError> {
610    let status = match e.attr("status") {
611        None => None,
612        Some("none") => Some(EventStatus::None),
613        Some("update") => Some(EventStatus::Update),
614        Some("repeat") => Some(EventStatus::Repeat),
615        Some(other) => return Err(invalid("Event@status", other)),
616    };
617    let unsupported = e
618        .children
619        .iter()
620        .any(|c| {
621            c.ns != Ns::Mpd
622                || !matches!(
623                    c.name.as_str(),
624                    "SupplementalProperty" | "EssentialProperty" | "ServiceDescription"
625                )
626        })
627        .then_some(Unsupported::EventXmlPayload);
628    Ok(StreamEvent {
629        presentation_time: opt_u64(e, "presentationTime")?.unwrap_or(0),
630        duration: opt_u64(e, "duration")?,
631        id: opt_u64(e, "id")?,
632        status,
633        message_data: opt_str(e, "messageData"),
634        text: e.text.trim().to_owned(),
635        supplemental_properties: descriptors(e, "SupplementalProperty")?,
636        essential_properties: descriptors(e, "EssentialProperty")?,
637        service_descriptions: e
638            .children_named("ServiceDescription")
639            .map(service_description)
640            .collect::<Result<Vec<_>, _>>()?,
641        unsupported,
642    })
643}
644
645fn event_stream(e: &Elem<Ns>, ctx: &mut Ctx<'_>) -> Result<EventStream, MpdError> {
646    Ok(EventStream {
647        xlink: xlink(e)?,
648        scheme_id_uri: e
649            .attr("schemeIdUri")
650            .ok_or_else(|| invalid("schemeIdUri", "missing on EventStream"))?
651            .to_owned(),
652        value: opt_str(e, "value"),
653        timescale: opt_u32(e, "timescale")?,
654        presentation_time_offset: opt_u64(e, "presentationTimeOffset")?,
655        events: e
656            .children_named("Event")
657            .map(event)
658            .collect::<Result<Vec<_>, _>>()?,
659        base_urls: base_urls(e, ctx)?,
660        request_params: e
661            .children_named("RequestParam")
662            .map(request_param)
663            .collect::<Result<Vec<_>, _>>()?,
664        essential_properties: descriptors(e, "EssentialProperty")?,
665        supplemental_properties: descriptors(e, "SupplementalProperty")?,
666    })
667}
668
669fn request_param(e: &Elem<Ns>) -> Result<RequestParam, MpdError> {
670    Ok(RequestParam {
671        query: url_query_info(e)?,
672        xlink: xlink(e)?,
673        include_in_requests: opt_str(e, "includeInRequests"),
674        header_param_source: opt_str(e, "headerParamSource"),
675        same_origin_only: opt_bool(e, "sameOriginOnly")?,
676        header: opt_str(e, "header"),
677    })
678}
679
680fn producer_reference_time(e: &Elem<Ns>) -> Result<ProducerReferenceTime, MpdError> {
681    let kind = match e.attr("type") {
682        None | Some("encoder") => ProducerReferenceKind::Encoder,
683        Some("captured") => ProducerReferenceKind::Captured,
684        Some("application") => ProducerReferenceKind::Application,
685        Some(other) => return Err(invalid("ProducerReferenceTime@type", other)),
686    };
687    Ok(ProducerReferenceTime {
688        id: opt_u32(e, "id")?.ok_or_else(|| invalid("ProducerReferenceTime@id", "missing"))?,
689        inband: opt_bool(e, "inband")?.unwrap_or(false),
690        kind,
691        application_scheme: opt_str(e, "applicationScheme"),
692        wall_clock_time: e
693            .attr("wallClockTime")
694            .ok_or_else(|| invalid("ProducerReferenceTime@wallClockTime", "missing"))?
695            .to_owned(),
696        presentation_time: opt_u64(e, "presentationTime")?
697            .ok_or_else(|| invalid("ProducerReferenceTime@presentationTime", "missing"))?,
698        utc_timing: e.child("UTCTiming").map(utc_timing).transpose()?,
699    })
700}
701
702fn producer_reference_times(e: &Elem<Ns>) -> Result<Vec<ProducerReferenceTime>, MpdError> {
703    e.children_named("ProducerReferenceTime")
704        .map(producer_reference_time)
705        .collect()
706}
707
708fn resyncs(e: &Elem<Ns>) -> Result<Vec<Resync>, MpdError> {
709    e.children_named("Resync")
710        .map(|r| {
711            Ok(Resync {
712                kind: sap_type(r, "type")?.unwrap_or(0),
713                dt: opt_u32(r, "dT")?,
714                d_imax: opt_f64(r, "dImax")?,
715                d_imin: opt_f64(r, "dImin")?,
716                marker: opt_bool(r, "marker")?.unwrap_or(false),
717            })
718        })
719        .collect()
720}
721
722fn preselection(e: &Elem<Ns>) -> Result<Preselection, MpdError> {
723    let order = match e.attr("order") {
724        None => None,
725        Some("undefined") => Some(PreselectionOrder::Undefined),
726        Some("time-ordered") => Some(PreselectionOrder::TimeOrdered),
727        Some("fully-ordered") => Some(PreselectionOrder::FullyOrdered),
728        Some(other) => return Err(invalid("Preselection@order", other)),
729    };
730    let unsupported = e
731        .children
732        .iter()
733        .any(|c| {
734            c.ns == Ns::Mpd
735                && matches!(
736                    c.name.as_str(),
737                    "Switching"
738                        | "RandomAccess"
739                        | "ContentPopularityRate"
740                        | "SegmentSequenceProperties"
741                )
742        })
743        .then_some(Unsupported::RepresentationBaseChild);
744    Ok(Preselection {
745        id: opt_str(e, "id"),
746        preselection_components: e
747            .attr("preselectionComponents")
748            .ok_or_else(|| invalid("Preselection@preselectionComponents", "missing"))?
749            .split_ascii_whitespace()
750            .map(str::to_owned)
751            .collect(),
752        lang: opt_str(e, "lang"),
753        order,
754        common: common(e)?,
755        audio_channel_configurations: descriptors(e, "AudioChannelConfiguration")?,
756        essential_properties: descriptors(e, "EssentialProperty")?,
757        supplemental_properties: descriptors(e, "SupplementalProperty")?,
758        accessibilities: descriptors(e, "Accessibility")?,
759        roles: descriptors(e, "Role")?,
760        ratings: descriptors(e, "Rating")?,
761        viewpoints: descriptors(e, "Viewpoint")?,
762        tag: opt_str(e, "tag"),
763        selection_priority: opt_u32(e, "selectionPriority")?,
764        start_with_sap: sap_type(e, "startWithSAP")?,
765        scan_type: opt_str(e, "scanType"),
766        segment_profiles: opt_str(e, "segmentProfiles"),
767        container_profiles: opt_str(e, "containerProfiles"),
768        maximum_sap_period: opt_f64(e, "maximumSAPPeriod")?,
769        frame_packings: descriptors(e, "FramePacking")?,
770        content_protections: descriptors(e, "ContentProtection")?,
771        output_protection: e.child("OutputProtection").map(descriptor).transpose()?,
772        inband_event_streams: descriptors(e, "InbandEventStream")?,
773        group_labels: labels(e, "GroupLabel")?,
774        labels: labels(e, "Label")?,
775        producer_reference_times: producer_reference_times(e)?,
776        resyncs: resyncs(e)?,
777        unsupported,
778    })
779}
780
781fn labels(e: &Elem<Ns>, name: &str) -> Result<Vec<Label>, MpdError> {
782    e.children_named(name)
783        .map(|l| {
784            Ok(Label {
785                id: opt_u32(l, "id")?.unwrap_or_default(),
786                lang: opt_str(l, "lang"),
787                text: l.text.trim().to_owned(),
788            })
789        })
790        .collect()
791}
792
793fn patch_location(e: &Elem<Ns>) -> Result<PatchLocation, MpdError> {
794    Ok(PatchLocation {
795        url: e.text.trim().to_owned(),
796        service_location: opt_str(e, "serviceLocation"),
797        ttl: opt_f64(e, "ttl")?,
798    })
799}
800
801fn content_steering(e: &Elem<Ns>) -> Result<ContentSteering, MpdError> {
802    Ok(ContentSteering {
803        url: e.text.trim().to_owned(),
804        default_service_location: opt_str(e, "defaultServiceLocation"),
805        query_before_start: opt_bool(e, "queryBeforeStart")?.unwrap_or(false),
806        client_requirement: opt_bool(e, "clientRequirement")?,
807    })
808}
809
810fn scan_features(e: &Elem<Ns>, f: &mut MpdFeatures) {
811    if e.ns == Ns::Mpd {
812        match e.name.as_str() {
813            "InbandEventStream" => f.inband_event_stream = true,
814            "ServiceDescription" => f.service_description = true,
815            _ => {}
816        }
817    }
818    for c in &e.children {
819        scan_features(c, f);
820    }
821}
822
823impl Mpd {
824    /// Parse an MPD document with the default [`Limits`].
825    ///
826    /// # Errors
827    /// [`MpdError`] for malformed XML, a non-MPD root, a schema violation the
828    /// core relies on, an exceeded bound, or an out-of-scope presentation type.
829    pub fn parse(bytes: &[u8]) -> Result<Self, MpdError> {
830        Self::parse_with(bytes, &Limits::default())
831    }
832
833    /// Parse an MPD document under explicit [`Limits`].
834    ///
835    /// # Errors
836    /// See [`Mpd::parse`].
837    pub fn parse_with(bytes: &[u8], limits: &Limits) -> Result<Self, MpdError> {
838        let root = read_tree::<Ns>(bytes, NS_MPD_ROOT, &limits.xml_limits())?;
839        let mut ctx = Ctx {
840            limits,
841            features: MpdFeatures::default(),
842        };
843        let kind = match root.attr("type") {
844            None | Some("static") => PresentationType::Static,
845            Some("dynamic") => PresentationType::Dynamic,
846            Some("list") => PresentationType::List,
847            Some(other) => return Err(invalid("MPD@type", other)),
848        };
849        let mut periods = Vec::new();
850        for p in root.children_named("Period") {
851            check_count(periods.len() + 1, limits.max_periods, Limit::Periods)?;
852            if let Some(period) = period(p, &mut ctx)? {
853                periods.push(period);
854            }
855        }
856        let mut period_ids = BTreeSet::new();
857        for id in periods.iter().filter_map(|p| p.id.as_deref()) {
858            if !period_ids.insert(id) {
859                return Err(invalid(
860                    "Period@id",
861                    format!("`{id}` names more than one Period"),
862                ));
863            }
864        }
865        let utc_timings = root
866            .children_named("UTCTiming")
867            .map(utc_timing)
868            .collect::<Result<Vec<_>, _>>()?;
869        let base = base_urls(&root, &mut ctx)?;
870        let mut features = ctx.features;
871        scan_features(&root, &mut features);
872        Ok(Self {
873            id: opt_str(&root, "id"),
874            profiles: root
875                .attr("profiles")
876                .map(|p| {
877                    p.split(PROFILE_SEPARATOR)
878                        .map(|s| s.trim().to_owned())
879                        .filter(|s| !s.is_empty())
880                        .collect()
881                })
882                .unwrap_or_default(),
883            kind,
884            availability_start_time_us: opt_time(&root, "availabilityStartTime")?,
885            availability_end_time_us: opt_time(&root, "availabilityEndTime")?,
886            publish_time_us: opt_time(&root, "publishTime")?,
887            media_presentation_duration: opt_duration(&root, "mediaPresentationDuration")?,
888            minimum_update_period: opt_duration(&root, "minimumUpdatePeriod")?,
889            min_buffer_time: opt_duration(&root, "minBufferTime")?,
890            time_shift_buffer_depth: opt_duration(&root, "timeShiftBufferDepth")?,
891            suggested_presentation_delay: opt_duration(&root, "suggestedPresentationDelay")?,
892            max_segment_duration: opt_duration(&root, "maxSegmentDuration")?,
893            max_subsegment_duration: opt_duration(&root, "maxSubsegmentDuration")?,
894            base_urls: base,
895            locations: root
896                .children_named("Location")
897                .map(|l| l.text.trim().to_owned())
898                .collect(),
899            patch_locations: root
900                .children_named("PatchLocation")
901                .map(patch_location)
902                .collect::<Result<Vec<_>, _>>()?,
903            service_descriptions: root
904                .children_named("ServiceDescription")
905                .map(service_description)
906                .collect::<Result<Vec<_>, _>>()?,
907            utc_timings,
908            essential_properties: descriptors(&root, "EssentialProperty")?,
909            supplemental_properties: descriptors(&root, "SupplementalProperty")?,
910            content_steering: root
911                .child("ContentSteering")
912                .map(content_steering)
913                .transpose()?,
914            periods,
915            features,
916        })
917    }
918}