These six packages address different aspects of video handling in web applications. hls.js is a low-level HLS streaming client that enables HTTP Live Streaming in browsers without native support. plyr is a lightweight, customizable media player with a consistent UI across browsers. plyr-react is a React wrapper around plyr for easier integration in React applications. react-player is a React component that supports multiple video platforms and formats with minimal configuration. video.js is a mature, feature-rich video player framework with extensive plugin support. videojs-record is a plugin for video.js that adds audio and video recording capabilities. Together, they cover everything from basic playback to advanced streaming and recording scenarios.
Building video experiences for the web means choosing between low-level control and ready-made solutions. The six packages we're comparing — hls.js, plyr, plyr-react, react-player, video.js, and videojs-record — each solve different parts of the video puzzle. Let's break down how they work, when to use them, and what trade-offs you'll face in real projects.
hls.js is a streaming protocol handler, not a complete player. It enables HTTP Live Streaming in browsers that don't support it natively (like desktop Chrome or Firefox).
// hls.js: Attach HLS to a video element
import Hls from 'hls.js';
const video = document.getElementById('video');
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource('https://example.com/stream.m3u8');
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => video.play());
}
plyr is a complete player with built-in controls, styling, and multiple source support.
// plyr: Initialize with minimal config
import Plyr from 'plyr';
const player = new Plyr('#player', {
controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'volume', 'settings', 'fullscreen'],
autoplay: true
});
plyr-react wraps plyr for React applications with proper lifecycle handling.
// plyr-react: React component usage
import Plyr from 'plyr-react';
import 'plyr-react/plyr.css';
function VideoComponent() {
return (
<Plyr
source={{
type: 'video',
sources: [{ src: 'https://example.com/video.mp4', type: 'video/mp4' }]
}}
options={{ controls: ['play', 'progress', 'volume'] }}
/>
);
}
react-player is a universal React component supporting multiple platforms.
// react-player: Multi-platform support
import ReactPlayer from 'react-player';
function VideoComponent() {
return (
<ReactPlayer
url='https://www.youtube.com/watch?v=xyz'
controls={true}
width='100%'
height='auto'
/>
);
}
video.js is a full-featured player framework with plugin architecture.
// video.js: Initialize with plugins
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
const player = videojs('my-video', {
controls: true,
autoplay: true,
sources: [{ src: 'https://example.com/video.mp4', type: 'video/mp4' }]
});
videojs-record adds recording capabilities to video.js.
// videojs-record: Enable recording
import videojs from 'video.js';
import 'videojs-record/dist/css/videojs.record.css';
import 'videojs-record/dist/videojs.record.js';
const player = videojs('my-video', {
controls: true,
plugins: {
record: {
audio: true,
video: true,
maxLength: 10,
debug: true
}
}
});
Streaming support is often the deciding factor for video projects.
hls.js specializes in HLS only. It handles manifest parsing, segment loading, and quality switching automatically.
// hls.js: Quality level control
hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
console.log('Quality level changed to:', data.level);
});
// Manual quality selection
hls.currentLevel = 2; // Force specific quality
plyr supports HLS through integration with hls.js or shaka-player, but requires manual setup.
// plyr: HLS integration requires hls.js
import Hls from 'hls.js';
import Plyr from 'plyr';
const video = document.getElementById('player');
const hls = new Hls();
hls.loadSource('stream.m3u8');
hls.attachMedia(video);
const player = new Plyr(video, {
controls: ['play', 'progress', 'volume']
});
plyr-react inherits plyr's HLS capabilities with the same integration pattern.
// plyr-react: HLS with custom source
<Plyr
options={{ controls: ['play', 'volume'] }}
source={{
type: 'video',
sources: [{ src: 'stream.m3u8', type: 'application/x-mpegURL' }]
}}
onReady={(player) => {
// HLS setup happens here if needed
}}
/>
react-player handles HLS automatically for supported platforms but doesn't expose low-level controls.
// react-player: HLS works transparently
<ReactPlayer
url='https://example.com/stream.m3u8'
config={{
file: {
attributes: { crossOrigin: 'true' }
}
}}
/>
video.js supports HLS and DASH through official plugins with robust error handling.
// video.js: HLS via http-streaming plugin (built-in v7+)
import videojs from 'video.js';
import '@videojs/http-streaming';
const player = videojs('my-video', {
sources: [{
src: 'https://example.com/stream.m3u8',
type: 'application/x-mpegURL'
}]
});
// DASH support
player.src({
src: 'https://example.com/stream.mpd',
type: 'application/dash+xml'
});
videojs-record focuses on recording, not streaming playback. It works with video.js streaming capabilities but doesn't add streaming features.
// videojs-record: Recording with streaming source
player.record(); // Start recording
player.stop(); // Stop recording
player.save(); // Save recorded blob
How much control do you need over the player's appearance and behavior?
hls.js provides no UI — you build everything yourself. This gives maximum flexibility but requires significant work.
// hls.js: Custom controls implementation
const video = document.getElementById('video');
const playBtn = document.getElementById('play');
playBtn.addEventListener('click', () => {
if (video.paused) {
video.play();
} else {
video.pause();
}
});
// Custom quality selector
qualitySelect.addEventListener('change', (e) => {
hls.currentLevel = parseInt(e.target.value);
});
plyr offers clean, customizable controls with CSS variables for theming.
// plyr: Custom control layout
const player = new Plyr('#player', {
controls: [
'play-large',
'play',
'progress',
'current-time',
'mute',
'volume',
'settings',
'pip',
'airplay',
'fullscreen'
]
});
// CSS customization via variables
:root {
--plyr-color-main: #007bff;
--plyr-control-radius: 4px;
}
plyr-react provides the same customization through React props.
// plyr-react: Props-based customization
<Plyr
options={{
controls: ['play', 'progress', 'volume'],
settings: ['quality', 'speed']
}}
onPlay={(event) => console.log('Playing', event)}
onPause={(event) => console.log('Paused', event)}
/>
react-player has limited UI customization — you get standard controls per platform.
// react-player: Limited control customization
<ReactPlayer
url='https://youtube.com/watch?v=xyz'
controls={true}
config={{
youtube: {
playerVars: { showinfo: 0, controls: 1 }
}
}}
/>
video.js offers extensive skinning and control bar customization through plugins and CSS.
// video.js: Custom control bar
const player = videojs('my-video', {
controlBar: {
children: [
'playToggle',
'volumePanel',
'currentTimeDisplay',
'timeDivider',
'durationDisplay',
'progressControl',
'fullscreenToggle'
]
}
});
// Custom plugin for additional controls
videojs.registerComponent('MyButton', videojs.getComponent('Button'));
player.controlBar.addChild('MyButton');
videojs-record adds recording-specific UI elements to video.js controls.
// videojs-record: Recording UI configuration
const player = videojs('my-video', {
plugins: {
record: {
audio: true,
video: true,
maxLength: 10,
displayMilliseconds: true
}
}
});
player.on('startRecord', () => console.log('Recording started'));
player.on('finishRecord', () => console.log('Recording finished'));
How well does each package work in modern React applications?
hls.js requires manual lifecycle management in React.
// hls.js: React hook implementation
import { useEffect, useRef } from 'react';
import Hls from 'hls.js';
function HLSVideo({ src }) {
const videoRef = useRef(null);
useEffect(() => {
const video = videoRef.current;
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(src);
hls.attachMedia(video);
return () => hls.destroy();
}
}, [src]);
return <video ref={videoRef} controls />;
}
plyr needs careful cleanup to avoid memory leaks in React.
// plyr: React useEffect pattern
import { useEffect, useRef } from 'react';
import Plyr from 'plyr';
function PlyrVideo({ source }) {
const playerRef = useRef(null);
useEffect(() => {
const player = new Plyr(playerRef.current);
return () => player.destroy();
}, []);
return <video ref={playerRef} data-plyr-provider={source.type} />;
}
plyr-react handles lifecycle automatically — the main advantage over raw plyr.
// plyr-react: Clean React integration
import Plyr from 'plyr-react';
function VideoComponent({ source }) {
return (
<Plyr
source={source}
options={{ controls: ['play', 'volume'] }}
onReady={(player) => console.log('Player ready', player)}
/>
);
}
react-player is built for React from the ground up with the simplest API.
// react-player: Minimal React setup
import ReactPlayer from 'react-player';
function VideoComponent({ url }) {
return (
<ReactPlayer
url={url}
playing={true}
onProgress={({ played, loaded }) => console.log(played, loaded)}
onEnded={() => console.log('Video ended')}
/>
);
}
video.js requires wrapper components for proper React integration.
// video.js: React wrapper component
import { useEffect, useRef } from 'react';
import videojs from 'video.js';
function VideoJS({ options, onReady }) {
const videoRef = useRef(null);
const playerRef = useRef(null);
useEffect(() => {
playerRef.current = videojs(videoRef.current, options, () => {
onReady && onReady(playerRef.current);
});
return () => {
if (playerRef.current) {
playerRef.current.dispose();
}
};
}, [options, onReady]);
return <video ref={videoRef} className="video-js" />;
}
videojs-record inherits video.js React integration requirements plus recording state management.
// videojs-record: React with recording state
import { useState } from 'react';
function RecordingVideo({ onRecordingComplete }) {
const [isRecording, setIsRecording] = useState(false);
const handleReady = (player) => {
player.on('startRecord', () => setIsRecording(true));
player.on('finishRecord', () => {
setIsRecording(false);
onRecordingComplete(player.recordedData);
});
};
return <VideoJS options={recordOptions} onReady={handleReady} />;
}
Only one package in this comparison focuses on recording.
videojs-record provides audio, video, and screen recording with multiple output formats.
// videojs-record: Recording configuration
const player = videojs('my-video', {
plugins: {
record: {
audio: true,
video: true,
screen: false,
maxLength: 10,
frameRate: 30,
videoMimeType: 'video/webm;codecs=vp9'
}
}
});
// Recording controls
player.record(); // Start
player.stop(); // Stop
player.save(); // Download
player.clear(); // Clear for new recording
hls.js, plyr, plyr-react, react-player, and video.js (without the record plugin) do not include recording features. You'd need to implement MediaRecorder API separately.
// Manual recording with native MediaRecorder API
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
const recorder = new MediaRecorder(stream);
const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
// Handle blob
};
recorder.start();
// ... later
recorder.stop();
How easy is it to add features beyond basic playback?
hls.js has no plugin system — you extend through events and configuration.
// hls.js: Event-based extension
hls.on(Hls.Events.FRAG_LOADED, (event, data) => {
// Custom logic when fragment loads
});
hls.on(Hls.Events.ERROR, (event, data) => {
// Custom error handling
if (data.fatal) {
hls.startLoad();
}
});
plyr has limited plugins but supports custom controls through events.
// plyr: Event-based customization
player.on('ready', (event) => {
console.log('Player ready', event.detail.plyr);
});
player.on('play', (event) => {
// Track playback analytics
});
plyr-react inherits plyr's event system through React props.
// plyr-react: Event handlers as props
<Plyr
source={source}
onPlay={(e) => trackAnalytics('play')}
onPause={(e) => trackAnalytics('pause')}
onEnded={(e) => trackAnalytics('ended')}
/>
react-player doesn't support plugins — you're limited to built-in features.
// react-player: No plugin support, only callbacks
<ReactPlayer
url={url}
onPlay={() => console.log('played')}
onPause={() => console.log('paused')}
onProgress={(state) => console.log(state)}
/>
video.js has the richest plugin ecosystem with official and community plugins.
// video.js: Plugin registration and usage
import videojs from 'video.js';
import 'videojs-contrib-ads';
import 'videojs-ima';
// Register custom plugin
videojs.registerPlugin('myPlugin', function(options) {
const player = this;
player.on('ready', () => {
console.log('Custom plugin initialized');
});
});
// Use plugin
const player = videojs('my-video', {
plugins: {
myPlugin: { option: 'value' }
}
});
videojs-record is itself a video.js plugin, demonstrating the extensibility model.
// videojs-record: Plugin integration example
import videojs from 'video.js';
import 'videojs-record/dist/videojs.record.js';
// Works alongside other video.js plugins
const player = videojs('my-video', {
plugins: {
record: { audio: true, video: true },
// Other plugins can coexist
hotspot: { points: [{ time: 10, label: 'Chapter 1' }] }
}
});
What happens when features aren't supported?
hls.js checks for native HLS support and falls back gracefully.
// hls.js: Support detection
if (Hls.isSupported()) {
// Use hls.js
const hls = new Hls();
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Native HLS support (Safari)
video.src = sourceUrl;
} else {
// No HLS support - show fallback
showFallbackMessage();
}
plyr degrades to native controls when features aren't available.
// plyr: Fallback handling
const player = new Plyr('#player', {
controls: ['play', 'volume']
});
// Check for feature support
if (!Plyr.supported.full) {
// Handle unsupported browsers
console.log('Full support not available');
}
plyr-react inherits plyr's compatibility checks.
// plyr-react: Support check before render
import { supported } from 'plyr-react';
if (supported.full) {
return <Plyr source={source} />;
} else {
return <video controls src={source.sources[0].src} />;
}
react-player handles compatibility internally per platform.
// react-player: Platform-specific fallback
<ReactPlayer
url={url}
fallback={<div>Video not supported</div>}
onReady={() => console.log('Player ready')}
onError={(e) => console.log('Error', e)}
/>
video.js has extensive browser detection and fallback mechanisms.
// video.js: Tech order for fallback
const player = videojs('my-video', {
techOrder: ['html5', 'flash'], // Flash fallback (deprecated)
html5: {
hls: { enableLowInitialPlaylist: true }
}
});
player.ready(() => {
console.log('Using tech:', player.techName_);
});
videojs-record requires modern browsers with MediaRecorder support.
// videojs-record: Browser support check
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
console.log('Recording not supported in this browser');
// Show fallback UI
}
| Package | Best For | Avoid When |
|---|---|---|
hls.js | Custom players, HLS-only streaming, fine-grained control | You need ready-made UI, multiple formats |
plyr | Simple projects, consistent UI, light customization | You need advanced streaming, enterprise features |
plyr-react | React apps wanting plyr's simplicity | The wrapper is unmaintained, you need video.js features |
react-player | Quick integration, multiple platforms, minimal config | You need custom controls, streaming protocols |
video.js | Enterprise apps, plugins, advanced streaming | You want minimal dependencies, simple use cases |
videojs-record | Recording features on top of video.js | You need standalone recording, no playback needs |
For streaming-first applications — Choose hls.js for control or video.js for features. If you're building a video platform with adaptive bitrate streaming, these are your foundation.
For content websites — Choose react-player for speed or plyr for customization. Marketing sites, blogs, and courses benefit from quick setup and broad format support.
For React applications — Choose react-player for simplicity or plyr-react for plyr features. Consider wrapping video.js yourself if you need enterprise capabilities.
For recording features — Choose videojs-record if you're already using video.js. Otherwise, implement MediaRecorder API directly for standalone recording needs.
Final Thought: The right choice depends on your project's complexity, not just features. Simple sites don't need video.js's power, but enterprise platforms will outgrow react-player quickly. Match the tool to your actual requirements — not what might be useful someday.
Choose hls.js when you need fine-grained control over HLS streaming behavior or when building a custom player UI from scratch. It's ideal for teams that want to handle streaming logic separately from UI concerns. Best for applications requiring custom quality switching, DRM integration, or specialized buffering strategies. Not recommended if you need a complete player solution with built-in controls.
Choose plyr when you want a lightweight, customizable player with a consistent look across browsers without heavy dependencies. It's perfect for projects that need HTML5 video, audio, YouTube, or Vimeo support with minimal setup. Best for marketing sites, blogs, or applications where player customization matters but advanced streaming features aren't required. Avoid if you need extensive plugin ecosystems or enterprise-level features.
Choose plyr-react when you're building a React application and want plyr's simplicity with proper React integration. It handles lifecycle management and prop updates automatically. Best for React projects that need plyr's features without manual DOM manipulation. However, verify current maintenance status before adopting, as wrapper libraries can fall behind main package updates.
Choose react-player when you need a drop-in React component that supports multiple video sources (YouTube, Vimeo, SoundCloud, local files) with zero configuration. It's ideal for content-heavy sites where you don't control the video hosting. Best for rapid development when you need broad format support without building custom players. Avoid if you need deep customization of player controls or streaming protocols.
Choose video.js when you need a mature, extensible player with a rich plugin ecosystem for enterprise applications. It's perfect for projects requiring HLS, DASH, live streaming, or custom plugin development. Best for video platforms, learning management systems, or applications needing advanced features like ads, analytics, or DRM. The learning curve is steeper but pays off for complex requirements.
Choose videojs-record when you need recording capabilities built on top of video.js infrastructure. It's ideal for applications requiring user-generated video content, video messaging, or screen recording features. Best when you've already chosen video.js for playback and need recording as an add-on. Not recommended as a standalone recording solution without video.js playback needs.
HLS.js is a JavaScript library that implements an HTTP Live Streaming client. It relies on HTML5 video and MediaSource Extensions for playback.
It works by transmuxing MPEG-2 Transport Stream and AAC/MP3 streams into ISO BMFF (MP4) fragments. Transmuxing is performed asynchronously using a Web Worker when available in the browser. HLS.js also supports HLS + fmp4, as announced during WWDC2016.
HLS.js works directly on top of a standard HTML<video> element.
HLS.js is written in ECMAScript6 (*.js) and TypeScript (*.ts) (strongly typed superset of ES6), and transpiled in ECMAScript5 using Babel and the TypeScript compiler.
Rollup is used to build the distro bundle and serve the local development environment.
SUPPLEMENTAL-CODECS attribute for codec selectionenableEmsgKLVMetadata)mjpg) renditionsFor details on the HLS format and these tags' meanings, see https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis
#EXT-X-STREAM-INF:<attribute-list>
<URI>#EXT-X-I-FRAME-STREAM-INF I-frame Media Playlist files#EXT-X-MEDIA:<attribute-list>#EXT-X-SESSION-DATA:<attribute-list>#EXT-X-SESSION-KEY:<attribute-list> EME Key-System selection and preloading#EXT-X-START:TIME-OFFSET=<n>#EXT-X-CONTENT-STEERING:<attribute-list> Content Steering#EXT-X-DEFINE:<attribute-list> Variable Substitution (NAME,VALUE,QUERYPARAM attributes)#EXTM3U (required format identifier)#EXT-X-VERSION:<n> (value is ignored)#EXT-X-INDEPENDENT-SEGMENTS (ignored)#EXT-X-I-FRAMES-ONLY#EXTINF:<duration>,[<title>]#EXT-X-ENDLIST#EXT-X-PLAYLIST-TYPE:<type-enum> (see "Not Supported" below)#EXT-X-MEDIA-SEQUENCE:<n>#EXT-X-TARGETDURATION:<n>#EXT-X-DISCONTINUITY#EXT-X-DISCONTINUITY-SEQUENCE:<n>#EXT-X-BITRATE:<rate>#EXT-X-BYTERANGE:<n>[@<o>]#EXT-X-MAP:<attribute-list>#EXT-X-KEY:<attribute-list> (KEYFORMAT="identity",METHOD=SAMPLE-AES is only supported with MPEG-2 TS segments)#EXT-X-PROGRAM-DATE-TIME:<date-time-msec>#EXT-X-START:TIME-OFFSET=<n>#EXT-X-SERVER-CONTROL:<attribute-list>#EXT-X-PART-INF:PART-TARGET=<n>#EXT-X-PART:<attribute-list>#EXT-X-SKIP:<attribute-list> Delta Playlists#EXT-X-RENDITION-REPORT:<attribute-list>#EXT-X-DATERANGE:<attribute-list> Metadata
#EXT-X-DEFINE:<attribute-list> Variable Import and Substitution (NAME,VALUE,IMPORT,QUERYPARAM attributes)#EXT-X-GAP (Skips loading GAP segments and parts. Skips playback of unbuffered program containing only GAP content and no suitable alternates. See #2940)Parsed but missing feature support:
#EXT-X-PRELOAD-HINT:<attribute-list> (See #5074)For a complete list of issues, see "Top priorities" in the Release Planning and Backlog project tab. Codec support is dependent on the runtime environment (for example, not all browsers on the same OS support HEVC).
REQ-VIDEO-LAYOUT is not used in variant filtering or selectionSAMPLE-AES method keys with fmp4, aac, mp3, vtt... segments (MPEG-2 TS only)org.w3.clearkey) is incomplete: the key system is recognized, but there is no way to supply key ID/key value pairs to the EME controller, so no license or session path exists (See #2934)light build (see __USE_M2TS_ADVANCED_CODECS__)require from a Node.js runtimeYou can safely require this library in Node and absolutely nothing will happen. A dummy object is exported so that requiring the library does not throw an error. HLS.js is not instantiable in Node.js. See #1841 for more details.
First, checkout the repository and install the required dependencies
git clone https://github.com/video-dev/hls.js.git
cd hls.js
# After cloning or pulling from the repository, make sure all dependencies are up-to-date
npm install ci
# Run dev-server for demo page (recompiles on file-watch, but doesn't write to actual dist fs artifacts)
npm run dev
# After making changes run the sanity-check task to verify all checks before committing changes
npm run sanity-check
The dev server will host files on port 8000. Once started, the demo can be found running at http://localhost:8000/demo/.
Before submitting a PR, please see our contribution guidelines. Join the discussion on Slack via video-dev.org in #hlsjs for updates and questions about development.
Build all flavors (suitable for prod-mode/CI):
npm install ci
npm run build
Only debug-mode artifacts:
npm run build:debug
Build and watch (customized dev setups where you'll want to host through another server - for example in a sub-module/project)
npm run build:watch
Only specific flavors (known configs are: full, fullMin, fullEsm, fullEsmMin, light, lightMin, lightEsm, lightEsmMin, worker, demo):
npm run build -- --configType fullMin # repeat --configType to build more than one
Report the size of the built dist/ files, and check them against the budgets in
dist-size-budget.json (the same check CI runs):
npm run size
npm run size:check
NOTE: hls.light.*.js dist files do not include alternate-audio, subtitles, CMCD, EME (DRM), Variable Substitution, Interstitials, I-frame trick-play, Media Capabilities, or MPEG-2 TS advanced codec (HEVC and AC-3) support. Content Steering is included. In addition, the following types are not available in the light build:
AudioStreamControllerAudioTrackControllerCuesInterfaceEMEControllerSubtitleStreamControllerSubtitleTrackControllerTimelineControllerCMCDControllerInterstitialsControllerInterstitialsManagerIFrameControllerHlsIFramesOnlyHlsImageIFramesOnlyRun linter:
npm run lint
Run linter with auto-fix mode:
npm run lint:fix
Run linter with errors only (no warnings)
npm run lint:quiet
Run prettier to format code
npm run prettier
Run type-check to verify TypeScript types
npm run type-check
Run all tests at once:
npm test
Run unit tests:
npm run test:unit
Run unit tests in watch mode:
npm run test:unit:watch
Run functional (integration) tests:
npm run test:func
An overview of this project's design, it's modules, events, and error handling can be found here.
Note you can access the docs for a particular version using "https://github.com/video-dev/hls.js/tree/deployments"
https://hlsjs.video-dev.org/demo
https://hlsjs-dev.video-dev.org/demo
Find the commit on https://github.com/video-dev/hls.js/tree/deployments.
This project is tested with BrowserStack. This project is tested with SauceLabs.
HLS.js is only compatible with browsers supporting MediaSource extensions (MSE) API with 'video/MP4' mime-type inputs.
HLS.js is supported on:
These versions are the targets passed to @babel/preset-env when building the UMD bundles in dist/. They share an ES2016 runtime baseline: ES5-style syntax plus native ES2016 globals (Map, Set, Promise, Array.from, Uint8Array.from, Array.prototype.includes, etc.). To keep bundle size small, no core-js polyfills are bundled.
Optional features such as CMCD pull in ES2017 APIs (e.g. Object.entries), so the full UMD bundle effectively requires an ES2017-capable runtime. The light bundle excludes those features and stays at the ES2016 baseline.
The dist/ folder ships two distribution variants:
dist/hls.js, dist/hls.min.js, dist/hls.light.js, dist/hls.light.min.js) — embeddable directly via a <script> tag (exposes a global Hls) or resolved by require('hls.js') via package.json's main field. Targets the browser list above. The companion dist/hls.worker.js is the bundled transmuxer Web Worker.dist/hls.mjs, dist/hls.light.mjs, plus the minified dist/hls.min.mjs and dist/hls.light.min.mjs) — import 'hls.js' resolves to the unminified dist/hls.mjs via the module field, which is what you want when a bundler will minify it for you. The .min.mjs files exist for loading straight from a CDN with <script type="module">. Built with @babel/preset-env's esmodules: true target (≈ Chrome 61+, Firefox 60+, Safari 10.1+, Edge 16+) and intended to be consumed by a modern bundler. Uses ES2015+ syntax but stays below ES2019 (no Array.prototype.flatMap, Object.fromEntries, etc.).The ESM builds do not bundle the transmuxer Web Worker. The UMD builds inline it, but
dist/hls.mjsanddist/hls.min.mjsdo not, so transmuxing runs on the main thread unless you pointworkerPathat the separately published worker:const hls = new Hls({ workerPath: 'https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.worker.js', });
If you import from src/ directly or include any of our runtime dependencies untranspiled in your own build, you bypass this Babel pipeline and become responsible for transpilation; those source modules can reach for ES2019+ APIs that are tree-shaken out of the bundles we publish.
To run on browsers below this baseline, supply your own polyfills for any missing globals before HLS.js loads.
Please note:
Safari browsers (iOS, iPadOS, and macOS) have built-in HLS support through the plain video "tag" source URL. See the example below (Using HLS.js) to run appropriate feature detection and choose between using HLS.js or natively built-in HLS support.
When a platform has neither MediaSource nor native HLS support, the browser cannot play HLS.
Keep in mind that if the intention is to support HLS on multiple platforms, beyond those compatible with HLS.js, the HLS streams need to strictly follow the specifications of RFC8216, especially if apps, smart TVs, and set-top boxes are to be supported.
Find a support matrix of the MediaSource API here: https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
Prepackaged builds are included with each release. Or install the hls.js as a dependency of your project:
npm install --save hls.js
A canary channel is also available if you prefer to work off the development branch (master):
npm install hls.js@canary
Directly include dist/hls.js or dist/hls.min.js in a script tag on the page. This setup prioritizes HLS.js MSE playback over native browser support for HLS playback in HTMLMediaElements:
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<!-- Or if you want the latest version from the main branch -->
<!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@canary"></script> -->
<video id="video"></video>
<script>
var video = document.getElementById('video');
var videoSrc = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8';
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
}
// HLS.js is not supported on platforms that do not have Media Source
// Extensions (MSE) enabled.
//
// When the browser has built-in HLS support (check using `canPlayType`),
// we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video
// element through the `src` property. This is using the built-in support
// of the plain video element, without using HLS.js.
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc;
}
</script>
To check for native browser support first and then fallback to HLS.js, swap these conditionals.
Note:
video.canPlayType('application/vnd.apple.mpegurl')returns a non-empty string ("maybe") in Safari, Chrome, and potentially other browsers. However, not all browsers support HLS content equally — for example, Chrome 147 reports support but may fail to play certain streams natively. UsingHls.isSupported()first (the default setup above) is recommended unless you specifically need native playback.
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<!-- Or if you want the latest version from the main branch -->
<!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@canary"></script> -->
<video id="video"></video>
<script>
var video = document.getElementById('video');
var videoSrc = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8';
//
// Only use native HLS in browsers with ManagedMediaSource (e.g. modern Safari)
// where native playback is well-supported. Other browsers may report HLS support
// via canPlayType but fail to play certain streams reliably.
//
if (
video.canPlayType('application/vnd.apple.mpegurl') &&
'ManagedMediaSource' in window
) {
video.src = videoSrc;
//
// If not using native HLS, check if HLS.js is supported
//
} else if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
}
</script>
HLS transcoding of an original video file often pushes the time of the first frame a bit. If you depend on having an exact match of frame times between original video and HLS stream, you need to account for this:
let tOffset = 0;
const getAppendedOffset = (eventName, { frag }) => {
if (frag.type === 'main' && frag.sn !== 'initSegment' && frag.elementaryStreams.video) {
const { start, startDTS, startPTS, maxStartPTS, elementaryStreams } = frag;
tOffset = elementaryStreams.video.startPTS - start;
hls.off(Hls.Events.BUFFER_APPENDED, getAppendedOffset);
console.log('video timestamp offset:', tOffset, { start, startDTS, startPTS, maxStartPTS, elementaryStreams });
}
}
hls.on(Hls.Events.BUFFER_APPENDED, getAppendedOffset);
// and account for this offset, for example like this:
const video = document.querySelector('video');
video.addEventListener('timeupdate', () => setTime(Math.max(0, video.currentTime - tOffset))
const seek = (t) => video.currentTime = t + tOffset;
const getDuration = () => video.duration - tOffset;
For more embed and API examples see docs/API.md.
All HLS resources must be delivered with CORS headers permitting GET requests.
Video is controlled through HTML <video> element HTMLVideoElement methods, events and optional UI controls (<video controls>).
The following players integrate HLS.js for HLS playback:
![]() | |||
![]() | ![]() | ![]() | |
![]() | ![]() | ![]() | |
![]() | ![]() | ![]() | |
![]() | ![]() | ||
![]() | ![]() | ![]() | ![]() |
![]() | |||
![]() | ![]() | ![]() | |
| cdn77 | ![]() | ||
![]() | ![]() | ||
![]() |
made by gramk, plays hls from address bar and m3u8 links
HLS.js is released under Apache 2.0 License