Skip to content

HLSProvider appends its <source> before hls.js attaches, so browsers with native HLS start a second, native load #1871

Description

@azataiot

Versions

  • @vidstack/react 1.15.6
  • hls.js 1.7.3, loaded lazily (provider.library = () => import('hls.js'))
  • Chrome 155 on macOS. document.createElement('video').canPlayType('application/x-mpegurl') returns "maybe", so this Chrome plays HLS natively.

What happens

  1. MediaProvider calls provider.setup(), then sets $providerSetup to true at once. HLSProvider.setup() only starts the hls.js import.
  2. Because $providerSetup is true, loadSource() runs next. It calls this.appendSource(src, 'application/x-mpegurl'). The <video> has no src yet, because hls.js has not attached.
  3. Inserting a <source> into an empty media element starts the HTML resource selection algorithm. Chrome can play application/x-mpegurl, so it starts its own native load of the playlist.
  4. When the import finishes, the lib-loader callback runs controller.setup(ctor). That calls attachMedia, which sets a blob src, and then calls loadSource() again. The native load stops, and hls.js plays.

Effect

  • Every mount sends a second request for the master playlist, from the media element (Playwright resourceType() is media, hls.js requests are xhr).
  • If the import is slow, the native pipeline also loads the variant playlist and the AES-128 key. That key request does not carry the headers that xhrSetup adds, so an authenticated key endpoint rejects it. If the key host is not in media-src, the page also gets a CSP violation.
  • In our end-to-end tests, the race also made play() fail to reach playing in about two out of three runs. With the change below, every run played.

Reproduction

  1. Render <MediaPlayer src={{ src: 'https://example.com/stream/master.m3u8', type: 'application/x-mpegurl' }}><MediaProvider /></MediaPlayer> and set provider.library = () => import('hls.js') in onProviderChange.
  2. Open the page in Chrome 155 or later with the DevTools Network panel open.
  3. Two requests for master.m3u8 show: one with initiator type media and one xhr.

Suggested fix

Append the source element only after hls.js has attached. The second loadSource() call from the lib-loader callback then adds it for AirPlay:

async loadSource(src, preload) {
  if (!isString(src.src)) {
    this.removeSource();
    return;
  }
  this.media.preload = preload || '';
  if (this.#controller.instance) this.appendSource(src, 'application/x-mpegurl');
  this.#controller.loadSource(src);
  this.currentSrc = src;
}

With this change, the media element made no playlist or key request in 6 of 6 runs.

Safari on macOS probably has the same race: hls.js 1.7.3 defaults preferManagedMediaSource to false, so it uses MediaSource, and Safari plays HLS natively. Not tested. DASHProvider.loadSource() has the same shape and probably needs the same guard.

Activity

  1. github-actions commented on Oct 10, 2026

    @github-actions
    Contributor

    Thanks for opening this, and for building with Vidstack.

    Vidstack Player is now in security-only maintenance. Until January 2028, we'll merge priority security fixes into 1.x and nothing else, so we're closing issues that aren't security reports.

    The teams behind Vidstack, Plyr, Media Chrome, and Video.js now work on Video.js 10. The migration guide maps Vidstack onto it: React · Web components and other frameworks.

    Migrating with a coding agent? Paste the prompt from the guide's AI Quickstart section into your agent: React · Web components and other frameworks. It installs the Video.js skill, checks the guide's known gaps against your player before changing code, and plans the installation with npx @videojs/cli agents init.

    If this still matters to you in Video.js 10, open an issue or discussion in videojs/video.js and link back here so we keep the context.

    If this is a security vulnerability, don't add details here. Report it privately as described in SECURITY.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions