1use 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
22pub const NS_MPD: &str = "urn:mpeg:dash:schema:mpd:2011";
24pub const NS_DVB: &str = "urn:dvb:dash:dash-extensions:2014-1";
26pub const NS_CENC: &str = "urn:mpeg:cenc:2013";
28pub const NS_XLINK: &str = "http://www.w3.org/1999/xlink";
30pub const NS_URLPARAM: &str = "urn:mpeg:dash:schema:urlparam:2014";
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub(crate) enum Ns {
36 None,
38 Mpd,
40 Dvb,
42 Cenc,
44 Xlink,
46 UrlParam,
48 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
549fn 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 pub fn parse(bytes: &[u8]) -> Result<Self, MpdError> {
830 Self::parse_with(bytes, &Limits::default())
831 }
832
833 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}