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:
- On the affected machine, set
maxLevel (or use a viewport) so the cap is 720p.
- Play the stream; observe ABR caps at 480p30 and never reaches 720p60.
navigator.mediaCapabilities.decodingInfo(...) for the 720p60 config returns smooth: false while videoEl.getVideoPlaybackQuality().droppedVideoFrames ≈ 0.
Overview
AbrController.findBestLevelhard-excludes any candidate whosedecodingInfoResults[0].smooth === false:For
type: 'media-source',smoothis a probabilistic prediction from Chromium'sVideoDecodePerfHistory(learned per codec-profile / resolution / fps bucket), not a deterministic capability likesupported. 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
{ supported: true, powerEfficient: true, smooth: false }for the 720p60avc1.640028rendition.smooth: truefor the identical config and stream.maxLevel/ viewport capping at 720p, the only candidate above 480p issmooth:falseand gets excluded → playback caps at 480p30.powerEfficient: trueindicates a hardware decoder is engaged; steady-statedroppedVideoFramesstays ≈ 0.Why this is an hls.js robustness concern (not only a Chromium bug)
smoothis soft/probabilistic;supportedis a hard fact.findBestLevelcurrently treats both as equally-hard exclusions.Proposal (no new config option)
Treat
smooth: falseas a soft signal infindBestLevel:smoothlevels when available.smooth: falselevel up to the configured cap only when (a) nosmoothcandidate exists at/under the cap, AND (b) that level ispowerEfficient: true(HW decoder engaged — the strongest false-negative signature).powerEfficient: false+smooth: falsestays excluded, preserving protection for genuinely weak setups.This bounds the behavior change to exactly the false-negative shape and keeps the
supported:falseprotection intact (souseMediaCapabilities: falseis not needed). Happy to open a PR.Environment
Video Decode: Hardware accelerated; no HW video overlay (NV12 overlay = SOFTWARE)Reproduction
decodingInfo query:
Multivariant playlist (relevant levels):
Steps:
maxLevel(or use a viewport) so the cap is 720p.navigator.mediaCapabilities.decodingInfo(...)for the 720p60 config returnssmooth: falsewhilevideoEl.getVideoPlaybackQuality().droppedVideoFrames≈ 0.