1use std::collections::{BTreeMap, BTreeSet};
12
13use url::Url;
14
15use super::*;
16use crate::client::action::{MpdValidators, UtcId};
17use crate::client::config::DashClockPolicy;
18use crate::client::meta::UtcResponse;
19use crate::client::utc_sampler::{Step, UtcSampler};
20use crate::timing::clock::{AdoptReason, Anchor, ClockSource, OfferOutcome, OfferPolicy};
21use crate::timing::live_edge::{join_index, oldest_in_window, presentation_delay_us};
22use crate::timing::period::duration_us;
23use crate::timing::utc_timing::{Exchange, UtcSample, sample_from_date_header};
24use broadcast_dash::descriptors::SCHEME_RESET;
25
26const FORCED_RESYNC_MIN_GAP_US: u64 = 30_000_000;
29const EARLY_RETRY_CAP_US: u64 = 1_000_000;
31const EXPIRED_REJOIN_RUN: u32 = 3;
33const PERIOD_FINAL_SEGMENTS: i128 = 2;
36const MAX_LANE_STEPS: usize = 8;
38const EARLY_SHIFT_MAX: u32 = 20;
40
41#[derive(Default)]
42pub(super) struct Refresh {
43 pub(super) next_at: Option<u64>,
44 pub(super) forced_at: Option<u64>,
45 pub(super) last_request_us: Option<u64>,
46 pub(super) publish_time_us: Option<i64>,
47 pub(super) body: Vec<u8>,
48 pub(super) validators: MpdValidators,
49 pub(super) redirect_url: Option<String>,
50 pub(super) awaiting_retry: bool,
51}
52
53#[derive(Default, Clone, Copy)]
54pub(super) struct Early {
55 attempts: u32,
56 retry_at: Option<u64>,
57}
58
59pub(super) struct Live {
60 pub(super) ready: bool,
61 sampler: Option<UtcSampler>,
62 next_resync_us: Option<u64>,
63 last_forced_resync_us: Option<u64>,
64 pub(super) refresh: Refresh,
65 pub(super) early: BTreeMap<ResourceId, Early>,
66 missing_seen: BTreeSet<RepId>,
67 announced_clock: Option<ClockSource>,
68 wake_utc: Option<i128>,
69 mpd_exchange: Exchange,
70 fetch_utc_us: Option<i128>,
71}
72
73impl Live {
74 pub(super) fn bookkeeping(&self) -> usize {
75 self.early.len() + self.missing_seen.len()
76 }
77
78 fn new(now_us: u64) -> Self {
79 Self {
80 ready: false,
81 sampler: None,
82 next_resync_us: None,
83 last_forced_resync_us: None,
84 refresh: Refresh::default(),
85 early: BTreeMap::new(),
86 missing_seen: BTreeSet::new(),
87 announced_clock: None,
88 wake_utc: None,
89 mpd_exchange: Exchange::new(now_us, now_us),
90 fetch_utc_us: None,
91 }
92 }
93}
94
95fn status_of(failure: Failure) -> Option<u16> {
96 match failure {
97 Failure::Status(s) => Some(s),
98 Failure::Transport | Failure::Timeout | Failure::TooLarge => None,
99 }
100}
101
102pub(super) fn failure_kind(failure: Failure) -> FailureKind {
103 match failure {
104 Failure::Status(STATUS_NOT_FOUND | STATUS_GONE) => FailureKind::Missing,
105 Failure::Status(STATUS_TOO_EARLY) => FailureKind::TooEarly,
106 Failure::Status(_) => FailureKind::Server,
107 Failure::Transport | Failure::Timeout => FailureKind::Transport,
108 Failure::TooLarge => FailureKind::Invalid,
109 }
110}
111
112fn i128_from_i64(v: i64) -> i128 {
113 i128::from(v)
114}
115
116fn unix_i64(us: i128) -> i64 {
117 i64::try_from(us.clamp(i128::from(i64::MIN), i128::from(i64::MAX))).unwrap_or(i64::MAX)
118}
119
120impl DashClient {
121 pub fn set_wall_clock(&mut self, now_us: u64, unix_us: u64) {
131 if self.stopped() {
132 return;
133 }
134 let sample = UtcSample {
135 mono_us: now_us,
136 utc_us: unix_i64(i128::from(unix_us)),
137 uncertainty_us: self.dash.host_clock_uncertainty_us,
138 };
139 let allow_downgrade = self.dash.clock_policy == DashClockPolicy::PreferHost;
140 let outcome = self.clock.offer(
141 sample,
142 ClockSource::Host,
143 now_us,
144 OfferPolicy::new(self.dash.utc_resync_us, allow_downgrade),
145 );
146 let stepped = matches!(outcome, OfferOutcome::Adopted(AdoptReason::Step));
147 let Some(live) = self.live.as_mut() else {
148 return;
149 };
150 if allow_downgrade && !live.ready {
151 let gating = live.sampler.as_ref().is_some_and(|s| s.gating);
152 if gating {
153 live.sampler = None;
154 self.finish_ladder(now_us);
155 }
156 }
157 if stepped {
158 self.force_refresh(now_us);
159 }
160 self.announce_clock();
161 }
162
163 #[must_use]
165 pub fn clock_anchor(&self) -> Option<Anchor> {
166 self.clock.anchor().copied()
167 }
168
169 #[must_use]
172 pub fn utc_at(&self, now_us: u64) -> Option<i64> {
173 self.clock.utc_at(now_us)
174 }
175
176 #[must_use]
181 pub fn next_wake(&self) -> Option<u64> {
182 let live = self.live.as_ref()?;
183 if self.stopped() {
184 return None;
185 }
186 let mut best: Option<u64> = None;
187 let mut take = |t: u64| best = Some(best.map_or(t, |b| b.min(t)));
188 if let Some(t) = self.refresh_due_at() {
189 take(t);
190 }
191 if let Some(s) = live.sampler.as_ref() {
192 if let Some(d) = s.deadline_us() {
193 take(d);
194 }
195 if let Some(w) = s.wait_deadline_us.filter(|_| s.gating) {
196 take(w);
197 }
198 } else if live.ready
199 && let Some(t) = live.next_resync_us
200 {
201 take(t);
202 }
203 for e in live.early.values() {
204 if let Some(t) = e.retry_at {
205 take(t);
206 }
207 }
208 if let Some(w) = live.wake_utc
209 && let Some(m) = self.clock.mono_for_utc(unix_i64(w))
210 {
211 take(m);
212 }
213 best
214 }
215
216 pub fn on_utc_response(
222 &mut self,
223 now_us: u64,
224 id: UtcId,
225 response: &UtcResponse<'_>,
226 ) -> Result<()> {
227 self.ensure_live()?;
228 let dash = self.dash;
229 let sample = {
230 let sampler = self
231 .live
232 .as_mut()
233 .and_then(|l| l.sampler.as_mut())
234 .filter(|s| s.awaits(id))
235 .ok_or(Error::UnrequestedUtc { id })?;
236 sampler.on_response(id, response, &dash)
237 };
238 if let Some(sample) = sample {
239 self.utc_sample(now_us, sample);
240 }
241 Ok(())
242 }
243
244 pub fn on_utc_failure(&mut self, _now_us: u64, id: UtcId, failure: Failure) -> Result<()> {
252 self.ensure_live()?;
253 let sampler = self
254 .live
255 .as_mut()
256 .and_then(|l| l.sampler.as_mut())
257 .filter(|s| s.awaits(id))
258 .ok_or(Error::UnrequestedUtc { id })?;
259 sampler.on_failure(id, failure);
260 Ok(())
261 }
262
263 pub fn on_mpd_unchanged(&mut self, now_us: u64, meta: &ResponseMeta<'_>) -> Result<()> {
269 self.ensure_live()?;
270 if !self.mpd_inflight || self.live.is_none() {
271 return Err(Error::MpdNotAwaited);
272 }
273 self.mpd_inflight = false;
274 self.note_exchange(now_us, meta);
275 self.schedule_refresh(now_us);
276 Ok(())
277 }
278
279 pub(super) fn is_live_mpd(&self) -> bool {
282 self.mpd
283 .as_ref()
284 .is_some_and(|m| m.kind == PresentationType::Dynamic)
285 }
286
287 pub(super) fn live_blocked(&self) -> bool {
288 self.is_live_mpd() && self.live.as_ref().is_none_or(|l| !l.ready)
289 }
290
291 pub(super) fn live_open(&self) -> bool {
292 self.mpd.as_ref().is_some_and(|m| {
293 m.kind == PresentationType::Dynamic
294 && m.minimum_update_period.is_some()
295 && m.media_presentation_duration.is_none()
296 })
297 }
298
299 pub(super) fn period_open(&self, pi: usize, now_us: u64) -> bool {
303 if !self.live_open() {
304 return false;
305 }
306 let Some(mpd) = self.mpd.as_ref() else {
307 return false;
308 };
309 let run = &self.periods[pi];
310 let Some(end) = period_timings(mpd).get(run.index).and_then(|t| t.end_us) else {
311 return true;
312 };
313 let len = end.saturating_sub(run.start_us);
314 let reaches = run.lanes.iter().filter_map(Lane::current).all(|r| {
315 r.plan.as_ref().is_some_and(|p| {
316 p.len()
317 .and_then(|n| n.checked_sub(1))
318 .map_or(Ok(None), |last| p.get(last))
319 .ok()
320 .flatten()
321 .is_some_and(|s| {
322 s.start_us
323 .saturating_add(s.duration_us)
324 .saturating_add(MISSING_TAIL_MAX_US)
325 >= len
326 })
327 })
328 });
329 if reaches {
330 return false;
331 }
332 let Some(utc) = self.utc_now(now_us) else {
333 return false;
334 };
335 let ast = i128::from(mpd.availability_start_time_us.unwrap_or(0));
336 let mup = mpd.minimum_update_period.map_or(0, duration_us);
337 let seg = mpd.max_segment_duration.map_or(0, duration_us);
338 let grace = mup.max(seg.saturating_mul(PERIOD_FINAL_SEGMENTS));
339 utc < ast.saturating_add(end).saturating_add(grace)
340 }
341
342 pub(super) fn can_activate(&self, next: usize) -> bool {
343 let Some(mpd) = self.mpd.as_ref() else {
344 return false;
345 };
346 if next >= mpd.periods.len() {
347 return false;
348 }
349 mpd.kind == PresentationType::Static
350 || period_timings(mpd)
351 .get(next)
352 .is_some_and(|t| t.start_us.is_some())
353 }
354
355 pub(super) fn utc_now(&self, now_us: u64) -> Option<i128> {
356 self.clock.utc_at(now_us).map(i128_from_i64)
357 }
358
359 pub(super) fn plan_options(&self) -> PlanOptions<'static> {
360 let mut o = PlanOptions::default()
361 .with_availability_time_offset(self.dash.use_availability_time_offset);
362 if let Some(h) = self.horizon() {
363 o = o.with_horizon_utc_us(h);
364 }
365 o
366 }
367
368 fn horizon(&self) -> Option<i128> {
369 let mpd = self.mpd.as_ref()?;
370 let mup = duration_us(mpd.minimum_update_period?);
371 let fetch = self.live.as_ref()?.fetch_utc_us?;
372 Some(fetch.saturating_add(mup))
373 }
374
375 pub(super) fn reset_wake(&mut self) {
378 if let Some(l) = self.live.as_mut() {
379 l.wake_utc = None;
380 }
381 }
382
383 pub(super) fn note_wake(&mut self, at_utc_us: i128) {
384 if let Some(l) = self.live.as_mut() {
385 l.wake_utc = Some(l.wake_utc.map_or(at_utc_us, |w| w.min(at_utc_us)));
386 }
387 }
388
389 pub(super) fn note_exhausted(&mut self, now_us: u64) {
390 if !self.live_open() {
391 return;
392 }
393 let mut failed: Vec<(usize, DropCause)> = Vec::new();
394 let tail_us = self
395 .periods
396 .get(self.issue)
397 .map(|p| {
398 p.lanes
399 .iter()
400 .enumerate()
401 .filter_map(|(li, l)| {
402 let r = l.current()?;
403 let plan = r.plan.as_ref()?;
404 let last = plan.len()?.checked_sub(1)?;
405 match plan.get(last) {
406 Ok(s) => s.map(|s| s.duration_us),
407 Err(e) => {
408 failed.push((li, DropCause::Addressing(address_name(&e))));
409 None
410 }
411 }
412 })
413 .max()
414 .unwrap_or(0)
415 })
416 .unwrap_or(0);
417 for (li, cause) in failed {
418 self.drop_rep(self.issue, li, cause);
419 }
420 let wait = u64::try_from(tail_us.max(0)).unwrap_or(u64::MAX);
421 self.force_refresh_after(now_us, wait);
422 }
423
424 fn refresh_due_at(&self) -> Option<u64> {
427 let live = self.live.as_ref()?;
428 if !self.live_open() || self.mpd_inflight || self.want_mpd || live.refresh.awaiting_retry {
429 return None;
430 }
431 match (live.refresh.next_at, live.refresh.forced_at) {
432 (Some(a), Some(b)) => Some(a.min(b)),
433 (a, b) => a.or(b),
434 }
435 }
436
437 pub(super) fn tick(&mut self, now_us: u64) {
438 if self.live.is_none() {
439 return;
440 }
441 let gate_expired = self
442 .live
443 .as_ref()
444 .and_then(|l| l.sampler.as_ref())
445 .is_some_and(|s| s.gating && s.wait_deadline_us.is_some_and(|d| now_us >= d));
446 if gate_expired {
447 if let Some(l) = self.live.as_mut() {
448 l.sampler = None;
449 l.next_resync_us = Some(now_us.saturating_add(self.dash.utc_resync_us));
450 }
451 self.finish_ladder(now_us);
452 if self.stopped() {
453 return;
454 }
455 }
456 let resync_due = self.live.as_ref().is_some_and(|l| {
457 l.ready && l.sampler.is_none() && l.next_resync_us.is_some_and(|t| now_us >= t)
458 });
459 if resync_due {
460 if let Some(l) = self.live.as_mut() {
461 l.next_resync_us = None;
462 }
463 self.start_sampler(now_us, false);
464 }
465 let mut due: Vec<ResourceId> = Vec::new();
466 if let Some(l) = self.live.as_mut() {
467 for (id, e) in &mut l.early {
468 if e.retry_at.is_some_and(|t| now_us >= t) {
469 e.retry_at = None;
470 due.push(*id);
471 }
472 }
473 }
474 for id in due.into_iter().rev() {
475 if self.reqs.contains_key(&id) && !self.inflight.contains(&id) {
476 self.queue.push_front(id);
477 }
478 }
479 if self.refresh_due_at().is_some_and(|t| now_us >= t) {
480 self.want_mpd = true;
481 }
482 }
483
484 pub(super) fn mpd_action(&mut self, now_us: u64) -> Option<Action> {
485 if !self.want_mpd || self.mpd_inflight {
486 return None;
487 }
488 self.want_mpd = false;
489 self.mpd_inflight = true;
490 let mut url = self.mpd_url.clone();
491 let mut validators = MpdValidators::default();
492 if let Some(live) = self.live.as_mut() {
493 live.refresh.last_request_us = Some(now_us);
494 live.refresh.next_at = None;
495 live.refresh.forced_at = None;
496 validators = live.refresh.validators.clone();
497 }
498 if let Some(u) = self.update_url() {
499 url = u;
500 }
501 Some(Action::FetchMpd { url, validators })
502 }
503
504 fn update_url(&self) -> Option<String> {
505 let live = self.live.as_ref()?;
506 let mpd = self.mpd.as_ref()?;
507 if let Some(loc) = mpd.locations.first() {
508 let base = self.doc_url.as_ref();
509 let resolved = match base {
510 Some(b) => b.join(loc).ok().map(|u| u.to_string()),
511 None => Url::parse(loc).ok().map(|u| u.to_string()),
512 };
513 if let Some(url) = resolved
514 && let Ok(parsed) = Url::parse(&url)
515 && matches!(parsed.scheme(), "http" | "https")
516 {
517 let pin = !self.dash.cross_origin_urls;
518 let origin_ok = !pin
519 || self
520 .doc_url
521 .as_ref()
522 .is_none_or(|d| same_origin(d, &parsed));
523 if origin_ok {
524 return Some(url);
525 }
526 }
527 }
528 live.refresh.redirect_url.clone()
529 }
530
531 pub(super) fn utc_action(&mut self, now_us: u64) -> Option<Action> {
532 let dash = self.dash;
533 let step = {
534 let live = self.live.as_mut()?;
535 let sampler = live.sampler.as_mut()?;
536 sampler.poll(now_us, &mut self.next_utc_id, &dash)
537 };
538 match step {
539 Step::Fetch(a) => Some(a),
540 Step::Sample(sample) => {
541 self.utc_sample(now_us, sample);
542 None
543 }
544 Step::Exhausted => {
545 self.sampler_exhausted(now_us);
546 None
547 }
548 Step::Idle => None,
549 }
550 }
551
552 pub(super) fn start_live(&mut self, now_us: u64, bytes: &[u8], meta: &ResponseMeta<'_>) {
555 self.live = Some(Live::new(now_us));
556 if let Some(f) = meta.final_url
557 && let Some(u) = self.accept_final_url(f)
558 {
559 if let Some(l) = self.live.as_mut() {
560 l.refresh.redirect_url = Some(u.to_string());
561 }
562 self.doc_url = Some(u);
563 }
564 self.note_exchange(now_us, meta);
565 if let Some(l) = self.live.as_mut() {
566 l.refresh.body = bytes.to_vec();
567 l.refresh.publish_time_us = self.mpd.as_ref().and_then(|m| m.publish_time_us);
568 l.refresh.last_request_us = Some(meta.sent_us.min(meta.received_us));
569 }
570 self.speculative_activate(now_us);
571 let want_sampler = self.begin_sampler(now_us, meta);
572 if !want_sampler {
573 self.finish_ladder(now_us);
574 }
575 self.schedule_refresh(now_us);
576 }
577
578 fn note_exchange(&mut self, now_us: u64, meta: &ResponseMeta<'_>) {
579 let adopted = meta.final_url.and_then(|f| self.accept_final_url(f));
580 let Some(live) = self.live.as_mut() else {
581 return;
582 };
583 live.mpd_exchange = Exchange::new(meta.sent_us.min(meta.received_us), meta.received_us);
584 if let Some(e) = meta.etag {
585 live.refresh.validators.etag = Some(e.to_owned());
586 }
587 if let Some(m) = meta.last_modified {
588 live.refresh.validators.last_modified = Some(m.to_owned());
589 }
590 if let Some(u) = adopted {
591 live.refresh.redirect_url = Some(u.to_string());
592 self.doc_url = Some(u);
593 }
594 if let Some(date) = meta.date {
595 let exch = live.mpd_exchange;
596 if let Ok(sample) = sample_from_date_header(date, exch, self.dash.utc_max_rtt_us) {
597 self.clock.offer(
598 sample,
599 ClockSource::MpdDate,
600 now_us,
601 OfferPolicy::new(self.dash.utc_resync_us, false),
602 );
603 }
604 }
605 let mid = self.live.as_ref().map_or(now_us, |l| {
606 l.mpd_exchange.sent_us + (l.mpd_exchange.received_us - l.mpd_exchange.sent_us) / 2
607 });
608 let fetch_utc = self.clock.utc_at(mid).map(i128_from_i64).or_else(|| {
609 self.mpd
610 .as_ref()
611 .and_then(|m| m.publish_time_us)
612 .map(i128_from_i64)
613 });
614 if let Some(l) = self.live.as_mut() {
615 l.fetch_utc_us = fetch_utc;
616 }
617 }
618
619 fn begin_sampler(&mut self, now_us: u64, meta: &ResponseMeta<'_>) -> bool {
620 let _ = meta;
621 let host_first = self.dash.clock_policy == DashClockPolicy::PreferHost
622 && self
623 .clock
624 .anchor()
625 .is_some_and(|a| a.source == ClockSource::Host);
626 if host_first {
627 return false;
628 }
629 self.start_sampler(now_us, true)
630 }
631
632 fn start_sampler(&mut self, now_us: u64, gating: bool) -> bool {
633 let host_holds = self.dash.clock_policy == DashClockPolicy::PreferHost
634 && self
635 .clock
636 .anchor()
637 .is_some_and(|a| a.source == ClockSource::Host);
638 if !gating && (host_holds || !self.is_live_mpd()) {
639 return false;
640 }
641 let Some(mpd) = self.mpd.as_ref() else {
642 return false;
643 };
644 let elements = mpd.utc_timings.clone();
645 let unsupported = elements.iter().any(|e| {
646 matches!(
647 e.scheme,
648 crate::timing::utc_timing::UtcScheme::Ntp
649 | crate::timing::utc_timing::UtcScheme::Sntp
650 | crate::timing::utc_timing::UtcScheme::Unknown
651 )
652 });
653 let doc = self.doc_url.clone();
654 let pin = !self.dash.cross_origin_urls;
655 let mut cross = false;
656 let exchange = self
657 .live
658 .as_ref()
659 .map_or(Exchange::new(now_us, now_us), |l| l.mpd_exchange);
660 let mut accept = |value: &str| -> Option<String> {
661 let url = match &doc {
662 Some(d) => d.join(value).ok()?,
663 None => Url::parse(value).ok()?,
664 };
665 if pin
666 && let Some(d) = &doc
667 && !same_origin(d, &url)
668 {
669 cross = true;
670 return None;
671 }
672 matches!(url.scheme(), "http" | "https").then(|| url.to_string())
673 };
674 let mut sampler = UtcSampler::new(&elements, &mut accept, exchange);
675 if unsupported {
676 self.notice_once(Notice::UtcSchemeUnsupported);
677 }
678 if cross {
679 self.notice_once(Notice::UtcCrossOrigin);
680 }
681 if sampler.is_empty() {
682 return false;
683 }
684 sampler.gating = gating;
685 if gating {
686 sampler.wait_deadline_us = Some(now_us.saturating_add(self.dash.utc_wait_us));
687 }
688 if let Some(l) = self.live.as_mut() {
689 l.sampler = Some(sampler);
690 }
691 true
692 }
693
694 fn utc_sample(&mut self, now_us: u64, sample: UtcSample) {
695 let outcome = self.clock.offer(
696 sample,
697 ClockSource::UtcTiming,
698 now_us,
699 OfferPolicy::new(self.dash.utc_resync_us, false),
700 );
701 let stepped = matches!(outcome, OfferOutcome::Adopted(AdoptReason::Step));
702 let ready = {
703 let Some(live) = self.live.as_mut() else {
704 return;
705 };
706 live.sampler = None;
707 live.next_resync_us = Some(now_us.saturating_add(self.dash.utc_resync_us));
708 live.ready
709 };
710 if stepped {
711 self.force_refresh(now_us);
712 }
713 if ready {
714 self.announce_clock();
715 } else {
716 self.finish_ladder(now_us);
717 }
718 }
719
720 fn sampler_exhausted(&mut self, now_us: u64) {
721 let ready = {
722 let Some(live) = self.live.as_mut() else {
723 return;
724 };
725 live.sampler = None;
726 live.next_resync_us = Some(now_us.saturating_add(self.dash.utc_resync_us));
727 live.ready
728 };
729 if !ready {
730 self.finish_ladder(now_us);
731 }
732 }
733
734 fn finish_ladder(&mut self, now_us: u64) {
735 if self.clock.anchor().is_none() {
736 self.go_fatal(FatalKind::ClockUnavailable);
737 return;
738 }
739 if let Some(live) = self.live.as_mut() {
740 if live.ready {
741 return;
742 }
743 live.ready = true;
744 }
745 self.announce_clock();
746 self.on_clock_ready(now_us);
747 }
748
749 fn announce_clock(&mut self) {
750 if self.stopped() {
751 return;
752 }
753 let Some(anchor) = self.clock.anchor().copied() else {
754 return;
755 };
756 let Some(live) = self.live.as_mut() else {
757 return;
758 };
759 if !live.ready || live.announced_clock == Some(anchor.source) {
760 return;
761 }
762 live.announced_clock = Some(anchor.source);
763 self.outputs.push_back(Output::ClockSynced {
764 source: anchor.source,
765 uncertainty_us: anchor.uncertainty_us,
766 });
767 }
768
769 fn request_resync(&mut self, now_us: u64) {
770 let Some(live) = self.live.as_mut() else {
771 return;
772 };
773 if !live.ready || live.sampler.is_some() {
774 return;
775 }
776 let spaced = live
777 .last_forced_resync_us
778 .is_none_or(|t| now_us >= t.saturating_add(FORCED_RESYNC_MIN_GAP_US));
779 if spaced {
780 live.last_forced_resync_us = Some(now_us);
781 live.next_resync_us = Some(now_us);
782 }
783 }
784
785 fn delay_us(&self, plan_hint_us: i128) -> i128 {
788 let Some(mpd) = self.mpd.as_ref() else {
789 return 0;
790 };
791 let max_seg = mpd.max_segment_duration.map_or(plan_hint_us, duration_us);
792 presentation_delay_us(
793 mpd.suggested_presentation_delay,
794 max_seg,
795 mpd.time_shift_buffer_depth.map(duration_us),
796 self.dash.live_delay_us.map(i128::from),
797 )
798 }
799
800 fn choose_join_period(&self, utc: i128) -> Option<usize> {
801 let mpd = self.mpd.as_ref()?;
802 let ast = i128::from(mpd.availability_start_time_us?);
803 let target = utc.saturating_sub(self.delay_us(0)).saturating_sub(ast);
804 let timings = period_timings(mpd);
805 let mut chosen: Option<usize> = None;
806 for (i, t) in timings.iter().enumerate() {
807 let Some(start) = t.start_us else { continue };
808 if chosen.is_none() || start <= target {
809 chosen = Some(i);
810 }
811 }
812 chosen
813 }
814
815 fn speculative_activate(&mut self, now_us: u64) {
816 let provisional = self.utc_now(now_us);
817 let pick = provisional
818 .and_then(|u| self.choose_join_period(u))
819 .or_else(|| {
820 let mpd = self.mpd.as_ref()?;
821 period_timings(mpd)
822 .iter()
823 .rposition(|t| t.start_us.is_some())
824 });
825 if let Some(index) = pick {
826 self.activate(index);
827 self.issue = self.periods.len() - 1;
828 }
829 }
830
831 fn on_clock_ready(&mut self, now_us: u64) {
832 let Some(utc) = self.utc_now(now_us) else {
833 return;
834 };
835 let target = self.choose_join_period(utc);
836 let current = self.periods.last().map(|p| p.index);
837 let started = self.periods.last().is_some_and(|p| p.started);
838 match (target, current) {
839 (Some(t), Some(c)) if t != c && !started => {
840 self.abandon_last_run();
841 self.activate(t);
842 }
843 (Some(t), None) => self.activate(t),
844 _ => {}
845 }
846 if !self.periods.is_empty() {
847 self.issue = self.periods.len() - 1;
848 if self.deliver >= self.periods.len() {
849 self.deliver = self.periods.len() - 1;
850 }
851 }
852 self.replan_all();
853 self.drain();
854 }
855
856 fn abandon_last_run(&mut self) {
857 let Some(run) = self.periods.pop() else {
858 return;
859 };
860 let pi = self.periods.len();
861 let mut ids: Vec<RepId> = Vec::new();
862 for lane in &run.lanes {
863 for rep in &lane.reps {
864 ids.push(rep.id);
865 }
866 }
867 self.rep_loc.retain(|_, (p, _, _)| *p != pi);
868 let rep_set: BTreeSet<RepId> = ids.iter().copied().collect();
869 let gone: Vec<ResourceId> = self
870 .reqs
871 .iter()
872 .filter(|(_, i)| rep_set.contains(&i.rep))
873 .map(|(id, _)| *id)
874 .collect();
875 for id in gone {
876 self.reqs.remove(&id);
877 self.inflight.remove(&id);
878 self.parked.remove(&id);
879 self.queue.retain(|q| *q != id);
880 }
881 let used: BTreeSet<InitKey> = self
882 .periods
883 .iter()
884 .flat_map(|p| p.lanes.iter())
885 .flat_map(|l| l.reps.iter())
886 .map(|r| r.init_key.clone())
887 .collect();
888 self.inits
889 .retain(|k, e| e.state == InitState::Ready || used.contains(k));
890 self.issue = self.periods.len().saturating_sub(1);
891 }
892
893 pub(super) fn live_lane_candidate(
894 &mut self,
895 pi: usize,
896 li: usize,
897 utc: i128,
898 now_us: u64,
899 ) -> Candidate {
900 let margin = i128::from(self.dash.availability_margin_us);
901 for _ in 0..MAX_LANE_STEPS {
902 let lane = &self.periods[pi].lanes[li];
903 let Some(rep) = lane.current() else {
904 return Candidate::Idle;
905 };
906 let Some(plan) = rep.plan.as_ref() else {
907 return Candidate::Idle;
908 };
909 if !rep.joined {
910 let hint = match plan.get(0) {
911 Ok(Some(s)) => s.duration_us,
912 Ok(None) => 0,
913 Err(e) => return Candidate::Unaddressable(address_name(&e)),
914 };
915 let delay = self.delay_us(hint);
916 match join_index(plan, utc, delay) {
917 Some(idx) => {
918 let last = match idx.checked_sub(1).map_or(Ok(None), |i| plan.get(i)) {
919 Ok(s) => s.map(|s| s.time_ticks),
920 Err(e) => return Candidate::Unaddressable(address_name(&e)),
921 };
922 let lane = &mut self.periods[pi].lanes[li];
923 let ri = lane.reps.len() - 1;
924 let rep = &mut lane.reps[ri];
925 rep.next_issue = idx;
926 rep.joined = true;
927 rep.last_time = last;
928 continue;
929 }
930 None => {
931 let first = match plan.get(0) {
932 Ok(s) => s,
933 Err(e) => return Candidate::Unaddressable(address_name(&e)),
934 };
935 let first = first
936 .and_then(|s| s.available_from_us)
937 .map(|a| a.saturating_add(margin));
938 return match first {
939 Some(a) if a > utc => Candidate::Wait(a),
940 _ => {
941 self.force_refresh(now_us);
942 self.request_resync(now_us);
943 Candidate::Idle
944 }
945 };
946 }
947 }
948 }
949 let seg = match plan.get(rep.next_issue) {
950 Ok(Some(seg)) => seg,
951 Ok(None) => return Candidate::Idle,
952 Err(e) => return Candidate::Unaddressable(address_name(&e)),
953 };
954 if seg.available_until_us.is_some_and(|u| u < utc) {
955 self.expire_before_request(pi, li, utc);
956 continue;
957 }
958 let from = seg
959 .available_from_us
960 .unwrap_or(i128::MIN)
961 .saturating_add(margin);
962 if utc < from {
963 return Candidate::Wait(from);
964 }
965 return Candidate::Ready(seg.start_us);
966 }
967 Candidate::Idle
968 }
969
970 fn expire_before_request(&mut self, pi: usize, li: usize, utc: i128) {
971 let (oldest, next) = {
972 let lane = &self.periods[pi].lanes[li];
973 let Some(rep) = lane.current() else { return };
974 let Some(plan) = rep.plan.as_ref() else {
975 return;
976 };
977 (oldest_in_window(plan, utc), rep.next_issue)
978 };
979 self.outputs.push_back(Output::Notice(Notice::FellBehind));
980 let lane = &mut self.periods[pi].lanes[li];
981 lane.expired_run = lane.expired_run.saturating_add(1);
982 let rejoin = lane.expired_run >= EXPIRED_REJOIN_RUN || oldest.is_none();
983 match oldest {
984 Some(o) if o > next && !rejoin => {
985 let ri = lane.reps.len() - 1;
986 lane.reps[ri].next_issue = o;
987 let read = {
988 let rep = &lane.reps[ri];
989 rep.plan.as_ref().map(|p| {
990 let at_o = p.get(o);
991 let before = o.checked_sub(1).map_or(Ok(None), |i| p.get(i));
992 (at_o, before)
993 })
994 };
995 let fail = match &read {
996 Some((Err(e), _)) | Some((_, Err(e))) => {
997 Some(DropCause::Addressing(address_name(e)))
998 }
999 _ => None,
1000 };
1001 let last = match &read {
1002 Some((Ok(_), Ok(last))) => last.as_ref().map(|s| s.time_ticks),
1003 _ => None,
1004 };
1005 lane.reps[ri].last_time = last;
1006 push_marker(lane);
1007 if let Some(cause) = fail {
1008 self.drop_rep(pi, li, cause);
1009 }
1010 }
1011 _ => self.rejoin_lane(pi, li),
1012 }
1013 }
1014
1015 fn rejoin_lane(&mut self, pi: usize, li: usize) {
1016 let lane = &mut self.periods[pi].lanes[li];
1017 lane.expired_run = 0;
1018 if let Some(rep) = lane.reps.last_mut() {
1019 rep.joined = false;
1020 }
1021 push_marker(lane);
1022 }
1023
1024 pub(super) fn segment_delivered(&mut self, id: ResourceId) {
1025 let rep = id.rep();
1026 if let Some(live) = self.live.as_mut() {
1027 live.early.remove(&id);
1028 live.missing_seen.remove(&rep);
1029 }
1030 if let Some((pi, li, _)) = self.rep_loc.get(&rep).copied()
1031 && let Some(lane) = self.periods.get_mut(pi).and_then(|p| p.lanes.get_mut(li))
1032 {
1033 lane.expired_run = 0;
1034 }
1035 }
1036
1037 pub(super) fn live_segment_failure(
1040 &mut self,
1041 now_us: u64,
1042 id: ResourceId,
1043 failure: Failure,
1044 ) -> Result<FailureClass> {
1045 let status = status_of(failure);
1046 if matches!(
1047 status,
1048 Some(STATUS_NOT_FOUND | STATUS_GONE | STATUS_TOO_EARLY)
1049 ) && let Some(utc) = self.utc_now(now_us)
1050 {
1051 let (from, until) = self
1052 .reqs
1053 .get(&id)
1054 .map_or((None, None), |r| (r.avail_from_us, r.avail_until_us));
1055 let grace = i128::from(self.dash.early_grace_us);
1056 let past_window = until.is_some_and(|u| utc > u);
1057 let refused_past_window = past_window
1058 && status == Some(STATUS_TOO_EARLY)
1059 && self
1060 .clock
1061 .anchor()
1062 .is_some_and(|a| a.source == ClockSource::UtcTiming);
1063 let early = !refused_past_window
1064 && (status == Some(STATUS_TOO_EARLY)
1065 || from.is_some_and(|f| utc < f.saturating_add(grace)));
1066 if early {
1067 return Ok(self.early_wait(now_us, id, from, utc));
1068 }
1069 if past_window {
1070 return Ok(self.expired_segment(now_us, id, utc));
1071 }
1072 }
1073 let kind = failure_kind(failure);
1074 if kind == FailureKind::Missing {
1075 let first = self
1076 .live
1077 .as_mut()
1078 .is_some_and(|l| l.missing_seen.insert(id.rep()));
1079 if first {
1080 self.force_refresh(now_us);
1081 self.request_resync(now_us);
1082 }
1083 }
1084 Ok(self.count_failure(now_us, id, kind, status))
1085 }
1086
1087 fn early_wait(
1088 &mut self,
1089 now_us: u64,
1090 id: ResourceId,
1091 from: Option<i128>,
1092 utc: i128,
1093 ) -> FailureClass {
1094 self.inflight.remove(&id);
1095 let base = self.dash.early_retry_us;
1096 let cap = EARLY_RETRY_CAP_US.max(base);
1097 let retry_at = {
1098 let Some(live) = self.live.as_mut() else {
1099 return FailureClass::Retry {
1100 attempt: 1,
1101 kind: FailureKind::TooEarly,
1102 };
1103 };
1104 let e = live.early.entry(id).or_default();
1105 e.attempts = e.attempts.saturating_add(1);
1106 let shift = e.attempts.saturating_sub(1).min(EARLY_SHIFT_MAX);
1107 let delay = base.saturating_mul(1u64 << shift).min(cap);
1108 let at = now_us.saturating_add(delay);
1109 e.retry_at = Some(at);
1110 at
1111 };
1112 if from.is_some_and(|f| utc >= f) {
1113 self.request_resync(now_us);
1114 }
1115 FailureClass::NotYetAvailable {
1116 retry_at_us: retry_at,
1117 }
1118 }
1119
1120 fn expired_segment(&mut self, _now_us: u64, id: ResourceId, utc: i128) -> FailureClass {
1121 self.inflight.remove(&id);
1122 self.failures.remove(&id);
1123 if let Some(live) = self.live.as_mut() {
1124 live.early.remove(&id);
1125 }
1126 self.mark_done(id);
1127 if let Some(slot) = self.slot_mut(id) {
1128 slot.state = SlotState::Skipped { marker: true };
1129 }
1130 self.outputs.push_back(Output::Notice(Notice::FellBehind));
1131 if let Some((pi, li, _)) = self.rep_loc.get(&id.rep()).copied() {
1132 let (oldest, next) = {
1133 let lane = &self.periods[pi].lanes[li];
1134 match lane.current().and_then(|r| r.plan.as_ref().map(|p| (r, p))) {
1135 Some((r, p)) => (oldest_in_window(p, utc), r.next_issue),
1136 None => (None, 0),
1137 }
1138 };
1139 let lane = &mut self.periods[pi].lanes[li];
1140 lane.expired_run = lane.expired_run.saturating_add(1);
1141 let rejoin = lane.expired_run >= EXPIRED_REJOIN_RUN || oldest.is_none();
1142 match oldest {
1143 Some(o) if o > next && !rejoin => {
1144 let ri = lane.reps.len() - 1;
1145 lane.reps[ri].next_issue = o;
1146 let read = lane.reps[ri]
1147 .plan
1148 .as_ref()
1149 .map(|p| o.checked_sub(1).map_or(Ok(None), |i| p.get(i)))
1150 .unwrap_or(Ok(None));
1151 let fail = match read {
1152 Ok(s) => {
1153 lane.reps[ri].last_time = s.map(|s| s.time_ticks);
1154 None
1155 }
1156 Err(e) => Some(DropCause::Addressing(address_name(&e))),
1157 };
1158 if let Some(cause) = fail {
1159 self.drop_rep(pi, li, cause);
1160 }
1161 }
1162 Some(o) if o <= next && !rejoin => {}
1163 _ => self.rejoin_lane(pi, li),
1164 }
1165 }
1166 self.drain();
1167 FailureClass::Expired
1168 }
1169
1170 pub(super) fn give_up_live_segment(
1171 &mut self,
1172 id: ResourceId,
1173 status: Option<u16>,
1174 ) -> FailureClass {
1175 self.inflight.remove(&id);
1176 self.failures.remove(&id);
1177 if let Some(live) = self.live.as_mut() {
1178 live.early.remove(&id);
1179 }
1180 self.mark_done(id);
1181 if let Some(slot) = self.slot_mut(id) {
1182 slot.state = SlotState::Skipped { marker: true };
1183 }
1184 self.outputs.push_back(Output::SegmentLost { id, status });
1185 self.drain();
1186 FailureClass::SegmentSkipped
1187 }
1188
1189 pub(super) fn after_rotation(&mut self, now_us: u64) {
1190 if self.is_live_mpd() {
1191 self.force_refresh(now_us);
1192 }
1193 }
1194
1195 pub(super) fn mpd_failed(&mut self, _now_us: u64) -> u32 {
1196 self.mpd_failures = self.mpd_failures.saturating_add(1);
1197 if let Some(live) = self.live.as_mut() {
1198 live.refresh.awaiting_retry = true;
1199 }
1200 self.mpd_failures
1201 }
1202
1203 fn force_refresh(&mut self, now_us: u64) {
1206 self.force_refresh_after(now_us, 0);
1207 }
1208
1209 fn force_refresh_after(&mut self, now_us: u64, extra_us: u64) {
1210 let floor = self.dash.min_mpd_refresh_us.max(extra_us);
1211 let live_open = self.live_open();
1212 let Some(live) = self.live.as_mut() else {
1213 return;
1214 };
1215 if !live_open {
1216 return;
1217 }
1218 let earliest = live
1219 .refresh
1220 .last_request_us
1221 .map_or(now_us, |t| t.saturating_add(floor))
1222 .max(now_us);
1223 live.refresh.forced_at = Some(live.refresh.forced_at.map_or(earliest, |f| f.min(earliest)));
1224 }
1225
1226 fn schedule_refresh(&mut self, _now_us: u64) {
1227 let floor = self.dash.min_mpd_refresh_us;
1228 let mup = self
1229 .mpd
1230 .as_ref()
1231 .and_then(|m| m.minimum_update_period)
1232 .map(|d| u64::try_from(duration_us(d)).unwrap_or(u64::MAX));
1233 let Some(live) = self.live.as_mut() else {
1234 return;
1235 };
1236 self.mpd_failures = 0;
1237 live.refresh.awaiting_retry = false;
1238 let ex = live.mpd_exchange;
1239 let mid = ex
1240 .sent_us
1241 .saturating_add(ex.received_us.saturating_sub(ex.sent_us) / 2);
1242 live.refresh.next_at = mup.map(|m| mid.saturating_add(m.max(floor)));
1243 }
1244
1245 pub(super) fn refresh_mpd(
1246 &mut self,
1247 now_us: u64,
1248 new: Mpd,
1249 bytes: &[u8],
1250 meta: &ResponseMeta<'_>,
1251 ) {
1252 if self.live.is_none() {
1253 return;
1254 }
1255 self.note_exchange(now_us, meta);
1256 let (stale, same) = {
1257 let live = self.live.as_ref().expect("live present");
1258 let stale = matches!(
1259 (new.publish_time_us, live.refresh.publish_time_us),
1260 (Some(n), Some(o)) if n < o
1261 );
1262 (stale, live.refresh.body == bytes)
1263 };
1264 if stale {
1265 self.schedule_refresh(now_us);
1266 let floor = self.dash.min_mpd_refresh_us;
1267 if let Some(l) = self.live.as_mut() {
1268 l.refresh.next_at = Some(now_us.saturating_add(floor));
1269 }
1270 return;
1271 }
1272 self.schedule_refresh(now_us);
1273 if same {
1274 return;
1275 }
1276 if let Some(l) = self.live.as_mut() {
1277 l.refresh.body = bytes.to_vec();
1278 l.refresh.publish_time_us = new.publish_time_us.or(l.refresh.publish_time_us);
1279 }
1280 self.apply_mpd(now_us, new);
1281 self.refresh_schedule_after_apply(now_us);
1282 }
1283
1284 fn refresh_schedule_after_apply(&mut self, now_us: u64) {
1285 self.schedule_refresh(now_us);
1286 }
1287
1288 fn apply_mpd(&mut self, now_us: u64, new: Mpd) {
1289 if new.periods.is_empty() {
1290 return;
1291 }
1292 let dynamic = new.kind == PresentationType::Dynamic;
1293 if dynamic && new.availability_start_time_us.is_none() {
1294 return;
1295 }
1296 let patch_location = new.has_patch_location();
1297 let event_streams = new.has_event_streams();
1298 let reset = {
1299 let old = self.mpd.as_ref().expect("mpd present");
1300 let ids_differ = matches!((&old.id, &new.id), (Some(a), Some(b)) if a != b);
1301 let has_reset = new
1302 .essential_properties
1303 .iter()
1304 .chain(new.supplemental_properties.iter())
1305 .any(|d| d.scheme_id_uri == SCHEME_RESET);
1306 let old_dynamic = old.kind == PresentationType::Dynamic;
1307 (old_dynamic
1308 && dynamic
1309 && old.availability_start_time_us != new.availability_start_time_us)
1310 || ids_differ
1311 || has_reset
1312 };
1313 self.mpd = Some(new);
1314 if patch_location {
1315 self.notice_once(Notice::PatchIgnored);
1316 }
1317 if event_streams {
1318 self.notice_once(Notice::EventStreamIgnored);
1319 }
1320 if reset || !self.remap_periods() {
1321 self.reset_presentation(now_us);
1322 return;
1323 }
1324 self.replan_all();
1325 self.drain();
1326 }
1327
1328 fn remap_periods(&mut self) -> bool {
1329 let Some(mpd) = self.mpd.as_ref() else {
1330 return false;
1331 };
1332 let timings = period_timings(mpd);
1333 let mut moves: Vec<(usize, usize)> = Vec::new();
1334 for (pi, run) in self.periods.iter().enumerate() {
1335 if run.finished {
1336 continue;
1337 }
1338 let found = mpd
1339 .periods
1340 .iter()
1341 .enumerate()
1342 .position(|(i, p)| match (&run.id, &p.id) {
1343 (Some(a), Some(b)) => a == b,
1344 (None, None) => timings.get(i).and_then(|t| t.start_us) == Some(run.start_us),
1345 _ => false,
1346 });
1347 match found {
1348 Some(ni) => moves.push((pi, ni)),
1349 None => return false,
1350 }
1351 }
1352 let mut vanished: Vec<(usize, usize)> = Vec::new();
1353 for (pi, ni) in moves {
1354 let policy = self.cfg.policy.clone();
1355 let mpd = self.mpd.as_ref().expect("mpd present");
1356 let selection = select(mpd, ni, &policy);
1357 let period = &mpd.periods[ni];
1358 let run = &mut self.periods[pi];
1359 run.index = ni;
1360 let mut kept_audio: Option<Option<RepRef>> = None;
1361 for (li, lane) in run.lanes.iter_mut().enumerate() {
1362 let audio_lane = lane.kind == SetClass::Audio;
1363 let Some(ri) = lane.reps.len().checked_sub(1) else {
1364 continue;
1365 };
1366 let rep = &mut lane.reps[ri];
1367 let new_set = period
1368 .adaptation_sets
1369 .iter()
1370 .position(|s| match rep.set_id {
1371 Some(id) => s.id == Some(id),
1372 None => false,
1373 });
1374 let new_set = new_set.or_else(|| {
1375 (rep.set_id.is_none() && rep.set_pos < period.adaptation_sets.len())
1376 .then_some(rep.set_pos)
1377 });
1378 let found = new_set.and_then(|si| {
1379 period.adaptation_sets[si]
1380 .representations
1381 .iter()
1382 .position(|r| r.id == rep.rep_key)
1383 .map(|rj| (si, rj))
1384 });
1385 match found {
1386 Some((si, rj)) => {
1387 rep.at = RepRef::at(ni, si, rj);
1388 rep.set_pos = si;
1389 if audio_lane {
1390 kept_audio = Some((!rep.dead).then_some(rep.at));
1391 }
1392 if let Some((p, s, r)) = rep.at.resolve(mpd) {
1393 rep.candidates = super::filter_candidates(
1394 representation_candidates(mpd, p, s, r),
1395 self.doc_url.as_ref(),
1396 self.dash.cross_origin_urls,
1397 );
1398 }
1399 }
1400 None => vanished.push((pi, li)),
1401 }
1402 }
1403 let chosen = run
1404 .lanes
1405 .iter()
1406 .filter_map(|l| l.current())
1407 .map(|r| r.at)
1408 .collect::<Vec<_>>();
1409 run.selection = selection;
1410 if let Some(audio) = kept_audio {
1411 run.selection.chosen_audio = audio;
1412 }
1413 for at in chosen {
1414 if let Some(pos) = run.selection.variants.iter().position(|v| *v == at) {
1415 run.selection.chosen_variant = Some(pos);
1416 }
1417 }
1418 }
1419 for (pi, li) in vanished {
1420 self.replace_vanished(pi, li);
1421 }
1422 true
1423 }
1424
1425 fn replace_vanished(&mut self, pi: usize, li: usize) {
1426 let (kind, index) = {
1427 let lane = &self.periods[pi].lanes[li];
1428 (lane.kind, self.periods[pi].index)
1429 };
1430 let replacement = {
1431 let sel = &self.periods[pi].selection;
1432 match kind {
1433 SetClass::Video => sel
1434 .chosen_variant
1435 .and_then(|i| sel.variants.get(i).copied()),
1436 _ => sel.chosen_audio,
1437 }
1438 };
1439 let switch = {
1440 let lane = &self.periods[pi].lanes[li];
1441 lane.current()
1442 .and_then(|c| c.plan.as_ref().map(|p| p.get(c.next_issue)))
1443 };
1444 let switch_us = match switch {
1445 Some(Ok(seg)) => seg.map(|s| s.start_us),
1446 Some(Err(e)) => {
1447 self.drop_rep(pi, li, DropCause::Addressing(address_name(&e)));
1448 None
1449 }
1450 None => None,
1451 };
1452 let Some(at) = replacement.filter(|a| a.period == index) else {
1453 if let Some(rep) = self.periods[pi].lanes[li].reps.last_mut() {
1454 rep.dead = true;
1455 }
1456 return;
1457 };
1458 match self.build_rep(at) {
1459 Ok(mut rep) => {
1460 if let (Some(plan), Some(us)) = (rep.plan.as_ref(), switch_us) {
1461 rep.next_issue = match plan.index_at_or_before(us) {
1462 Ok(Some(i)) => i,
1463 _ => 0,
1464 };
1465 rep.joined = true;
1466 match rep
1467 .next_issue
1468 .checked_sub(1)
1469 .map_or(Ok(None), |i| plan.get(i))
1470 {
1471 Ok(seg) => {
1472 rep.last_time = seg.map(|s| s.time_ticks);
1473 }
1474 Err(e) => {
1475 self.outputs.push_back(Output::TrackDropped {
1476 period: index,
1477 set: at.set,
1478 rep: Some(at.rep),
1479 cause: DropCause::Addressing(address_name(&e)),
1480 });
1481 if let Some(r) = self.periods[pi].lanes[li].reps.last_mut() {
1482 r.dead = true;
1483 }
1484 return;
1485 }
1486 }
1487 }
1488 let ri = self.periods[pi].lanes[li].reps.len();
1489 push_marker(&mut self.periods[pi].lanes[li]);
1490 self.periods[pi].lanes[li].reps.push(rep);
1491 self.periods[pi].issue_done = false;
1492 self.register_rep(pi, li, ri);
1493 }
1494 Err(cause) => {
1495 self.outputs.push_back(Output::TrackDropped {
1496 period: index,
1497 set: at.set,
1498 rep: Some(at.rep),
1499 cause,
1500 });
1501 if let Some(rep) = self.periods[pi].lanes[li].reps.last_mut() {
1502 rep.dead = true;
1503 }
1504 }
1505 }
1506 }
1507
1508 pub(super) fn replan_all(&mut self) {
1509 let mut targets: Vec<(usize, usize, usize)> = Vec::new();
1510 for (pi, run) in self.periods.iter().enumerate() {
1511 if run.finished {
1512 continue;
1513 }
1514 for (li, lane) in run.lanes.iter().enumerate() {
1515 if let Some(ri) = lane.reps.len().checked_sub(1)
1516 && !lane.reps[ri].dead
1517 {
1518 targets.push((pi, li, ri));
1519 }
1520 }
1521 }
1522 for (pi, li, ri) in targets {
1523 self.replan_rep(pi, li, ri);
1524 }
1525 }
1526
1527 fn replan_rep(&mut self, pi: usize, li: usize, ri: usize) {
1528 let (at, has_index) = {
1529 let rep = &self.periods[pi].lanes[li].reps[ri];
1530 (rep.at, rep.index.is_some())
1531 };
1532 if has_index {
1533 return;
1534 }
1535 let planned = {
1536 let mpd = self.mpd.as_ref().expect("mpd present");
1537 plan_representation(mpd, at, &self.plan_options())
1538 };
1539 if let Ok(p) = planned {
1540 let marker = {
1541 let rep = &mut self.periods[pi].lanes[li].reps[ri];
1542 if rep.joined {
1543 let bound = p.plan.len().unwrap_or(u64::MAX / 2);
1544 rep.next_issue = match rep.last_time {
1545 Some(t) => p
1546 .plan
1547 .leading_true(bound, |s| s.time_ticks <= t)
1548 .unwrap_or(0),
1549 None => 0,
1550 };
1551 }
1552 rep.plan = Some(p.plan);
1553 rep.joined && rep.next_issue == 0 && rep.last_time.is_some()
1554 };
1555 if marker {
1556 push_marker(&mut self.periods[pi].lanes[li]);
1557 }
1558 }
1559 }
1560
1561 fn reset_presentation(&mut self, now_us: u64) {
1562 self.periods.clear();
1563 self.issue = 0;
1564 self.deliver = 0;
1565 self.queue.clear();
1566 self.reqs.clear();
1567 self.inflight.clear();
1568 self.parked.clear();
1569 self.failures.clear();
1570 self.done.clear();
1571 self.remembered_done.clear();
1572 self.outstanding = 0;
1573 self.rep_loc.clear();
1574 self.prev = None;
1575 self.inits.clear();
1576 if let Some(live) = self.live.as_mut() {
1577 live.early.clear();
1578 live.missing_seen.clear();
1579 }
1580 self.on_clock_ready(now_us);
1581 }
1582}
1583
1584fn push_marker(lane: &mut Lane) {
1585 if !lane
1586 .queue
1587 .back()
1588 .is_some_and(|s| matches!(s.state, SlotState::Marker))
1589 {
1590 lane.queue.push_back(Slot {
1591 id: None,
1592 rep: 0,
1593 plan_index: 0,
1594 state: SlotState::Marker,
1595 });
1596 }
1597}