- Rust 47.6%
- JavaScript 28.7%
- HTML 12.6%
- PHP 4.8%
- CSS 2.8%
- Other 3.5%
The player now shows the matroska.js watermark by DEFAULT and forces it while unlicensed — opts.watermark is ignored until a license is present. This makes the freemium nudge the library's baseline rather than something each consumer opts into. Removal paths: - opts.license: a signed key verified offline (Ed25519, signature-only) via the new license.js. A valid key drops the default AND lets the caller set their own watermark via opts.watermark. - opts.embedderValidatedLicense (undocumented): a first-party host that validated licensing through its own channel vouches for the session. Used by the Nextcloud app, which keeps its server-side instance-bound check and passes this trusted boolean instead of exposing the key/email to the frontend. - opts.iRefuseToSupportTheDeveloper (undocumented): force-off escape hatch. AGPL makes removal unpreventable anyway; this keeps the supported bypass self-identifying in a config rather than a silent fork. Demos drop their now-redundant explicit watermark option; the Nextcloud view vouches on licensed instances instead of setting the watermark on unlicensed ones. PUBLIC_KEY_HEX in license.js is a placeholder test key to replace before selling licenses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| ebml-spec | ||
| ebml-wasm | ||
| matroska-web | ||
| mkv-player | ||
| player-embed | ||
| player-lib | ||
| player-nextcloud | ||
| player-web | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| IMPLEMENTATION.md | ||
| LICENSE.txt | ||
| README.md | ||
| TODO.txt | ||
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.
⚠️ 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, Blu-ray 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 |
mkv-player-ui — 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 mkv-player-ui. (AGPL-3.0) |
player-embed |
A headless embeddable build: no chrome, just the video filling the frame, wrapping mkv-player-ui with controls: 'full'. Loads the video given in the embedding URL (?src=), so it can be dropped into an <iframe>. (AGPL-3.0) |
player-nextcloud |
A Nextcloud app that plays .mkv / .mka files in the Files Viewer via mkv-player-ui. (AGPL-3.0) |
matroska-web |
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. |
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 |
Blu-ray 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, TrueHD, 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
Both frontends consume the shared
mkv-player-uilibrary inplayer-lib/(a workspacefile:dependency), so runnpm installinplayer-libbefore the apps.npm run buildinplayer-web/player-embedruns themkv-playerwasm 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 embeddable player (player-embed)
A chrome-free build that plays whatever video URL is passed in the page URL — meant to be
hosted once and dropped into an <iframe> by anyone.
# Build the WASM remuxer first (mkv-player, step 1 above), then:
cd player-embed
npm install
npm run dev # dev server
npm run build # → dist/, deploy as a static site
Point an embedder at the deployed page and pass the video URL as ?src= (URL-encoded):
<iframe
src="https://your-host.example/?src=https%3A%2F%2Fmedia.example.com%2Fvideo.mkv"
width="800" height="450" allowfullscreen></iframe>
?url= is accepted as an alias, and a Base64-encoded #hash works too (matching the
demo page's "Copy" link). The video server must support HTTP byte ranges and, if it's
a different origin, send CORS headers (Access-Control-Allow-Origin) — the embed
preflights and shows a clear message otherwise. Add allowfullscreen to the iframe so the
fullscreen button works.
The EBML inspector (matroska-web)
# 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-web/
# then serve the frontend (any static server works)
cd ../matroska-web
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 mkv-player --example dump_segments -- ebml-wasm/example/toaru.mkv /tmp/out
License
This repository is split by component:
ebml-specandebml-wasm— the EBML/Matroska parser core — are licensed under the MIT License (seeebml-wasm/LICENSE). Use them freely, including in closed-source projects.mkv-player(the MKV→fMP4 remuxer/player), themkv-player-uilibrary (player-lib), and the player frontends (player-web,player-embed) are licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) — seeLICENSE.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.