video-dev / video-dev/hls.js

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

Open
#7,917 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

media-capabilities
Dominant language
TypeScript
Stars
16.9k
Forks
2.8k
Avg merge
1d 12h
Merged PRs (30d)
27

Description

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)

  • smooth is soft/probabilistic; supported is a hard fact. findBestLevel currently treats both as equally-hard exclusions.
  • hls.js already works around Windows MediaCapabilities unreliability for HEVC (#7046, #6849). This is the same class of false-negative for H.264.
  • Filed upstream at Chromium (accepted & assigned): https://issues.chromium.org/issues/525612738

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/controller/abr-controller.ts at AbrController.findBestLevel and trace how supportedResult.decodingInfoResults[0].smooth and powerEfficient affect candidate exclusion under maxLevel. Use the supplied MediaCapabilities reproduction and capped playlist to verify the fallback case; done means smooth levels remain preferred while a power-efficient smooth:false level can be selected only when no smooth candidate exists within the cap.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
64/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.