14 KiB
status, date, definition
| status | date | definition |
|---|---|---|
| draft | 2026-05-20 | technical |
Rendition selection caps
Policy-driven constraints that narrow the candidate set of video
renditions before quality selection runs. Each cap is a separate
constraint slot read by selectQuality (or the ABR equivalent) and
applied as a filter on presentation.videoTracks before selection.
The selection slot itself (selectedVideoTrackId) remains
single-writer; caps participate as constraints, not as competing
selectors.
A Player feature in the framing from clusters.md § Feature classification axes: additive functionality not tied to making any source play, used by the player to bias delivery for billing, device, viewport, or other consumer-side reasons.
Status
- Composition: not implemented in
createSimpleHlsEngine. TodayselectQualityoperates over allpresentation.videoTrackswith no candidate-set narrowing beyonduserVideoTrackSelection(single-track manual override) and bandwidth-driven safety margins. - Definition depth: technical — scope and constraints articulated; no implementation. Source material: SPF Epics Working Doc Cluster E entries #13 (1080p+ Resolution Cap, eng S, validation S) and NEW-C (Screen-Size / Player-Size Resolution Cap, eng S–M, validation S), unified per that doc's "Resolution-cap unification" open question.
Phases of complexity
Phases. The first four are motivation slices — different signal
sources (config / ResizeObserver) feeding the same constraint+filter
mechanism (a per-cap state slot read by selectQuality as a filter on
the candidate set before selection). The fifth is meta-policy on top:
it modifies how the combination of caps resolves, not a cap of its
own.
| Phase | What | Mechanism |
|---|---|---|
| Billing-driven max-height cap | Config-driven maxHeight (e.g., 1080) excludes higher-resolution variants from the candidate set. Drives the Mux billing use case (cap delivery at 1080p+ for tier-pricing alignment) |
Policy — config field consumed by selectQuality's filter step; no new behavior. Matching axis is configurable: height (default, simpler) or total pixels (e.g., for exact alignment with resolution-based pricing tiers — 1080p = 2,073,600 pixels ensures anamorphic / non-standard-aspect variants are matched correctly) |
| Viewport-driven height cap | Cap the candidate set to renditions that fit the player element's rendered dimensions. Avoids serving higher-resolution variants the viewer can't perceive | Middle pattern — ResizeObserver monitor writes cap state on player-element size changes; selectQuality reads. New behavior. Pairs with the cap-floor phase below — the floor counters this phase's tendency to over-aggressively downgrade on small players |
| Max-bitrate cap | Config-driven maxBitrate excludes variants above the threshold. Useful for bandwidth-constrained delivery contexts (mobile, billing) |
Policy — same shape as max-height cap |
| Max-FPS cap | Config-driven maxFps excludes high-frame-rate variants. Less common motivation but mentioned in video-abr.md's "What's not implemented" |
Policy — same shape |
| Cap floor (minimum effective cap) | Counter-pressure to other caps: when the combination of upper-bound caps (viewport, max-bitrate, etc.) would narrow the effective candidate set below a configured floor (e.g., 720p), the floor wins — the effective cap is max(otherCaps, floor). Motivation: viewport-driven caps over-aggressively downgrade quality on small players; the floor prevents perceptual quality from dropping too far when the player is small |
Meta-policy — not a cap of its own. New constraint slot (e.g., videoMinMaxHeight) read by selectQuality's filter step alongside the other cap slots; combination takes the max over the other caps' effective bounds. Mux Player's MinCapLevelController.minMaxResolution = 720 is the reference implementation |
The first four phases share a single mechanism: a constraint slot +
filter step before selectQuality consumes the candidates. They
differ in signal source (one-shot config vs reactive ResizeObserver)
and constraint axis (height / bitrate / FPS). The cap-floor phase is
meta-policy on top — it modifies how the combination of caps resolves,
not a cap of its own. Per-phase implementation is small (S / S-M per
the Epics doc).
What's in scope vs out of scope
In scope:
- All five phases above for video renditions
- Constraint+filter pattern: per-cap state slots + filter step in
selectQualitybefore bandwidth-based selection runs - Config surface under existing
quality?: {…}engine config - Edge case handling: empty candidate set after cap filtering (open question — fallback policy)
Out of scope (separate candidate features):
- multi-signal-abr — incorporates non- bandwidth signals (CPU / thermal / network type / battery / viewport / customer preference) into the ABR algorithm itself. Different concern: caps narrow the candidate set; multi-signal-abr biases selection within it. Caps could feed multi-signal-abr as one input, but the algorithm-modification work is its own feature.
- audio-abr — audio quality switching now documented; its "Audio caps inclusion" open question explicitly references this doc as the resolution candidate. When audio-ABR ships, the audio-caps integration question becomes load-bearing.
audio-only-mode-override/video-only-mode-override— use-case compositions that subtract behaviors for mode-only delivery. Different mechanism (composition, not constraint).
Out of scope (different architectural layer):
- Above-engine consumers that write cap state from React / HTML
observers (e.g., a React hook that observes the player container
and writes a cap value). This feature owns the SPF-side state
slots and filter logic; where the writer lives (engine-side
ResizeObserverbehavior vs adapter-level observation writing to the slot) is partly an open question (see below). - The Mux Video element's customer-facing attributes
(
max-resolution,cap-rendition-to-player-size) — those are adapter-layer API surfaces that consume this feature's config / state.
Likely cross-cutting impact
Things this feature probably forces decisions on, not just additions:
selectQualityfilter-then-select shape. Today'sselectQualitytakes(candidates, bandwidth, config)and returns a track. Adding caps means either (a) pre-filtering candidates outsideselectQualityand passing the filtered set in, or (b) extendingselectQuality's signature to take the constraint slots and filter internally. Option (a) keepsselectQualitypure and matches theuserVideoTrackSelectionprecedent (where the filtering happens inswitchVideoQualitybefore invokingselectQuality). Option (b) bakes caps into the ABR module directly.- State-slot granularity. Per-cap slots (
videoMaxHeight,videoMaxBitrate,videoMaxFps,videoViewportHeight) vs a unified slot (videoRenditionConstraints: { maxHeight?, maxBitrate?, maxFps?, viewportHeight? }). Per-cap slots align with the constraint+filter pattern's "one writer per slot" shape; a unified slot complicates multi-writer semantics if different caps have different writers (config-driven for some,ResizeObserver-driven for others). - Viewport-driven cap signal-source location. The cap state is
SPF-side, but the player-element dimensions are DOM-side. A new
engine-side behavior could read
mediaSource.media(the<video>element) andResizeObserverit, or adapter-level code could observe the player container and write the cap state slot. Affects which layer owns the DOM dependency. - Empty-candidate-set fallback. If caps narrow candidates to
zero (e.g.,
maxHeight: 480on a source with only 720p+ variants),selectQualityhas nothing to return. Fallback options: ignore the cap entirely, pick lowest available rendition above the cap, or surface an engine error. Configurable behavior or engine-level decision. - Cap-combination semantics. When multiple caps are active
(viewport + max-bitrate, or viewport + cap-floor), they combine
via intersection on the candidate set — each upper-bound cap
narrows independently. The cap-floor inverts: it computes the
most restrictive other-cap result and enforces a lower bound on
it (
max()). Where this combination lives is part of the filter-then-select shape question above: option (a) puts it in the dispatcher (switchVideoQuality) beforeselectQualityis invoked; option (b) bakes it intoselectQualityitself.
Open questions
- Per-cap slots vs unified constraint slot. See cross-cutting bullet above. Open until the second cap lands and the multi-writer shape becomes concrete.
- Viewport-driven cap signal-source location. Engine-side
ResizeObserveronmediaSource.media, or adapter-side observation writing to the cap slot? Affects feature shape (one behavior vs one slot) and DOM dependency placement. - Empty-candidate-set fallback policy. Ignore / pick-lowest / error? Config-driven or engine-fixed?
- max-FPS cap inclusion. Listed in video-abr.md's "What's not implemented" but lower priority than max-height / max-bitrate. Document as Phase 4 or defer?
- Audio caps. Audio renditions don't have height analogs but could have max-bitrate or max-channels caps. Extend this feature to cover audio or carry audio caps separately?
- Interaction with capability-probing. Capability filtering narrows candidates to "what the browser can play"; caps narrow to "what the player chooses to deliver." Apply capability filter first, then policy caps (per current cluster framing). Confirm ordering when both land.
- Cap-floor scope: policy caps only, or all candidate-narrowing signals? The floor counters the viewport-driven cap (and potentially max-bitrate / max-FPS caps), but should not override capability-filter physics. If capability-probing removes unsupported renditions, the floor can't add them back. Confirm the boundary: floor operates over the policy-cap-narrowed set, not over the capability-filtered set.
- Cap-floor default value. Mux Player hardcodes 720p as
MinCapLevelController.minMaxResolution. For SPF, should the floor default to a value, or require explicit opt-in? Defaulting risks unexpected high-bitrate delivery on tiny embeds; not defaulting means most consumers won't get the perceptual benefit unless they configure it.
Related features
- video-abr — primary consumer.
selectQualityis the read-side;userVideoTrackSelectionis the existing constraint+filter precedent. Cap slots layer on top of the same pattern. - multi-signal-abr — different concern (ABR algorithm extension) but caps could feed it as inputs.
- audio-abr — audio quality switching that may extend this feature with audio-side caps; see that doc's "Audio caps inclusion in rendition-selection-caps.md" open question.
- capability-probing — adjacent. Both narrow the candidate set, but along different axes (capability = physics, caps = policy). The cluster framing places caps in cluster E (selection policy) and capability filtering in cluster D (capability probing primitive); the line matters for composition order.
- hevc-variant-selection — sibling constraint+filter feature on the codec axis. Same filter-then-select shape; the open question about per-cap slots vs unified slot is shared.
audio-only-mode-override/video-only-mode-override— use-case compositions for mode-only delivery. Different mechanism than caps (subtract behaviors vs filter candidates).
See also
- clusters.md § Selection policy — cluster E description; this feature is the first concrete cluster E member, and the cluster's documented foundation
- clusters.md § Feature classification axes — the Player-feature framing this doc instantiates; mixes Policy mechanism (config-driven caps) and Middle-pattern mechanism (viewport-driven cap)
- clusters.md § Constraint + filter
— cross-cluster pattern this feature instantiates;
video-abr'suserVideoTrackSelectionis the precedent - video-abr.md — primary consumer;
selectQualityis the read-side,userVideoTrackSelectionthe constraint precedent - SPF Epics Working Doc — source material; Epic #13 (1080p+ Resolution Cap) and NEW-C (Screen-Size / Player-Size Resolution Cap), unified per that doc's "Resolution-cap unification" open question
- Mux Video Permutations Matrix
— Stream Type / Selection Policy section; consumer-facing API
surfaces (
max-resolution,cap-rendition-to-player-size) documented in the Mux Video element README