Files
v10/internal/design/spf/features/dvr-event-stream-support.md
T

16 KiB

status, date, definition
status date definition
draft 2026-05-20 technical

DVR / event-stream support

DVR (digital video recorder) and event-stream HLS support: engine handles HLS playlists with growing-window semantics (segments never roll off the start) instead of sliding-window (live). Adds back-seek capability through history, event-stream recognition via #EXT-X-PLAYLIST-TYPE:EVENT, and the transition from event-stream to VOD when termination commits. The third cluster A doc — sibling to live-stream-support (foundation) and ll-hls-support (low-latency extension).

A Media-src feature in the framing from clusters.md § Feature classification axes: without it, DVR / event sources either don't play (engine treats the growing playlist as a malformed live stream) or back-seek doesn't work (buffer-management evicts history before the user reaches it).

DVR and event-stream are documented together because they share the core mechanism — a growing playlist with no roll-off — and structural implementation. The HLS-spec-defined PLAYLIST-TYPE:EVENT is one producer-side construct; DVR is the capability that exposes back-seek through whatever history the server retains. Both consume the same client-side engine surface.

Status

  • Composition: not implemented. Hard prerequisite live-stream-support is also not implemented. Today's parseMediaPlaylist recognizes #EXT-X-PLAYLIST-TYPE: as a known-tag pass-through but doesn't surface the value to the track output.
  • Definition depth: technical — scope and SPF touchpoints articulated against live-stream-support's structure; implementation specifics open. Source material: SPF Epics Working Doc — epic #3 DVR / Event Stream Support (cluster A, Media-src, eng M, validation M; "Growing playlist; extension of #2 (live-stream-support).").
  • Hard prerequisite: live-stream-support. The reload loop, target-duration pacing, Infinity duration semantics, and setLiveSeekableRange writer-behavior shape all come from that feature; DVR/event-stream supplies the windowing variance. The "DVR / event boundary" open question in live-stream-support.md's Open questions section is resolved by this doc's existence: DVR is its own feature, not a phase of live-stream-support.

Phases of complexity

Content phases (capability slices). Each phase is a distinct capability slice of "DVR/event-stream supported."

Phase What Notes
Growing-window playlist semantics Playlist grows from the recording / event start; no roll-off. Engine retains full history. setLiveSeekableRange start value derives from the playlist's first segment media time (or 0) rather than the sliding-back-N derivation live uses. The reload-loop and target-duration-pacing machinery is unchanged from live The core windowing difference. Variant-specific producer behavior (computes seekableStart from the playlist's first retained segment) feeds the variant-agnostic setLiveSeekableRange writer that live also uses
Event-stream recognition (PLAYLIST-TYPE:EVENT) Parser surfaces #EXT-X-PLAYLIST-TYPE:EVENT from the media playlist (today the tag is recognized but not extracted; track output gains playlistType: 'EVENT' | 'VOD' | undefined). Engine uses the value to distinguish event-stream from live-with-large-window — semantically distinct even if structurally similar Parser-side change; the value isn't yet surfaced. PLAYLIST-TYPE:VOD is the orthogonal case (already-finished recording) that the parser would surface uniformly
Back-seek through history User can seek arbitrarily far back into the recorded portion. Buffer-management's seek phase already handles non-contiguous gap-fill (per buffer-management.md's "Seek handling" phase); DVR exercises this aggressively with large seek distances. No new behavior; existing seek code path under different load The "DVR works" experience surface. Stress-tests the existing seek-from-arbitrary-position planning
DVR-aware back-buffer policy Default backBuffer.keepSegments: 2 from buffer-management is too aggressive for DVR (history is evicted before user can seek back to it). Either configurable (large value for DVR variants) or a variant-specific policy ("retain N seconds back" or "retain indefinitely") Variant-specific behavior. Affects buffer-management.md's back-buffer eviction phase. Open question: shape of the policy (configurable threshold vs subtractive composition)
Event-stream termination → VOD transition When #EXT-X-ENDLIST appears on an event-stream, live-stream-support's terminated-state-transition phase handles the mechanics (reload loop stops, last segment stabilizes, mediaSource.duration flips from Infinity to the finite value, clearLiveSeekableRange() paired with the transition). This phase notes the additional semantic that the stream now behaves equivalently to VOD: seekable to start, finite duration, normal endOfStream gating Composes with live-stream-support's termination phases. No new termination behavior; this phase is the semantic note about what post-termination means for event-stream specifically

