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
MediaProvider calls provider.setup(), then sets $providerSetup to true at once. HLSProvider.setup() only starts the hls.js import.
- 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.
- 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.
- 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
- 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.
- Open the page in Chrome 155 or later with the DevTools Network panel open.
- 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.
Versions
@vidstack/react1.15.6hls.js1.7.3, loaded lazily (provider.library = () => import('hls.js'))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.$providerSetupistrue,loadSource()runs next. It callsthis.appendSource(src, 'application/x-mpegurl'). The<video>has nosrcyet, because hls.js has not attached.<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.controller.setup(ctor). That callsattachMedia, which sets a blobsrc, and then callsloadSource()again. The native load stops, and hls.js plays.Effect
resourceType()ismedia, hls.js requests arexhr).xhrSetupadds, so an authenticated key endpoint rejects it. If the key host is not inmedia-src, the page also gets a CSP violation.play()fail to reachplayingin about two out of three runs. With the change below, every run played.Reproduction
<MediaPlayer src={{ src: 'https://example.com/stream/master.m3u8', type: 'application/x-mpegurl' }}><MediaProvider /></MediaPlayer>and setprovider.library = () => import('hls.js')inonProviderChange.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: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
preferManagedMediaSourcetofalse, so it usesMediaSource, and Safari plays HLS natively. Not tested.DASHProvider.loadSource()has the same shape and probably needs the same guard.