The Problem — HEVC Everywhere Except the Browser
HEVC/H.265 is the standard codec for Netflix, Apple, broadcasters, 4K/HDR. It saves 30-50% bandwidth versus H.264 at equivalent quality — millions in annual CDN savings for streaming services.
But browser support is a mess.
macOS — Safari, Chrome, Edge, Firefox all decode HEVC natively via VideoToolbox. No extension needed.
Chrome 107+ on Windows — uses D3D11VA directly. No Microsoft extension required, but needs a GPU with hardware HEVC decoder (Intel Skylake 2015+, NVIDIA Maxwell 2nd gen+, AMD Fiji+). No software fallback.
Edge on Windows — uses Media Foundation. Requires the Microsoft HEVC Video Extension ($1 on the Store). Without it, no HEVC regardless of GPU.
Firefox 133+ on Windows — same MFT path, same extension dependency.
Linux — Chrome with VAAPI, maybe. Firefox, no.
The root cause is licensing. MPEG LA and Access Advance impose per-unit royalties. Microsoft passes this to users via the Store extension. Google negotiated a direct D3D11VA path. Mozilla relies on Microsoft's extension. The result: publishers must either encode everything twice (H.264 + HEVC) or accept that some users get a black screen.
The Solution — Decode HEVC Client-Side in WebAssembly
What if the browser didn't need to know it's playing HEVC?
hevc.js decodes HEVC in a Web Worker and re-encodes to H.264 via WebCodecs, delivering standard H.264 to Media Source Extensions. The player doesn't know it's happening.
fMP4 HEVC → mp4box.js (demux) → NAL units
→ WASM H.265 decoder → YUV frames
→ WebCodecs VideoEncoder → H.264
→ custom fMP4 muxer → MSE → <video>
The HEVC decoder is a from-scratch C++17 implementation of ITU-T H.265 (716 pages), compiled to WebAssembly. 236 KB gzipped. Zero dependencies. No special server headers needed.
dash.js integration
The plugin intercepts MediaSource.addSourceBuffer(). When dash.js creates an HEVC SourceBuffer, a proxy accepts the HEVC MIME type but feeds the real SourceBuffer with H.264. ABR, seek, live — everything works unmodified.
import dashjs from 'dashjs';
import { attachHevcSupport } from '@hevcjs/dashjs-plugin';
const player = dashjs.MediaPlayer().create();
await attachHevcSupport(player, {
workerUrl: '/transcode-worker.js',
wasmUrl: '/hevc-decode.js',
});
player.initialize(videoElement, mpdUrl, true);
Smart detection
MediaSource.isTypeSupported() can lie — Firefox on Windows reports HEVC support even without the Video Extension installed. hevc.js actually creates a SourceBuffer to probe; only activates transcoding on failure. When native HEVC works: zero overhead, WASM never loaded.
Browser Compatibility
| Browser + OS | Native HEVC | hevc.js activates? | Transcoding? |
|---|---|---|---|
| Safari 13+ (macOS/iOS) | Yes (VideoToolbox) | No | — |
| Chrome/Edge/Firefox (Mac) | Yes (VideoToolbox) | No | — |
| Chrome 107+ (Win, HEVC GPU) | Yes (D3D11VA) | No | — |
| Chrome 107+ (Win, no HEVC GPU) | No | Yes | Yes |
| Edge (Win, with extension) | Yes (MFT) | No | — |
| Edge (Win, no extension) | No | Yes | Yes |
| Firefox 133+ (Win, with extension) | Yes (MFT) | No | — |
| Firefox 133+ (Win, no extension) | False positive | Yes | Yes |
| Chrome/Edge 94-106 | No | Yes | Yes |
| Chrome (Linux, no VAAPI) | No | Yes | Yes |
Requirements: WebAssembly, Web Workers, Secure Context (HTTPS), WebCodecs with H.264 encoding support.
Performance
Single-threaded, Apple Silicon:
| Native C++ | WASM (Chrome) | |
|---|---|---|
| 1080p decode | 76 fps | 61 fps |
| 4K decode | 28 fps | 21 fps |
| 1080p transcode | — | ~2.5x realtime |
WASM reaches 80% of native C++ speed, and 83% of libde265 (a mature 10-year-old HEVC decoder) when both are compiled to WASM.
Conformance: 128/128 test bitstreams pixel-perfect against ffmpeg. Zero drift.
The Tradeoff
The first segment takes 2-3 seconds to transcode — that's the startup latency cost of software decode versus native hardware. After buffering, playback is smooth.
This makes hevc.js a good fit for:
- Streaming platforms with existing HEVC catalogs
- Infrastructure simplification (single HEVC pipeline, no H.264 fallback)
- VOD or moderate-latency live
- Controlled environments (IPTV, B2B)
Not ideal for: low-end mobile (CPU/battery), 4K on underpowered machines, or ultra-low-latency live sports.
Try It
Live demo: hevcjs.dev/demo/dash.html — toggle "Force transcoding" to test the WASM path even if your browser has native HEVC.
Install:
npm install @hevcjs/dashjs-plugin dashjs
GitHub: github.com/privaloops/hevc.js
MIT license. Feedback and contributions welcome.
Top comments (0)