HTML5 live video: connect an HLS playlist to a player

A live event's .m3u8 address is a playlist, so treating it like an ordinary MP4 does not work consistently across browsers. Use a browser's native HLS support or an HLS client such as hls.js, then inspect the playlist and the media segments it requests. The player consumes a stream that already exists; adding a video element does not broadcast a camera or create an HLS service.

Start with the actual stream address

Get the HLS playback URL from the streaming provider or your existing encoder pipeline. It must identify an HLS playlist, not an event landing page or a dashboard URL. A multivariant playlist can point to other playlists, which in turn identify the media segments.

For a fictional workshop, the playback path might be /streams/workshop/live.m3u8. That path is a placeholder for your supplied stream. It will not play until the server actually serves a valid playlist and its referenced resources.

The HLS specification defines playlist updates, media sequence numbers, and the EXT-X-ENDLIST tag. A file ending in .m3u8 can describe on-demand content too. Inspect how the playlist changes over time before calling it a live feed.

Load hls.js where supported, with a native fallback

Obtain a released hls.js UMD browser bundle and save it at vendor/hls.min.js in this example's served folder. Keep the version in your dependency or deployment record. The original integration below follows the project's hls.js-first feature-detection order:

<video id="workshop" controls playsinline
  style="width:100%;max-width:48rem"></video>
<p id="stream-status" role="status">Preparing the live player…</p>
<p><a href="/event">Open the event information and viewing options</a></p>
<script src="vendor/hls.min.js"></script>
<script>
  const video = document.querySelector('#workshop');
  const status = document.querySelector('#stream-status');
  const stream = '/streams/workshop/live.m3u8';

  if (window.Hls && Hls.isSupported()) {
    const hls = new Hls();
    hls.on(Hls.Events.ERROR, (_event, data) => {
      if (data.fatal) {
        status.textContent = 'The stream cannot continue. Check event viewing options.';
        hls.destroy();
      }
    });
    hls.on(Hls.Events.MANIFEST_PARSED, () => {
      status.textContent = 'Stream playlist loaded. Press Play.';
    });
    hls.loadSource(stream);
    hls.attachMedia(video);
  } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = stream;
    status.textContent = 'Using the browser player. Press Play.';
  } else {
    status.textContent = 'HLS playback is unavailable in this browser.';
  }
  video.addEventListener('playing', () => {
    status.textContent = 'Playing the workshop stream.';
  });
  video.addEventListener('error', () => {
    status.textContent = 'Video playback failed. Check event viewing options.';
  });
</script>

Replace /event with a real information page. No autoplay is requested, so the viewer chooses when to start. The example stops on a fatal streaming error; production retry and recovery behavior needs a policy suited to the event.

The current hls.js README recommends checking its supported playback path first unless native HLS is specifically needed. A nonempty canPlayType() result is a capability hint, not proof that every stream will decode. Test the actual codecs and delivery setup in the browsers your event requires.

Trace the first failed streaming resource

Open Network before reloading. Find the initial playlist, any variant playlist it selects, and the following media requests. A successful initial .m3u8 request does not prove its segments are reachable.

Observation Next check
Playlist response contains HTML Replace a dashboard, login, or error URL with the playback URL
Playlist arrives; segments return 404 Resolve segment paths relative to the playlist that names them
Requests fail across origins Check CORS for playlists, segments, and other stream resources
Old segments are repeatedly requested Inspect playlist freshness, caching, and the current sequence
Requests succeed; decoding fails Compare stream codecs with the tested playback path

The project's CORS requirement applies to all HLS resources, not only the first manifest. Fix delivery permissions at the serving origin; adding crossorigin to an element cannot grant server permission.

Check the live session, not just the first frame

Watch long enough for new segments and playlist updates. Pause and resume, then see whether playback remains delayed or returns toward the live position. Live delay depends on the stream packaging and player configuration; a loaded manifest does not prove low-latency delivery.

For a static clip, the ordinary HTML video element is simpler. Before an actual live event, retain the tested stream URL, player version, target browsers, and fallback viewing instructions together. An event page is ready when viewers can start and continue the real stream, not when an empty player displays controls.

Keep reading