11 KiB
status, date, definition
| status | date | definition |
|---|---|---|
| implemented | 2026-05-20 | sketched |
Video ABR
Bandwidth-driven adaptive bitrate selection for video. The engine measures throughput from in-progress segment fetches, maintains a dual-EWMA bandwidth estimate, and dynamically picks the highest-quality video track that fits the current bandwidth — with hysteresis to prevent oscillation. Manual track overrides coexist with ABR via a single constraint slot.
This doc captures the capability surface: what works, what doesn't, which behavior implements it, and how it relates to other features.
Status
- Composition:
createSimpleHlsEngine(HLS VoD) - Definition depth: sketched — algorithm, config, state flow documented; deeper algorithm justification (why Shaka's dual-EWMA, why these margins) is not yet written down
Phases of complexity
| Phase | What | Notes |
|---|---|---|
| Bandwidth-aware initial selection | On presentation resolve, pick the highest track that fits initialBandwidth (default 5 Mbps) |
Custom picker config can override (e.g., "first track regardless of bandwidth, then let ABR adjust") |
| Dynamic bandwidth-driven adjustment | Re-pick whenever bandwidthState changes — downgrade immediately, upgrade only past upgradeMargin headroom |
Hysteresis is structural (margin check), not temporal (no smoothing window beyond EWMA itself) |
| Dual-EWMA bandwidth estimate | Fast (2s half-life) + slow (5s half-life) accumulators; ABR consumes min(fast, slow) |
Asymmetric by design — adapts down rapidly, upgrades sluggishly. Zero-factor correction applied at read time, not sample time |
| Safety margin on candidate selection | selectQuality requires currentBandwidth >= track.bandwidth / safetyMargin (default 0.85 = 15% headroom) |
Pairs with upgradeMargin to prevent thrash; track uses ≤85% of measured bandwidth |
Manual override via userVideoTrackSelection |
Consumer sets userVideoTrackSelection = { id: 'X' } to narrow candidates; when only one remains, ABR short-circuits |
No abrDisabled flag — the constraint pattern is the mechanism |
| ABR re-enable | Clear userVideoTrackSelection to restore dynamic selection |
Symmetric with override |
What's not implemented
- Audio ABR — sampling infrastructure exists (
createTrackedFetch), butsetupAudioBufferActorsuses plainfetchStreaminstead. Wiring it through is a one-liner; a separate audio-quality-switching behavior would still be needed. See audio-abr.md for the design surface — bandwidth-state sharing, multi-writer coordination with multi-language-audio's Tier 2, EWMA accumulator design under mixed-source sampling. - Rendition selection caps — no max-height, max-bitrate, or max-FPS constraints on the candidate set.
selectQualityoperates over all video tracks. See rendition-selection-caps.md for the umbrella feature covering billing-driven, viewport-driven, max-bitrate, and max-FPS caps. - Screen-size / viewport adaptation — initial pick could be viewport-aware via custom
picker, but ABR re-selection ignores device dimensions. - Non-bandwidth signals — CPU/thermal throttling, network-type (WiFi vs. cellular), battery state are not factored. See multi-signal-abr for the scope — five per-signal phases (network / thermal / battery / visibility / customer preference) + a signal-fusion algorithm-extension phase. Bias-factor weighting is the leaning fusion strategy.
- Buffer fullness as ABR input — forward buffer (default 30s) gates when segment loading starts, not which track is selected. Bandwidth is the sole ABR input.
- Pluggable strategy — EWMA + hysteresis are fixed. Custom logic is only injectable for initial selection via
picker; ABR re-evaluation always usesselectQuality.
Implementation surface
Composition: packages/spf/src/playback/engines/hls/engine.ts — composed in place of selectVideoTrack (the two are alternatives; only one writes selectedVideoTrackId for a given engine).
Behaviors:
| Behavior | File | Responsibility |
|---|---|---|
switchVideoQuality |
packages/spf/src/playback/behaviors/quality-switching.ts |
ABR-driven selectedVideoTrackId lifecycle: pick default, adjust on bandwidth changes, clear on src unload |
Algorithm:
| Module | File | Role |
|---|---|---|
selectQuality |
packages/spf/src/media/abr/quality-selection.ts |
Pure function: given candidate tracks, current bandwidth, and config, return the track to pick (applies safety margin + upgrade margin) |
sampleBandwidth / getBandwidthEstimate |
packages/spf/src/network/bandwidth-estimator.ts |
Dual-EWMA accumulator update + read-time estimate computation (with zero-factor correction) |
createTrackedFetch |
packages/spf/src/network/fetch.ts |
Fetch wrapper that emits per-chunk bandwidth samples; consumed by setupVideoBufferActors to populate bandwidthState |
State slots:
- Reads:
presentation,bandwidthState,selectedVideoTrackId(when constrained),userVideoTrackSelection - Writes:
selectedVideoTrackId(sole writer for ABR-driven cases;userVideoTrackSelectionis the consumer-facing constraint, not a direct write to selection) - Cross-feature dependency:
bandwidthStateis written bysetupVideoBufferActors(sampling baked into segment-loader fetch), not by an ABR-specific behavior. Sampling and selection are decoupled — sampling lives with segment loading; selection lives with ABR.
Alternative composition: selectVideoTrack (in packages/spf/src/playback/behaviors/select-tracks.ts) is a simpler "pick first track" default; not composed in the current HLS engine but available for engine variants that don't want ABR (e.g., a fixed-quality engine). The two behaviors are mutually exclusive — both write selectedVideoTrackId.
Config surface
{
bandwidth?: {
fastHalfLife?: number; // default 2000ms — fast EWMA reactivity
slowHalfLife?: number; // default 5000ms — slow EWMA stability
minTotalBytes?: number; // sample filtering thresholds
minBytes?: number;
minDuration?: number;
};
quality?: {
safetyMargin?: number; // default 0.85 — track-bandwidth headroom
upgradeMargin?: number; // default 1.15 — upgrade hysteresis margin
};
initialBandwidth?: number; // default 5_000_000 — bps, fallback before samples
picker?: TrackPicker<QualitySwitchingConfig>; // override initial selection
}
picker is invoked once when the slot is empty in 'presentation-resolved' state. If it returns undefined, falls back to the bandwidth-aware default pick. ABR re-evaluation (after initial selection) always uses selectQuality — picker does not participate.
Verification
- Unit tests:
packages/spf/src/playback/behaviors/tests/quality-switching.test.ts(~450 lines) — lifecycle (clear on unload, re-pick after reset), default-pick with initialBandwidth fallback, downgrade/upgrade hysteresis, user-constraint narrowing + ABR short-circuit, picker override, effect re-firing on bandwidth changespackages/spf/src/network/tests/bandwidth-estimator.test.ts— EWMA math, zero-factor correction, sample filteringpackages/spf/src/media/abr/tests/quality-selection.test.ts—selectQualityalgorithm in isolation
- Sandbox:
apps/sandbox/src/spf-segment-loading/exposes the ABR surface: current bandwidth estimate (fast/slow), track list with bitrates, selected track + mode label, manual override buttons, ABR re-enable button. Tuning knobs (margins, half-lives) are not surfaced — they use defaults.
Related features
- single-video-track-default-selection (not yet documented) —
selectVideoTrackbehavior, alternative toswitchVideoQualityfor non-ABR engine variants. - buffer-management —
bandwidthStateis written bysetupVideoBufferActorsviacreateTrackedFetch; samples land onbuffer-management's fetch path (fetchBytesinsideSegmentLoaderActor). Sampling is structurally co-located with segment loading, not ABR. - bandwidth-estimation (coarse, not yet documented) — dual-EWMA accumulator is a reusable primitive in
network/. Could be promoted to its own feature doc when audio ABR or other consumers arrive. - audio-abr — parallel-sibling feature on the audio axis. Same sampling-baked-into-loading pattern, same
selectQualityalgorithm reuse, same constraint-slot manual-override. Audio-specific design surface (bandwidth-state sharing, multi-writer coordination with multi-language-audio Tier 2) captured there. - rendition-selection-caps — billing-driven (1080p+), viewport-driven (screen-size), max-bitrate, and max-FPS caps. All filter the candidate set before
selectQualityruns;userVideoTrackSelectionis the constraint+filter precedent that feature builds on. - multi-signal-abr — algorithm extension on
selectQuality's input axis. CPU/thermal throttling, network type, battery state, viewport visibility, customer preference as additional inputs. Distinct from rendition-selection-caps (constraint+filter): multi-signal-abr biases selection within the candidate set rather than narrowing it. - capability-probing — narrows the candidate set ABR operates over.
selectQualitydoesn't change shape; just sees a filtered candidate set with browser-unsupported renditions already excluded. - hevc-variant-selection — codec-aware variant filtering (HEVC if supported, AVC fallback). Same constraint+filter pattern as
userVideoTrackSelection; narrows the candidate set ABR operates over by codec axis. Tier 2 mid-streamchangeType()phase introduces cross-codec ABR (no algorithm change, just buffer-side switching).
Use cases that compose this feature
video-only-mode-override(coarse) — Phase 1 baseline constituent. The video-only delivery variant composes video-abr as-is for multi-bitrate video selection.
See also
- presentation-modeling.md — architectural deep-dive on the format-neutral data shape; ABR consumes resolved video tracks surfaced by the parser interface this layer defines.
- preload-modes.md — gates this feature indirectly via
resolvePresentation; ABR can't fire until the presentation is resolved, and resolution is gated by the preload-modes contract. - mse-mms-pipeline.md — where
setupVideoBufferActorslives and wherecreateTrackedFetchis wired into segment-loader construction; bandwidth sampling is structurally co-located with MSE buffer setup, ABR selection is the separate consumer documented here. - conventions/behaviors.md — when to define a behavior; behavior shape
- conventions/signals.md — multi-writer slot conventions (relevant for
selectedVideoTrackIdwrite coordination if audio ABR follows the same pattern) - packages/spf/docs/hls-engine.md — full HLS engine composition walkthrough