What's in scope vs out of scope

In scope:

  • All five phases above for HLS event-stream and DVR-style growing- playlist sources
  • Parser surface for EXT-X-PLAYLIST-TYPE:EVENT (and :VOD for uniformity)
  • Variant-specific producer behavior for the seekable-range start signal
  • Variant-specific back-buffer policy
  • Composition with live-stream-support's reload loop and termination phases

Out of scope (separate Media-src candidate features):

  • ll-hls-support — orthogonal sibling extension. DVR can compose with LL-HLS for low-latency live with back-seek; both extensions compose on the same reload-loop foundation but address different concerns (LL-HLS = latency reduction at the live edge; DVR = back-seek through history). The composition shape is "DVR + LL-HLS engine variant" with both feature's behaviors composed in.

Out of scope (different architectural layer):

  • Adapter-layer customer-facing affordances ("seek back 30 seconds" buttons, scrub bar with history range, DVR window UI). Consume this feature's seekable-range signal; not SPF concerns themselves.
  • User-facing DVR window configuration (e.g., "only show last 24 hours"). Adapter-level policy; SPF respects what the server provides in the playlist.
  • Server-side DVR retention policy. The engine handles whatever segments the server lists; it doesn't enforce retention.

Likely cross-cutting impact

Things this feature probably forces decisions on, not just additions:

  • Composition-variant placement for variant-specific behaviors. Per the failure-mode catalog's composition-variant entry: behaviors with variant-specific value derivation (the seekable-range start producer, the back-buffer policy) compose into the DVR variant, not as runtime branches in always-on behaviors. Same shape as ll-hls-support's variant-composition analysis. The live-stream- support engine variant, the ll-hls-support extension variant, and the dvr-event-stream-support variant are three distinct compositions of the reload-loop foundation; each adds / replaces variant-specific behaviors.
  • Seekable-range producer split. live-stream-support documents setLiveSeekableRange as a new behavior reading derived live-edge data; for DVR, the value derivation differs (start = 0 / first- segment-time vs sliding-back-N). The cleanest split: a variant- specific producer behavior computes the (seekableStart, seekableEnd) signal; a variant-agnostic writer behavior consumes the signal and calls mediaSource.setLiveSeekableRange(). The writer doesn't branch; the producer is variant-specific. Same pattern as updateMediaSourceDuration's uniform-across-variants shape, mirrored — uniform writer, variant-specific upstream.
  • Back-buffer policy shape under DVR. Today's backBuffer. keepSegments: 2 is a single config knob. DVR may want one of: (a) a large configurable value, (b) a "retain N seconds back" alternative axis, (c) "retain indefinitely" (no eviction within the DVR window). The choice affects whether the back-buffer policy becomes variant-specific behavior (different policy composed for DVR variant) or stays uniform with a richer config surface.
  • DVR-vs-live-with-large-window distinction. A DVR stream without PLAYLIST-TYPE:EVENT is structurally indistinguishable from "live with a very large sliding window" until the engine observes that segments don't roll off. The engine has options: (a) configure DVR vs live at the adapter layer (consumer opts in via config), (b) detect from PLAYLIST-TYPE:EVENT when present, (c) observe the playlist's behavior over multiple reloads and infer (engine flips to "DVR mode" once it sees segments stay). (a) is the simplest; (c) is the most adaptive but adds detection complexity.
  • endOfStream gate under DVR. live-stream-support documents that endOfStream doesn't naturally fire for live (playlist keeps growing). For DVR-during-recording, same — endOfStream waits for termination. For DVR-post-termination, the gate becomes reachable the same way live-post-termination does (last segment stabilizes). No new gate semantics needed; the existing isLastSegmentAppended + currentTime gate logic from mse-mms-pipeline.md applies uniformly.
  • Server-side retention edge case. A server may stop serving older segments while still listing them in the playlist (e.g., "we keep 24 hours of history; older segments 404 on fetch"). The engine references segments via URI; if a back-seek targets a segment URI that 404s, the fetch fails. Whether this falls under this feature's scope (recover gracefully — surface a "history expired" error to the consumer) or under [unsupported-case-error-mapping] (generic fetch-failure mapping) is an open boundary.
  • DVR + LL-HLS composition. Both are cluster A extensions on the same reload loop. Composition order: LL-HLS replaces the standard reload-loop behavior with its variant; DVR replaces the back-buffer policy and seekable-range producer. The two extensions are orthogonal — neither's behaviors conflict with the other's. A "DVR + LL-HLS" engine variant composes both extension sets atop the live-stream-support foundation.

