A Rust→WebAssembly library that brings Matroska (MKV) parsing — and, down the road, playback — to the web. https://mkvjs-viewer.pages.dev/
  • Rust 52.2%
  • JavaScript 28.9%
  • HTML 13.5%
  • CSS 3%
  • Shell 1.4%
  • Other 1%
Find a file
davidprojekt 539347f539
Revise commercial licensing section in README
Reworded the section on commercial licensing for clarity and conciseness.
2026-07-19 19:29:14 +02:00
ebml-spec refactor: rename crates/packages to the matroska-js naming scheme 2026-07-12 13:19:40 +02:00
ebml-wasm refactor: rename crates/packages to the matroska-js naming scheme 2026-07-12 13:19:40 +02:00
matroska-inspector refactor: rename crates/packages to the matroska-js naming scheme 2026-07-12 13:19:40 +02:00
mkv-player chore(player+remux): bump player to 0.1.1 and remux to 0.1.2 for the README-refresh republish 2026-07-15 18:19:54 +02:00
player-lib chore(player+remux): bump player to 0.1.1 and remux to 0.1.2 for the README-refresh republish 2026-07-15 18:19:54 +02:00
player-web refactor: rename crates/packages to the matroska-js naming scheme 2026-07-12 13:19:40 +02:00
.gitignore refactor: split MIT parser core from AGPL player/remuxer crate 2026-06-27 17:21:26 +02:00
Cargo.lock chore(player+remux): bump player to 0.1.1 and remux to 0.1.2 for the README-refresh republish 2026-07-15 18:19:54 +02:00
Cargo.toml refactor: split MIT parser core from AGPL player/remuxer crate 2026-06-27 17:21:26 +02:00
IMPLEMENTATION.md implementation guide 2026-06-24 20:32:50 +02:00
LICENSE.txt Initial commit: consolidate into a single monorepo 2026-06-24 11:23:43 +02:00
README.md Revise commercial licensing section in README 2026-07-19 19:29:14 +02:00

matroska.js

An experimental Matroska / EBML toolkit written in Rust, compiled to WebAssembly so it can parse .mkv / .webm files directly in the browser — nothing is uploaded.

Live demos: play a .mkv · explore EBML structure

⚠️ Early prototype. APIs are unstable, there are rough edges throughout. Expect bugs.

Goal

A full MKV web player. The container is parsed in Rust/WASM and remuxed on the fly into fragmented MP4 (fMP4) that is fed to a <video> element through Media Source Extensions — no upload, and video is never transcoded. The browser plays it as long as the video codec is web-compatible, with switchable audio and subtitle tracks, chapters, and seeking. Audio in a codec the browser can't decode is transcoded in-browser by a royalty-free ffmpeg.wasm core (offline-first, no CDN dependency); video still plays untouched.

Two subtitle families are rendered over a canvas overlay, and up to two can show at once (e.g. dialogue + signs):

  • ASS/SSA via libass (through JASSUB), with embedded fonts.
  • PGS (S_HDMV/PGS, HDMV bitmap subtitles) via libpgs.

