DEV Community

Cover image for Playing HEVC in a Browser Without Plugin — An H.265 Decoder in WebAssembly
Thibaut Lion
Thibaut Lion

Posted on • Originally published at developpement.ai

Playing HEVC in a Browser Without Plugin — An H.265 Decoder in WebAssembly

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>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

GitHub: github.com/privaloops/hevc.js

MIT license. Feedback and contributions welcome.

Top comments (0)