Open questions

  • Back-buffer policy shape. Configurable keepSegments with large default for DVR vs "retain N seconds back" alternative axis vs "retain indefinitely" mode. Affects whether back-buffer becomes variant-specific behavior or stays uniform with richer config.
  • DVR-vs-live distinction signal source. Adapter-config opt-in vs PLAYLIST-TYPE:EVENT detection vs observe-over-reloads inference. (a) is simplest; (b) only covers spec-flagged event streams; (c) is the most adaptive but adds complexity. Likely combination: (b) when the tag is present; otherwise (a) until (c) matures.
  • Seekable-range start derivation under server retention. When the server keeps only the last N hours of segments and the playlist's first segment slides forward over time, the seekableStart value should track that. Same writer (variant- agnostic setLiveSeekableRange writer), but the variant-specific producer needs to read the playlist's current first-segment, not a constant 0.
  • Server-side retention error handling. Segment 404 on back-seek fetch. Scope of this feature vs [unsupported-case-error-mapping].
  • PLAYLIST-TYPE:VOD semantics. When a media playlist arrives with PLAYLIST-TYPE:VOD from the start (fully-resolved at first fetch, no reload needed), the engine should recognize this and skip reload-loop composition. Adjacent concern; may belong here (parser surfacing the value uniformly) or in live-stream-support (engine-side reload-loop activation gate).
  • Event-stream-during-pre-event. An event-stream playlist may exist with metadata but few or no segments before the event starts. Reload-loop pacing handles this naturally (target-duration reload until segments appear), but pre-roll segment-zero semantics are worth verifying.
  • DVR + LL-HLS composition specifics. Order of behavior composition, shared state between DVR and LL-HLS extensions. Verify when both extensions get implementation work.
  • liveSeekableRange clearance on DVR termination. For live → termination, live-stream-support's terminated-state-transition phase pairs clearLiveSeekableRange() with the transition (so the browser's seekable reverts to buffer-derived semantics). For event-stream → VOD transition, the seekable range stays a finite range from playlist start to last segment — but does it still go through setLiveSeekableRange (now with finite end) or through clearLiveSeekableRange() + mediaSource.duration setter? Spec allows either; spec-conformant browsers should behave equivalently. Verify when implementation arrives.
  • live-stream-support (hard prerequisite) — provides the reload loop, target-duration pacing, Infinity duration semantics, termination detection (ENDLIST + miss-counter), and the setLiveSeekableRange writer-behavior shape. DVR/event-stream supplies the windowing variance and the variant-specific seekable-range start producer.
  • ll-hls-support — orthogonal cluster A extension. DVR + LL-HLS compose; both extensions sit atop the same reload-loop foundation but address different concerns (latency reduction vs back-seek). A DVR + LL-HLS engine variant is the cross-extension composition.
  • buffer-management — back-buffer policy needs DVR-aware shape. Seek-from-arbitrary-position gap-fill is exercised aggressively under DVR. Both phases of buffer-management interact with this feature; the open question on back-buffer policy shape (variant-specific behavior vs configurable threshold) is shared.
  • mse-mms-pipelineInfinity duration semantics during recording; finite duration on event- stream termination. Same MSE writer semantics as live; endOfStream gate logic applies uniformly.
  • non-zero-pts-support — DVR and event streams typically have PTS far from zero (recording starts at wall-clock time, not at PTS 0). currentTime / seekable semantics require non-zero-PTS support to be correct.
  • [unsupported-case-error-mapping] (candidate) — server- side retention 404 errors on back-seek fall under this candidate's scope for consumer-facing mapping.

See also