Skip to content

ABR: findBestLevel hard-excludes smooth:false levels, capping quality below maxLevel (MediaCapabilities false-negative) #7917

Description

@hongjun-bae

Overview

AbrController.findBestLevel hard-excludes any candidate whose decodingInfoResults[0].smooth === false:

// src/controller/abr-controller.ts
(levelInfo.supportedResult &&
  !levelInfo.supportedResult.decodingInfoResults?.[0].smooth)
// → levelsSkipped.push(i); continue;

For type: 'media-source', smooth is a probabilistic prediction from Chromium's VideoDecodePerfHistory (learned per codec-profile / resolution / fps bucket), not a deterministic capability like supported. When a false-negative lands on the only renditions at/under the configured cap, ABR is permanently stranded a tier lower — even though the level decodes fine.

Concrete case

  • A Windows machine (AMD Radeon 860M iGPU) returns { supported: true, powerEfficient: true, smooth: false } for the 720p60 avc1.640028 rendition.
  • macOS and a second, same-spec Windows machine return smooth: true for the identical config and stream.
  • The ladder's only high-fps level ≤ 720p is 720p60 (there is no 720p30). With maxLevel / viewport capping at 720p, the only candidate above 480p is smooth:false and gets excluded → playback caps at 480p30.
  • powerEfficient: true indicates a hardware decoder is engaged; steady-state droppedVideoFrames stays ≈ 0.

Note: we are still confirming via chrome://media-internals whether this specific machine has real startup/level-switch drop bursts. But independent of that measurement, the design issue stands: hls.js has no graceful fallback when the only in-cap level is smooth:false.

Why this is an hls.js robustness concern (not only a Chromium bug)

Proposal (no new config option)

Treat smooth: false as a soft signal in findBestLevel:

  • Prefer smooth levels when available.
  • Allow a smooth: false level up to the configured cap only when (a) no smooth candidate exists at/under the cap, AND (b) that level is powerEfficient: true (HW decoder engaged — the strongest false-negative signature).
  • powerEfficient: false + smooth: false stays excluded, preserving protection for genuinely weak setups.

This bounds the behavior change to exactly the false-negative shape and keeps the supported:false protection intact (so useMediaCapabilities: false is not needed). Happy to open a PR.

Environment

  • hls.js v1.6.0
  • Chrome / Edge / Whale (all Chromium) 149.0.7827.156, Windows 11 24H2 (26100.8655)
  • GPU: AMD Radeon 860M, driver 32.0.13058.2; Video Decode: Hardware accelerated; no HW video overlay (NV12 overlay = SOFTWARE)

Reproduction

decodingInfo query:

navigator.mediaCapabilities.decodingInfo({
  type: 'media-source',
  video: { contentType: 'video/mp4; codecs="avc1.640028"', width: 1280, height: 720, framerate: 60, bitrate: 3192000 },
});

Multivariant playlist (relevant levels):

#EXT-X-STREAM-INF:BANDWIDTH=3192000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=60.00
#EXT-X-STREAM-INF:BANDWIDTH=1692000,CODECS="avc1.4D001F,mp4a.40.2",RESOLUTION=852x480,FRAME-RATE=30.00
#EXT-X-STREAM-INF:BANDWIDTH=696000,CODECS="avc1.4D001E,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=30.00
#EXT-X-STREAM-INF:BANDWIDTH=192000,CODECS="avc1.4D000C,mp4a.40.2",RESOLUTION=256x144,FRAME-RATE=30.00
#EXT-X-STREAM-INF:BANDWIDTH=6336000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=30.00

Steps:

  1. On the affected machine, set maxLevel (or use a viewport) so the cap is 720p.
  2. Play the stream; observe ABR caps at 480p30 and never reaches 720p60.
  3. navigator.mediaCapabilities.decodingInfo(...) for the 720p60 config returns smooth: false while videoEl.getVideoPlaybackQuality().droppedVideoFrames ≈ 0.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions