mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
252 lines
16 KiB
Markdown
252 lines
16 KiB
Markdown
---
|
|
status: draft
|
|
date: 2026-05-20
|
|
definition: technical
|
|
---
|
|
|
|
# Live stream support
|
|
|
|
The engine's foundation for playing **live** HLS sources: periodic
|
|
media-playlist refetch, sliding-window segment tracking, target-duration
|
|
pacing, `Infinity` duration semantics, and termination detection
|
|
(transitioning out of live mode when the stream ends). Distinct from
|
|
sibling capabilities for low-latency live (LL-HLS) and DVR / event
|
|
streams — those are extensions on top of this foundation, tracked as
|
|
separate candidate features.
|
|
|
|
A **Media-src feature** in the framing from
|
|
[clusters.md § Feature classification axes](./clusters.md#feature-classification-axes):
|
|
without it, live HLS sources don't play correctly.
|
|
|
|
## Status
|
|
|
|
- **Composition:** not implemented in `createSimpleHlsEngine`. Today's
|
|
engine assumes VOD: `resolvePresentation` fetches the manifest once;
|
|
media playlists are fetched once per resolved track; no reload loop.
|
|
- **Definition depth:** technical — scope and constraints articulated;
|
|
no implementation. Source material: [SPF Epics Working Doc — Live
|
|
Stream Support (epic #2)](https://www.notion.so/35f97a7f89d08123a13fecab1ca1cac4)
|
|
(cluster A foundation, eng size L, validation M).
|
|
- **Foundational** for the manifest-reload-loop cluster —
|
|
[ll-hls-support](./ll-hls-support.md) and
|
|
[dvr-event-stream-support](./dvr-event-stream-support.md) build on
|
|
this feature.
|
|
|
|
## Phases of complexity
|
|
|
|
Capability slices for the foundational live-stream-support feature.
|
|
Each phase below is part of "live works (and terminates) at all";
|
|
richer live variants (LL-HLS, DVR) sit in sibling features.
|
|
|
|
| Phase | What | Notes |
|
|
|---|---|---|
|
|
| Manifest reload loop | Periodic media-playlist refetch keyed off `#EXT-X-TARGETDURATION` pacing per HLS spec. Each selected track's media playlist reloads independently as long as the source is live | The core primitive; cluster A foundation |
|
|
| Sliding-window segment tracking | Engine handles segments dropping off the start of the playlist as the window slides forward. Already-buffered segments past the window are still playable; un-fetched segments past the window are no longer fetchable | Affects the segment-loader's planning + back-buffer policy |
|
|
| Live duration semantics | `presentation.duration = Infinity` flows through `config.resolveDuration` (already pluggable). Downstream `updateMediaSourceDuration` propagates to `mediaSource.duration = Infinity` per MSE spec for live | The pluggable `resolveDuration` hook from `mse-mms-pipeline` is the surface; no new state slot needed |
|
|
| Live edge tracking | Engine tracks the latest segment available in the current playlist snapshot. Distinct from `currentTime` (the playhead); the gap between them is the buffer + the user's distance from live edge. DOM exposure via `mediaSource.setLiveSeekableRange(start, end)` so the browser's `HTMLMediaElement.seekable` reflects the live window (without it, `seekable` is empty under `duration === Infinity`) | Likely a derived signal (computed) rather than a new state slot |
|
|
| Reload jitter / backoff | Pacing variations under server delays or slow networks. Naive: poll on target-duration; full: jitter to avoid thundering herd, backoff on consecutive identical-playlist responses | Naive depth matches what hls.js does; full depth adds vendor-specific tuning |
|
|
| Per-type reload coordination | Audio / video / text media playlists each reload independently. Today the per-type `resolveXTrack` family is one-shot; live requires extending or replacing it with a reloading variant | Open question: extend in place, add a sibling `reloadXTrack` behavior, or compose differently |
|
|
| Termination detection (manifest signal) | Recognize when the reload loop should stop. **Naive**: `#EXT-X-ENDLIST` recognition only (assumes spec-compliant servers). Today's parser matches the literal `#EXT-X-ENDLIST` line but doesn't surface the value to the track output — the parser-side fix is part of this phase. **Full**: ENDLIST + unchanged-playlist miss-counter as a fallback for servers that stop updating without emitting `ENDLIST` | Naive vs Full depth per [clusters.md § Feature classification axes](./clusters.md#naive-vs-full-implementation-depth) |
|
|
| Terminated state transition | Engine flips out of live mode for the affected track. Reload loop stops scheduling that track's playlist. The track's segment list stops mutating, which makes the existing `endOfStream` gate naturally reachable (last segment now exists permanently). Per-type independence: audio / video can terminate at different times | The state transition is the only new orchestration; `endOfStream` doesn't need new code, just the playlist to stabilize. `clearLiveSeekableRange()` pairs with the transition so the browser's `seekable` returns to buffer-derived semantics |
|
|
|
|
## What's in scope vs out of scope
|
|
|
|
**In scope:**
|
|
- All phases above for HLS live VOD content with `#EXT-X-TARGETDURATION` pacing
|
|
- Standard sliding-window behavior (segments roll off the start)
|
|
- `Infinity` duration semantics through MSE
|
|
- Naive reload pacing (target-duration interval; no jitter)
|
|
- `#EXT-X-ENDLIST` recognition + unchanged-playlist miss-counter fallback for termination detection
|
|
|
|
**Out of scope (separate Media-src candidate features):**
|
|
- **[ll-hls-support](./ll-hls-support.md)** — blocking reload, partial
|
|
segments, delta playlists, preload hints. Largest single live-related
|
|
gap per the permutation matrix; builds directly on this feature's
|
|
reload loop.
|
|
- **[dvr-event-stream-support](./dvr-event-stream-support.md)** —
|
|
DVR / event streams: growing playlist (non-sliding); user can seek
|
|
backwards through history. Extension of this feature with different
|
|
windowing semantics.
|
|
|
|
**Out of scope (related but separate concerns):**
|
|
- **[non-zero-pts-support](./non-zero-pts-support.md)** — live streams' PTS advances continuously
|
|
from stream start, typically far from zero. Live needs this for
|
|
correct `currentTime` / `seekable` semantics, but the time-mapping
|
|
primitive itself is a separate cluster B feature.
|
|
- **`[buffer-stall-recovery]`** — affects live more than VoD due to
|
|
ingest variability, but is a separate borderline feature.
|
|
- **`[viewer-rate-limiting-audit]`** — reload-loop pacing must respect
|
|
server-side rate limiting; the audit itself is a separate borderline
|
|
feature.
|
|
|
|
## Likely cross-cutting impact
|
|
|
|
Things this feature probably forces decisions on, not just additions:
|
|
|
|
- **`resolvePresentation` reload variant** — today's behavior is
|
|
one-shot: parse manifest → write resolved presentation. Live requires
|
|
re-fetching the *media playlists* (not the multivariant), so the
|
|
reload loop sits below `resolvePresentation` rather than replacing
|
|
it. Most likely a per-type `reloadXTrack` family alongside the
|
|
existing `resolveXTrack` family, or an extension to the existing
|
|
`resolve-track.ts` shape. Touches the `parseMediaPlaylist` direct
|
|
import (see [presentation-modeling.md](../presentation-modeling.md))
|
|
— same parser, but called repeatedly per track.
|
|
- **Segment-loader sliding-window awareness** — today's planner
|
|
iterates over all segments in `track.segments`. Live needs the
|
|
planner to handle the segment list mutating mid-flight (segments
|
|
appended at the live edge, segments removed from the start). The
|
|
existing back-buffer eviction policy may need extension to honor
|
|
"no longer in the playlist" as an eviction signal independent of
|
|
`keepSegments` count.
|
|
- **`bandwidthState` resume semantics** — already preserved across
|
|
source resets (see [video-abr.md](./video-abr.md)). For live,
|
|
bandwidth-aware ABR continues to function, but bandwidth measurement
|
|
during live is more variable (network conditions matter more without
|
|
the full-buffer-ahead cushion).
|
|
- **Time mapping for live edge** — `state.currentTime` is the playhead;
|
|
the live edge is a derived value computed from the playlist's last
|
|
segment. Tools like "seek to live edge" or "is at live edge" would
|
|
consume this derived signal. Doesn't necessarily need its own state
|
|
slot.
|
|
- **End-of-stream handling** — `endOfStream` today gates on
|
|
`isLastSegmentAppended` + `currentTime >= lastSegStart`, *not* on
|
|
`presentation.duration` finiteness. For live, the gate naturally
|
|
doesn't fire because the playlist keeps growing — no segment is
|
|
permanently "the last." Once termination commits via this feature's
|
|
termination-detection phases, the last segment stabilizes and the
|
|
gate becomes reachable for normal reasons. **Subtlety:** there's a
|
|
possible race if reload pacing lags playhead consumption — the
|
|
current last segment could meet the gate before the next reload
|
|
appends a new one, firing `endOfStream` spuriously. Whether this
|
|
happens in practice depends on reload pacing relative to
|
|
forward-buffer depth.
|
|
- **Parser-side ENDLIST surfacing** — `parseMediaPlaylist` currently
|
|
recognizes `#EXT-X-ENDLIST` (skips the line) but doesn't extract the
|
|
value. The `MediaPlaylistInfo.endList: boolean` type field exists
|
|
but is orphaned (the parser returns a `Track`, not
|
|
`MediaPlaylistInfo`). The termination-detection phases need the
|
|
parser to surface the value to the track output.
|
|
- **`mediaSource.*` third-writer pattern — `setLiveSeekableRange`** —
|
|
`mediaSource.duration` already has two non-overlapping writers
|
|
(`updateMediaSourceDuration` for the initial `Infinity` write;
|
|
`endOfStream` for the deterministic final value — see
|
|
[mse-mms-pipeline.md](./mse-mms-pipeline.md) on DOM-property
|
|
multi-writer). Live introduces a structurally *different* third
|
|
writer on the same `mediaSource` resource: ongoing reactive
|
|
`setLiveSeekableRange(start, end)` calls keyed off live-edge updates,
|
|
plus `clearLiveSeekableRange()` paired with the terminated-state
|
|
transition. Distinct from the existing two writers along all three
|
|
characterization axes — decision domain (derived from playlist
|
|
snapshot vs. presentation / buffered), trigger (ongoing reactive vs.
|
|
one-shot transitions), and method (range setter rather than
|
|
`.duration` assignment). `start` = earliest still-fetchable segment
|
|
(sliding-window-aware); `end` = live edge. **Lives as a new live-only
|
|
behavior composed into the live engine variant**, not as a runtime
|
|
branch inside an existing MSE behavior — live vs VoD is a
|
|
composition-time distinction, and `updateMediaSourceDuration` is
|
|
deliberately uniform-across-variants (see
|
|
[conventions/behaviors.md](../conventions/behaviors.md) → *Inverse:
|
|
behaviors that operate uniformly across tracks* and the
|
|
`updateMediaSourceDuration` worked example). Two SPF-shaped options
|
|
for placement within the live variant: (a) baked into the
|
|
(yet-to-exist) live track-polling / reload-loop behavior that
|
|
produces edge data, or (b) a separate behavior reading presentation /
|
|
segment state and writing to DOM. Current lean: (b) — single-purpose
|
|
composition keeps other consumers of the same derived live-edge
|
|
signal (above-engine "seek to live edge," ABR live-edge-distance if
|
|
ever introduced) pluggable on the same data without coupling to the
|
|
polling behavior. Gates on MediaSource `'open'`; buffers-idle gating
|
|
is an implementation detail for the behavior itself.
|
|
|
|
## Open questions
|
|
|
|
- **Per-type reload coordination.** Audio / video / text media
|
|
playlists each have their own `EXT-X-TARGETDURATION`. Reload them
|
|
independently per their own pacing, or coordinate (e.g., reload all
|
|
on the shortest target duration)? The Epics doc doesn't take a
|
|
position; HLS spec allows independent. Practical question: does the
|
|
engine want symmetric coordination for stalls (one playlist behind →
|
|
block segment-loader from advancing) or independent (each playlist
|
|
paces itself)?
|
|
- **Reload behavior extension vs new behavior.** Extending the
|
|
existing `resolveXTrack` family to keep reloading vs adding a
|
|
sibling `reloadXTrack` family vs composing differently. Affects
|
|
cleanup-cascade semantics — the existing family ties cancellation
|
|
to source identity via reactor state-exit; a reload variant needs
|
|
to honor the same contract.
|
|
- **Default `defaultResolveDuration` already handles VoD; does live
|
|
need a different wired default in `createSimpleHlsEngine`, or do
|
|
live consumers wire their own?** The `resolveDuration` hook is
|
|
already pluggable; the only question is whether the default engine
|
|
variant covers live or requires opt-in.
|
|
- **Miss-counter threshold.** Heuristic feature — how many identical-
|
|
manifest reloads constitute termination? hls.js uses some count;
|
|
SPF needs its own choice. Threshold affects false-positive vs
|
|
false-negative rate.
|
|
- **Per-type termination semantics.** When audio terminates before
|
|
video, what's the engine's consumer-facing surface? "Live until all
|
|
tracks terminate" or "terminated when any track terminates"? Likely
|
|
the former, but worth confirming the precedent. Aligns with the
|
|
per-type reload-coordination question above.
|
|
- **`endOfStream` race under live reload pacing.** Could the gate fire
|
|
spuriously between reloads on a heavily-buffered live source?
|
|
Likely not in practice (reload pace beats consumption pace) but
|
|
worth verifying.
|
|
- **`setLiveSeekableRange` behavior shape within the live variant.**
|
|
Two SPF-shaped options — (a) call from inside the live track-polling
|
|
/ reload-loop behavior that produces edge data, or (b) a separate
|
|
behavior reacting to presentation / track state changes. Lean: (b),
|
|
for single-purpose composition and so other consumers of the same
|
|
derived live-edge signal can plug in alongside without coupling to
|
|
the polling behavior. Revisit once the polling behavior's shape
|
|
lands.
|
|
|
|
## Related features
|
|
|
|
- **[ll-hls-support](./ll-hls-support.md)** — builds on this feature's
|
|
reload loop. Adds blocking reload, partial segments, delta playlists,
|
|
preload hints. Largest live-related gap.
|
|
- **[dvr-event-stream-support](./dvr-event-stream-support.md)** —
|
|
different windowing semantics on top of the same reload loop.
|
|
Growing playlist + back-seek through history; resolves this doc's
|
|
prior "DVR / event boundary" decomposition question (DVR is its
|
|
own feature, not a phase here).
|
|
- **[non-zero-pts-support](./non-zero-pts-support.md)** — live PTS
|
|
starts far from zero. Live without non-zero PTS handling means
|
|
`currentTime` is wrong. Cluster B foundation that live consumes.
|
|
- **mse-mms-pipeline** — `Infinity` duration via `config.resolveDuration`
|
|
is already supported there; live writes the value, MSE pipeline
|
|
propagates it. The `endOfStream` gate uses segment + currentTime,
|
|
not duration finiteness; it naturally doesn't fire for live (growing
|
|
playlist) and becomes reachable once termination commits.
|
|
- **buffer-management** — sliding-window segment tracking interacts
|
|
with back-buffer eviction. The planner's currentTime-driven plan
|
|
shape applies; the playlist itself mutating mid-flight is new.
|
|
- **video-abr** / **audio-playback** / **subtitles** — all per-type
|
|
consumers continue to work in live, but each reads the resolved
|
|
track which now changes over time (segments append / roll off).
|
|
Quality-aware buffer planning preserves buffered higher-quality
|
|
content; for live this still applies but the windowing changes the
|
|
buffered-set turnover rate.
|
|
- **source-replacement** — orthogonal; live and VoD source changes
|
|
use the same in-place cascade. Live → live source change tears
|
|
down and rebuilds the reload loop along with everything else.
|
|
|
|
## See also
|
|
|
|
- [clusters.md § Manifest reload loop](./clusters.md#manifest-reload-loop)
|
|
— cluster A description; this feature is the foundation
|
|
- [clusters.md § Feature classification axes](./clusters.md#feature-classification-axes)
|
|
— the Media-src feature framing this doc instantiates
|
|
- [presentation-modeling.md](../presentation-modeling.md) —
|
|
architectural deep-dive; the reload loop sits below
|
|
`resolvePresentation` and re-uses `parseMediaPlaylist` per cycle.
|
|
When `parseMediaPlaylist` pluggability arrives (see that doc's
|
|
Open questions), live + format support intersect
|
|
- [SPF Epics Working Doc](https://www.notion.so/35f97a7f89d08123a13fecab1ca1cac4)
|
|
— source material; cluster A epic candidates and decompositions
|
|
- [Mux Video Permutations Matrix](https://www.notion.so/32c97a7f89d08191b84dd30f06685490)
|
|
— Stream Type section; SPF column shows ⚠️ for live + DVR (manifest
|
|
re-polling unverified), 🔲 for LL-HLS
|