1mod live;
6
7use std::collections::{BTreeMap, BTreeSet, VecDeque};
8use std::sync::Arc;
9
10use container_probe::segment;
11use transmux::{TrackId, TrackSpec};
12use url::Url;
13
14use super::action::{Action, RepId, ResourceId};
15use super::config::{DashConfig, PullLimits};
16use super::demux::{self, Staged};
17use super::error::{Error, Failure, FailureClass, FailureKind, FatalKind, Rejection, Result};
18use super::meta::ResponseMeta;
19use super::output::{AudioRendition, DropCause, ManifestSelection, Notice, Output, VariantInfo};
20use crate::address::base_index;
21use crate::address::{AddressError, PlanError, PlanOptions, SegmentPlan, plan_representation};
22use crate::error::Unsupported;
23use crate::mpd::urls::{BaseCandidate, append_query, representation_candidates, segment_url_query};
24use crate::selection::{RepRef, Selection, SetClass, select};
25use crate::timing::clock::UtcClock;
26use crate::timing::period::period_timings;
27use broadcast_dash::descriptors::SCHEME_PERIOD_CONTINUITY;
28use broadcast_dash::inherit::{SegmentInfo, effective_common, resolve_segment_info};
29use broadcast_dash::{ByteRange, Descriptor, Mpd, PresentationType};
30
31const MISSING_TAIL_MAX_US: i128 = 50_000;
35const ROTATE_AFTER_FAILURES: u32 = 2;
38const STATUS_NOT_FOUND: u16 = 404;
39const STATUS_GONE: u16 = 410;
40const STATUS_TOO_EARLY: u16 = 425;
41const MAX_REMEMBERED_DONE: usize = 4096;
45const START_WITH_SAP_1: u8 = 1;
46const START_WITH_SAP_2: u8 = 2;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49enum Phase {
50 NeedMpd,
51 Running,
52 Ended,
53 Fatal,
54}
55
56enum Candidate {
57 Ready(i128),
58 Wait(i128),
59 Idle,
60 Unaddressable(&'static str),
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
64struct InitKey {
65 url: String,
66 range: Option<(u64, u64)>,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70enum InitState {
71 Wanted,
72 InFlight,
73 Ready,
74}
75
76struct InitEntry {
77 state: InitState,
78 bytes: Option<Arc<Vec<u8>>>,
79 specs: Vec<TrackSpec>,
80 skipped_reported: BTreeSet<(Option<u32>, String)>,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84enum ReqKind {
85 Init,
86 Index,
87 Segment,
88}
89
90struct ReqInfo {
91 rep: RepId,
92 reference: String,
93 range: Option<(u64, u64)>,
94 kind: ReqKind,
95 avail_from_us: Option<i128>,
96 avail_until_us: Option<i128>,
97}
98
99struct IndexReq {
100 range: ByteRange,
101}
102
103struct RepState {
104 id: RepId,
105 at: RepRef,
106 candidates: Vec<BaseCandidate>,
107 cand: usize,
108 cand_failures: u32,
109 query: Option<String>,
110 plan: Option<SegmentPlan>,
111 index: Option<IndexReq>,
112 init_key: InitKey,
113 next_issue: u64,
114 start_time_us: Option<i128>,
115 dead: bool,
116 set_id: Option<u64>,
117 set_pos: usize,
118 rep_key: String,
119 joined: bool,
120 last_time: Option<u64>,
121}
122
123enum SlotState {
124 Issued,
125 Held(Vec<u8>),
126 Staged(Staged),
127 Skipped { marker: bool },
128 Marker,
129}
130
131struct Slot {
132 id: Option<ResourceId>,
133 rep: usize,
134 plan_index: u64,
135 state: SlotState,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
139struct TrackIdentity {
140 kind: SetClass,
141 set_id: Option<u64>,
142 group: Option<u64>,
143 lang: Option<String>,
144 roles: Vec<(String, Option<String>)>,
145}
146
147struct Lane {
148 kind: SetClass,
149 set_id: Option<u64>,
150 identity: TrackIdentity,
151 reps: Vec<RepState>,
152 queue: VecDeque<Slot>,
153 base: u32,
154 count: u32,
155 announced_rep: Option<usize>,
156 announced_init: Option<InitKey>,
157 expired_run: u32,
158}
159
160impl Lane {
161 fn current(&self) -> Option<&RepState> {
162 self.reps.last().filter(|r| !r.dead)
163 }
164
165 fn exhausted(&self) -> bool {
166 match self.current() {
167 None => true,
168 Some(rep) => rep
169 .plan
170 .as_ref()
171 .is_some_and(|p| p.len().is_some_and(|n| rep.next_issue >= n)),
172 }
173 }
174}
175
176struct PeriodRun {
177 index: usize,
178 id: Option<String>,
179 start_us: i128,
180 lanes: Vec<Lane>,
181 selection: Selection,
182 notices: Vec<Output>,
183 started: bool,
184 issue_done: bool,
185 finished: bool,
186}
187
188struct PrevPeriod {
189 id: Option<String>,
190 lanes: Vec<(SetClass, Option<u64>, InitKey)>,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
195#[non_exhaustive]
196pub struct StateSize {
197 pub periods: usize,
199 pub requests: usize,
201 pub inflight: usize,
203 pub parked: usize,
205 pub failures: usize,
207 pub done: usize,
209 pub inits: usize,
211 pub reps: usize,
213 pub queued: usize,
215 pub outputs: usize,
217 pub live: usize,
219 pub track_identities: usize,
221}
222
223pub struct DashClient {
238 cfg: PullLimits,
239 dash: DashConfig,
240 mpd_url: String,
241 doc_url: Option<Url>,
242 mpd_query: Option<String>,
243 phase: Phase,
244 want_mpd: bool,
245 mpd_inflight: bool,
246 mpd: Option<Mpd>,
247 periods: Vec<PeriodRun>,
248 issue: usize,
249 deliver: usize,
250 next_rep: u32,
251 next_track_id: u32,
252 track_ids: BTreeMap<TrackIdentity, (u32, u32)>,
253 previous_identities: Vec<TrackIdentity>,
254 reqs: BTreeMap<ResourceId, ReqInfo>,
255 queue: VecDeque<ResourceId>,
256 inflight: BTreeSet<ResourceId>,
257 parked: BTreeSet<ResourceId>,
258 failures: BTreeMap<ResourceId, u32>,
259 done: BTreeSet<ResourceId>,
260 inits: BTreeMap<InitKey, InitEntry>,
261 rep_loc: BTreeMap<RepId, (usize, usize, usize)>,
262 outputs: VecDeque<Output>,
263 announced_tracks: Option<String>,
264 prev: Option<PrevPeriod>,
265 any_started: bool,
266 outstanding: usize,
267 notices_sent: Vec<Notice>,
268 clock: UtcClock,
269 mpd_failures: u32,
270 live: Option<live::Live>,
271 next_utc_id: u32,
272 last_was_discontinuity: bool,
273 remembered_done: VecDeque<ResourceId>,
274}
275
276fn without_query(url: &str) -> String {
277 match Url::parse(url) {
278 Ok(mut u) => {
279 u.set_query(None);
280 u.set_fragment(None);
281 u.to_string()
282 }
283 Err(_) => url.to_owned(),
284 }
285}
286
287fn same_origin(a: &Url, b: &Url) -> bool {
288 a.scheme() == b.scheme()
289 && a.host_str() == b.host_str()
290 && a.port_or_known_default() == b.port_or_known_default()
291}
292
293pub(crate) fn filter_candidates(
294 candidates: Vec<BaseCandidate>,
295 doc_url: Option<&Url>,
296 cross_origin_urls: bool,
297) -> Vec<BaseCandidate> {
298 if cross_origin_urls {
299 return candidates;
300 }
301 let Some(doc) = doc_url else {
302 return candidates;
303 };
304 candidates
305 .into_iter()
306 .filter(|c| {
307 c.resolve(Some(doc), "")
308 .and_then(|u| Url::parse(&u).ok())
309 .map(|p| same_origin(doc, &p))
310 .unwrap_or(false)
311 })
312 .collect()
313}
314
315fn address_name(e: &AddressError) -> &'static str {
316 match e {
317 AddressError::InvalidTemplate { .. } => "invalid_template",
318 AddressError::MissingTemplateValue { .. } => "missing_template_value",
319 AddressError::Unsupported(u) => u.name(),
320 AddressError::MissingMediaTemplate => "missing_media_template",
321 AddressError::MissingDuration => "missing_duration",
322 AddressError::TimelineNotIncreasing { .. } => "timeline_not_increasing",
323 AddressError::NumberNotIncreasing { .. } => "number_not_increasing",
324 AddressError::TimelineTooLong { .. } => "timeline_too_long",
325 AddressError::ListTimelineMismatch { .. } => "list_timeline_mismatch",
326 AddressError::Sidx(_) => "bad_sidx",
327 AddressError::Overflow => "overflow",
328 AddressError::IndexRequired => "index_required",
329 AddressError::EmptySegmentUrl => "empty_segment_url",
330 AddressError::ZeroSegmentDuration => "zero_segment_duration",
331 _ => "unsupported_feature",
332 }
333}
334
335fn plan_error_name(e: &PlanError) -> &'static str {
336 match e {
337 PlanError::NoSuchRepresentation => "no_such_representation",
338 PlanError::Inherit(_) => "inherit",
339 PlanError::Address(a) => address_name(a),
340 PlanError::PeriodUnresolved => "period_unresolved",
341 PlanError::MissingAvailabilityStartTime => "missing_availability_start_time",
342 }
343}
344
345fn range_pair(r: Option<ByteRange>) -> std::result::Result<Option<(u64, u64)>, DropCause> {
346 r.map(|r| r.offset_len().map_err(|u| DropCause::Addressing(u.name())))
347 .transpose()
348}
349
350fn continuity_signals(set: &broadcast_dash::AdaptationSet, previous: &str) -> bool {
351 let named = |d: &Descriptor| {
352 d.scheme_id_uri == SCHEME_PERIOD_CONTINUITY && d.value.as_deref() == Some(previous)
353 };
354 set.supplemental_properties.iter().any(named) || set.essential_properties.iter().any(named)
355}
356
357#[derive(Debug, Clone)]
360#[non_exhaustive]
361pub struct DashClientConfig {
362 pub mpd_url: String,
364 pub dash: DashConfig,
366 pub limits: PullLimits,
368}
369
370impl DashClientConfig {
371 pub fn new(mpd_url: impl Into<String>) -> Self {
374 DashClientConfig {
375 mpd_url: mpd_url.into(),
376 dash: DashConfig::default(),
377 limits: PullLimits::default(),
378 }
379 }
380
381 #[must_use]
383 pub fn with_dash(mut self, dash: DashConfig) -> Self {
384 self.dash = dash;
385 self
386 }
387
388 #[must_use]
390 pub fn with_limits(mut self, limits: PullLimits) -> Self {
391 self.limits = limits;
392 self
393 }
394}
395
396impl DashClient {
397 #[must_use]
399 pub fn new(config: DashClientConfig) -> Self {
400 let DashClientConfig {
401 mpd_url,
402 dash,
403 limits,
404 } = config;
405 let doc_url = Url::parse(&mpd_url).ok();
406 let mpd_query = doc_url.as_ref().and_then(|u| u.query()).map(str::to_owned);
407 Self {
408 cfg: limits,
409 dash,
410 mpd_url,
411 doc_url,
412 mpd_query,
413 phase: Phase::NeedMpd,
414 want_mpd: true,
415 mpd_inflight: false,
416 mpd: None,
417 periods: Vec::new(),
418 issue: 0,
419 deliver: 0,
420 next_rep: 0,
421 next_track_id: 1,
422 track_ids: BTreeMap::new(),
423 previous_identities: Vec::new(),
424 reqs: BTreeMap::new(),
425 queue: VecDeque::new(),
426 inflight: BTreeSet::new(),
427 parked: BTreeSet::new(),
428 failures: BTreeMap::new(),
429 done: BTreeSet::new(),
430 inits: BTreeMap::new(),
431 rep_loc: BTreeMap::new(),
432 outputs: VecDeque::new(),
433 announced_tracks: None,
434 prev: None,
435 any_started: false,
436 outstanding: 0,
437 notices_sent: Vec::new(),
438 clock: UtcClock::new(),
439 mpd_failures: 0,
440 live: None,
441 next_utc_id: 0,
442 last_was_discontinuity: false,
443 remembered_done: VecDeque::new(),
444 }
445 }
446
447 #[must_use]
449 pub fn mpd_url(&self) -> &str {
450 &self.mpd_url
451 }
452
453 pub fn set_mpd_base(&mut self, base: impl Into<String>) {
457 self.doc_url = Url::parse(&base.into()).ok();
458 }
459
460 #[must_use]
463 pub fn is_static(&self) -> Option<bool> {
464 self.mpd
465 .as_ref()
466 .map(|m| m.kind == PresentationType::Static)
467 }
468
469 #[must_use]
471 pub fn presentation_ended(&self) -> bool {
472 self.phase == Phase::Ended
473 }
474
475 #[must_use]
477 pub fn max_segment_duration_secs(&self) -> Option<u64> {
478 let d = self.mpd.as_ref()?.max_segment_duration?;
479 Some(d.as_secs() + u64::from(d.subsec_nanos() > 0))
480 }
481
482 #[must_use]
486 pub fn state_size(&self) -> StateSize {
487 StateSize {
488 periods: self.periods.len(),
489 requests: self.reqs.len(),
490 inflight: self.inflight.len(),
491 parked: self.parked.len(),
492 failures: self.failures.len(),
493 done: self.done.len(),
494 inits: self.inits.len(),
495 reps: self.rep_loc.len(),
496 queued: self.queue.len(),
497 outputs: self.outputs.len(),
498 live: self.live.as_ref().map_or(0, live::Live::bookkeeping),
499 track_identities: self.track_ids.len(),
500 }
501 }
502
503 #[must_use]
508 pub fn awaits(&self, id: ResourceId) -> bool {
509 self.inflight.contains(&id)
510 }
511
512 pub fn next_output(&mut self) -> Option<Output> {
514 self.outputs.pop_front()
515 }
516
517 fn stopped(&self) -> bool {
518 matches!(self.phase, Phase::Ended | Phase::Fatal)
519 }
520
521 fn ensure_live(&self) -> Result<()> {
522 if self.stopped() {
523 Err(Error::Stopped)
524 } else {
525 Ok(())
526 }
527 }
528
529 fn go_fatal(&mut self, kind: FatalKind) {
530 if self.phase == Phase::Fatal {
531 return;
532 }
533 self.phase = Phase::Fatal;
534 self.queue.clear();
535 self.live = None;
536 self.outputs.push_back(Output::Fatal(kind));
537 }
538
539 fn discontinuity(&mut self) {
540 if !self.last_was_discontinuity {
541 self.last_was_discontinuity = true;
542 self.outputs.push_back(Output::Discontinuity);
543 }
544 }
545
546 fn mark_done(&mut self, id: ResourceId) {
547 if self.done.insert(id) {
548 self.remembered_done.push_back(id);
549 while self.remembered_done.len() > MAX_REMEMBERED_DONE {
550 if let Some(old) = self.remembered_done.pop_front() {
551 self.done.remove(&old);
552 }
553 }
554 }
555 }
556
557 fn notice_once(&mut self, n: Notice) {
558 if !self.notices_sent.contains(&n) {
559 self.notices_sent.push(n);
560 self.outputs.push_back(Output::Notice(n));
561 }
562 }
563
564 pub fn on_mpd(&mut self, now_us: u64, bytes: &[u8]) -> Result<()> {
573 self.on_mpd_with(now_us, bytes, &ResponseMeta::at(now_us))
574 }
575
576 pub fn on_mpd_with(
585 &mut self,
586 now_us: u64,
587 bytes: &[u8],
588 meta: &ResponseMeta<'_>,
589 ) -> Result<()> {
590 self.ensure_live()?;
591 if !self.mpd_inflight {
592 return Err(Error::MpdNotAwaited);
593 }
594 self.mpd_inflight = false;
595 let mpd = match Mpd::parse_with(bytes, &self.cfg.limits) {
596 Ok(m) => m,
597 Err(e) => {
598 self.mpd_failed(now_us);
599 return Err(Error::Mpd(e));
600 }
601 };
602 if self.mpd.is_some() {
603 self.refresh_mpd(now_us, mpd, bytes, meta);
604 return Ok(());
605 }
606 if mpd.kind == PresentationType::List {
607 self.go_fatal(FatalKind::Unsupported(Unsupported::PresentationTypeList));
610 return Ok(());
611 }
612 if mpd.periods.is_empty() {
613 self.go_fatal(FatalKind::MpdInvalid {
614 reason: "no_period",
615 });
616 return Ok(());
617 }
618 let dynamic = mpd.kind == PresentationType::Dynamic;
619 if dynamic && mpd.availability_start_time_us.is_none() {
620 self.go_fatal(FatalKind::MpdInvalid {
621 reason: "missing_availability_start_time",
622 });
623 return Ok(());
624 }
625 let features = mpd.features;
626 let event_streams = mpd.has_event_streams();
627 let preselections = mpd.has_preselections();
628 let patch_location = mpd.has_patch_location();
629 self.mpd = Some(mpd);
630 self.phase = Phase::Running;
631 if event_streams {
632 self.notice_once(Notice::EventStreamIgnored);
633 }
634 if preselections {
635 self.notice_once(Notice::PreselectionIgnored);
636 }
637 if patch_location {
638 self.notice_once(Notice::PatchIgnored);
639 }
640 if dynamic && features.low_latency {
641 self.notice_once(Notice::LowLatencyIgnored);
642 }
643 if dynamic {
644 self.start_live(now_us, bytes, meta);
645 } else {
646 if let Some(u) = meta.final_url.and_then(|f| self.accept_final_url(f)) {
647 self.doc_url = Some(u);
648 }
649 self.activate(0);
650 self.issue = 0;
651 }
652 Ok(())
653 }
654
655 pub fn on_mpd_failure(&mut self, now_us: u64, failure: Failure) -> FailureClass {
660 self.mpd_inflight = false;
661 self.want_mpd = false;
662 let attempt = self.mpd_failed(now_us);
663 let kind = match failure {
664 Failure::Status(STATUS_NOT_FOUND | STATUS_GONE) => FailureKind::Missing,
665 Failure::Status(STATUS_TOO_EARLY) => FailureKind::TooEarly,
666 Failure::Status(_) => FailureKind::Server,
667 Failure::Transport | Failure::Timeout => FailureKind::Transport,
668 Failure::TooLarge => FailureKind::Invalid,
669 };
670 FailureClass::Retry { attempt, kind }
671 }
672
673 pub fn retry_mpd(&mut self, _now_us: u64) {
675 if self.mpd_inflight || self.stopped() {
676 return;
677 }
678 let waiting = self.phase == Phase::NeedMpd
679 || self.live.as_ref().is_some_and(|l| l.refresh.awaiting_retry);
680 if waiting {
681 self.want_mpd = true;
682 }
683 }
684
685 pub fn poll(&mut self, now_us: u64) -> Option<Action> {
691 if self.stopped() {
692 return None;
693 }
694 self.tick(now_us);
695 if self.stopped() {
696 return None;
697 }
698 if let Some(a) = self.mpd_action(now_us) {
699 return Some(a);
700 }
701 if self.phase != Phase::Running {
702 return None;
703 }
704 if let Some(a) = self.utc_action(now_us) {
705 return Some(a);
706 }
707 loop {
708 if self.queue.is_empty() {
709 self.fill_queue(now_us);
710 if self.queue.is_empty() {
711 self.drain();
712 }
713 }
714 let id = self.queue.pop_front()?;
715 if let Some(action) = self.emit(id) {
716 return Some(action);
717 }
718 if self.stopped() {
719 return None;
720 }
721 }
722 }
723
724 fn window_open(&self) -> bool {
725 self.outstanding < self.cfg.window()
726 }
727
728 fn fill_queue(&mut self, now_us: u64) {
729 self.reset_wake();
730 loop {
731 let Some(pi) = (self.issue < self.periods.len()).then_some(self.issue) else {
732 return;
733 };
734 if self.live_blocked() {
735 return;
736 }
737 let exhausted = self.periods[pi].lanes.iter().all(Lane::exhausted);
738 if self.periods[pi].issue_done || (exhausted && !self.period_open(pi, now_us)) {
739 self.periods[pi].issue_done = true;
740 let next = self.periods[pi].index + 1;
741 if self.can_activate(next) {
742 self.activate(next);
743 self.issue = self.periods.len() - 1;
744 continue;
745 }
746 return;
747 }
748 if exhausted {
749 self.note_exhausted(now_us);
750 return;
751 }
752 if !self.window_open() {
753 return;
754 }
755 let utc = self.utc_now(now_us);
756 let mut pick: Option<(usize, i128)> = None;
757 for li in 0..self.periods[pi].lanes.len() {
758 match self.lane_candidate(pi, li, utc, now_us) {
759 Candidate::Ready(start_us) => {
760 if pick.is_none_or(|(_, best)| start_us < best) {
761 pick = Some((li, start_us));
762 }
763 }
764 Candidate::Wait(at_utc_us) => self.note_wake(at_utc_us),
765 Candidate::Idle => {}
766 Candidate::Unaddressable(reason) => {
767 self.rep_segment_error(pi, li, reason);
768 if self.stopped() {
769 return;
770 }
771 match self.lane_candidate(pi, li, utc, now_us) {
772 Candidate::Ready(start_us) => pick = Some((li, start_us)),
773 Candidate::Wait(at_utc_us) => self.note_wake(at_utc_us),
774 Candidate::Idle | Candidate::Unaddressable(_) => {}
775 }
776 }
777 }
778 }
779 let Some((li, _)) = pick else {
780 return;
781 };
782 if !self.issue_segment(pi, li) {
783 if self.stopped() || self.periods[pi].lanes[li].current().is_some() {
784 return;
785 }
786 continue;
787 }
788 }
789 }
790
791 fn lane_candidate(
792 &mut self,
793 pi: usize,
794 li: usize,
795 utc: Option<i128>,
796 now_us: u64,
797 ) -> Candidate {
798 match utc {
799 Some(utc) if self.is_live_mpd() => self.live_lane_candidate(pi, li, utc, now_us),
800 _ => {
801 let lane = &self.periods[pi].lanes[li];
802 let Some(rep) = lane.current() else {
803 return Candidate::Idle;
804 };
805 let Some(plan) = rep.plan.as_ref() else {
806 return Candidate::Idle;
807 };
808 match plan.get(rep.next_issue) {
809 Ok(Some(s)) => Candidate::Ready(s.start_us),
810 Ok(None) => Candidate::Idle,
811 Err(e) => Candidate::Unaddressable(address_name(&e)),
812 }
813 }
814 }
815 }
816
817 fn issue_segment(&mut self, pi: usize, li: usize) -> bool {
818 let lane = &mut self.periods[pi].lanes[li];
819 let ri = lane.reps.len() - 1;
820 let rep = &mut lane.reps[ri];
821 let seg = match rep.plan.as_ref().map(|p| p.get(rep.next_issue)) {
822 Some(Ok(Some(seg))) => seg,
823 Some(Err(e)) => {
824 let reason = address_name(&e);
825 self.rep_segment_error(pi, li, reason);
826 return false;
827 }
828 _ => return false,
829 };
830 let range = match range_pair(seg.byte_range) {
831 Ok(r) => r,
832 Err(cause) => {
833 let reason = match cause {
834 DropCause::Addressing(r) => r,
835 _ => "unusable_url",
836 };
837 self.go_fatal(FatalKind::UnsupportedAddressing { reason });
838 return false;
839 }
840 };
841 let id = ResourceId::Segment {
842 rep: rep.id,
843 number: seg.number,
844 };
845 let plan_index = rep.next_issue;
846 rep.next_issue += 1;
847 rep.last_time = Some(seg.time_ticks);
848 let rep_id = rep.id;
849 lane.queue.push_back(Slot {
850 id: Some(id),
851 rep: ri,
852 plan_index,
853 state: SlotState::Issued,
854 });
855 self.outstanding += 1;
856 self.reqs.insert(
857 id,
858 ReqInfo {
859 rep: rep_id,
860 reference: seg.reference,
861 range,
862 kind: ReqKind::Segment,
863 avail_from_us: seg.available_from_us,
864 avail_until_us: seg.available_until_us,
865 },
866 );
867 self.queue.push_back(id);
868 true
869 }
870
871 fn rep(&self, id: RepId) -> Option<&RepState> {
872 let (p, l, r) = *self.rep_loc.get(&id)?;
873 self.periods.get(p)?.lanes.get(l)?.reps.get(r)
874 }
875
876 fn rep_mut(&mut self, id: RepId) -> Option<&mut RepState> {
877 let (p, l, r) = *self.rep_loc.get(&id)?;
878 self.periods.get_mut(p)?.lanes.get_mut(l)?.reps.get_mut(r)
879 }
880
881 fn rep_segment_error(&mut self, pi: usize, li: usize, reason: &'static str) -> bool {
882 if self.periods[pi].lanes[li].current().is_none() {
883 return false;
884 }
885 self.drop_rep(pi, li, DropCause::Addressing(reason));
886 if self
887 .periods
888 .iter()
889 .all(|p| p.lanes.iter().all(|l| l.current().is_none()))
890 {
891 self.go_fatal(FatalKind::UnsupportedAddressing { reason });
892 }
893 true
894 }
895
896 fn drop_rep(&mut self, pi: usize, li: usize, cause: DropCause) {
897 if let Some(ri) = self.periods[pi].lanes[li].reps.len().checked_sub(1) {
898 self.drop_rep_at(pi, li, ri, cause);
899 }
900 self.note_wake(i128::MIN);
901 }
902
903 fn drop_rep_in(&mut self, rep: RepId, cause: DropCause) {
904 if let Some((pi, li, _)) = self.rep_loc.get(&rep).copied() {
905 self.drop_rep(pi, li, cause);
906 }
907 }
908
909 fn drop_rep_at(&mut self, pi: usize, li: usize, ri: usize, cause: DropCause) {
910 self.periods[pi].lanes[li].reps[ri].dead = true;
911 let at = self.periods[pi].lanes[li].reps[ri].at;
912 self.outputs.push_back(Output::TrackDropped {
913 period: self.periods[pi].index,
914 set: at.set,
915 rep: Some(at.rep),
916 cause,
917 });
918 }
919
920 fn resolve(
921 &self,
922 rep: &RepState,
923 reference: &str,
924 media: bool,
925 ) -> std::result::Result<String, DropCause> {
926 let cand = rep
927 .candidates
928 .get(rep.cand % rep.candidates.len().max(1))
929 .ok_or(DropCause::UnusableUrl)?;
930 let mut url = cand
931 .resolve(self.doc_url.as_ref(), reference)
932 .ok_or(DropCause::UnusableUrl)?;
933 if media && let Some(q) = &rep.query {
934 url = append_query(&url, q);
935 }
936 let parsed = Url::parse(&url).map_err(|_| DropCause::UnusableUrl)?;
937 if parsed.scheme() == "data" {
938 return Err(DropCause::Unsupported(Unsupported::DataUrl));
939 }
940 if !matches!(parsed.scheme(), "http" | "https") {
941 return Err(DropCause::UnusableUrl);
942 }
943 if !self.dash.cross_origin_urls
944 && let Some(doc) = &self.doc_url
945 && !same_origin(doc, &parsed)
946 {
947 return Err(DropCause::CrossOrigin);
948 }
949 Ok(url)
950 }
951
952 pub(super) fn accept_final_url(&self, final_url: &str) -> Option<Url> {
953 let parsed = Url::parse(final_url).ok()?;
954 if !matches!(parsed.scheme(), "http" | "https") {
955 return None;
956 }
957 let pinned_elsewhere = !self.dash.cross_origin_urls
958 && self
959 .doc_url
960 .as_ref()
961 .is_some_and(|d| !same_origin(d, &parsed));
962 (!pinned_elsewhere).then_some(parsed)
963 }
964
965 fn emit(&mut self, id: ResourceId) -> Option<Action> {
966 let (rep_id, reference, range, kind) = {
967 let info = self.reqs.get(&id)?;
968 (info.rep, info.reference.clone(), info.range, info.kind)
969 };
970 let url = {
971 let rep = self.rep(rep_id)?;
972 match self.resolve(rep, &reference, kind == ReqKind::Segment) {
973 Ok(u) => u,
974 Err(cause) => {
975 let reason = match cause {
976 DropCause::CrossOrigin => "cross_origin",
977 DropCause::Unsupported(u) => u.name(),
978 _ => "unusable_url",
979 };
980 self.go_fatal(FatalKind::UnsupportedAddressing { reason });
981 return None;
982 }
983 }
984 };
985 self.parked.remove(&id);
986 self.inflight.insert(id);
987 match kind {
988 ReqKind::Init => {
989 let key = self.rep(rep_id)?.init_key.clone();
990 if let Some(e) = self.inits.get_mut(&key) {
991 e.state = InitState::InFlight;
992 }
993 Some(Action::FetchInit {
994 id,
995 url,
996 byte_range: range,
997 })
998 }
999 ReqKind::Index => Some(Action::FetchIndex {
1000 id,
1001 url,
1002 byte_range: range,
1003 }),
1004 ReqKind::Segment => Some(Action::FetchSegment {
1005 id,
1006 url,
1007 byte_range: range,
1008 }),
1009 }
1010 }
1011
1012 fn activate(&mut self, index: usize) {
1015 let Some(mpd) = self.mpd.as_ref() else { return };
1016 let timing = period_timings(mpd).get(index).copied();
1017 let start_us = timing.and_then(|t| t.start_us).unwrap_or(0);
1018 let selection = select(mpd, index, &self.cfg.policy);
1019 let period_id = mpd.periods.get(index).and_then(|p| p.id.clone());
1020 let mut notices = Vec::new();
1021 let mut encryption = false;
1022 for d in &selection.dropped {
1023 if d.reason == crate::selection::DropReason::UnsupportedEncryption {
1024 encryption = true;
1025 }
1026 notices.push(Output::TrackDropped {
1027 period: index,
1028 set: d.set,
1029 rep: d.rep,
1030 cause: DropCause::Selection(d.reason),
1031 });
1032 }
1033 if encryption {
1034 notices.push(Output::Notice(Notice::UnsupportedEncryption));
1035 }
1036 if selection
1037 .dropped
1038 .iter()
1039 .any(|d| d.reason == crate::selection::DropReason::UnresolvedXlink)
1040 {
1041 notices.push(Output::Notice(Notice::XlinkSkipped));
1042 }
1043 let video = selection
1044 .chosen_variant
1045 .and_then(|i| selection.variants.get(i).copied());
1046 let audio = selection.chosen_audio;
1047 let mut run = PeriodRun {
1048 index,
1049 id: period_id,
1050 start_us,
1051 lanes: Vec::new(),
1052 selection,
1053 notices,
1054 started: false,
1055 issue_done: false,
1056 finished: false,
1057 };
1058 let pi = self.periods.len();
1059 for (kind, at) in [(SetClass::Video, video), (SetClass::Audio, audio)] {
1060 let Some(at) = at else { continue };
1061 match self.build_rep(at) {
1062 Ok(rep) => {
1063 let resolved = at.resolve(self.mpd.as_ref().expect("mpd present"));
1064 let set_id = resolved.and_then(|(_, s, _)| s.id);
1065 let identity = resolved.map_or_else(
1066 || TrackIdentity {
1067 kind,
1068 set_id: None,
1069 group: None,
1070 lang: None,
1071 roles: Vec::new(),
1072 },
1073 |(_, s, _)| TrackIdentity {
1074 kind,
1075 set_id: s.id,
1076 group: s.group,
1077 lang: s.lang.clone(),
1078 roles: s
1079 .roles
1080 .iter()
1081 .map(|d| (d.scheme_id_uri.clone(), d.value.clone()))
1082 .collect(),
1083 },
1084 );
1085 run.lanes.push(Lane {
1086 kind,
1087 set_id,
1088 identity,
1089 reps: vec![rep],
1090 queue: VecDeque::new(),
1091 base: 0,
1092 count: 0,
1093 announced_rep: None,
1094 announced_init: None,
1095 expired_run: 0,
1096 });
1097 }
1098 Err(cause) => run.notices.push(Output::TrackDropped {
1099 period: index,
1100 set: at.set,
1101 rep: Some(at.rep),
1102 cause,
1103 }),
1104 }
1105 }
1106 self.periods.push(run);
1107 for li in 0..self.periods[pi].lanes.len() {
1108 self.register_rep(pi, li, 0);
1109 }
1110 }
1111
1112 fn init_reference(
1113 mpd: &Mpd,
1114 at: RepRef,
1115 planned: &std::result::Result<crate::address::PlannedRepresentation, PlanError>,
1116 ) -> std::result::Result<Option<(String, Option<ByteRange>)>, DropCause> {
1117 match planned {
1118 Ok(p) => Ok(p.plan.init().map(|r| (r.reference, r.byte_range))),
1119 Err(PlanError::Address(AddressError::IndexRequired)) => {
1120 let Some((period, set, rep)) = at.resolve(mpd) else {
1121 return Ok(None);
1122 };
1123 Ok(match resolve_segment_info(period, set, rep).ok() {
1124 Some(SegmentInfo::Base(b)) => b
1125 .initialization
1126 .as_ref()
1127 .map(|u| (u.source_url.clone().unwrap_or_default(), u.range)),
1128 _ => None,
1129 })
1130 }
1131 Err(e) => Err(DropCause::Addressing(plan_error_name(e))),
1132 }
1133 }
1134
1135 fn build_rep(&mut self, at: RepRef) -> std::result::Result<RepState, DropCause> {
1136 let mpd = self.mpd.as_ref().expect("mpd present");
1137 let (period, set, rep) = at
1138 .resolve(mpd)
1139 .ok_or(DropCause::Addressing("no_such_representation"))?;
1140 let candidates = filter_candidates(
1141 representation_candidates(mpd, period, set, rep),
1142 self.doc_url.as_ref(),
1143 self.dash.cross_origin_urls,
1144 );
1145 if candidates.is_empty() && !self.dash.cross_origin_urls {
1146 return Err(DropCause::CrossOrigin);
1147 }
1148 let query = segment_url_query(mpd, period, set, rep, self.mpd_query.as_deref())
1149 .map_err(|_| DropCause::UnusableUrl)?;
1150 let planned = plan_representation(mpd, at, &self.plan_options());
1151 let init = Self::init_reference(mpd, at, &planned)?.ok_or(DropCause::NoInitialization)?;
1152 let (plan, index) = match planned {
1153 Ok(p) => {
1154 if p.plan.len().is_none() && mpd.kind != PresentationType::Dynamic {
1155 return Err(DropCause::UnboundedPlan);
1156 }
1157 (Some(p.plan), None)
1158 }
1159 Err(PlanError::Address(AddressError::IndexRequired)) => {
1160 let info = resolve_segment_info(period, set, rep)
1161 .map_err(|_| DropCause::Addressing("inherit"))?;
1162 let range = match info {
1163 SegmentInfo::Base(b) => b.index_range,
1164 _ => None,
1165 };
1166 let range = range.ok_or(DropCause::Addressing("index_required"))?;
1167 (None, Some(IndexReq { range }))
1168 }
1169 Err(e) => return Err(DropCause::Addressing(plan_error_name(&e))),
1170 };
1171 let init_range = range_pair(init.1)?;
1172 let id = RepId(self.next_rep);
1173 self.next_rep = self.next_rep.saturating_add(1);
1174 let mut state = RepState {
1175 id,
1176 at,
1177 candidates,
1178 cand: 0,
1179 cand_failures: 0,
1180 query,
1181 plan,
1182 index,
1183 init_key: InitKey {
1184 url: String::new(),
1185 range: init_range,
1186 },
1187 next_issue: 0,
1188 start_time_us: None,
1189 dead: false,
1190 set_id: set.id,
1191 set_pos: at.set,
1192 rep_key: rep.id.clone(),
1193 joined: mpd.kind != PresentationType::Dynamic,
1194 last_time: None,
1195 };
1196 let init_url = self.resolve(&state, &init.0, false)?;
1197 state.init_key.url = without_query(&init_url);
1198 if let Some(ix) = &state.index {
1199 range_pair(Some(ix.range))?;
1200 self.resolve(&state, "", false)?;
1201 }
1202 let first = state
1203 .plan
1204 .as_ref()
1205 .map(|p| p.get(0))
1206 .transpose()
1207 .map_err(|e| DropCause::Addressing(address_name(&e)))?
1208 .flatten();
1209 if let Some(seg) = first {
1210 range_pair(seg.byte_range)?;
1211 self.resolve(&state, &seg.reference, true)?;
1212 }
1213 Ok(state)
1214 }
1215
1216 fn register_rep(&mut self, pi: usize, li: usize, ri: usize) {
1217 let rep = &self.periods[pi].lanes[li].reps[ri];
1218 let rep_id = rep.id;
1219 let key = rep.init_key.clone();
1220 let has_index = rep.index.is_some();
1221 let index_range = rep.index.as_ref().map(|i| i.range);
1222 let init_reference = {
1223 let mpd = self.mpd.as_ref().expect("mpd present");
1224 let planned = rep.plan.as_ref().map(|p| p.init());
1225 match planned {
1226 Some(init) => init.map(|r| (r.reference, r.byte_range)),
1227 None => Self::init_reference(
1228 mpd,
1229 rep.at,
1230 &Err(PlanError::Address(AddressError::IndexRequired)),
1231 )
1232 .ok()
1233 .flatten(),
1234 }
1235 };
1236 self.rep_loc.insert(rep_id, (pi, li, ri));
1237 if let std::collections::btree_map::Entry::Vacant(v) = self.inits.entry(key) {
1238 v.insert(InitEntry {
1239 state: InitState::Wanted,
1240 bytes: None,
1241 specs: Vec::new(),
1242 skipped_reported: BTreeSet::new(),
1243 });
1244 let (reference, range) = init_reference.unwrap_or_default();
1245 let init_id = ResourceId::Init { rep: rep_id };
1246 self.reqs.insert(
1247 init_id,
1248 ReqInfo {
1249 rep: rep_id,
1250 reference,
1251 range: range.and_then(|r| r.offset_len().ok()),
1252 kind: ReqKind::Init,
1253 avail_from_us: None,
1254 avail_until_us: None,
1255 },
1256 );
1257 self.queue.push_back(init_id);
1258 }
1259 if has_index && let Some(r) = index_range {
1260 let index_id = ResourceId::Index { rep: rep_id };
1261 self.reqs.insert(
1262 index_id,
1263 ReqInfo {
1264 rep: rep_id,
1265 reference: String::new(),
1266 range: r.offset_len().ok(),
1267 kind: ReqKind::Index,
1268 avail_from_us: None,
1269 avail_until_us: None,
1270 },
1271 );
1272 self.queue.push_back(index_id);
1273 }
1274 }
1275
1276 fn requested(&self, id: ResourceId) -> Result<()> {
1279 if self.done.contains(&id) {
1280 return Err(Error::DuplicateResource { id });
1281 }
1282 if !self.inflight.contains(&id) {
1283 return Err(Error::UnrequestedResource { id });
1284 }
1285 Ok(())
1286 }
1287
1288 fn body_rejection(&self, id: ResourceId, bytes: &[u8]) -> Option<Rejection> {
1289 if bytes.len() > self.cfg.max_segment_bytes {
1290 return Some(Rejection::TooLarge);
1291 }
1292 let range = self.reqs.get(&id).and_then(|r| r.range)?;
1293 (u64::try_from(bytes.len()).ok() != Some(range.1)).then_some(Rejection::WrongLength)
1294 }
1295
1296 fn max_failures(&self, id: ResourceId) -> u32 {
1297 match id {
1298 ResourceId::Segment { .. } => self.cfg.max_segment_failures,
1299 ResourceId::Init { .. } | ResourceId::Index { .. } => self.cfg.max_init_failures,
1300 }
1301 }
1302
1303 fn count_failure(
1304 &mut self,
1305 now_us: u64,
1306 id: ResourceId,
1307 kind: FailureKind,
1308 status: Option<u16>,
1309 ) -> FailureClass {
1310 self.inflight.remove(&id);
1311 let mut rotated = false;
1312 let count = {
1313 let n = self.failures.entry(id).or_insert(0);
1314 *n = n.saturating_add(1);
1315 *n
1316 };
1317 if count >= self.max_failures(id) {
1318 if self.is_live_mpd() && matches!(id, ResourceId::Segment { .. }) {
1319 return self.give_up_live_segment(id, status);
1320 }
1321 let fatal = match id {
1322 ResourceId::Segment { .. } => FatalKind::SegmentFetchFailed { id, status },
1323 ResourceId::Init { .. } | ResourceId::Index { .. } => {
1324 FatalKind::InitFetchFailed { id, status }
1325 }
1326 };
1327 self.go_fatal(fatal);
1328 return FailureClass::GiveUp;
1329 }
1330 let doc = self.doc_url.clone();
1331 let cross = self.dash.cross_origin_urls;
1332 if let Some(rep) = self.rep_mut(id.rep()) {
1333 rep.cand_failures += 1;
1334 if rep.cand_failures >= ROTATE_AFTER_FAILURES && rep.candidates.len() > 1 {
1335 let len = rep.candidates.len();
1336 let mut next = (rep.cand + 1) % len;
1337 let mut found = false;
1338 for _ in 0..len {
1339 let ok = cross
1340 || doc.as_ref().is_none_or(|d| {
1341 rep.candidates[next]
1342 .resolve(Some(d), "")
1343 .and_then(|u| Url::parse(&u).ok())
1344 .map(|p| same_origin(d, &p))
1345 .unwrap_or(false)
1346 });
1347 if ok {
1348 found = true;
1349 break;
1350 }
1351 next = (next + 1) % len;
1352 }
1353 if found && next != rep.cand {
1354 rep.cand = next;
1355 rep.cand_failures = 0;
1356 rotated = true;
1357 }
1358 }
1359 }
1360 if rotated {
1361 self.after_rotation(now_us);
1362 }
1363 self.parked.insert(id);
1364 FailureClass::Retry {
1365 attempt: count,
1366 kind,
1367 }
1368 }
1369
1370 fn reject(&mut self, now_us: u64, id: ResourceId, reason: Rejection) -> Error {
1371 let class = self.count_failure(now_us, id, FailureKind::Invalid, None);
1372 Error::Rejected { id, reason, class }
1373 }
1374
1375 fn clear_success(&mut self, id: ResourceId) {
1376 self.inflight.remove(&id);
1377 self.parked.remove(&id);
1378 self.failures.remove(&id);
1379 self.mark_done(id);
1380 }
1381
1382 pub fn on_init(&mut self, now_us: u64, id: ResourceId, bytes: &[u8]) -> Result<()> {
1390 self.ensure_live()?;
1391 let ResourceId::Init { rep } = id else {
1392 return Err(Error::UnrequestedResource { id });
1393 };
1394 self.requested(id)?;
1395 if let Some(r) = self.body_rejection(id, bytes) {
1396 return Err(self.reject(now_us, id, r));
1397 }
1398 if !segment::progressive_movie(bytes) {
1399 return Err(self.reject(now_us, id, Rejection::InitUndecodable));
1400 }
1401 let info = match demux::init_info(bytes) {
1402 Ok(i) => i,
1403 Err(_) => return Err(self.reject(now_us, id, Rejection::DemuxFailed)),
1404 };
1405 let key = self
1406 .rep(rep)
1407 .ok_or(Error::UnrequestedResource { id })?
1408 .init_key
1409 .clone();
1410 if let Some(e) = self.inits.get_mut(&key) {
1411 e.state = InitState::Ready;
1412 e.bytes = Some(Arc::new(bytes.to_vec()));
1413 e.specs = info.specs;
1414 for s in info.skipped {
1415 let k = (s.track_id.map(TrackId::value), s.fourcc.clone());
1416 if e.skipped_reported.insert(k) {
1417 self.outputs.push_back(Output::TrackSkipped {
1418 track_id: None,
1419 fourcc: s.fourcc,
1420 reason: s.reason,
1421 });
1422 }
1423 }
1424 }
1425 self.clear_success(id);
1426 self.reqs.remove(&id);
1427 let held = self.demux_held(now_us, &key);
1428 self.drain();
1429 held
1430 }
1431
1432 fn demux_held(&mut self, now_us: u64, key: &InitKey) -> Result<()> {
1433 let Some(init) = self.inits.get(key).and_then(|e| e.bytes.clone()) else {
1434 return Ok(());
1435 };
1436 let mut first_error = None;
1437 let from = self.deliver.min(self.periods.len());
1438 for pi in from..self.periods.len() {
1439 for li in 0..self.periods[pi].lanes.len() {
1440 let n = self.periods[pi].lanes[li].queue.len();
1441 for qi in 0..n {
1442 let (id, matches) = {
1443 let lane = &self.periods[pi].lanes[li];
1444 let slot = &lane.queue[qi];
1445 let m = matches!(slot.state, SlotState::Held(_))
1446 && lane.reps.get(slot.rep).is_some_and(|r| &r.init_key == key);
1447 (slot.id, m)
1448 };
1449 if !matches {
1450 continue;
1451 }
1452 let slot = &mut self.periods[pi].lanes[li].queue[qi];
1453 let SlotState::Held(bytes) =
1454 std::mem::replace(&mut slot.state, SlotState::Issued)
1455 else {
1456 continue;
1457 };
1458 match demux::demux_segment(&init, &bytes) {
1459 Ok(staged) => {
1460 self.periods[pi].lanes[li].queue[qi].state = SlotState::Staged(staged);
1461 }
1462 Err(_) => {
1463 if let Some(id) = id {
1464 self.inflight.insert(id);
1465 let e = self.reject(now_us, id, Rejection::DemuxFailed);
1466 first_error.get_or_insert(e);
1467 }
1468 }
1469 }
1470 }
1471 }
1472 }
1473 match first_error {
1474 Some(e) => Err(e),
1475 None => Ok(()),
1476 }
1477 }
1478
1479 pub fn on_index(&mut self, now_us: u64, id: ResourceId, bytes: &[u8]) -> Result<()> {
1484 self.ensure_live()?;
1485 let ResourceId::Index { rep } = id else {
1486 return Err(Error::UnrequestedResource { id });
1487 };
1488 self.requested(id)?;
1489 if let Some(r) = self.body_rejection(id, bytes) {
1490 return Err(self.reject(now_us, id, r));
1491 }
1492 let sidx = match base_index::parse_index(bytes) {
1493 Ok(s) => s,
1494 Err(_) => return Err(self.reject(now_us, id, Rejection::BadIndex)),
1495 };
1496 let (at, range) = {
1497 let r = self.rep(rep).ok_or(Error::UnrequestedResource { id })?;
1498 let range = r.index.as_ref().map(|i| i.range);
1499 (r.at, range)
1500 };
1501 let Some(range) = range else {
1502 return Err(Error::UnrequestedResource { id });
1503 };
1504 let anchor = match base_index::anchor_after(&range) {
1505 Ok(a) => a,
1506 Err(_) => return Err(self.reject(now_us, id, Rejection::BadIndex)),
1507 };
1508 let planned = {
1509 let mpd = self.mpd.as_ref().expect("mpd present");
1510 plan_representation(mpd, at, &self.plan_options().with_index(&sidx, anchor))
1511 };
1512 self.clear_success(id);
1513 self.reqs.remove(&id);
1514 match planned {
1515 Ok(p) => {
1516 let start = self.rep_mut(rep).and_then(|r| r.start_time_us.take());
1517 match start.map_or(Ok(None), |t| p.plan.index_at_or_before(t)) {
1518 Ok(index) => {
1519 if let Some(r) = self.rep_mut(rep) {
1520 r.next_issue = index.unwrap_or(0);
1521 r.plan = Some(p.plan);
1522 }
1523 }
1524 Err(e) => self.drop_rep_in(rep, DropCause::Addressing(address_name(&e))),
1525 }
1526 }
1527 Err(e) => self.drop_rep_in(rep, DropCause::Addressing(plan_error_name(&e))),
1528 }
1529 self.drain();
1530 Ok(())
1531 }
1532
1533 pub fn on_segment(&mut self, now_us: u64, id: ResourceId, bytes: &[u8]) -> Result<()> {
1539 self.ensure_live()?;
1540 let ResourceId::Segment { rep, .. } = id else {
1541 return Err(Error::UnrequestedResource { id });
1542 };
1543 self.requested(id)?;
1544 if let Some(r) = self.body_rejection(id, bytes) {
1545 return Err(self.reject(now_us, id, r));
1546 }
1547 if segment::progressive_movie(bytes) {
1548 self.inflight.remove(&id);
1549 self.go_fatal(FatalKind::NonFragmentedMp4 { id });
1550 return Err(Error::Rejected {
1551 id,
1552 reason: Rejection::NonFragmentedMp4,
1553 class: FailureClass::GiveUp,
1554 });
1555 }
1556 if !segment::media_fragment(bytes) {
1557 return Err(self.reject(now_us, id, Rejection::NotMedia));
1558 }
1559 let key = self
1560 .rep(rep)
1561 .ok_or(Error::UnrequestedResource { id })?
1562 .init_key
1563 .clone();
1564 let init = self.inits.get(&key).and_then(|e| e.bytes.clone());
1565 let state = match init {
1566 Some(init) => match demux::demux_segment(&init, bytes) {
1567 Ok(staged) => SlotState::Staged(staged),
1568 Err(_) => return Err(self.reject(now_us, id, Rejection::DemuxFailed)),
1569 },
1570 None => SlotState::Held(bytes.to_vec()),
1571 };
1572 let Some(slot) = self.slot_mut(id) else {
1573 return Err(Error::UnrequestedResource { id });
1574 };
1575 slot.state = state;
1576 self.inflight.remove(&id);
1577 self.parked.remove(&id);
1578 self.failures.remove(&id);
1579 self.mark_done(id);
1580 self.segment_delivered(id);
1581 self.drain();
1582 Ok(())
1583 }
1584
1585 fn slot_mut(&mut self, id: ResourceId) -> Option<&mut Slot> {
1586 let (p, l, _) = *self.rep_loc.get(&id.rep())?;
1587 self.periods
1588 .get_mut(p)?
1589 .lanes
1590 .get_mut(l)?
1591 .queue
1592 .iter_mut()
1593 .find(|s| s.id == Some(id))
1594 }
1595
1596 pub fn on_failure(
1602 &mut self,
1603 now_us: u64,
1604 id: ResourceId,
1605 failure: Failure,
1606 ) -> Result<FailureClass> {
1607 self.ensure_live()?;
1608 if !self.inflight.contains(&id) {
1609 return Err(Error::UnrequestedResource { id });
1610 }
1611 if self.is_live_mpd() && matches!(id, ResourceId::Segment { .. }) {
1612 return self.live_segment_failure(now_us, id, failure);
1613 }
1614 let status = match failure {
1615 Failure::Status(s) => Some(s),
1616 Failure::Transport | Failure::Timeout | Failure::TooLarge => None,
1617 };
1618 if matches!(status, Some(STATUS_NOT_FOUND | STATUS_GONE)) && self.is_missing_tail(id) {
1619 self.inflight.remove(&id);
1620 self.failures.remove(&id);
1621 if let Some(live) = self.live.as_mut() {
1622 live.early.remove(&id);
1623 }
1624 self.mark_done(id);
1625 if let Some(slot) = self.slot_mut(id) {
1626 slot.state = SlotState::Skipped { marker: false };
1627 }
1628 self.drain();
1629 return Ok(FailureClass::EndTail);
1630 }
1631 Ok(self.count_failure(now_us, id, live::failure_kind(failure), status))
1632 }
1633
1634 fn is_missing_tail(&self, id: ResourceId) -> bool {
1635 let ResourceId::Segment { rep, .. } = id else {
1636 return false;
1637 };
1638 let Some((pi, li, _)) = self.rep_loc.get(&rep).copied() else {
1639 return false;
1640 };
1641 let lane = &self.periods[pi].lanes[li];
1642 let Some(slot) = lane.queue.iter().find(|s| s.id == Some(id)) else {
1643 return false;
1644 };
1645 let Some(r) = lane.reps.get(slot.rep) else {
1646 return false;
1647 };
1648 let Some(plan) = r.plan.as_ref() else {
1649 return false;
1650 };
1651 let last = plan.len().and_then(|n| n.checked_sub(1));
1652 last == Some(slot.plan_index)
1653 && plan.len().is_some_and(|n| n > 1)
1654 && plan
1655 .get(slot.plan_index)
1656 .is_ok_and(|s| s.is_some_and(|s| s.duration_us < MISSING_TAIL_MAX_US))
1657 }
1658
1659 pub fn retry(&mut self, _now_us: u64, id: ResourceId) -> bool {
1663 if self.stopped() || !self.parked.remove(&id) {
1664 return false;
1665 }
1666 self.queue.push_front(id);
1667 true
1668 }
1669
1670 pub fn skip(&mut self, _now_us: u64, id: ResourceId) -> bool {
1674 if self.stopped() || !matches!(id, ResourceId::Segment { .. }) || !self.parked.remove(&id) {
1675 return false;
1676 }
1677 self.failures.remove(&id);
1678 if let Some(live) = self.live.as_mut() {
1679 live.early.remove(&id);
1680 }
1681 self.mark_done(id);
1682 if let Some(slot) = self.slot_mut(id) {
1683 slot.state = SlotState::Skipped { marker: true };
1684 }
1685 self.drain();
1686 true
1687 }
1688
1689 fn drain(&mut self) {
1692 loop {
1693 if self.phase != Phase::Running {
1694 return;
1695 }
1696 let Some(pi) = (self.deliver < self.periods.len()).then_some(self.deliver) else {
1697 return;
1698 };
1699 if !self.periods[pi].started {
1700 if !self.ready_to_start(pi) {
1701 return;
1702 }
1703 self.start_period(pi);
1704 }
1705 self.deliver_slots(pi);
1706 let p = &self.periods[pi];
1707 let complete = p.issue_done && p.lanes.iter().all(|l| l.queue.is_empty());
1708 if !complete {
1709 return;
1710 }
1711 self.periods[pi].finished = true;
1712 self.periods[pi].lanes.clear();
1713 let total = self.mpd.as_ref().map_or(0, |m| m.periods.len());
1714 let last_of_mpd = self.periods[pi].index + 1 >= total;
1715 self.deliver += 1;
1716 self.prune_finished();
1717 if last_of_mpd {
1718 if self.live_open() {
1719 return;
1720 }
1721 if self.any_started {
1722 self.outputs.push_back(Output::EndOfStream);
1723 self.phase = Phase::Ended;
1724 self.live = None;
1725 } else {
1726 self.go_fatal(FatalKind::MpdInvalid {
1727 reason: "no_playable_representation",
1728 });
1729 }
1730 return;
1731 }
1732 }
1733 }
1734
1735 fn prune_finished(&mut self) {
1736 while self.deliver > 0 && self.issue > 0 && self.periods.first().is_some_and(|p| p.finished)
1737 {
1738 self.periods.remove(0);
1739 self.deliver -= 1;
1740 self.issue -= 1;
1741 self.rep_loc.retain(|_, loc| loc.0 != 0);
1742 for loc in self.rep_loc.values_mut() {
1743 loc.0 -= 1;
1744 }
1745 }
1746 let mut keep: BTreeSet<InitKey> = self
1747 .periods
1748 .iter()
1749 .flat_map(|p| p.lanes.iter())
1750 .flat_map(|l| l.reps.iter())
1751 .map(|r| r.init_key.clone())
1752 .collect();
1753 if let Some(prev) = &self.prev {
1754 keep.extend(prev.lanes.iter().map(|(_, _, k)| k.clone()));
1755 }
1756 self.inits.retain(|k, _| keep.contains(k));
1757 }
1758
1759 fn ready_to_start(&self, pi: usize) -> bool {
1760 !self.live_blocked()
1761 && self.periods[pi].lanes.iter().all(|lane| {
1762 lane.current().is_none_or(|rep| {
1763 self.inits
1764 .get(&rep.init_key)
1765 .is_some_and(|e| e.state == InitState::Ready)
1766 })
1767 })
1768 }
1769
1770 fn start_period(&mut self, pi: usize) {
1771 self.periods[pi].started = true;
1772 let mut assignments: Vec<(usize, u32, u32)> = Vec::new();
1773 let mut used_ranges: Vec<(u32, u32)> = Vec::new();
1774 for li in 0..self.periods[pi].lanes.len() {
1775 let n = self.periods[pi].lanes[li]
1776 .current()
1777 .and_then(|r| self.inits.get(&r.init_key))
1778 .map_or(0, |e| u32::try_from(e.specs.len()).unwrap_or(u32::MAX));
1779 let identity = self.periods[pi].lanes[li].identity.clone();
1780 let base = allocate_track_base(
1781 &mut self.track_ids,
1782 &mut self.next_track_id,
1783 &mut used_ranges,
1784 identity,
1785 n,
1786 );
1787 assignments.push((li, base, n));
1788 }
1789 for (li, base, n) in assignments {
1790 let lane = &mut self.periods[pi].lanes[li];
1791 lane.base = base;
1792 lane.count = n;
1793 }
1794 let current: Vec<TrackIdentity> = self.periods[pi]
1795 .lanes
1796 .iter()
1797 .map(|lane| lane.identity.clone())
1798 .collect();
1799 prune_track_ids(
1800 &mut self.track_ids,
1801 current.iter().chain(self.previous_identities.iter()),
1802 );
1803 self.previous_identities = current;
1804 let notices = std::mem::take(&mut self.periods[pi].notices);
1805 self.outputs.extend(notices);
1806 let live: Vec<usize> = (0..self.periods[pi].lanes.len())
1807 .filter(|&li| self.periods[pi].lanes[li].current().is_some())
1808 .collect();
1809 if live.is_empty() {
1810 self.periods[pi].issue_done = true;
1811 return;
1812 }
1813 let first = !self.any_started;
1814 self.any_started = true;
1815 let continuous = !first && self.is_continuous(pi);
1816 let manifest = self.manifest(pi);
1817 let p = &self.periods[pi];
1818 self.outputs.push_back(Output::Manifest(manifest));
1819 self.outputs.push_back(Output::PeriodChanged {
1820 index: p.index,
1821 id: p.id.clone(),
1822 start_us: p.start_us,
1823 continuous,
1824 });
1825 if !first && !continuous {
1826 self.discontinuity();
1827 }
1828 let mut summary = Vec::new();
1829 for li in live {
1830 let lane = &mut self.periods[pi].lanes[li];
1831 let ri = lane.reps.len() - 1;
1832 let key = lane.reps[ri].init_key.clone();
1833 summary.push((lane.kind, lane.set_id, key.clone()));
1834 if !continuous && let Some(bytes) = self.inits.get(&key).and_then(|e| e.bytes.as_ref())
1835 {
1836 self.outputs.push_back(Output::Init(bytes.as_ref().clone()));
1837 }
1838 lane.announced_init = Some(key);
1839 lane.announced_rep = Some(ri);
1840 }
1841 self.announce_tracks(pi);
1842 self.prev = Some(PrevPeriod {
1843 id: self.periods[pi].id.clone(),
1844 lanes: summary,
1845 });
1846 }
1847
1848 fn is_continuous(&self, pi: usize) -> bool {
1849 let Some(prev) = &self.prev else { return false };
1850 let Some(prev_id) = prev.id.as_deref() else {
1851 return false;
1852 };
1853 let Some(mpd) = self.mpd.as_ref() else {
1854 return false;
1855 };
1856 let p = &self.periods[pi];
1857 let live: Vec<&Lane> = p.lanes.iter().filter(|l| l.current().is_some()).collect();
1858 if live.len() != prev.lanes.len() {
1859 return false;
1860 }
1861 live.iter()
1862 .zip(&prev.lanes)
1863 .all(|(lane, (kind, set_id, key))| {
1864 let Some(rep) = lane.current() else {
1865 return false;
1866 };
1867 let Some((_, set, _)) = rep.at.resolve(mpd) else {
1868 return false;
1869 };
1870 lane.kind == *kind
1871 && lane.set_id.is_some()
1872 && lane.set_id == *set_id
1873 && &rep.init_key == key
1874 && continuity_signals(set, prev_id)
1875 })
1876 }
1877
1878 fn announce_tracks(&mut self, pi: usize) {
1879 let mut specs: Vec<TrackSpec> = Vec::new();
1880 for lane in &self.periods[pi].lanes {
1881 let Some(rep) = lane.current() else { continue };
1882 let Some(entry) = self.inits.get(&rep.init_key) else {
1883 continue;
1884 };
1885 for (pos, spec) in entry.specs.iter().enumerate() {
1886 let pos = u32::try_from(pos).unwrap_or(u32::MAX);
1887 if pos >= lane.count {
1888 break;
1889 }
1890 let mut s = spec.clone();
1891 s.track_id = TrackId::new(lane.base.saturating_add(pos));
1892 specs.push(s);
1893 }
1894 }
1895 let fingerprint = format!("{specs:?}");
1896 if self.announced_tracks.as_deref() != Some(fingerprint.as_str()) {
1897 self.announced_tracks = Some(fingerprint);
1898 self.outputs.push_back(Output::Tracks(specs));
1899 }
1900 }
1901
1902 fn deliver_slots(&mut self, pi: usize) {
1903 for li in 0..self.periods[pi].lanes.len() {
1904 while self.periods[pi].lanes[li]
1905 .queue
1906 .front()
1907 .is_some_and(|f| !matches!(f.state, SlotState::Issued | SlotState::Held(_)))
1908 {
1909 let Some(slot) = self.periods[pi].lanes[li].queue.pop_front() else {
1910 break;
1911 };
1912 if !matches!(slot.state, SlotState::Marker) {
1913 self.outstanding = self.outstanding.saturating_sub(1);
1914 }
1915 if let Some(id) = slot.id {
1916 self.reqs.remove(&id);
1917 }
1918 match slot.state {
1919 SlotState::Marker => self.discontinuity(),
1920 SlotState::Skipped { marker } => {
1921 if marker {
1922 self.discontinuity();
1923 }
1924 }
1925 SlotState::Staged(staged) => {
1926 self.emit_staged(pi, li, slot.rep, slot.id, staged);
1927 }
1928 SlotState::Issued | SlotState::Held(_) => {}
1929 }
1930 }
1931 }
1932 }
1933
1934 fn emit_staged(
1935 &mut self,
1936 pi: usize,
1937 li: usize,
1938 rep_idx: usize,
1939 id: Option<ResourceId>,
1940 staged: Staged,
1941 ) {
1942 if self.periods[pi].lanes[li].announced_rep != Some(rep_idx) {
1943 let key = self.periods[pi].lanes[li].reps[rep_idx].init_key.clone();
1944 if self.periods[pi].lanes[li].announced_init.as_ref() != Some(&key) {
1945 if let Some(bytes) = self.inits.get(&key).and_then(|e| e.bytes.as_ref()) {
1946 self.outputs.push_back(Output::Init(bytes.as_ref().clone()));
1947 }
1948 self.periods[pi].lanes[li].announced_init = Some(key);
1949 }
1950 self.periods[pi].lanes[li].announced_rep = Some(rep_idx);
1951 self.announce_tracks(pi);
1952 }
1953 let lane = &self.periods[pi].lanes[li];
1954 let key = &lane.reps[rep_idx].init_key;
1955 let locals: Vec<u32> = self
1956 .inits
1957 .get(key)
1958 .map(|e| e.specs.iter().map(|s| s.track_id.value()).collect())
1959 .unwrap_or_default();
1960 let (base, count) = (lane.base, lane.count);
1961 let map = |local: u32| -> Option<TrackId> {
1962 let pos = locals.iter().position(|l| *l == local)?;
1963 let pos = u32::try_from(pos).ok()?;
1964 (pos < count).then(|| TrackId::new(base.saturating_add(pos)))
1965 };
1966 if staged.damaged > 0
1967 && let Some(id) = id
1968 {
1969 self.outputs.push_back(Output::FragmentDamaged {
1970 id,
1971 skipped: staged.damaged,
1972 });
1973 }
1974 for s in staged.skipped {
1975 let k = (s.track_id.map(TrackId::value), s.fourcc.clone());
1976 let first = self
1977 .inits
1978 .get_mut(&self.periods[pi].lanes[li].reps[rep_idx].init_key.clone())
1979 .is_some_and(|e| e.skipped_reported.insert(k));
1980 if first {
1981 self.outputs.push_back(Output::TrackSkipped {
1982 track_id: s.track_id.and_then(|t| map(t.value())),
1983 fourcc: s.fourcc,
1984 reason: s.reason,
1985 });
1986 }
1987 }
1988 for (local, samples) in staged.tracks {
1989 if samples.is_empty() {
1990 continue;
1991 }
1992 if let Some(track_id) = map(local) {
1993 self.last_was_discontinuity = false;
1994 self.outputs
1995 .push_back(Output::Samples { track_id, samples });
1996 }
1997 }
1998 }
1999
2000 fn init_url_of(&self, at: RepRef) -> std::result::Result<Option<String>, DropCause> {
2003 let Some(mpd) = self.mpd.as_ref() else {
2004 return Ok(None);
2005 };
2006 let planned = plan_representation(mpd, at, &PlanOptions::default());
2007 let Some((reference, _)) = Self::init_reference(mpd, at, &planned)? else {
2008 return Ok(None);
2009 };
2010 let Some((period, set, rep)) = at.resolve(mpd) else {
2011 return Ok(None);
2012 };
2013 let cands = filter_candidates(
2014 representation_candidates(mpd, period, set, rep),
2015 self.doc_url.as_ref(),
2016 self.dash.cross_origin_urls,
2017 );
2018 Ok(cands
2019 .first()
2020 .and_then(|c| c.resolve(self.doc_url.as_ref(), &reference)))
2021 }
2022
2023 fn manifest(&self, pi: usize) -> ManifestSelection {
2024 let mpd = self.mpd.as_ref().expect("mpd present");
2025 let p = &self.periods[pi];
2026 let period = &mpd.periods[p.index];
2027 let audio_group = p
2028 .selection
2029 .chosen_audio
2030 .and_then(|a| period.adaptation_sets.get(a.set))
2031 .and_then(|s| s.group.or(s.id))
2032 .map(|g| g.to_string());
2033 let variants = p
2034 .selection
2035 .variants
2036 .iter()
2037 .filter_map(|&at| {
2038 let (_, set, rep) = at.resolve(mpd)?;
2039 let common = effective_common(set, rep);
2040 let init_url = self.init_url_of(at).ok().flatten();
2041 Some(VariantInfo {
2042 rep: at,
2043 init_url,
2044 bandwidth: rep.bandwidth,
2045 resolution: common.width.zip(common.height),
2046 codecs: common.codecs,
2047 audio_group: audio_group.clone(),
2048 })
2049 })
2050 .collect();
2051 let audio: Vec<AudioRendition> = p
2052 .selection
2053 .audio_sets
2054 .iter()
2055 .filter_map(|a| {
2056 let set = period.adaptation_sets.get(a.set)?;
2057 let first = a.reps.first()?;
2058 let (_, _, first_rep) = first.resolve(mpd)?;
2059 let chosen = p
2060 .selection
2061 .chosen_audio
2062 .filter(|c| c.set == a.set)
2063 .unwrap_or(*first);
2064 Some(AudioRendition {
2065 set: a.set,
2066 group: set.group.or(set.id).map(|g| g.to_string()),
2067 name: set
2068 .labels
2069 .first()
2070 .map(|l| l.text.clone())
2071 .or_else(|| set.lang.clone())
2072 .unwrap_or_else(|| first_rep.id.clone()),
2073 language: set.lang.clone(),
2074 default: a.is_main,
2075 autoselect: true,
2076 init_url: self.init_url_of(chosen).ok().flatten(),
2077 reps: a.reps.clone(),
2078 })
2079 })
2080 .collect();
2081 let chosen_audio = p
2082 .selection
2083 .chosen_audio
2084 .and_then(|c| p.selection.audio_sets.iter().position(|a| a.set == c.set));
2085 ManifestSelection {
2086 period: p.index,
2087 variants,
2088 chosen: p.selection.chosen_variant,
2089 audio,
2090 chosen_audio,
2091 }
2092 }
2093
2094 pub fn select_representation(&mut self, index: usize) -> Result<()> {
2107 self.ensure_live()?;
2108 let pi = self.switchable_period()?;
2109 let at = *self.periods[pi]
2110 .selection
2111 .variants
2112 .get(index)
2113 .ok_or(Error::NoSuchVariant)?;
2114 self.switch_lane(pi, SetClass::Video, at)?;
2115 self.periods[pi].selection.chosen_variant = Some(index);
2116 Ok(())
2117 }
2118
2119 pub fn select_audio(&mut self, index: Option<usize>) -> Result<()> {
2133 self.ensure_live()?;
2134 let pi = self.switchable_period()?;
2135 let Some(index) = index else {
2136 let lane = self.periods[pi]
2137 .lanes
2138 .iter_mut()
2139 .find(|l| l.kind == SetClass::Audio)
2140 .ok_or(Error::NotSwitchable)?;
2141 if let Some(rep) = lane.reps.last_mut() {
2142 rep.dead = true;
2143 }
2144 self.periods[pi].selection.chosen_audio = None;
2145 return Ok(());
2146 };
2147 let at = self.best_audio_rep(pi, index)?;
2148 self.switch_lane(pi, SetClass::Audio, at)?;
2149 self.periods[pi].selection.chosen_audio = Some(at);
2150 Ok(())
2151 }
2152
2153 fn switchable_period(&self) -> Result<usize> {
2154 let pi = self.deliver;
2155 if pi != self.issue || self.periods.get(pi).is_none_or(|p| !p.started) {
2156 return Err(Error::NotSwitchable);
2157 }
2158 Ok(pi)
2159 }
2160
2161 fn best_audio_rep(&self, pi: usize, index: usize) -> Result<RepRef> {
2162 let mpd = self.mpd.as_ref().expect("mpd present");
2163 let set = self.periods[pi]
2164 .selection
2165 .audio_sets
2166 .get(index)
2167 .ok_or(Error::NoSuchAudio)?;
2168 let bandwidth = |r: &RepRef| r.resolve(mpd).map_or(0, |(_, _, rep)| rep.bandwidth);
2169 set.reps
2170 .iter()
2171 .max_by_key(|r| bandwidth(r))
2172 .copied()
2173 .ok_or(Error::NoSuchAudio)
2174 }
2175
2176 fn switch_lane(&mut self, pi: usize, kind: SetClass, at: RepRef) -> Result<()> {
2177 let revive = kind == SetClass::Audio;
2178 let li = self.periods[pi]
2179 .lanes
2180 .iter()
2181 .position(|l| l.kind == kind && (revive || l.current().is_some()))
2182 .ok_or(Error::NotSwitchable)?;
2183 let read = {
2184 let lane = &self.periods[pi].lanes[li];
2185 let cur = lane.reps.last().ok_or(Error::NotSwitchable)?;
2186 let plan = cur.plan.as_ref().ok_or(Error::NotSwitchable)?;
2187 let end = plan
2188 .len()
2189 .and_then(|n| n.checked_sub(1))
2190 .map_or(Ok(None), |last| plan.get(last));
2191 (cur.at, cur.dead, plan.get(cur.next_issue), end)
2192 };
2193 let (current_at, was_dead, current, end) = read;
2194 let switch_us = match current {
2195 Ok(Some(seg)) => seg.start_us,
2196 found => {
2197 let end = end.map_err(|_| Error::NotSwitchable)?;
2198 let end = end.ok_or(Error::NotSwitchable)?;
2199 if let Err(e) = found {
2200 self.rep_segment_error(pi, li, address_name(&e));
2201 return Err(Error::NotSwitchable);
2202 }
2203 end.start_us.saturating_add(end.duration_us)
2204 }
2205 };
2206 if current_at == at && !was_dead {
2207 return Ok(());
2208 }
2209 let aligned = {
2210 let mpd = self.mpd.as_ref().expect("mpd present");
2211 at.resolve(mpd).is_some_and(|(_, set, _)| {
2212 set.segment_alignment
2213 && matches!(
2214 set.start_with_sap,
2215 Some(START_WITH_SAP_1 | START_WITH_SAP_2)
2216 )
2217 })
2218 };
2219 let mut rep = match self.build_rep(at) {
2220 Ok(r) => r,
2221 Err(cause) => {
2222 self.drop_rep(pi, li, cause);
2223 return Err(Error::NotSwitchable);
2224 }
2225 };
2226 match rep.plan.as_ref() {
2227 Some(plan) => {
2228 rep.next_issue = match plan.index_at_or_before(switch_us) {
2229 Ok(Some(i)) => i,
2230 _ => 0,
2231 };
2232 rep.joined = true;
2233 let last = rep
2234 .next_issue
2235 .checked_sub(1)
2236 .map_or(Ok(None), |i| plan.get(i))
2237 .map_err(|_| Error::NotSwitchable)?;
2238 rep.last_time = last.map(|s| s.time_ticks);
2239 }
2240 None => rep.start_time_us = Some(switch_us),
2241 }
2242 if !aligned {
2243 self.periods[pi].lanes[li].queue.push_back(Slot {
2244 id: None,
2245 rep: 0,
2246 plan_index: 0,
2247 state: SlotState::Marker,
2248 });
2249 }
2250 let ri = self.periods[pi].lanes[li].reps.len();
2251 self.periods[pi].lanes[li].reps.push(rep);
2252 self.periods[pi].issue_done = false;
2253 self.register_rep(pi, li, ri);
2254 Ok(())
2255 }
2256}
2257
2258fn ranges_overlap(a_base: u32, a_len: u32, b_base: u32, b_len: u32) -> bool {
2259 let a_end = a_base.saturating_add(a_len);
2260 let b_end = b_base.saturating_add(b_len);
2261 a_base < b_end && b_base < a_end
2262}
2263
2264fn allocate_track_base(
2265 track_ids: &mut BTreeMap<TrackIdentity, (u32, u32)>,
2266 next_track_id: &mut u32,
2267 used: &mut Vec<(u32, u32)>,
2268 identity: TrackIdentity,
2269 n: u32,
2270) -> u32 {
2271 if n == 0 {
2272 return 0;
2273 }
2274 if let Some((existing, len)) = track_ids.get(&identity).copied()
2275 && len >= n
2276 && !used.iter().any(|&(b, l)| ranges_overlap(existing, n, b, l))
2277 {
2278 used.push((existing, n));
2279 return existing;
2280 }
2281 let base = *next_track_id;
2282 *next_track_id = next_track_id.saturating_add(n);
2283 track_ids.insert(identity, (base, n));
2284 used.push((base, n));
2285 base
2286}
2287
2288fn prune_track_ids<'a>(
2293 track_ids: &mut BTreeMap<TrackIdentity, (u32, u32)>,
2294 keep: impl Iterator<Item = &'a TrackIdentity>,
2295) {
2296 let keep: BTreeSet<&TrackIdentity> = keep.collect();
2297 track_ids.retain(|identity, _| keep.contains(identity));
2298}
2299
2300#[cfg(test)]
2301mod track_id_allocation_tests {
2302 use super::*;
2303
2304 #[test]
2305 fn two_same_identity_lanes_get_unique_bases() {
2306 let mut track_ids: BTreeMap<TrackIdentity, (u32, u32)> = BTreeMap::new();
2307 let mut next = 1u32;
2308 let mut used = Vec::new();
2309 let id = TrackIdentity {
2310 kind: SetClass::Audio,
2311 set_id: None,
2312 group: None,
2313 lang: Some("en".to_string()),
2314 roles: Vec::new(),
2315 };
2316 let a = allocate_track_base(&mut track_ids, &mut next, &mut used, id.clone(), 1);
2317 let b = allocate_track_base(&mut track_ids, &mut next, &mut used, id, 1);
2318 assert_ne!(a, b, "duplicate base for two lanes with equal identity");
2319 }
2320
2321 #[test]
2322 fn growing_identity_does_not_overlap_neighbor() {
2323 let mut track_ids: BTreeMap<TrackIdentity, (u32, u32)> = BTreeMap::new();
2324 let mut next = 1u32;
2325 let mut used = Vec::new();
2326 let a = TrackIdentity {
2327 kind: SetClass::Video,
2328 set_id: None,
2329 group: None,
2330 lang: None,
2331 roles: Vec::new(),
2332 };
2333 let b = TrackIdentity {
2334 kind: SetClass::Audio,
2335 set_id: None,
2336 group: None,
2337 lang: Some("en".to_string()),
2338 roles: Vec::new(),
2339 };
2340 let a1 = allocate_track_base(&mut track_ids, &mut next, &mut used, a.clone(), 1);
2342 let b1 = allocate_track_base(&mut track_ids, &mut next, &mut used, b.clone(), 1);
2344 let mut used2 = Vec::new();
2346 let a2 = allocate_track_base(&mut track_ids, &mut next, &mut used2, a.clone(), 2);
2347 let mut ids = std::collections::BTreeSet::new();
2349 ids.insert(a1);
2350 ids.insert(b1);
2351 ids.insert(a2);
2352 ids.insert(a2 + 1);
2353 assert_eq!(ids.len(), 4, "range overlap or duplicate ids");
2354 }
2355
2356 fn video(set_id: u64) -> TrackIdentity {
2357 TrackIdentity {
2358 kind: SetClass::Video,
2359 set_id: Some(set_id),
2360 group: None,
2361 lang: None,
2362 roles: Vec::new(),
2363 }
2364 }
2365
2366 fn audio(set_id: u64) -> TrackIdentity {
2367 TrackIdentity {
2368 kind: SetClass::Audio,
2369 set_id: Some(set_id),
2370 group: None,
2371 lang: None,
2372 roles: Vec::new(),
2373 }
2374 }
2375
2376 #[test]
2377 fn renumbered_periods_keep_the_identity_map_bounded_and_ids_unique_per_kind() {
2378 let mut track_ids: BTreeMap<TrackIdentity, (u32, u32)> = BTreeMap::new();
2379 let mut next = 1u32;
2380 let mut owner: BTreeMap<u32, SetClass> = BTreeMap::new();
2381 let mut previous: Vec<TrackIdentity> = Vec::new();
2382 let mut peak = 0usize;
2383 for period in 0..200u64 {
2384 let current = vec![video(period), audio(period)];
2385 let mut used = Vec::new();
2386 for identity in ¤t {
2387 let base =
2388 allocate_track_base(&mut track_ids, &mut next, &mut used, identity.clone(), 1);
2389 let kind = owner.entry(base).or_insert(identity.kind);
2390 assert_eq!(*kind, identity.kind, "id {base} announced for two kinds");
2391 }
2392 prune_track_ids(&mut track_ids, current.iter().chain(previous.iter()));
2393 peak = peak.max(track_ids.len());
2394 previous = current;
2395 }
2396 assert!(peak <= 4, "identity map grew to {peak}");
2397 assert_eq!(next, 401, "ids must stay monotonic, never recycled");
2398 }
2399}