Why Movi-Player? โ
See how Movi-Player compares to other popular video players.
Feature Comparison โ
| Feature | Movi-Player | video.js | hls.js | Plyr |
|---|---|---|---|---|
| WebCodecs | โ | โ | โ | โ |
| HDR Support | โ | โ | โ | โ |
| MKV / MPEG-TS | โ | โ | TS only | โ |
| Canvas Renderer | โ | โ | โ | โ |
| Modular | โ | โ | โ | โ |
| FFmpeg WASM | โ | โ | โ | โ |
| No Server Processing | โ | โ | โ | โ |
| HLS/DASH | โ | โ | โ | โ |
| Custom UI | โ | โ | โ | โ |
Alternatives โ
Which player fits your use case?
- video.js, Plyr, Vidstack, Media Chrome โ polished UI players for browser-native formats (MP4/WebM) and HLS/DASH streams. They can't open a raw MKV, HEVC or AV1 file. Use Movi-Player instead when your source isn't a format the browser already decodes.
- hls.js, dash.js, Shaka Player โ streaming engines for adaptive HLS/DASH; they need content pre-packaged server-side and don't play arbitrary local files. Movi-Player plays those streams and raw files through one canvas pipeline.
- ffmpeg.wasm โ an FFmpeg-in-WASM transcode/processing library, not a player: it CPU-decodes (heavy, no GPU) and ships no UI. Movi-Player uses WebCodecs for GPU-accelerated decode and gives you a finished player. Reach for it when you want playback, not a transcoding toolkit.
- libmedia โ the closest peer: a WASM + WebCodecs media SDK that also plays many formats. Movi-Player's difference is packaging โ a drop-in
<movi-player>web component with a batteries-included UI (HDR, chapters, multi-audio, subtitles, ambient mode, Document PiP, encrypted playback) rather than a lower-level toolkit.
In short, Movi-Player is a practical alternative to video.js / hls.js / Shaka Player for non-native files, and a friendlier, GPU-accelerated alternative to ffmpeg.wasm / libmedia when you want a player rather than a library.
Bundle Size โ
| Player | Full Bundle | Minimal |
|---|---|---|
| Movi-Player | 410KB | 45KB |
| video.js | 500KB+ | N/A |
| hls.js | 300KB | 300KB |
| Plyr | 100KB | N/A |
Key Advantages โ
1. No Server-Side Processing โ
Other players require server-side transcoding for:
- Format conversion (MKV โ MP4)
- Codec transcoding (HEVC โ H.264)
- HLS/DASH packaging
Movi-Player processes everything in the browser.
// Direct MKV playback - no server conversion needed!
<movi-player src="video.mkv" controls></movi-player>2. HDR Content Support โ
Movi-Player is the only web player with full HDR support:
const videoTrack = player.getVideoTracks()[0];
if (videoTrack.isHDR) {
console.log("HDR Format:", videoTrack.colorTransfer);
// "smpte2084" (HDR10) or "arib-std-b67" (HLG)
}3. Multi-Track Without Processing โ
Switch audio/subtitle tracks without server-side extraction:
// Get all audio tracks
const audioTracks = player.getAudioTracks();
// [{ id: 0, language: 'eng' }, { id: 1, language: 'jpn' }]
// Switch to Japanese audio
player.selectAudioTrack(1);4. Local File Privacy โ
Play files directly from user's device:
import { FileSource } from "movi-player/player";
// File never leaves the browser
const source = new FileSource(userSelectedFile);
player.load({ type: "file", file: userSelectedFile });Privacy Benefit
User files are never uploaded to any server. All processing happens locally.
Migration from Other Players โ
From video.js โ
Before โ video.js requires the script, the stylesheet, the placeholder <video> element, and a JS call to attach the player:
<link href="https://vjs.zencdn.net/8.10.0/video-js.css" rel="stylesheet" />
<video id="my-video" class="video-js" controls preload="auto" data-setup="{}">
<source src="video.mp4" type="video/mp4" />
</video>import videojs from "video.js";
import "video.js/dist/video-js.css";
const player = videojs("my-video", {
controls: true,
autoplay: false,
preload: "auto",
});
player.on("ended", () => console.log("done"));After โ Movi Player ships as a custom element. Side-effect import registers <movi-player>; no stylesheet, no videojs() call, no placeholder <video>:
<movi-player id="my-video" src="video.mp4" controls preload="auto"></movi-player>import "movi-player";
const player = document.getElementById("my-video");
player.addEventListener("ended", () => console.log("done"));The element implements the same play() / pause() / currentTime / events surface as <video>, so most existing logic carries over with a getElementById instead of a videojs() factory call.
Or change nothing at all โ
If the markup is not yours to edit โ a CMS template, a third-party embed, a page you would rather not touch โ take the <video> elements over instead:
From a CDN this needs no JavaScript at all โ data-upgrade on the script tag does it:
<script type="module" src="https://cdn.jsdelivr.net/npm/movi-player/dist/element.js"
data-upgrade data-upgrade-watch></script>Or call it:
import { upgradeVideoElements } from "movi-player";
upgradeVideoElements(); // every <video> on the page
upgradeVideoElements("video.hero"); // only these
upgradeVideoElements({ watch: true }); // โฆand any added later
upgradeVideoElements({ attributes: { thumb: "" } }); // with extras appliedEach <video> is replaced by a <movi-player> carrying its attributes and its children โ sources, caption tracks, thumbnail tracks, poster, data-setup. The element's id moves too, so getElementById keeps finding "the player".
The original element stays in the page, hidden, with its API pointed at the new one, so code that still holds it keeps working:
const video = document.getElementById("hero-native"); // the old element
video.play(); // plays through movi-player
video.currentTime = 60; // seeks it
video.addEventListener("ended", โฆ); // fires from the playerThe markup carries over as-is โ
The pieces video.js reads from the element are read here too, so a page can usually change <video class="video-js"> to <movi-player> and stop:
<movi-player class="video-js" controls preload="auto" width="640" height="360"
poster="poster.jpg" data-setup='{"muted": true}'>
<source src="movie.mp4" type="video/mp4">
<track kind="captions" src="en.vtt" srclang="en" label="English" default>
<track kind="metadata" label="thumbnails" src="thumbs.vtt">
<p class="vjs-no-js">To view this video please enable JavaScriptโฆ</p>
</movi-player>data-setupis applied as attributes โcontrols,autoplay,muted,loop,poster,preload,width,height, the first ofsources, andspriteThumbnails. An attribute written out longhand wins over the JSON.<track kind="captions">/kind="subtitles"become subtitle tracks, anddefaultselects one.<track kind="metadata" label="thumbnails">โ video.js's spelling โ becomes scrub previews: a sprite VTT with#xywh=rectangles, or one image per cue.- Unslotted children like
<p class="vjs-no-js">are not rendered.
The videojs-sprite-thumbnails options work unchanged, either through data-setup or as a property:
player.storyboard = {
url: "sprite-{index}.jpg",
width: 160, height: 90, columns: 5, rows: 5, interval: 3,
};Multiple sources / split source โ
Movi Player accepts child <source> elements just like <video>, so the <source> fallback pattern keeps working:
<movi-player controls>
<source src="movie.av1.mp4" type="video/mp4; codecs=av01.0.05M.08" />
<source src="movie.h264.mp4" type="video/mp4" />
</movi-player>In addition, Movi extends the syntax with kind="audio" so you can serve a video-only file alongside a separate audio track (DASH-style split source) โ something native <video> and video.js can't do without manual MSE wiring:
<movi-player controls>
<source src="video-only.mp4" type="video/mp4" />
<source src="audio-only.m4a" type="audio/mp4" kind="audio" />
</movi-player>The two streams are kept in sync automatically. The same is available via JS:
player.source({
video: { src: "video-only.mp4", type: "video/mp4" },
audio: { src: "audio-only.m4a", type: "audio/mp4" },
});From hls.js โ
Before โ hls.js wires up MSE manually on a <video> element:
<video id="my-video" controls></video>import Hls from "hls.js";
const video = document.getElementById("my-video");
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource("https://example.com/stream.m3u8");
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, () => video.play());
}After โ Movi Player handles HLS internally via hls.js; just point src at the manifest:
<movi-player src="https://example.com/stream.m3u8" controls autoplay></movi-player>import "movi-player";For DRM-protected HLS streams, add the drm and licenseurl attributes โ Movi switches to the native <video> + EME pipeline automatically.