Skip to main content

dash_runtime/client/engine/
live.rs

1//! The live half of [`DashClient`]: the clock ladder, `UTCTiming` sampling,
2//! MPD refresh, the join near the live edge, availability gating and the
3//! classification of early, expired and missing segments.
4//!
5//! **Spec:** ISO/IEC 23009-1:2022 5.3.9.5.3 (availability), 5.4 (MPD
6//! updates and Reset), 5.8.5.7 (`UTCTiming`), Annex A.2, A.7, A.11;
7//! DASH-IF IOP v4.3 4.3.3, 4.4.4, 4.7, 4.8; RFC 8470. Verbatim text:
8//! `private/specs-verified/dash/23009-1/` and `dashif-iop-4.3/`. Design:
9//! `docs/dash-runtime/DESIGN.md` sections 3, 5.2-5.4 and 12.
10
11use 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
26/// A forced second look at the UTC clock after a segment error is taken at
27/// most this often (23009-1 A.7: check the clock first, then the MPD).
28const FORCED_RESYNC_MIN_GAP_US: u64 = 30_000_000;
29/// Cap of the early-request cadence (DESIGN 5.4: 250 ms doubling to 1 s).
30const EARLY_RETRY_CAP_US: u64 = 1_000_000;
31/// Expiries in a row on one lane before the client rejoins the live edge.
32const EXPIRED_REJOIN_RUN: u32 = 3;
33/// A closed Period's lists are final this many segment durations (or one
34/// `minimumUpdatePeriod`, if longer) after its end (23009-1 5.4.1).
35const PERIOD_FINAL_SEGMENTS: i128 = 2;
36/// Bound on the steps one lane evaluation may take.
37const MAX_LANE_STEPS: usize = 8;
38/// The early-retry doubling stops growing here (the cap applies first).
39const 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    // Public surface ---------------------------------------------------------
122
123    /// At the monotonic instant `now_us` the host's UTC clock read `unix_us`
124    /// microseconds since 1970-01-01T00:00:00Z (POSIX, no leap seconds). Both
125    /// reads must be taken back to back. Callable at any time and repeatedly
126    /// (at open, every ten minutes, on a system clock-change notification):
127    /// a dynamic presentation uses it when no `UTCTiming` answer is available
128    /// (or first, with [`DashClockPolicy::PreferHost`]); a static one ignores
129    /// it.
130    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    /// The current clock anchor of a dynamic presentation.
164    #[must_use]
165    pub fn clock_anchor(&self) -> Option<Anchor> {
166        self.clock.anchor().copied()
167    }
168
169    /// UTC in Unix microseconds at the monotonic instant `now_us`, once an
170    /// anchor exists.
171    #[must_use]
172    pub fn utc_at(&self, now_us: u64) -> Option<i64> {
173        self.clock.utc_at(now_us)
174    }
175
176    /// The earliest monotonic instant at which [`DashClient::poll`] may have
177    /// something to do although no response arrived: a refresh due, a
178    /// segment becoming available, an early retry, a `UTCTiming` timeout or
179    /// resync. `None` when only a response (or a delivery) can move the client.
180    #[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    /// Report a `UTCTiming` answer.
217    ///
218    /// # Errors
219    /// [`Error::UnrequestedUtc`] when `id` is not the request awaited;
220    /// [`Error::Stopped`].
221    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    /// Report a failed `UTCTiming` request (the client abandons it by itself
245    /// after `DashConfig::utc_timeout_us`; a late answer then returns
246    /// [`Error::UnrequestedUtc`], which the caller ignores).
247    ///
248    /// # Errors
249    /// [`Error::UnrequestedUtc`] when `id` is not the request awaited;
250    /// [`Error::Stopped`].
251    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    /// The MPD request was answered `304`: the held MPD is current. The
264    /// exchange moves `FetchTime` forward (23009-1 A.2).
265    ///
266    /// # Errors
267    /// [`Error::MpdNotAwaited`]; [`Error::Stopped`].
268    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    // Predicates --------------------------------------------------------------
280
281    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    /// Whether more segments may still be added to Period `pi` by a refresh:
300    /// its end is unknown, or it is closed but its lists do not yet reach the
301    /// end and the grace after which 5.4.1 makes them final has not passed.
302    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    // Wake bookkeeping --------------------------------------------------------
376
377    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    // Timers ------------------------------------------------------------------
425
426    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    // Clock ladder --------------------------------------------------------------
553
554    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    // Join ----------------------------------------------------------------------
786
787    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    // Failures ------------------------------------------------------------------
1038
1039    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    // Refresh ---------------------------------------------------------------------
1204
1205    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}