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 |
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
fetchor XHR, so the CDN must sendAccess-Control-Allow-Originfor your site, and allow theRangeheader 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
withCredentialsin the player configuration andSameSite=None; Securecookies, 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 (
startLevelin hls.js,abrandbandwidthestimates in Shaka Player and dash.js). -
Unload. Destroy players on unmount, otherwise SPA navigation leaks MSE buffers and keeps downloading.