Desktop Chrome does not play HLS natively. Pasting an .m3u8 URL into the address bar, or setting it as a <video src>, will do nothing. Safari on macOS and iOS, and most Android browsers, support HLS natively; Chrome on the desktop needs a JavaScript player such as hls.js, which feeds the segments into the browser through Media Source Extensions (MSE). This single difference explains most "works on my iPhone, broken on Chrome" reports.
<video id="v" controls muted style="width:100%"></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<script>
const url = 'https://your-domain/hls/index.m3u8';
const v = document.getElementById('v');
if (v.canPlayType('application/vnd.apple.mpegurl')) {
v.src = url; // native HLS (Safari, iOS)
} else if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(url);
hls.attachMedia(v);
hls.on(Hls.Events.ERROR, (_, data) => {
if (data.fatal) console.error(data.type, data.details);
});
} else {
console.error('HLS is not supported in this browser');
}
</script>
The canPlayType branch is important: on Safari you should use native playback rather than MSE, and on browsers without MSE at all you need a graceful fallback. Pin hls.js to a specific version in production instead of floating @1.
Open DevTools and check the Network tab before blaming the player code. You are looking for three separate requests: the master playlist, the variant playlist, and the first segment.
curl -i and confirm the first line is #EXTM3U.MANIFEST_PARSING_ERROR in the console.Access-Control-Allow-Origin for your page's origin. Setting CORS only on the playlist is not enough — segments and the encryption key need it too.https:// page cannot load an http:// playlist. Serve everything over TLS.muted and let the user click play, or handle the rejected play() promise.application/vnd.apple.mpegurl and .ts segments need video/mp2t. With nosniff enabled, browsers will not guess.#EXT-X-KEY points at a key file that must also be reachable and CORS-enabled. A missing key stops playback before any segment is decoded.If curl returns a valid playlist and a valid segment, the stream itself is fine and the problem is in the browser layer: player library, CORS, MIME types, or autoplay. If curl fails, fix the server first and do not touch the JavaScript. Testing the same URL in Safari is a useful second data point: when Safari plays it and Chrome does not, it is almost always one of the browser-layer issues above rather than the encode.