Skip to content

capLevelToPlayerSize never engages when the media element size is stable (1.7 ResizeObserver regression) #8007

Description

@jeanbaptistelaine

What version of HLS.js are you using?

v1.7.1 (also reproduced on v1.7.2-0.canary.11984 via the dev demo). Not reproducible on v1.6.19.

What browser (including version) are you using?

Chrome 139.0.7258 (also Chromium via Playwright)

What OS (including version) are you using?

macOS 26

Test stream

https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8

Configuration

{
  "capLevelToPlayerSize": true
}

Additional player setup steps

The media element must have a stable layout size (explicit CSS width/height) before attachMedia. This is the common production case (fixed player, poster). The default demo page masks the bug because its video element resizes when media dimensions arrive, which happens to re-fire the ResizeObserver.

Checklist

  • The issue observed is not already reported by searching on Github
  • The issue occurs in the stable client (latest release) on https://hlsjs.video-dev.org/demo and not just on my page
  • The issue occurs in the latest client (main branch) on https://hlsjs-dev.video-dev.org/demo and not just on my page
  • The stream has correct Access-Control-Allow-Origin headers (CORS)
  • There are no network errors such as 404s in the browser console when trying to play the stream

Steps to reproduce

  1. Open https://hlsjs.video-dev.org/demo/ and run this in the console:
window.hls.destroy();
const video = document.querySelector('video');
video.style.width = '640px'; video.style.height = '360px'; // stable layout, like a real player
setTimeout(() => {
  const h = new Hls({ capLevelToPlayerSize: true, debug: true });
  window.h = h;
  h.loadSource('https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8');
  h.attachMedia(video);
  video.muted = true;
  setTimeout(() => console.log('autoLevelCapping after 4s:', h.autoLevelCapping), 4000);
}, 300);
  1. Wait a few seconds, then check h.autoLevelCapping.

Expected behaviour

A 640x360 player should be capped (level 2, 848x480 on this stream), as in v1.6.x.

What actually happened?

h.autoLevelCapping stays -1 for the whole session; the 1920x1080 level plays. CapLevelController's internal autoLevelCapping is still Infinity (its constructor value), i.e. detectPlayerSize() never ran with levels loaded.

Calling it manually proves the machinery itself is fine — the trigger is what's missing:

h.capLevelController.detectPlayerSize();
h.autoLevelCapping; // 2, immediately

Cause (from reading cap-level-controller.ts, introduced by #7108):

  1. onMediaAttaching calls observe(), creating the ResizeObserver before the manifest is loaded.
  2. The observer's single initial callback fires on the next rendering frame → detectPlayerSize() runs with levels.length === 0 and does nothing.
  3. MANIFEST_PARSED / BUFFER_CODECSstartCapping()if (this.timer || this.observer || ...) return; — the observer already exists, so it early-returns before reaching its detectPlayerSize() call.
  4. onLevelsUpdated can't recover either: Number.isFinite(this.autoLevelCapping) is false (still Infinity) and this.observer !== undefined.

Nothing re-triggers detection unless the element happens to resize after the manifest is parsed. In v1.6.x the setInterval in startCapping recovered within 1s regardless.

If the manifest loads within one rendering frame of attachMedia (e.g. cached), the initial observer callback lands after MANIFEST_PARSED and everything works — which makes this intermittent on fast networks and consistent on real ones.

Console output

(debug: true — no capping log for the entire session; only after the manual call:)
[log] > Setting autoLevelCapping to 2: 480p@836280 for media 640x360
[log] > set autoLevelCapping:2

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugConfirmedBug report confirmed or reproduced.

    Type

    No type

    Projects

    Status
    Top priorities

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions