Files
v10/internal/design/spf/features/ll-hls-support.md
T

245 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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)