Open source · MIT license

hevc.js

Play HEVC/H.265 in every browser.
No plugin. No server changes. No double-encoding.

A from-scratch HEVC decoder compiled to WebAssembly, with a drop-in plugin for dash.js. Transcodes HEVC to H.264 client-side for the ~6% of browsers that don't support it natively.

Key numbers

~6%
browsers covered
that can't play HEVC natively
128/128
pixel-perfect
vs ffmpeg on every test
60fps
1080p decode
WASM, single-thread
236KB
WASM binary
gzipped, zero dependencies
716
pages of spec
ITU-T H.265 v8, transcribed to C++17
83%
of libde265 speed
a decoder with 10 years of optimizations

How it works

When the browser can't play HEVC natively, hevc.js intercepts the media pipeline and transcodes to H.264 — transparent to the player.

// The player (hls.js, dash.js) doesn't know.
// It requests HEVC segments.
// MSE intercept delivers H.264 to the SourceBuffer.
// Seek, ABR, live streams — everything works.

HEVC stream  ──►  Web Worker  ──►  H.264 to MSE
              demux → decode → encode → mux
Tradeoff: the first segment takes 2-3s to transcode (vs instant with native hardware decode). Once buffered, playback is smooth. This is inherent to any software HEVC decoder — when native support is available, hevc.js detects it and does nothing.

Playground

Drop an HEVC file and watch it decode in real time, frame by frame.

Drop an .h265 or .hevc file

or

Decoding runs entirely in your browser. No data is uploaded.

Why this exists

You serve HEVC because it saves bandwidth — 30-50% smaller than H.264 at the same quality. But some browsers still can't play it. Those users get a black screen, a fallback to lower quality, or nothing.

The usual fix is double-encoding: serve HEVC for browsers that support it, H.264 for the rest. That means maintaining two encoding pipelines, two sets of manifests, and twice the storage on your CDN.

hevc.js takes a different approach. Keep your single HEVC pipeline. For the ~6% of browsers that need it, the transcoding happens client-side, in a Web Worker, invisible to your player and your infrastructure.

The decoder is a direct implementation of the ITU-T H.265 spec — 716 pages of it, transcribed to C++17 and compiled to WebAssembly. Every frame is validated pixel-perfect against ffmpeg.

Drop-in plugin

HEVC playback for dash.js. 3 lines of code.
Activates only when native HEVC is unavailable.

dash.js

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

await attachHevcSupport(player);
npm install @hevcjs/dashjs-plugin

Spec conformance

Implemented per ITU-T H.265 v8 — transcribed directly from the spec, not from ffmpeg or libde265.

CABAC arithmetic decoding (§9.3)
35 intra prediction modes (§8.4)
Inter prediction — merge, AMVP, TMVP (§8.5)
8-tap luma / 4-tap chroma interpolation
Weighted prediction — default + explicit
Inverse transform — DCT 4-32, DST 4
Deblocking filter + SAO (§8.7)
10-bit decoding (Main 10 profile)
Multi-slice (dependent + independent)
WPP (Wavefront Parallel Processing)

Reaches 83% of libde265 speed — a decoder with 10 years of optimizations.