Introduction
What wasmpeg is, what it's for, and how it differs from a general-purpose FFmpeg build — a decode-focused FFmpeg/WebAssembly library for the browser and Node.
wasmpeg is FFmpeg compiled to WebAssembly, focused on the decode → display loop. Load a video, get RGBA frames, scale them on the GPU, probe metadata, grab thumbnails — from a 3.2 MB gzipped WASM, with no SharedArrayBuffer and no COOP/COEP headers required.
await wasmpeg.load();
const dec = await wasmpeg.decode(file); // any File / Blob / URL / Uint8Array
let frame;
while ((frame = dec.nextFrame())) {
// frame is a Uint8ClampedArray of RGBA8 pixels (dec.width × dec.height)
ctx.putImageData(new ImageData(frame, dec.width, dec.height), 0, 0);
}
dec.close();
What it’s for
wasmpeg focuses on the decode side: pulling frames, audio, and metadata out of media files, plus single-frame encode for thumbnails. It runs in modern browsers and in Node ≥ 18, loads without cross-origin isolation, and scales frames on the GPU when WebGPU is available (falling back to CPU otherwise).
It’s the right tool when you need pixels or samples in JavaScript: a frame-accurate scrubber, a thumbnail strip, a waveform, a “what’s in this file” inspector, a client-side preview before upload. The whole library is built around getting that data across the WASM boundary cheaply and predictably.
No header requirements
SharedArrayBuffer, COOP, or COEP. No special server config, no
worker setup, no build step.Decode-first
GPU when available
Same API in Node
What you get out
Every decode call resolves to one of three plain JavaScript shapes. There’s no custom frame class to learn and nothing to dispose beyond the session itself.
| Output | Type | Layout |
|---|---|---|
| Video frame | Uint8ClampedArray | RGBA8, row-major, width × height × 4 bytes — drops straight into ImageData or texImage2D |
| Audio | Float32Array | Interleaved 32-bit float PCM, samples in [-1, 1] |
| Probe | plain object | Container name, duration, bitrate, and per-stream video/audio fields |
| Image encode | Uint8Array | The encoded container bytes (e.g. a JPEG or PNG) |
What it is not
wasmpeg is not a general-purpose transcoder. It does not mux to an output
container file — there is no ffmpeg -i in.mp4 out.webm equivalent. The pipeline is
built to get pixels and samples out, run a filtergraph, and optionally encode single
frames. If you need full file-to-file transcoding, use a server-side FFmpeg.
A few specifics worth setting expectations on up front:
- No output-file path.
exec(['-i', 'in.mp4', 'out.webm'])runs the decode/filter pipeline and returns the result in memory; it never writesout.webm. - No streaming mux or remux. There’s no path that concatenates packets into a new container.
- Single-frame encode, not video encode.
encode()is built for thumbnails and stills. There’s no audio/video muxing into a playable file from the high-level API. - H.264/H.265 encode is GPL-only. Decode of both is in the default build; encode
lives in
wasmpeg-full.
wasmpeg-full package.How it compares to ffmpeg.wasm
If you’ve used @ffmpeg/ffmpeg, the headline difference is deployment. That project’s
performant build is multi-threaded, which means SharedArrayBuffer, which means your
server has to send Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy
headers — and that breaks third-party embeds, some ad scripts, and a lot of static hosts.
wasmpeg is single-threaded SIMD, so none of that applies: drop it on any static host and
it runs.
The trade-off is scope. ffmpeg.wasm aims to be a full FFmpeg CLI in the browser, output files and all. wasmpeg deliberately stops at decode, filter, probe, and single-frame encode. If you only ever needed frames and metadata, you were carrying the cross-origin isolation tax for features you didn’t use. wasmpeg drops both.
For migration, the FFmpeg compat class mirrors enough of the
@ffmpeg/ffmpeg v0.12 surface (load, writeFile, exec, on('log')) to port simple
snippets with small edits — within the decode-focused limits above.