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

# Multi-signal ABR

Extension to the existing video and audio ABR algorithm to incorporate
non-bandwidth signals: network type / Save-Data preference, thermal /
decode-pressure inference, battery state, viewport visibility, and
customer-facing quality-preference policy. Today's `selectQuality`
takes `(candidates, bandwidth, config) → track`; this feature widens
the input axis so selection responds to more than just throughput.

A **Media-src feature** at the baseline (sensing-driven biases that
make playback more appropriate to device + network conditions) layered
with **Player feature** at the customer-preference tier (additive
config-driven biases). Cluster C member, sibling to
[video-abr](./video-abr.md) and [audio-abr](./audio-abr.md). Distinct
from [rendition-selection-caps](./rendition-selection-caps.md): caps
*narrow the candidate set* before selection; multi-signal-ABR extends
the *selection algorithm itself*.

## Status

- **Composition:** not implemented in `createSimpleHlsEngine`. Today
  `switchVideoQuality` (and the upcoming `switchAudioQuality` per
  [audio-abr](./audio-abr.md)) read only `bandwidthState` for the
  algorithm input.
- **Definition depth:** technical — per-signal phases articulable
  from known browser APIs and the existing video-abr precedent;
  signal-fusion algorithm shape is open.
- **Prerequisite chain:** depends on
  [video-abr](./video-abr.md) (and optionally
  [audio-abr](./audio-abr.md)) for the base algorithm being extended.
  Independent of [rendition-selection-caps](./rendition-selection-caps.md)
  — caps and signals are complementary, not blocking.

## Phases of complexity

Content phases by signal type. Each row except the last is a discrete
signal-source unit; the final "Signal fusion" row covers the
algorithm-extension work that consumes them.