Per-block zlib-compressed subtitle tracks (mkvmerge's default) are decompressed transparently. Plain-text / WebVTT subtitle tracks are not currently working; DVD (VobSub) and DVBSUB bitmap subtitles are not yet supported.

The WASM side extracts encoded frames (copying length-prefixed NALs straight through), reconstructs DTS/composition offsets for B-frames, and writes fMP4 init + media segments. Seeking uses the Matroska Cues index, with all positions anchored to the Segment element's content-start offset. A cold-start access speculatively fetches the first 32 KB and last 256 KB in parallel to warm the cache.

What's in here

This is a Cargo workspace + a small web frontend:

Crate / dir What it is
ebml-spec A proc-macro that ingests the official EBML/Matroska schema XML at compile time, so every element ID knows its name and type. (MIT)
ebml-wasm The EBML/Matroska parser core: a forward-only async iterator over EbmlElements plus the byte sources (FetchSource, FsSource, MemSource), exposed to JS via wasm-bindgen (EbmlReader, FetchSourceEbml). Container parsing only — no remuxing. (MIT)
mkv-player The MKV→fMP4 remuxer and player, built on ebml-wasm and exposed to JS via wasm-bindgen. MatroskaPlayer (in player.rs) exposes open(url), tracks(), init_segment(), media_segment(), audio_chunk() (for transcoding), cue_offset(), cue_times(), chapters(), font_attachments(), and the subtitle accessors subtitles() (text→WebVTT), subtitle_header() / subtitle_events() (ASS), and subtitle_bitmap_events() (PGS→.sup). The fMP4 box writer lives in fmp4.rs, block/sample extraction and subtitle-block decompression in remux.rs / track.rs, the seek index in index.rs, and the streaming byte source in stream_source.rs. (AGPL-3.0)
player-lib @matroska-js/player — the reusable player library all frontends are built on: video.js v10 (@videojs/html) UI driving MSE from the mkv-player WASM remuxer, ASS/SSA subtitles via libass (JASSUB), PGS subtitles via libpgs, and in-browser audio transcoding via a bundled royalty-free ffmpeg.wasm core. createPlayer(container, opts) builds the control bar (controls are configurable/removable). (AGPL-3.0)
player-web The MKV player demo: a URL box, local-file picker, and copy-shareable-link button around @matroska-js/player. (AGPL-3.0)
matroska-player-nextcloud A Nextcloud app that plays .mkv / .mka files in the Files Viewer via @matroska-js/player. Lives in its own repository (built on the published npm package). (AGPL-3.0)
matroska-inspector A browser demo UI: drop a local video file and explore its EBML structure as a tree with a hex inspector, plus a quick metadata summary (tracks, languages, resolution, duration). Uses only the ebml-wasm parser. (MIT)

Supported codecs

The remuxer copies encoded frames straight into fMP4 — it never transcodes. A track plays only if both the remuxer can wrap its codec (below) and the browser can decode that codec in MP4 via Media Source Extensions. Tracks the remuxer can't wrap are listed as not muxable; tracks it wraps but the browser can't decode are flagged unsupported (e.g. HEVC in Firefox, AC-3 in Chrome/Firefox).

Kind Matroska CodecID Codec fMP4 sample entry
Video V_MPEG4/ISO/AVC H.264 / AVC avc1
Video V_MPEGH/ISO/HEVC H.265 / HEVC hvc1
Video V_VP9 VP9 vp09
Video V_AV1 AV1 av01
Audio A_AAC AAC mp4a
Audio A_OPUS Opus Opus
Audio A_AC3 AC-3 ac-3
Audio A_EAC3 E-AC-3 ec-3
Audio A_FLAC FLAC fLaC
Audio A_MPEG/L3 MP3 mp4a (.6B)
Subtitle S_TEXT/ASS, S_TEXT/SSA rendered via libass (JASSUB) (extracted)
Subtitle S_HDMV/PGS HDMV bitmap, rendered via libpgs (extracted, reconstructed .sup)
Subtitle S_TEXT/UTF8, S_TEXT/WEBVTT, S_TEXT/ASCII text → WebVTT — not working yet (extracted)

Audio the browser can't decode natively (e.g. AC-3/E-AC-3 in some browsers) is transcoded in-browser to AAC (or Opus) by the bundled ffmpeg.wasm core, so it plays without a native decoder. Other audio codecs not listed above (DTS, MLP, Vorbis, PCM) route through the same transcoder. Video is never transcoded, so an undecodable video codec (e.g. HEVC in Firefox, or MPEG-2/older) is listed but won't play. MP3 frame durations assume MPEG-1 Layer III (1152 samples/frame); the rarer MPEG-2/2.5 Layer III (576) is not yet distinguished. DVD (VobSub) and DVBSUB bitmap subtitles are not yet supported.

Build & run

You'll need the Rust toolchain and wasm-pack.

The player (player-web)

# 1. Build the WASM remuxer (outputs mkv-player/pkg, consumed by player-web).
cd mkv-player
wasm-pack build --target web

# 2. Serve the sample media with Range support + CORS on :8501.
#    (player-web defaults to http://localhost:8501/example/toaru.mkv)
cd ../ebml-wasm
npm start            # simple-http-server -i --cors --port 8501

# 3. In another shell, install the shared player library, then run the dev server.
cd ../player-lib
npm install          # installs the library's deps (video.js, jassub, libpgs, ffmpeg.wasm)
cd ../player-web
npm install
npm run dev          # open the printed Vite URL

The demo consumes the shared @matroska-js/player library in player-lib/ (a workspace file: dependency), so run npm install in player-lib before it. npm run build in player-web runs the mkv-player wasm build for you, so step 1 is only needed for the dev server.

Put .mkv files under ebml-wasm/example/ and point the URL box at them. Codecs the browser can't decode (e.g. HEVC in Firefox, AC-3 in Chrome/Firefox) are listed but flagged unsupported.

The EBML inspector (matroska-inspector)

# from ebml-wasm/: build the wasm module and copy it into the inspector
cd ebml-wasm
wasm-pack build --target web && cp -r ./pkg ../matroska-inspector/

# then serve the frontend (any static server works)
cd ../matroska-inspector
npx simple-http-server -i --cors --port 8501
# open http://localhost:8501 and drop in an .mkv / .webm file

Validating the remuxer without a browser

# Dumps init + first media segment per muxable track for ffprobe/mp4box to check.
cargo run -p matroska-remux --example dump_segments -- ebml-wasm/example/toaru.mkv /tmp/out

License

This repository is split by component:

  • ebml-spec, ebml-wasm, and matroska-inspector — the EBML/Matroska parser core and its browser demo — are licensed under the MIT License (see ebml-wasm/LICENSE). Use them freely, including in closed-source projects.
  • mkv-player (the MKV→fMP4 remuxer/player), the @matroska-js/player library (player-lib), the player demo (player-web), and the Nextcloud app (matroska-player-nextcloud, separate repo) are licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) — see LICENSE.txt. You're free to use, study, modify, and share them, but if you distribute them or run a modified version as a network service, you must release your source under the same license.

Commercial licensing

The AGPL components may also be offered under a separate commercial license for organizations that require terms different from the AGPL-3.0-only license — including use of @matroska-js/player in closed-source or SaaS products without the AGPL's source-disclosure obligations, or for watermark-free builds.

The author holds the necessary copyright and is preparing commercial licensing options. To discuss future licensing arrangements, contact licensing@davidschneider.xyz.