mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
245 lines
15 KiB
Markdown
245 lines
15 KiB
Markdown
---
|
||
status: draft
|
||
date: 2026-05-20
|
||
definition: technical
|
||
---
|
||
|
||
# LL-HLS support
|
||
|
||
Low-Latency HLS extensions on top of the [live-stream-support](./live-stream-support.md)
|
||
reload-loop foundation: **blocking reload** for server-gated playlist
|
||
refresh, **partial segments** for sub-segment append granularity at the
|
||
live edge, **preload hints** for pre-fetching the next part before it's
|
||
announced, and **delta playlists** for bandwidth-efficient reloads.
|
||
Together these mechanisms collapse the engine's distance from the live
|
||
edge from "≥3× target duration" (regular live) to "≤ a few parts"
|
||
(low-latency live).
|
||
|
||
A **Media-src feature** in the framing from
|
||
[clusters.md § Feature classification axes](./clusters.md#feature-classification-axes):
|
||
LL-HLS-encoded sources play *technically* via the regular live reload
|
||
loop (each full segment lands when announced, just with regular-live
|
||
latency), but "actual support" — engaging the partials / blocking-reload
|
||
/ preload mechanisms the server advertises — requires this feature.
|
||
Without it, LL-HLS sources play at regular-live latency, not the
|
||
low-latency the producer optimized the stream for.
|
||
|
||
## Status
|
||
|
||
- **Composition:** not implemented. Hard prerequisite [live-stream-support](./live-stream-support.md)
|
||
is also not implemented. None of `parseMediaPlaylist`, the reload
|
||
loop (which doesn't exist yet), `forward-buffer`'s planner, or
|
||
`createTrackedFetch` currently handle LL-HLS shapes.
|
||
- **Definition depth:** technical — scope and SPF touchpoints
|
||
articulated against the HLS LL-HLS spec extensions; implementation
|
||
specifics open. Source material: [SPF Epics Working Doc — LL-HLS
|
||
Support (epic #1)](https://www.notion.so/35f97a7f89d08123a13fecab1ca1cac4)
|
||
(cluster A, eng size XL, validation M-L, "Largest single gap").
|
||
- **Hard prerequisite:** [live-stream-support](./live-stream-support.md).
|
||
The four LL-HLS mechanisms all build on the reload-loop, sliding-
|
||
window, live-edge-tracking, and `Infinity`-duration primitives that
|
||
feature introduces. LL-HLS is not independently scopable.
|
||
|
||
## Phases of complexity
|
||
|
||
Capability slices, each one an HLS LL-HLS spec extension. Naive vs Full
|
||
depth applies *within* each phase per
|
||
[clusters.md § Feature classification axes](./clusters.md#naive-vs-full-implementation-depth);
|
||
e.g., blocking reload at naive depth without preload hints, full depth
|
||
combining the two.
|
||
|
||
| Phase | What | Notes |
|
||
|---|---|---|
|
||
| Blocking reload | `_HLS_msn=N&_HLS_part=P` query params on playlist reload; server holds the response until the target msn/part is available. Replaces target-duration interval polling with response-driven request chaining. Parser must surface `#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES` to enable the mechanism | Largest shift from regular live: the reload loop's *pacing model* changes (response-driven, not interval-driven). `createTrackedFetch` needs long-poll-aware request semantics — a single request may legitimately take ~target-duration to respond |
|
||
| Partial segments | `#EXT-X-PART:URI=...,DURATION=...,INDEPENDENT=...,GAP=...,BYTERANGE=...` tags within the most-recent few segments. Client fetches and appends partials before the full segment is announced via `#EXTINF`. Parser must surface `#EXT-X-PART-INF:PART-TARGET=...` (target part duration) and per-part attributes | Append still goes through the same `SourceBufferActor` (same variant, same codec — no `changeType()`). The forward-buffer planner must extend beyond `track.segments[]` to include a "partial head" past the last complete segment; reconcile a part eventually being superseded by its containing full segment (same media interval, two playlist entries) |
|
||
| Preload hints | `#EXT-X-PRELOAD-HINT:TYPE=PART,URI=...` (or `TYPE=MAP`). Client may pre-fetch the indicated URL before it appears in the playlist as a regular `EXT-X-PART`. Used in combination with blocking reload to pipeline the next fetch behind the in-flight blocking-reload response | Pre-fetched response is retained and consumed when the URL appears as a regular `EXT-X-PART` on the next reload; double-fetch must be avoided. Pre-fetch is *only* a latency optimization — never gates other phases |
|
||
| Delta playlists | `_HLS_skip=YES` query param on reload; server responds with a playlist containing `#EXT-X-SKIP:SKIPPED-SEGMENTS=N` denoting N segments removed from the start. Client merges the delta into its retained playlist state. Parser must surface `#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=...` (the minimum-skippable boundary advertised by the server) | Requires the client to retain playlist state across reloads — today's `parseMediaPlaylist` returns a fresh `Track` per fetch, so the reload-loop behavior gains a delta-merge step. Bandwidth optimization, not latency: complements but doesn't replace blocking reload + partials |
|
||
|
||
## What's in scope vs out of scope
|
||
|
||
**In scope:**
|
||
- All four phases above, applied to HLS LL-HLS-encoded sources where
|
||
`#EXT-X-SERVER-CONTROL` advertises the capability
|
||
- Parser surface for `EXT-X-SERVER-CONTROL`, `EXT-X-PART-INF`,
|
||
`EXT-X-PART`, `EXT-X-PRELOAD-HINT`, `EXT-X-SKIP`, `EXT-X-RENDITION-REPORT`
|
||
- Long-poll-aware fetch shape (timeout policy compatible with
|
||
blocking-reload response holds)
|
||
- Forward-buffer planner extension for partial-head tracking
|
||
- Client-side playlist-state retention across reloads (for delta merge)
|
||
|
||
**Out of scope (sister Media-src candidate features):**
|
||
- **`[live-edge-distance-abr]`** *(candidate, cluster C extension)* —
|
||
using distance-from-live-edge as an ABR signal alongside bandwidth.
|
||
Consumer of LL-HLS's tighter live-edge tracking but not part of
|
||
LL-HLS itself.
|
||
|
||
**Out of scope (different architectural layer):**
|
||
- Above-engine UI affordances ("seek to live edge," "currently live,"
|
||
live-button states). Consume LL-HLS-tightened live-edge data via the
|
||
live-stream-support feature's derived live-edge signal; not SPF
|
||
concerns themselves.
|
||
|
||
## Likely cross-cutting impact
|
||
|
||
Things this feature probably forces decisions on, not just additions:
|
||
|
||
- **Reload-loop composition variant** — LL-HLS isn't a runtime branch
|
||
inside a regular-live reload behavior; it's a *different* reload-loop
|
||
behavior composed into a low-latency live engine variant. Same shape
|
||
as `live-stream-support.md`'s `setLiveSeekableRange` placement
|
||
decision: a new variant-specific behavior, not a conditional inside
|
||
an existing variant-agnostic behavior. Per the failure-mode catalog
|
||
and `conventions/behaviors.md` § *Inverse: behaviors that operate
|
||
uniformly across tracks*, the existing `updateMediaSourceDuration`
|
||
is the canonical example of a behavior that stays composition-
|
||
variant-agnostic; the LL-HLS reload-loop is its inverse — a
|
||
variant-specific behavior that exists *only* in the LL-HLS engine.
|
||
- **Variant-decision point — open** — where does the engine commit to
|
||
the LL-HLS variant: adapter-level upfront (consumer opts in), or
|
||
after-first-playlist-parse from `EXT-X-SERVER-CONTROL` flags? The
|
||
spec allows a stream to advertise some LL-HLS capabilities and not
|
||
others (e.g., partials without blocking-reload), so per-mechanism
|
||
composition is possible. Either way the *behaviors* are variant-
|
||
specific; the question is when the composition is finalized.
|
||
- **`parseMediaPlaylist` extensions** — parser today returns a `Track`
|
||
with segments only. LL-HLS adds: `serverControl` flags, `partInf`
|
||
(part target duration), `parts[]` within the head segment(s), a
|
||
current `preloadHints[]`, optional `skip` metadata. The parser
|
||
output schema grows; the parsed-track shape that downstream
|
||
behaviors consume gains LL-HLS-specific fields. Intersects with
|
||
[presentation-modeling.md](../presentation-modeling.md)'s open
|
||
question on `parseMediaPlaylist` pluggability — LL-HLS-aware
|
||
parsing is a strong forcing function for explicit parser-extension
|
||
shape.
|
||
- **`createTrackedFetch` request shape** — today the wrapper handles
|
||
streaming responses chunk-by-chunk with EWMA bandwidth sampling. No
|
||
changes needed for *partial-segment loading* (same wrapper, smaller
|
||
payloads — bandwidth sampling continues working). **Required change
|
||
for blocking reload:** the wrapper has no long-poll-aware timeout
|
||
policy today. A blocking-reload request may legitimately take up to
|
||
`target-duration` to respond; needs either a configurable per-request
|
||
timeout or a request-type discriminant. Per the sampling-baked-into-
|
||
loading pattern, the sample producer doesn't move.
|
||
- **Forward-buffer planner extension** — current planner iterates
|
||
`track.segments[]` (full segments) and filters by buffered ranges
|
||
within a fixed look-ahead. LL-HLS extends the candidate set with
|
||
partials past the last complete segment, plus a reconciliation rule:
|
||
if part `P` covers `[t0, t1]` and the same interval later appears as
|
||
part of full segment `S`, both entries map to the same media
|
||
interval; the planner must not double-load. Low-latency-aware
|
||
buffer-duration setting is a separate tunable — the default 30s
|
||
target buffer is incompatible with low-latency-live's "stay within
|
||
a few parts of live edge" goal.
|
||
- **Sliding-window awareness under partials** — the segment list
|
||
mutates more frequently (partials append at the live edge between
|
||
full-segment announcements). Existing sliding-window-aware back-
|
||
buffer policy from `live-stream-support` continues to apply to
|
||
full segments; partials past the head are transient by definition.
|
||
- **`liveSeekableRange` update frequency** — `setLiveSeekableRange` from
|
||
live-stream-support advances as the live edge advances. Under
|
||
LL-HLS, the edge advances at part granularity (sub-second updates,
|
||
not target-duration interval). Same writer, higher update rate; no
|
||
new multi-writer concern.
|
||
- **Per-type partial coordination** — audio playlists can also carry
|
||
`EXT-X-PART`. Inherits live-stream-support's open question on per-
|
||
type reload coordination, with the additional concern that audio
|
||
and video parts may have different target durations.
|
||
|
||
## Open questions
|
||
|
||
- **Variant-decision point.** Per the cross-cutting note above:
|
||
adapter-level upfront vs after-first-playlist-parse vs per-mechanism
|
||
granular composition. Affects how the engine is built and how the
|
||
capability-detection signal flows. Resolving this likely shapes how
|
||
the regular-live engine variant is composed too.
|
||
- **Pre-fetch retention scope.** Preload hints pre-fetched in response
|
||
to one playlist snapshot may be invalidated by the next snapshot
|
||
(server changed its mind about the next part URI). Retention policy:
|
||
abort and discard, or hold pending validation against the next
|
||
playlist? Spec leaves this to implementations.
|
||
- **Long-poll fetch shape vs `createTrackedFetch` extension.** Add a
|
||
new request-type discriminant to the existing wrapper (with longer
|
||
timeout), or a sibling `fetchBlockingReload` wrapper that delegates
|
||
to the same chunk-streaming infrastructure? Trade-off: one wrapper
|
||
with conditional timeout vs two wrappers with shared chunk handling.
|
||
- **Planner extension shape — extend `forward-buffer` or sibling.** The
|
||
current `getForwardBufferSegments` is a pure function over
|
||
`Segment[]`. Either it grows a `parts?: PartSegment[]` parameter
|
||
(and a reconciliation rule baked in), or a sibling
|
||
`getForwardBufferPartials` runs alongside and the loader merges. The
|
||
reconciliation logic (part superseded by containing full segment) is
|
||
the load-bearing piece either way.
|
||
- **Capability advertisement vs runtime probing.** If the server
|
||
advertises `CAN-SKIP-UNTIL` but the client has already retained less
|
||
history than the skip boundary, the client must fall back to a
|
||
full-playlist reload. Where this fallback decision lives — inside
|
||
the delta-merge step or as a reload-shape decision upstream — is
|
||
open.
|
||
- **`#EXT-X-RENDITION-REPORT` consumption.** Spec lets the server
|
||
advertise the LAST-MSN/LAST-PART for *other* renditions in the
|
||
playlist; clients switching renditions can resume at the right point
|
||
without an extra round-trip. Useful for ABR-driven variant switching
|
||
under LL-HLS but a separate optimization. May be a sub-phase of
|
||
partial-segments or its own follow-on.
|
||
- **GAP=YES handling.** `#EXT-X-PART` may carry `GAP=YES` indicating
|
||
the encoder failed to produce the part on time; clients are spec-
|
||
advised to fetch the next part anyway and let the buffer underrun.
|
||
How this interacts with `endOfStream` gating (a "gap" past the live
|
||
edge isn't a true terminator) and stall recovery (the underrun
|
||
isn't a network stall) is open.
|
||
|
||
## Related features
|
||
|
||
- **[live-stream-support](./live-stream-support.md)** *(hard
|
||
prerequisite)* — provides the reload loop, sliding-window tracking,
|
||
live-edge tracking, `Infinity`-duration semantics, termination
|
||
detection, and `setLiveSeekableRange` placement. LL-HLS is
|
||
structurally an extension of this feature's reload loop with a
|
||
different pacing model, finer-grained segment list, and added
|
||
optimizations.
|
||
- **[dvr-event-stream-support](./dvr-event-stream-support.md)** —
|
||
sibling extension on the same reload loop with different windowing
|
||
semantics. Orthogonal to LL-HLS: a DVR stream can also be low-
|
||
latency (DVR + LL-HLS), in which case both features compose.
|
||
- **[presentation-modeling](../presentation-modeling.md)** —
|
||
`parseMediaPlaylist` extensions for LL-HLS tags forces the question
|
||
of parser pluggability raised in that doc's open questions. The two
|
||
features intersect tightly.
|
||
- **[buffer-management](./buffer-management.md)** — forward-buffer
|
||
planner extension for partials lives in the same `media/buffer/`
|
||
neighborhood; same back-buffer policy applies for full segments
|
||
below the live edge.
|
||
- **[video-abr](./video-abr.md)** — variant switching under LL-HLS
|
||
uses the same `bandwidthState` signal; partial-segment chunked-
|
||
sampling continues to feed it via `createTrackedFetch`.
|
||
`EXT-X-RENDITION-REPORT` consumption (deferred, see Open questions)
|
||
is the LL-HLS-aware variant-switch optimization.
|
||
- **[non-zero-pts-support](./non-zero-pts-support.md)** — live streams
|
||
including LL-HLS typically have PTS far from zero; the time-mapping
|
||
primitive is consumed by live (including LL-HLS) for correct
|
||
`currentTime` / `seekable` semantics.
|
||
- **[mse-mms-pipeline](./mse-mms-pipeline.md)** — partial-segment
|
||
append is same-codec append on the same `SourceBufferActor`; no
|
||
`changeType()`, no buffer recreation. The MSE codec-change check
|
||
explicitly does not fire for LL-HLS.
|
||
|
||
## See also
|
||
|
||
- [live-stream-support](./live-stream-support.md) — the cluster A
|
||
foundation this feature extends
|
||
- [clusters.md § Manifest reload loop](./clusters.md#manifest-reload-loop)
|
||
— cluster A description
|
||
- [clusters.md § Feature classification axes](./clusters.md#feature-classification-axes)
|
||
— Media-src feature framing
|
||
- [presentation-modeling.md](../presentation-modeling.md) — parser
|
||
interface; LL-HLS-aware parsing is a strong forcing function on
|
||
parser pluggability
|
||
- [SPF Epics Working Doc](https://www.notion.so/35f97a7f89d08123a13fecab1ca1cac4)
|
||
— source material; epic #1, "Could split: blocking reload, partial
|
||
segments, delta playlists, preload hints"
|
||
- [Mux Video Permutations Matrix](https://www.notion.so/32c97a7f89d08191b84dd30f06685490)
|
||
— Stream Type section; SPF column shows 🔲 for LL-HLS
|
||
- [HLS Spec — Low-Latency HLS](https://datatracker.ietf.org/doc/html/rfc8216bis)
|
||
(§4.4.5 EXT-X-PART, §4.4.3 EXT-X-SERVER-CONTROL, §4.4.5 EXT-X-PRELOAD-HINT,
|
||
§6.2.5 Delta Playlists)
|