@hevcjs/dashjs-plugin

HEVC Plugin for dash.js

Play H.265 streams in every browser. 3 lines of code.

Transparently transcodes HEVC to H.264 client-side via WebAssembly.
When native HEVC is available, the plugin does nothing.

Quick start

Install the package, import one function, call it on your dash.js player.

$ npm install @hevcjs/dashjs-plugin dashjs
import dashjs from 'dashjs';
import { attachHevcSupport } from '@hevcjs/dashjs-plugin';

const video = document.querySelector('video');
const player = dashjs.MediaPlayer().create();

// One line — that's it
attachHevcSupport(player);

player.initialize(video, 'https://example.com/stream/manifest.mpd', true);

How it works

The plugin intercepts the MSE pipeline. The player never knows HEVC was involved.

1
Patches MediaSource.isTypeSupported() — returns true for HEVC codecs (hev1/hvc1)
2
Patches navigator.mediaCapabilities.decodingInfo() — same, for dash.js 4.x+
3
Registers a custom capabilities filter — accepts HEVC representations in the manifest
4
Intercepts addSourceBuffer() — creates an H.264 SourceBuffer and returns a Proxy
5
Proxy intercepts appendBuffer() — demux, decode HEVC (WASM), encode H.264 (WebCodecs), mux fMP4, append to real SourceBuffer
6
Proper updating state management — the proxy reports updating = true during transcoding, so dash.js waits between segments
Audio & subtitles pass through untouched. Only the HEVC video track is transcoded.

Browser compatibility

~94% of browsers play HEVC natively (hardware decode). hevc.js activates only for the ~6% that don't.

Browser Native HEVC hevc.js needed? Transcoding works?
Safari 13+ Yes (hardware) No — bypassed
Chrome/Edge 107+ (Win/Mac) Yes (hardware GPU) No — bypassed
Chrome 94-106 (all) No Yes Yes (WebCodecs H.264)
Chrome < 94 No Yes No (no WebCodecs) — falls back to AVC
Firefox 137+ (Win) Partial (hardware) No — bypassed
Firefox (Linux, older) No Yes No — H.264 encoding broken
Linux Chrome (no VAAPI) No Yes Yes (software encode)

Other requirements (supported by all modern browsers):

WebAssembly Web Workers Secure Context (HTTPS) WebCodecs VideoEncoder

API reference

One function to set up, one function to tear down.

attachHevcSupport(player, config?)

Patches the browser APIs and registers the dash.js capabilities filter. Returns a cleanup() function that reverses all patches.

const cleanup = attachHevcSupport(player, {
  workerUrl: '/transcode-worker.js',   // Web Worker URL
  wasmUrl:   '/hevc-decode.js',         // WASM glue location
  fps:       25,                        // Target framerate
  bitrate:   4_000_000,                 // H.264 encode bitrate
});

// Remove all patches when done
cleanup();

Options

workerUrl string

URL of the Web Worker script for off-main-thread transcoding. If omitted, transcoding runs on the main thread.

wasmUrl string

Path to the WASM glue JS file. Auto-detected from the package if omitted.

fps number default: 25

Target framerate for H.264 encoding. Should match the source stream.

bitrate number

H.264 encode bitrate in bits/second. If omitted, WebCodecs chooses automatically.

Lower-level API

For advanced use cases — manual MSE patching or direct transcoding without dash.js.

import { installMSEIntercept, uninstallMSEIntercept }
  from '@hevcjs/dashjs-plugin';
import { SegmentTranscoder }
  from '@hevcjs/dashjs-plugin';

// Manual MSE patching (without dash.js)
installMSEIntercept({ wasmUrl: '/hevc-decode.js' });

// Or use the transcoder directly
const transcoder = new SegmentTranscoder({ fps: 25 });
await transcoder.init();
await transcoder.processInitSegment(initSegmentBytes);
const h264Segment = await transcoder.processMediaSegment(mediaSegmentBytes);

Performance

Real numbers from a single-threaded WebAssembly decoder.

60fps
1080p decode
WASM, single-thread
236KB
WASM binary
gzipped, zero deps
2-3s
startup latency
first segment transcode
Tradeoff: the first segment takes 2-3s to transcode (vs instant with native hardware decode). Once buffered, playback is smooth. When native HEVC is available, the plugin detects it and does nothing — zero overhead.

FAQ

Does it work with live streams?

Yes. The plugin intercepts segments as they arrive, so live (low-latency) and VOD streams both work. The only difference is that the first segment takes 2-3 seconds to transcode, which adds to the initial live edge latency.

What happens when native HEVC is available?

Nothing. The plugin checks for native HEVC support at startup. If the browser can play HEVC natively (Safari, Chrome 107+ on Windows/Mac), the plugin does not patch anything and the WASM decoder is never loaded. Zero overhead.

Do I need special server headers?

No. The WASM decoder is single-threaded and does not use SharedArrayBuffer, so no Cross-Origin-Embedder-Policy or Cross-Origin-Opener-Policy headers are needed. It works on any static file server with HTTPS.

What about HLS?

There is no hls.js plugin at this time. The MSE intercept pattern works the same way, but hls.js support has not been implemented yet. Only dash.js is supported via @hevcjs/dashjs-plugin.

Get started

Install the package and start playing HEVC in every browser.

npm install @hevcjs/dashjs-plugin