HLSProvider appends its <source> before hls.js attaches, so browsers with native HLS start a second, native load #1871
Copy link
Copy link
Closed as not planned
Closed as not planned
Copy link
Description
Versions
@vidstack/react1.15.6hls.js1.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
MediaProvidercallsprovider.setup(), then sets$providerSetuptotrueat once.HLSProvider.setup()only starts the hls.js import.- Because
$providerSetupistrue,loadSource()runs next. It callsthis.appendSource(src, 'application/x-mpegurl'). The<video>has nosrcyet, because hls.js has not attached. - Inserting a
<source>into an empty media element starts the HTML resource selection algorithm. Chrome can playapplication/x-mpegurl, so it starts its own native load of the playlist. - When the import finishes, the lib-loader callback runs
controller.setup(ctor). That callsattachMedia, which sets a blobsrc, and then callsloadSource()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()ismedia, hls.js requests arexhr). - 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
xhrSetupadds, so an authenticated key endpoint rejects it. If the key host is not inmedia-src, the page also gets a CSP violation. - In our end-to-end tests, the race also made
play()fail to reachplayingin about two out of three runs. With the change below, every run played.
Reproduction
- Render
<MediaPlayer src={{ src: 'https://example.com/stream/master.m3u8', type: 'application/x-mpegurl' }}><MediaProvider /></MediaPlayer>and setprovider.library = () => import('hls.js')inonProviderChange. - Open the page in Chrome 155 or later with the DevTools Network panel open.
- Two requests for
master.m3u8show: one with initiator typemediaand onexhr.
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.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
github-actions commentedon Oct 10, 2026
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.