hls.js vs plyr vs plyr-react vs react-player vs video.js vs videojs-record
Video Playback and Recording Solutions for Modern Web Applications
hls.jsplyrplyr-reactreact-playervideo.jsvideojs-recordSimilar Packages:

Video Playback and Recording Solutions for Modern Web Applications

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
hls.js016,87332.6 MB826 hours agoApache-2.0
plyr029,9455.33 MB9367 months agoMIT
plyr-react052260.4 kB358 months agoMIT
react-player010,28140.5 kB599 months agoMIT
video.js039,84818.1 MB6649 days agoApache-2.0
videojs-record01,4331.55 MB722 years agoMIT

Video Playback and Recording Solutions: A Technical Deep-Dive

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.

🎯 Core Purpose: What Problem Does Each Solve?

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 Protocol Support

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

🎨 UI Customization and Control

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'));

⚛️ React Integration Patterns

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} />;
}

🎤 Recording Capabilities

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();

🔧 Plugin Ecosystem and Extensibility

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' }] }
  }
});

🌐 Browser Compatibility and Fallbacks

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
}

📊 Summary: When to Use Each Package

PackageBest ForAvoid When
hls.jsCustom players, HLS-only streaming, fine-grained controlYou need ready-made UI, multiple formats
plyrSimple projects, consistent UI, light customizationYou need advanced streaming, enterprise features
plyr-reactReact apps wanting plyr's simplicityThe wrapper is unmaintained, you need video.js features
react-playerQuick integration, multiple platforms, minimal configYou need custom controls, streaming protocols
video.jsEnterprise apps, plugins, advanced streamingYou want minimal dependencies, simple use cases
videojs-recordRecording features on top of video.jsYou need standalone recording, no playback needs

💡 The Big Picture

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.

How to Choose: hls.js vs plyr vs plyr-react vs react-player vs video.js vs videojs-record

  • hls.js:

    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.

  • plyr:

    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.

  • plyr-react:

    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.

  • react-player:

    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.

  • video.js:

    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.

  • videojs-record:

    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.

README for hls.js

npm npm Sauce Test Status jsDeliver

HLS.js

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.

Features

  • VOD & Live playlists
    • DVR support on Live playlists
    • Low-Latency HLS (Partial Segments, Blocking Playlist Reload, Playlist Delta Updates, and Rendition Reports)
  • Fragmented MP4 container
    • HEVC, AV1, VP9, and Dolby Vision video, subject to runtime support
    • AC-3, EC-3, FLAC, Opus, and ALAC audio, subject to runtime support
    • SUPPLEMENTAL-CODECS attribute for codec selection
  • MPEG-2 TS container
    • ITU-T Rec. H.264 and ISO/IEC 14496-10 Elementary Stream
    • ITU-T Rec. H.265 and ISO/IEC 23008-2 Elementary Stream (full build only)
    • ISO/IEC 13818-7 ADTS AAC Elementary Stream
    • ISO/IEC 11172-3 / ISO/IEC 13818-3 (MPEG-1/2 Audio Layer III) Elementary Stream
    • ATSC A/52 / AC-3 / Dolby Digital Elementary Stream (full build only)
    • Packetized metadata (ID3v2.3.0) Elementary Stream
  • AAC container (audio only streams)
  • MPEG Audio container (MPEG-1/2 Audio Layer III audio only streams)
  • Timed Metadata for HTTP Live Streaming (ID3 format carried in MPEG-2 TS, Emsg in CMAF/Fragmented MP4, and DATERANGE playlist tags)
    • MISB KLV metadata in MPEG-2 TS (opt-in via enableEmsgKLVMetadata)
  • AES-128, AES-256, and AES-256-CTR decryption
  • "identity" format SAMPLE-AES decryption of MPEG-2 TS segments only
  • Encrypted media extensions (EME) support for DRM (digital rights management)
    • FairPlay, PlayReady, and Widevine CDMs with fmp4 segments
  • Level capping based on HTMLMediaElement resolution, dropped-frames, and HDCP-Level
  • CEA-608/708 captions
  • WebVTT subtitles
  • IMSC1 (TTML) subtitles, limited to the text profile and a subset of TTML styling
  • Adaptive streaming
    • Manual & Auto Quality Switching
      • 3 Quality Switching modes are available (controllable through API means)
        • Instant switching (immediate quality switch at current video position)
        • Smooth switching (quality switch for next loaded fragment)
        • Bandwidth conservative switching (quality switch change for next loaded fragment, without flushing the buffer)
      • In Auto-Quality mode, emergency switch down in case bandwidth is suddenly dropping to minimize buffering.
  • Alternate Audio Track Rendition (Multivariant Playlist with Alternative Audio) for VoD and Live playlists
  • HLS Interstitials (ad insertion and content replacement scheduled with DATERANGE tags)
  • I-frame trick-play, including image I-frame (mjpg) renditions
  • Accurate Seeking on VoD & Live (not limited to fragment or keyframe boundary)
  • Ability to seek in buffer and back buffer without redownloading segments
  • Built-in Analytics
    • All internal events can be monitored (Network Events, Video Events)
    • Playback session metrics are also exposed
    • Common Media Client Data (CMCD)
  • Content Steering
  • Resilience to errors
    • Retry mechanism embedded in the library
    • Recovery actions can be triggered fix fatal media or network errors
  • Redundant/Failover Playlists
  • HLS Variable Substitution

