refactor(spf): split track loading from live-reload scheduling

Decompose the live media-playlist reload into two single-responsibility
behaviors, matching the content-snapshot ([1]) vs refetch-policy ([3])
category split in live-presentation-modeling.md:

- resolve-track (loader, [1]) now also services live reloads: it watches a
  per-type reload-epoch slot and re-fetches when it advances (gate: load if
  unresolved OR epoch > last serviced), carries the prior snapshot's timeline
  forward, and sets presentation.streamType. ConcurrentRunner id-dedup +
  recording the epoch at schedule time gives drop-if-busy coalescing.
- schedule-track-reload (scheduler, [3]) is what remains of reload-track once
  the fetching is removed: it bumps the reload epoch on a target-duration
  cadence (half on unchanged), inert for VoD via #EXT-X-ENDLIST, and enters on
  track *selection* so its bumps also drive first-resolve retries.

reload-track is removed; the live-hls and live-playlist-spike engines compose
resolve* + schedule* in its place.

Also fix updateMediaSourceDuration for live: it only wrote Infinity when the
MediaSource was already open at entry and returned without scheduling a wait
otherwise. The presentation can resolve to Infinity before setupMediaSource
opens the MediaSource, so the eager write was missed and the first append
pinned a finite live-edge duration — stalling once the window slid past it.
Now it defers to sourceopen when not yet open.

Verified live playback end-to-end (real-time, Infinity duration, seeked to
edge) against a Mux LL-HLS stream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2026-06-25 09:59:22 -07:00
co-authored by Claude Opus 4.8
parent 0f10a280e5
commit e268f5a350
11 changed files with 588 additions and 404 deletions
@@ -103,6 +103,15 @@ export interface SimpleHlsEngineState {
failedCdns?: string[];
currentTime?: number;
loadActivated?: boolean;
/**
* Per-type live-reload triggers. Owned by `scheduleTrackReload` (composed
* only by live engines), which bumps them on a target-duration cadence;
* `resolveTrack` watches them to re-fetch the media playlist. Inert for VoD
* (no scheduler → never bumped → loader resolves once).
*/
videoReloadEpoch?: number;
audioReloadEpoch?: number;
textReloadEpoch?: number;
}
/**