DEV Community

Cover image for Play HEVC, AV1 and MKV in the browser with WebCodecs (no server transcoding)
Ujjawal Kashyap
Ujjawal Kashyap

Posted on

Play HEVC, AV1 and MKV in the browser with WebCodecs (no server transcoding)

TL;DR

Movi Player uses WebCodecs + a WebAssembly demuxer (FFmpeg compiled to WASM) to play HEVC, AV1, MKV, 4K HDR, and multi-audio files directly in the browser. No server transcoding. No FFmpeg on your backend. Drop-in replacement for <video>.

npm i movi-player
Enter fullscreen mode Exit fullscreen mode
<movi-player src="movie.mkv" controls></movi-player>
Enter fullscreen mode Exit fullscreen mode

moviplayer.com · Live demo · GitHub · Chrome extension · VS Code extension


Why WebCodecs matters for web video

Every major browser ships with WebCodecs — a low-level API that gives you direct access to the GPU's hardware video decoder. The same silicon that decodes Netflix, YouTube, your OS video player. Sitting right there in window.VideoDecoder.

And yet the entire web video ecosystem still acts like it's 2015:

  • <video> plays maybe MP4/H.264 reliably and shrugs at everything else
  • video.js, hls.js, Plyr — all wrappers around that same <video> tag
  • Anything fancy → "transcode it on the server with FFmpeg"

So I asked the obvious question: what if you skipped the <video> tag entirely and went straight to WebCodecs?

That's Movi Player.

Codecs supported by WebCodecs in 2026

Once you stop fighting <video> and decode frames yourself via WebCodecs, the codec wall basically falls over:

  • H.264 / AVC — yes
  • H.265 / HEVC — yes (the one Safari guards behind paywalls)
  • VP8 / VP9 — yes
  • AV1 — yes (next-gen, almost no native <video> support)
  • MPEG-2, MPEG-4 Part 2 — yes (legacy stuff that breaks <video>)
  • Hardware decode where available, software fallback where not

Same story for audio: AAC, MP3, Opus, FLAC, AC-3, E-AC-3 — all decoded in the browser.

The browser had this power the whole time. We just had to use it.

WebCodecs alone isn't enough — you need a demuxer

WebCodecs decodes frames. It doesn't know what an MKV file is. It doesn't parse containers, doesn't pull tracks, doesn't read HDR metadata, doesn't do seeking, doesn't handle subtitles.

That's the other half of Movi Player: a WebAssembly demuxer (FFmpeg compiled to WASM) that cracks open the file, pulls each track out, and feeds raw packets to WebCodecs.

File → WASM demuxer → packets → WebCodecs decoder → Canvas
Enter fullscreen mode Exit fullscreen mode

Result: you get the formats FFmpeg understands (basically everything) decoded at the speed of native hardware. Zero server transcoding. Zero <video> tag.

<movi-player src="movie.mkv" controls></movi-player>
Enter fullscreen mode Exit fullscreen mode

That one line plays MKV, HEVC, AV1, 4K HDR, multi-audio, embedded subtitles — everything <video> can't.

Try it now: npm i movi-playerlive demo.

Standalone WASM demuxer for video metadata (50KB)

Sometimes you don't even want to play the video. You want to know what's in it — codecs, resolution, HDR flags, audio languages, chapter list.

Movi ships a separate demuxer-only build for that. ~50KB of JS + the shared WASM binary, no playback code, no UI:

import { Demuxer, HttpSource } from "movi-player/demuxer";

const demuxer = new Demuxer(new HttpSource("video.mkv"));
const info = await demuxer.open();

console.log(`Duration: ${info.duration}s`);
console.log(`Format: ${info.formatName}`);
console.log(`Chapters: ${info.chapters.length}`);

const video = demuxer.getVideoTracks()[0];
console.log(`${video.width}x${video.height} ${video.codec} ${video.frameRate}fps`);
console.log(`HDR: ${video.isHDR}, ${video.colorPrimaries}/${video.colorTransfer}`);

const audio = demuxer.getAudioTracks();
// [{ codec: "ac3", language: "hin" }, { codec: "aac", language: "eng" }, ...]

const subs = demuxer.getSubtitleTracks();
// [{ codec: "srt", language: "eng" }, { codec: "ass", language: "jpn" }, ...]

demuxer.close();
Enter fullscreen mode Exit fullscreen mode

Use cases:

  • Video validators before upload — reject 8K HDR uploads on a free plan, no server round-trip
  • Asset management dashboards — show codec, resolution, language tracks at a glance
  • HDR detection pipelines — flag content for the HDR rendering path
  • Search indexing — extract chapter titles, audio languages, subtitle text
  • Pre-transcode analysis — check what you're about to spend money re-encoding