Supported HLS tags

For details on the HLS format and these tags' meanings, see https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis

Multivariant Playlist tags

  • #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)

Media Playlist tags

  • #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
    • HLS EXT-X-DATERANGE Schema for Interstitials
  • #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)

Not Supported

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).

  • #EXT-X-PLAYLIST-TYPE is not used to determine if media playlists should be reloaded based on "Expires" header value (#7082)
  • REQ-VIDEO-LAYOUT is not used in variant filtering or selection
  • "identity" format SAMPLE-AES method keys with fmp4, aac, mp3, vtt... segments (MPEG-2 TS only)
  • MPEG-2 TS segments with FairPlay Streaming, PlayReady, or Widevine encryption
  • FairPlay Streaming legacy keys (For com.apple.fps.1_0 use native Safari playback)
  • ClearKey (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)
  • EC-3 (Dolby Digital Plus) in MPEG-2 TS and in containerless (audio only) elementary streams. EC-3 is supported in Fragmented MP4 segments
  • HEVC and AC-3 in MPEG-2 TS are excluded from the light build (see __USE_M2TS_ADVANCED_CODECS__)

Server-side-rendering (SSR) and require from a Node.js runtime

You 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.

Getting started with development

Open in StackBlitz

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 tasks

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:

  • AudioStreamController
  • AudioTrackController
  • CuesInterface
  • EMEController
  • SubtitleStreamController
  • SubtitleTrackController
  • TimelineController
  • CMCDController
  • InterstitialsController
  • InterstitialsManager
  • IFrameController
  • HlsIFramesOnly
  • HlsImageIFramesOnly

Linter (ESlint)

Run 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

Formatting Code

Run prettier to format code

npm run prettier

Type Check

Run type-check to verify TypeScript types

npm run type-check

Automated tests (Mocha/Karma)

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

Design

An overview of this project's design, it's modules, events, and error handling can be found here.

API docs and usage guide

Note you can access the docs for a particular version using "https://github.com/video-dev/hls.js/tree/deployments"

Demo

Latest Release

https://hlsjs.video-dev.org/demo

Master

https://hlsjs-dev.video-dev.org/demo

Specific Version

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.

Compatibility

HLS.js is only compatible with browsers supporting MediaSource extensions (MSE) API with 'video/MP4' mime-type inputs.

HLS.js is supported on:

  • Chrome 47+ for Desktop
  • Firefox 51+ for Desktop
  • Edge for Windows 10+
  • Safari 10+ for macOS 10.11+
  • Safari for iPadOS 13+
  • Safari for iOS 17.1+ since HLS version 1.5.0 using Managed Media Source (MMS) WebKit blog
  • Chrome for Android 5+
  • Firefox for Android 5+

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:

  • UMD (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.
  • ESM (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.mjs and dist/hls.min.mjs do not, so transmuxing runs on the main thread unless you point workerPath at 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

Using HLS.js

Installation

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

Embedding HLS.js

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>

Alternative setup

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. Using Hls.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>

Ensure correct time in video

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.

CORS

All HLS resources must be delivered with CORS headers permitting GET requests.

Video Control

Video is controlled through HTML <video> element HTMLVideoElement methods, events and optional UI controls (<video controls>).

Build a Custom UI

Player Integration

The following players integrate HLS.js for HLS playback:

They use HLS.js in production!

cdn77

Chrome/Firefox integration

made by gramk, plays hls from address bar and m3u8 links

License

HLS.js is released under Apache 2.0 License