Quick start
Decode frames, scale a frame, probe metadata, decode audio, and grab a thumbnail — each in a few lines with the high-level wasmpeg API.
Everything below uses the high-level default export. Call load() once up front; it’s
safe to call again (subsequent calls resolve immediately).
await wasmpeg.load();
Every method accepts any input type — a File, Blob, an http(s) URL, a
Uint8Array/ArrayBuffer, or a live HTMLVideoElement / HTMLCanvasElement /
ImageData. The examples use file as a stand-in for whichever you have.
The method at a glance
| Method | Returns | Owns a slot? |
|---|---|---|
probe(input) | metadata object | No (closes internally) |
decode(input, { format }) | decoder with nextFrame() / close() | Yes — you close it |
decodeAudio(input, { format }) | decoder with nextSamples() / close() | Yes — you close it |
scale(input, w, h, filter?) | Uint8ClampedArray RGBA8 | No |
encode(input, opts) | Uint8Array container bytes | No |
run(input, args) | RGBA pixels or a decoder | Depends on the command |
Decode video frame by frame
const dec = await wasmpeg.decode(file);
console.log(dec.width, dec.height, dec.fps);
let frame;
while ((frame = dec.nextFrame())) {
// Uint8ClampedArray, length = dec.width * dec.height * 4 (RGBA8)
}
dec.close();
// Decode straight to a target size (one GPU scale, no intermediate copy):
const small = dec.nextFrame(320, 180);
nextFrame() returns null at end of stream. Each frame is a fresh copy that survives the
next call, so you can hold onto it. Pass a target width and height to resize during decode
rather than after — see How it works.
→ Guide
Painting frames to a canvas
const ctx = canvas.getContext('2d');
const dec = await wasmpeg.decode(file);
canvas.width = dec.width;
canvas.height = dec.height;
let frame;
while ((frame = dec.nextFrame())) {
ctx.putImageData(new ImageData(frame, dec.width, dec.height), 0, 0);
await new Promise(requestAnimationFrame);
}
dec.close();
Forcing a demuxer
Most containers identify themselves. For formats that don’t (some legacy and game audio),
pass format to name the demuxer explicitly:
const dec = await wasmpeg.decode(bytes, { format: 'g722' });
Scale / filter a single frame
// Returns a Uint8ClampedArray of RGBA8 pixels at the target size.
const rgba = await wasmpeg.scale(file, 1280, 720);
// Any FFmpeg filtergraph works (output size is taken from the scale= in the graph):
const flipped = await wasmpeg.scale(file, 1280, 720, 'scale=1280:720,hflip');
scale() grabs the first frame, runs the filtergraph, and returns the result — it opens and
closes its own decoder, so there’s nothing for you to clean up. When you pass a custom
filtergraph, the output dimensions come from the scale= in the graph, so keep it in sync
with the width/height arguments. → Guide
Probe metadata (no decoding)
const info = await wasmpeg.probe(file);
// {
// format: 'mov,mp4,m4a,3gp,3g2,mj2',
// duration: 12.4, // seconds, or null
// bitrate: 2400, // kb/s
// streams: [{ index: 0, type: 'video' }, { index: 1, type: 'audio' }],
// video: { width: 1920, height: 1080, fpsNum: 30000, fpsDen: 1001 },
// audio: { sampleRate: 48000, channels: 2 },
// }
probe() reads container and stream metadata without decoding any frames, so it’s fast and
cheap to call on upload. duration is null when the container doesn’t store it; fpsNum/
fpsDen give the exact frame rate as a fraction (here 30000/1001 ≈ 29.97). Stream type is
one of video, audio, data, subtitle, or attachment. → Guide
Decode audio to PCM
const aud = await wasmpeg.decodeAudio(file);
console.log(aud.channels, aud.sampleRate);
let chunk;
while ((chunk = aud.nextSamples())) {
// Float32Array, interleaved samples in [-1, 1]
}
aud.close();
nextSamples() returns one decoded frame’s worth of interleaved float samples, or null at
EOF. Interleaved means channels alternate per sample (L, R, L, R, …); deinterleave if your
sink wants planar data. Like decode(), the audio decoder holds a session slot until you
close() it. → Guide
Grab a thumbnail / encode frames
// First-frame JPEG thumbnail (image2pipe is the default container):
const jpg = await wasmpeg.encode(file, { codec: 'mjpeg', frames: 1 });
// Encode a canvas straight to PNG:
const png = await wasmpeg.encode(canvas, { codec: 'png' });
encode() returns a Uint8Array of the encoded container bytes. The common options:
| Option | Default | Meaning |
|---|---|---|
codec | mjpeg | Encoder name — mjpeg, png, etc. |
fmt | image2pipe | Container; the in-memory single-stream image container |
width / height | source size | Output dimensions |
frames | all | Cap on how many frames to encode |
fps | 30 | Frame rate as a number or { num, den } |
bitrate | codec default | Target bitrate in bits/s |
For a still, set frames: 1. → Guide
close()
when you’re done so the slot frees up — otherwise a long-lived page can exhaust them
and the next decode() will throw. probe(), scale(), and encode() clean up their own
slots; the two iterators you open yourself are the ones you have to close.Drive it with FFmpeg arguments
If you’d rather pass an FFmpeg-style command, run() dispatches one through the same
pipeline:
// Filter op → returns RGBA pixels:
const rgba = await wasmpeg.run(file, ['-i', 'in.mp4', '-vf', 'scale=640:360']);
// Decode-only → returns a decoder you iterate and close:
const dec = await wasmpeg.run(file, ['-i', 'in.mp4']);
run() parses the args and acts on the decode/filter subset. There’s no output-file path: a
trailing output filename is parsed and ignored, and the result comes back in memory.
Next, read The three APIs to understand when to drop to a lower level.