All client-side. No server. No FFmpeg on a Lambda.

Multi-audio track switching in the browser

Once you have the tracks, you can let users use them.

<video> will never give you this. Movi exposes every audio track in the file and lets the user switch live, mid-playback. Press B or pick from the menu.

  • Hindi dub → English original → Japanese → director's commentary
  • All from one file
  • Zero server work
  • Subtitles (V key) switch the same way — SRT, ASS, embedded PGS, all of it

For anime sites, regional OTT, language learning apps, film archives — this alone is worth the swap.

Other features: HDR, subtitles, encrypted playback

  • HDR rendering — real BT.2020 / PQ / HLG tone-mapping on supported displays. No other web player does this.
  • Embedded subtitles — pulled straight out of the MKV. No external WebVTT conversion step.
  • Encrypted playback — AES-256-GCM, HMAC-signed tokens, 2-second expiry. No DRM license server, no Widevine contract.
  • Canvas-based render — no <video> element exposed, right-click "save video" disabled by default.
  • Picture-in-Picture, ambient mode, chapters, thumbnails, rotation — all built on top of the canvas pipeline.

Movi Player vs video.js vs hls.js vs Plyr

Movi video.js hls.js Plyr
WebCodecs-based decode Yes No No No
HEVC / AV1 Yes No No No
Multi-audio switching (client-side) Yes No HLS only No
HDR rendering Yes No No No
Embedded subs (SRT/ASS/PGS) Yes No No No
No server transcoding Yes No No No
Encrypted (no DRM server) Yes No No No

Bundle sizes

Build What you get Size
movi-player/demuxer Metadata, track info, HDR detection ~50KB
movi-player/player Programmatic playback, no UI ~180KB
movi-player Full player with controls, gestures, themes ~410KB

WASM binary is shared across all three and Brotli-friendly.

FAQ

Can browsers play MKV files natively?

Not reliably. Chrome plays some H.264-in-MKV files but multi-audio, HEVC, and AV1 inside MKV fail silently or throw "format not supported". Movi Player uses WebCodecs + a WASM demuxer to decode every track in the file regardless of browser.

Can I play HEVC (H.265) in the browser without Safari?

Yes. Chrome 107+, Edge, and Safari 16.4+ expose HEVC decoders through WebCodecs even when <video> refuses to play HEVC. Movi Player taps directly into those decoders.

Does AV1 work in the browser?

Yes — most modern browsers expose AV1 in WebCodecs (hardware decode where available, software fallback otherwise). Movi Player handles both transparently.

Is WebCodecs supported in all browsers?

Chrome 94+, Edge 94+, Safari 16.4+. Firefox support is in progress.

Do I need server-side FFmpeg or transcoding?

No. All decoding happens client-side via WebCodecs + WebAssembly. Your server just needs to support HTTP range requests for seeking.

How big is the bundle?

50KB for the demuxer-only build, 180KB for programmatic playback, 410KB for the full player UI. WASM binary is shared and Brotli-compresses well.

Use it without writing code: web app, Chrome + VS Code extensions

Don't want to integrate the library yet? Just use the player.

  • moviplayer.com — drop any video file into the browser and it plays. MKV, HEVC, AV1, HDR, multi-audio — all client-side, nothing uploaded. The fastest way to test what Movi can do with your own files.
  • Movi Player for Chrome — adds a play button overlay on video links across the web, right-click "Open with Movi Player" on any URL, or paste a video URL into the popup. MKV, HEVC, AV1, HDR — all play in-browser.
  • Movi Player for VS Code — single-click any video file in the Explorer to play it inside VS Code. MKV, HEVC, AV1, HDR, even .iso / .vob via "Open With…". 100% local, no upload.

Same WebCodecs + WASM demuxer pipeline under the hood — these are a way to dogfood (and demo) the library on real-world files.

Try it

What I'd love feedback on

I built this because the browser already had the tools — WebCodecs, WebAssembly, Canvas — and nobody was stitching them together for real-world video. Now I want to know what you need:

  • Is the WebCodecs-based decode the part that interests you, or is it multi-audio / HDR / encryption?
  • Would you use the standalone demuxer (50KB) for upload validation or asset metadata?
  • What integrations matter — Next.js, React Native Web, Vue, Svelte components?
  • Is the 410KB full bundle a dealbreaker, or fine for a video-heavy app?

Drop a comment. Genuinely curious what's missing.

Top comments (0)