Video Playback and Streaming in the Browser

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Baseline date: 2026-10-07. The packaging that produces the files this page plays is in video streaming: HLS, DASH and CMAF. On the client, adaptive streaming needs a player that downloads manifests and segments and feeds them to the <video> element.

The video element

The attributes (poster, preload, playsinline, muted) are explained with the other native attributes in native HTML images and lazy loading. A single MP4 with faststart is the simplest option, see encoding. Use adaptive streaming for anything long, or for audiences on varied connections.

How adaptive playback works

Safari, iOS and tvOS play HLS natively: set video.src to the .m3u8 URL. Other browsers cannot play HLS or DASH natively, so a JavaScript player downloads the manifest, fetches segments with fetch, and appends them to a SourceBuffer with the Media Source Extensions (MSE) API. The player measures throughput and the buffer level, and chooses the next segment’s rendition, which is the ABR (adaptive bitrate) algorithm. iPhones historically lacked MSE. Recent iOS versions provide ManagedMediaSource, a variant of MSE in which the browser can ask the page to stop or resume buffering (to save power), and current versions of hls.js and Shaka Player use it. Check MDN ManagedMediaSource for the supported versions.

hls.js

hls.js plays HLS in browsers that support MSE, and is the usual choice when you only need HLS.

npm install hls.js
import Hls from 'hls.js';

export function attachHls(video, url) {
  if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = url;                              // native HLS: Safari, iOS
    return () => {};
  }
  if (Hls.isSupported()) {
    const hls = new Hls({ capLevelToPlayerSize: true });   // do not pick a rendition larger than the player
    hls.loadSource(url);
    hls.attachMedia(video);
    hls.on(Hls.Events.ERROR, (_event, data) => {
      if (data.fatal && data.type === Hls.ErrorTypes.NETWORK_ERROR) hls.startLoad();
      else if (data.fatal && data.type === Hls.ErrorTypes.MEDIA_ERROR) hls.recoverMediaError();
      else if (data.fatal) hls.destroy();
    });
    return () => hls.destroy();                   // call on unmount
  }
  return () => {};
}

Order matters. Prefer native HLS where it exists, since it uses hardware decoding and the platform’s own ABR. Reference: hls.js API documentation.

dash.js

dash.js is the reference DASH player of the DASH Industry Forum.

import * as dashjs from 'dashjs';

const player = dashjs.MediaPlayer().create();
player.initialize(document.querySelector('video'), 'https://cdn.example.com/videos/42/manifest.mpd', false);
player.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } });

It needs MSE, so it does not play on browsers without it. Use it when you must serve DASH, for example with a DASH-only DRM setup. Reference: dash.js documentation.

Shaka Player

Shaka Player plays both HLS and DASH, with DRM (Widevine, PlayReady, FairPlay), offline storage and a UI library. It is the right choice when you want one player for both manifest types.

import shaka from 'shaka-player';

shaka.polyfill.installAll();
if (shaka.Player.isBrowserSupported()) {
  const video = document.querySelector('video');
  const player = new shaka.Player();
  await player.attach(video);
  player.configure({ abr: { enabled: true } });
  await player.load('https://cdn.example.com/videos/42/master.m3u8');   // or a .mpd URL
}

The import path and the attach call differ between major versions, so follow the basic usage tutorial for the version you install.

Video.js

Video.js is a full player with a skin, plugins and accessibility features. It plays HLS and DASH through its bundled streaming engine (VHS) where MSE is available, and falls back to native HLS.

<video id="player" class="video-js" controls preload="none" width="1280" height="720"
       poster="/videos/42/poster.jpg" data-setup="{}">
  <source src="https://cdn.example.com/videos/42/master.m3u8" type="application/x-mpegURL">
</video>

Choose Video.js when you need a consistent UI across browsers and a plugin ecosystem (thumbnails on the progress bar from a sprite sheet, analytics, ads). Choose hls.js or Shaka Player with the native controls when you want the smallest page.

Which player

Player Formats DRM Notes

Native <video>

MP4, WebM, and HLS in Safari

Platform DRM

Smallest. No ABR outside Safari.

hls.js

HLS

Basic (EME)

The common choice for HLS only. Lightweight.

dash.js

DASH

Yes

DASH Industry Forum reference player.

Shaka Player

HLS and DASH

Yes

One player for both. Includes offline storage and a UI.

Video.js

HLS and DASH

Via plugins

Full UI and plugins.

Delivery details

  • CORS. A JavaScript player fetches manifests and segments with fetch or XHR, so the CDN must send Access-Control-Allow-Origin for your site, and allow the Range header if byte ranges are used.

  • Private video. Authorise a whole path prefix with signed cookies or a token, not one signed URL per file, see video streaming. Cross-origin cookies need withCredentials in the player configuration and SameSite=None; Secure cookies, so serving the video from a subdomain of your site is simpler.

  • Posters and covers. Generate them as described in video covers and thumbnails, and show a BlurHash while the poster loads, see BlurHash in the browser.

  • Start-up time. Start on a low or middle rendition, keep segments short (2 to 6 seconds) and serve the first segments from the CDN edge. The first rendition chosen is set in the player (startLevel in hls.js, abr and bandwidth estimates in Shaka Player and dash.js).

  • Unload. Destroy players on unmount, otherwise SPA navigation leaks MSE buffers and keeps downloading.