| Phase | Signal source | Mechanism | Notes |
|---|---|---|---|
| Network-type / Save-Data awareness | `navigator.connection.effectiveType` (`'slow-2g'` / `'2g'` / `'3g'` / `'4g'`) and `navigator.connection.saveData` boolean. `connection.change` event for mid-playback transitions | Middle-pattern monitor behavior writes a `networkConditions` state slot; ABR algorithm reads as a downward bias factor (e.g., `'slow-2g'`/`'2g'` aggressively bias down; `saveData=true` similar). Naive: read once at startup; full: react to `connection.change` | Browser support: Chrome / Edge / Firefox have Network Information API; Safari does not expose it. Applies to both video and audio ABR (network conditions affect both equally). Available signal is coarse-grained but covers the high-value cases (cellular detection) |
| Thermal / decode-pressure awareness | Derived from `VideoPlaybackQuality.droppedVideoFrames` delta over a rolling window. Possibly supplemented by `PerformanceObserver` long-task durations as a CPU-pressure proxy. No direct browser thermal API | Middle-pattern monitor behavior samples dropped-frames periodically; computes a rolling drop rate; writes `thermalPressure` (or `decodePressure`) state slot; ABR reads as a downward bias when sustained drops exceed a threshold | Heuristic-only; the hardest signal to detect reliably. Naive: dropped-frames threshold + downgrade. Full: combine dropped-frames + long-task signals. Video-ABR primarily; audio-ABR rarely thermally-pressured (audio decode is cheap). One open question: distinguishing decode-pressure (real thermal/CPU issue) from network-driven gaps (which dropped-frames doesn't directly distinguish from underflow) |
| Battery awareness | `navigator.getBattery()` returning `{ level, charging, chargingTime, dischargingTime }` + `levelchange` / `chargingchange` events | Middle-pattern monitor behavior subscribes to battery events; writes `batteryState` state slot; ABR reads with policy like "when `level < 0.2 && !charging`, bias toward lower bitrate to extend battery" | Browser support: Chrome desktop yes; Firefox removed (security/fingerprinting concerns); Safari no. Treat as best-effort enhancement — when `getBattery` unavailable, slot stays `undefined` and bias is skipped. Applies to both video and audio ABR but video bias is more impactful (higher decode + GPU cost) |
| Visibility awareness | `document.visibilityState` (`'visible'` / `'hidden'`) + `IntersectionObserver` on the video element for in-page off-screen detection. Possibly Picture-in-Picture state via `navigator.mediaSession` or `document.pictureInPictureElement` for distinguishing "hidden tab" from "PiP-visible" | Middle-pattern monitor behavior tracks both signals; writes `playerVisibility` state slot (e.g., `'visible'` / `'hidden'` / `'pip'` / `'off-screen'`); ABR reads: pause aggressive upgrades when not visible; consider downgrade for prolonged hidden state | Mostly video-applicable (audio plays through headphones regardless of visual visibility). PiP is a critical case: tab can be hidden but PiP is visible to user, so don't downgrade. Cross-cutting with the broader engine — segment-loading might also want to pause forward-buffer fetching when hidden, but that's buffer-management's concern, not this feature's |
| Customer-preference policy | Config-driven `qualityPreference: 'auto' \| 'data-saver' \| 'best-quality' \| 'specific-level'` or similar | No sensing; config slot read by ABR as a bias-strength scalar. `'data-saver'` applies a strong downward bias; `'best-quality'` applies an upward bias respecting safety margins; `'auto'` is the default neutral behavior | Pure config / Player-feature-flavored. Customer policy reigns over automatic biases (e.g., explicit `'best-quality'` overrides battery-low downward bias, but never overrides bandwidth-safety floor). Applies to both video and audio ABR. Adapter-level customer-facing UI ("Auto" / "Data saver" toggles) consumes this config |
| Signal fusion / algorithm extension | Integration phase: combine bandwidth + per-signal biases into a single `selectQuality` decision. Today's `selectQuality(candidates, bandwidth, config)` becomes `selectQuality(candidates, bandwidth, signals, config)` — signals is a struct of optional inputs from the five sources above | Algorithm extension. Open question on weighting strategy: bias-factor weighting (each signal produces a multiplier on the effective bandwidth) vs decision-tree (signals narrow / shift candidate set) vs scoring-function (each candidate scored against all inputs, highest score wins) | The hard algorithmic part. Bandwidth supremacy: non-bandwidth biases must not override the bandwidth safety floor (`track.bandwidth ≤ measuredBandwidth × safetyMargin`). Applies uniformly to video and audio ABR — the algorithm extension is signal-shape-agnostic |

## What's in scope vs out of scope

**In scope:**
- All six phases above for HLS multi-bitrate sources (video and audio
  variants where ABR is enabled)
- Per-signal middle-pattern monitor behaviors with graceful no-op when
  the underlying browser API is unavailable
- New state slots for each signal: `networkConditions`,
  `thermalPressure`, `batteryState`, `playerVisibility`,
  `customerQualityPreference`
- Algorithm extension to `selectQuality` and the `switchXQuality`
  behaviors
- Customer-policy config surface (`qualityPreference`)

**Out of scope (separate features):**
- **[rendition-selection-caps](./rendition-selection-caps.md)** —
  constraint+filter, different mechanism. Caps narrow the candidate
  set; this feature biases selection within it. Composable: caps run
  first, then signals bias.
- **`[bandwidth-estimation]`** *(candidate)* — the dual-EWMA primitive
  that produces `bandwidthState`. Not changed by this feature; consumed
  unchanged.
- **`[non-zero-pts-support]`** *(candidate)* and other cluster B
  candidates — time-domain concerns; orthogonal to signal-driven
  selection.

**Out of scope (different architectural layer):**
- Customer-facing UI for quality-preference toggles (adapter-level).
  The SPF feature owns the state slot + algorithm; the adapter renders
  the UI ("Auto" / "Data saver" / "Best quality" menu items) and writes
  to the config.
- Signal-availability detection at the platform level (Chrome on Mac
  vs Chrome on Android vs iOS Safari). The SPF feature handles graceful
  no-op when an API is unavailable; the adapter / consumer doesn't
  need to know about platform-specific availability.
- Manual quality-pinning. That's the `userVideoTrackSelection`
  constraint+filter precedent from video-abr — orthogonal to multi-
  signal-ABR.

## Likely cross-cutting impact

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

- **Signal-fusion algorithm shape.** The current `selectQuality` is a
  pure function over candidates + bandwidth. Extending to multi-input
  requires a fusion strategy. Three shapes worth considering:
  (a) Bias-factor weighting — each signal produces a multiplier
  (e.g., `effectiveBandwidth = measuredBandwidth × networkBias ×
  batteryBias × thermalBias × preferenceBias`); selectQuality runs
  against the effective bandwidth. Simple, composable, easy to disable
  per-signal by setting bias to `1.0`.
  (b) Decision-tree / staged-filter — each signal further narrows
  the candidate set or shifts the bandwidth thresholds. More
  expressive; harder to compose.
  (c) Scoring function — each candidate gets a multi-input score;
  highest score wins. Most flexible; most opaque to tune.
  Lean: (a) for simplicity; revisit if real-world tuning needs more
  expressiveness.
- **Bandwidth safety floor.** Non-bandwidth biases must not override
  the bandwidth-safety floor — even with `'best-quality'` customer
  preference, the engine can't pick a rendition above `measuredBandwidth
  × safetyMargin` (it'll rebuffer). Bandwidth supremacy is structural.
- **Per-signal availability fallback.** Each monitor behavior gracefully
  no-ops when the underlying browser API is unavailable. The state slot
  stays `undefined`; the algorithm tolerates `undefined` by skipping
  that signal's bias (bias factor → `1.0`). This is the well-precedented
  pattern from `userVideoTrackSelection` (default-undefined). No
  composition-variant complexity needed.
- **Audio-vs-video signal applicability.** Some signals apply uniformly
  (battery, network, customer-preference); some are video-specific
  (thermal-pressure, visibility). The algorithm extension is signal-
  shape-agnostic; per-type relevance is in the bias-factor calculation
  per signal. Audio-ABR ignores thermal and visibility because its bias
  factors are always `1.0` for those signals.
- **Monitor-behavior composition shape.** Each signal monitor is a
  middle-pattern behavior. They compose into ABR-enabled engine
  variants alongside the algorithm. Two shapes possible: (a) compose
  *all* signal monitors into ABR variants (always-on with
  slot-undefined fallback for unavailable APIs); (b) compose *opt-in*
  per signal (each is a separate behavior consumers add to their engine
  variant). Lean: (a) for default discoverability; (b) is achievable
  by simply removing the monitor and letting the slot stay undefined.
- **Customer preference vs automatic biases.** Customer preference is
  the final layer in the bias-factor stack: it should be able to bias
  *upward* (`'best-quality'` overrides battery-low downward bias) but
  never override the bandwidth safety floor. This is the "policy reigns
  over automatic biases, physics reigns over policy" layering.
- **Cross-feature with [audio-abr](./audio-abr.md).** When audio-ABR
  lands, it consumes the same signal slots as video-ABR. Signal
  monitors don't duplicate; signal slots are shared. The fusion
  algorithm is per-type because the bias-factor calculations differ
  (audio doesn't bias on thermal/visibility); the *infrastructure* is
  shared.

## Open questions

- **Fusion algorithm strategy.** Bias-factor weighting (lean), decision-
  tree, or scoring function. Choice affects how customer-policy
  expressivity composes; bias-factors are easiest to tune empirically.
- **Default bias strengths.** Per-signal, what's the default scalar?
  Network `'slow-2g'` → 0.3? Battery `level < 0.2` → 0.7? These need
  empirical tuning, but a reasonable default set must ship.
- **Thermal-pressure detection heuristic.** Dropped-frames threshold
  alone vs combined with long-task signals. Distinguishing decode-
  pressure (real thermal/CPU issue) from network-driven gaps (which
  look similar in dropped-frames) is non-trivial. Naive depth:
  threshold-only. Full depth: combine signals + hysteresis.
- **Customer-preference shape.** Single enum (`'auto'` /
  `'data-saver'` / `'best-quality'`) vs scalar bias-strength
  (`qualityBias: -1.0 to 1.0`) vs per-signal customer overrides (more
  granular control). Customer use cases drive this; lean enum for
  discoverability + a Tier 2 scalar tuning slot for advanced consumers.
- **Audio-ABR signal subset finalization.** Some signals (battery,
  network) clearly apply to both; some (thermal, visibility) are video-
  specific. Distinguish at the algorithm-extension level (audio ignores
  some inputs) or at the signal-slot-population level (audio doesn't
  read those slots). Implementation question.
- **Picture-in-Picture distinction.** PiP makes `document.visibilityState`
  unreliable for "is the user watching?" — PiP is visible to user
  even when document is hidden. Visibility signal needs to fold in
  PiP state. Open: which APIs reliably surface PiP state across
  browsers, and what's the fallback when not detectable.
- **Signal-staleness handling.** A monitor that reports `effectiveType:
  '4g'` once at startup and never updates is stale on a long playback
  session. Should the algorithm time-bound signal freshness, or trust
  monitors to keep slots updated? Lean: trust monitors (event-driven
  update is the precedent).
- **Cross-feature ordering with rendition-selection-caps.** When both
  features ship, the pipeline is `capability → caps → bandwidth-and-
  signals → selection`. Filter order is implicit but worth confirming.

## Related features

- **[video-abr](./video-abr.md)** *(prerequisite)* — base algorithm
  being extended. `selectQuality` is the read-side; this feature
  widens its input axis.
- **[audio-abr](./audio-abr.md)** *(parallel sibling)* — extension
  applies to both video and audio ABR. Shared signal-monitor
  infrastructure; per-type bias calculation.
- **[rendition-selection-caps](./rendition-selection-caps.md)** —
  complementary feature on the constraint+filter axis. Caps narrow the
  candidate set; signals bias selection within it. Composable in the
  pipeline: caps → bandwidth+signals → selection.
- **[buffer-management](./buffer-management.md)** — when visibility
  signal lands, buffer-management may also want to pause forward-buffer
  fetching when hidden. Cross-cutting concern; lives in
  buffer-management not here.
- **`[bandwidth-estimation]`** *(candidate)* — the dual-EWMA primitive
  for `bandwidthState`. Not changed by this feature; the existing
  `bandwidthState` slot is the bandwidth-side input to fusion.
- **`[capability-probing]`** — adjacent for advanced cases (e.g.,
  hardware-DRM security-level pressure may interact with multi-signal
  bias if a thermally-pressured device can't sustain hardware decode);
  out of scope for v1.

## See also

- [video-abr.md](./video-abr.md) — base ABR algorithm; this feature
  extends `selectQuality`'s input axis
- [audio-abr.md](./audio-abr.md) — parallel sibling; shared signal
  infrastructure
- [rendition-selection-caps.md](./rendition-selection-caps.md) —
  complementary constraint+filter feature; precedent for middle-pattern
  signal monitors (viewport-driven cap)
- [5.1-surround-selection.md](./5.1-surround-selection.md) — middle-
  pattern precedent for environment-aware signal (downstream channel
  count); same mechanism shape as this feature's signal monitors
- [clusters.md § Track & variant registry](./clusters.md#track--variant-registry)
  — cluster C description
- [clusters.md § Feature classification axes](./clusters.md#feature-classification-axes)
  — Composition vs Policy vs middle pattern; this feature is the
  canonical multi-signal middle-pattern example
- [`@navigator.connection`](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API)
  — Network Information API
- [`navigator.getBattery()`](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API)
  — Battery Status API
- [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API)
  — `document.visibilityState`
- [`VideoPlaybackQuality`](https://developer.mozilla.org/en-US/docs/Web/API/VideoPlaybackQuality)
  — dropped-frame stats for thermal-pressure inference
