Skip to main content

Which video API?

FFmpeg.wasm vs WebCodecs vs MediaRecorder: choosing a browser video stack

Compare FFmpeg.wasm, WebCodecs and MediaRecorder for browser video: speed, codec support, file size, frame accuracy and which one to choose for an editor.

14 min readEngineering

Part 15 of 15Build a browser video editor

Which video API?azelify
Engineering

FFmpeg.wasm vs WebCodecs vs MediaRecorder: choosing a browser video stack

Key takeaways

  • MediaRecorder captures a live stream in real time; it is easy, but it cannot render faster than playback or promise exact frames.
  • FFmpeg.wasm brings the whole FFmpeg toolbox to the browser but runs on the CPU in WebAssembly, so it is far slower than native.
  • WebCodecs exposes the device's hardware codecs frame by frame, which suits editors, but leaves demuxing and muxing to you.

FFmpeg.wasm vs WebCodecs is one of the first comparisons people make when they want video inside a web page, and the honest answer depends on the job. This guide puts FFmpeg.wasm, WebCodecs and MediaRecorder side by side. It explains what each one is, shows a short piece of code for each, compares speed, frame accuracy, codecs and browser support, and ends with a decision tree you can follow for a browser video editor.

Three takeaways frame the comparison. MediaRecorder captures a live stream in real time. It is easy to use, but it cannot render faster than playback, and it gives you no control over exact frames. FFmpeg.wasm brings the whole FFmpeg toolbox into the browser, but it runs on the processor as WebAssembly, so it is far slower than FFmpeg running natively. WebCodecs exposes the browser's encoders and decoders frame by frame, which suits an editor, but it leaves demuxing and muxing to you. The export guide shows the WebCodecs path in full, and this article places it beside its alternatives.

You do not need to have built a video feature before to follow along. The guide assumes only that you can read JavaScript. Every term is defined where it first appears, and each code block is written to be read line by line, not pasted unchanged into a product.

FFmpeg.wasm vs WebCodecs: what each one is#

Start with the vocabulary. WebAssembly is a binary format for code that runs in the browser at speeds close to a native program, which lets code written in languages such as C run on a web page.[1] FFmpeg is a large open-source collection of libraries and command-line tools for audio and video. It can read, convert and write most of the formats people meet.[2] FFmpeg.wasm is a port of FFmpeg to WebAssembly. Its README describes it as a pure WebAssembly and JavaScript port of FFmpeg.[3]

WebCodecs is a browser API. It gives JavaScript access to the browser's own audio and video encoders and decoders, together with raw video frames and image decoders.[4] It is a set of building blocks rather than a complete tool. You read the file, split it into chunks, decode or encode those chunks, and write any output container yourself. The W3C specification defines the interfaces for that work.[5]

MediaRecorder is the simplest of the three. It belongs to the MediaStream Recording API, and it records a MediaStream, such as a camera feed or the output of a canvas, into a file the browser chooses.[6][7]

The difference that matters is the level at which you work. FFmpeg.wasm works on whole files and commands. WebCodecs works on individual frames and chunks. MediaRecorder works on a stream that is already running, and you start and stop it without touching any single picture.

Why do three tools exist when the browser already has media APIs? Each one answers a different question. FFmpeg asks, in effect, how to convert this file into that one using every option I know. WebCodecs asks how my code can produce exactly these frames. MediaRecorder asks how to save what this stream shows as it plays. A video editor asks the second question most of the time, which is why this comparison tends to end there. A conversion service asks the first, and a screen or canvas recorder asks the third.

Three tools, three shapes of code#

The code for each tool looks different, and the difference explains most of the trade-offs. The snippets below are minimal. They show the shape of the work rather than a finished product.

FFmpeg.wasm: a command line inside the page#

FFmpeg.wasm lets you run an FFmpeg command from JavaScript. You load the core, copy the input file into the library's virtual file system, run a command with the same arguments you would type in a terminal, and then read the output back as bytes.

import { FFmpeg } from '@ffmpeg/ffmpeg';

const ffmpeg = new FFmpeg();
await ffmpeg.load(); // loads the core, which runs inside a web worker

// Copy the user's file into the library's virtual file system.
await ffmpeg.writeFile('input.mp4', new Uint8Array(await file.arrayBuffer()));

// The same arguments you would type in a terminal. Zero means success.
const code = await ffmpeg.exec(['-i', 'input.mp4', '-vf', 'scale=540:960', 'output.mp4']);
if (code !== 0) throw new Error(`ffmpeg exited with ${code}`);

const bytes = await ffmpeg.readFile('output.mp4'); // Uint8Array by default

The appeal is that every FFmpeg filter and option you already know is available. The cost is that the whole job runs inside a WebAssembly build of the processor, and the documentation is candid about the speed. The FAQ says that WebAssembly is still a lot slower than native code.[8] The figure shows the four stages every command goes through.

Fig. 01
An FFmpeg.wasm job loads, writes input, runs a command and reads outputIllustrative sequence of four stages in a browser FFmpeg job. The core is loaded first, which runs in a web worker. The input bytes are written into the virtual file system. The command runs with the arguments given. The output file is read back as bytes that the page can save or upload.
Load coreruns in a web worker
Write inputbytes into virtual files
Run execFFmpeg arguments
Read outputbytes back to the page
  1. 01 → Load coreruns in a web worker
  2. 02 → Write inputbytes into virtual files
  3. 03 → Run execFFmpeg arguments
  4. 04 → Read outputbytes back to the page
An FFmpeg.wasm job loads, writes input, runs a command and reads output
Illustrative, not data[ffmpeg-gh]

Memory deserves attention before you choose this route. Files written into the library's virtual file system live in memory. A long, high-resolution input and its output can therefore occupy a large share of the browser tab's memory at the same time. For short clips that is manageable. For a feature-length file it is a reason to look for a route that streams chunks, which is the approach that WebCodecs and the export guide take.

WebCodecs: frame by frame#

With WebCodecs you describe the output, create an encoder, and then feed it one frame at a time. The encoder hands back compressed chunks, and something else must package them into a container. The export guide covers the packaging step in detail.

const chunks: EncodedVideoChunk[] = [];
const encoder = new VideoEncoder({
  output: (chunk) => chunks.push(chunk), // a muxer would write each chunk to a file instead
  error: (error) => console.error(error),
});
encoder.configure({
  codec: 'vp09.00.40.08.00',
  width: 1280,
  height: 720,
  bitrate: 4_000_000,
  framerate: 30,
});

for (let i = 0; i < 90; i++) {
  const timestamp = Math.round((i * 1_000_000) / 30); // microseconds
  const frame = new VideoFrame(canvas, { timestamp });
  encoder.encode(frame, { keyFrame: i % 30 === 0 });
  frame.close(); // release the picture once it has been handed over
}
await encoder.flush(); // resolves when the queued frames have been encoded

The MDN guide notes that the codec string must be fully specified, which is why the example uses vp09.00.40.08.00 rather than a short name.[10] Because you choose each timestamp, the result is frame accurate. Because the encoder is the browser's own, it can use hardware where it is available. The price is that you must write or import the container step yourself. WebCodecs does not handle networking or container storage, and the Chrome guide places both in the application's code.[4]

In practice a WebCodecs pipeline has three parts that you assemble yourself. A demuxer reads a source file and splits it into encoded chunks, which a decoder turns back into frames you can draw. On the way out, an encoder produces chunks from your frames, and a muxer packages them into a container. Keeping these parts separate lets you replace or tune each one, which matters when a single file must be read on one device and written on another.

MediaRecorder: record a canvas as it plays#

MediaRecorder needs a stream. A canvas can supply one through captureStream, which returns a MediaStream containing a real-time video capture of the canvas contents.[11] The recorder then delivers data in pieces while it runs.

const stream = canvas.captureStream(30); // a live stream fed by the canvas
const mimeType = MediaRecorder.isTypeSupported('video/mp4') ? 'video/mp4' : 'video/webm';
const recorder = new MediaRecorder(stream, { mimeType });

const parts: Blob[] = [];
recorder.ondataavailable = (event) => parts.push(event.data);
recorder.onstop = () => {
  const file = new Blob(parts, { type: recorder.mimeType });
  // file is the recording; save it or upload it here
};

recorder.start(1000); // deliver a chunk every second, in milliseconds
// ... later, when the recording should end:
recorder.stop();

The start method takes an optional timeslice in milliseconds. Without it, the recording arrives as one large chunk when recording ends. With it, a dataavailable event fires for each piece, and stop fires one final event with the remaining data.[6] The figure shows that rhythm.

Fig. 02
Chunk 10–1s
Chunk 21–2s
Chunk 32–3s
Chunk 43–4s
Final chunk4–5s
0s5s
5s · stop()
Illustrative: with a one-second timeslice, data arrives in one-second piecesA five-second recording timeline. Four one-second chunks arrive through dataavailable events while recording continues. The final chunk arrives after stop is called at the five-second mark, which is the last data the recorder delivers.12345
  1. 1Chunk 1 0–1sfirst dataavailable
  2. 2Chunk 2 1–2ssecond dataavailable
  3. 3Chunk 3 2–3sthird dataavailable
  4. 4Chunk 4 3–4sfourth dataavailable
  5. 5Final chunk 4–5safter stop()
Illustrative: with a one-second timeslice, data arrives in one-second pieces
Illustrative, not data[mdn-mediarecorder]

The recorder is the easiest of the three to start, and the timing is the reason. It records what the stream produces while the stream runs, so it cannot render faster than playback. It also gives you no per-frame timestamps to control. If you need a particular frame at a particular time, MediaRecorder cannot provide it.

The output format is also partly out of your hands. You can ask whether a type is supported with isTypeSupported and request one through the options, but the browser decides the encoding details. The recorder's mimeType property reports what was actually selected, so read it before you name the file or upload it, and let the extension match the recording.

FFmpeg.wasm vs WebCodecs vs MediaRecorder: speed, accuracy and codecs#

The table compares the three on the questions that usually decide the choice. Each row states what the tool actually does, and the notes after it explain the reasoning.

QuestionFFmpeg.wasmWebCodecsMediaRecorder
What does the workWebAssembly build of FFmpeg, on the processorThe browser's own encoders and decodersThe browser's own recorder
SpeedMuch slower than native FFmpegAs fast as the browser's encoder, often hardware-assistedReal time: no faster than playback
Frame accuracyExact, through the command you writeExact, because you pick each timestampNo per-frame control
CodecsWhatever the build includesChecked with VideoEncoder.isConfigSupportedChecked with MediaRecorder.isTypeSupported
ContainerBuilt in, for many formatsYou add a muxerChosen by the browser
ThreadsMulti-thread build needs cross-origin isolationAvailable in dedicated workersHandled by the browser
DownloadA large WebAssembly file, loaded firstNothing to downloadNothing to download
Browser supportWhere WebAssembly and workers runChrome 94, Edge 94, Firefox 130 on desktop, Safari 26Widely available since April 2021

Speed is the first row. FFmpeg.wasm pays for running a processor build of FFmpeg inside a sandbox, and the project's FAQ says that the multi-thread build can be around twice as fast as the single-thread build, with a warning that it is less stable.[8] That multi-thread build relies on shared memory, which browsers expose only to pages that are cross-origin isolated, meaning the page must send specific security headers.[12] WebCodecs and MediaRecorder use the browser's own machinery, so the pace of the work follows the hardware and the stream.

Published speed claims are a starting point, not an answer. The only number that matters is the one your own code achieves on the devices your users actually have. Our editor measures this directly. Every completed export records the frame rate it sustained, keyed by codec and resolution, and the estimate for the next export reads from that history rather than from a fixed table. A table of guesses goes wrong on hardware it has never seen, so measurement is the more reliable habit for any of the three tools.

Frame accuracy is the second row. It means that when you ask for the picture at 12.5 seconds, you get that picture, and when you write frame 375 at 30 fps, it lands at 12.5 seconds. An editor depends on that promise for trimming, scrubbing and matching a preview to an export. FFmpeg.wasm can be exact because you write the command, and WebCodecs can be exact because you choose each timestamp. MediaRecorder cannot make that promise, because it records whatever the stream produces at the moment it runs. For a screen recorder that is acceptable. For a timeline it is not.

Support is the other row that changes decisions. Can I Use lists WebCodecs with full support from Chrome and Edge 94, Firefox 130 on desktop, and Safari 26, with partial support in some earlier Safari versions.[13] MediaRecorder has been widely available across browsers since April 2021, according to MDN.[6] A product that must run on older Safari should plan a fallback, and FFmpeg.wasm can be that fallback where WebAssembly runs.

Which one should you choose?#

Start from the question you need answered. If each edit must land on exact frames, you need WebCodecs or FFmpeg.wasm. If you only need to keep a live canvas, MediaRecorder is enough. If you are converting one finished file and do not want to write any packaging code, FFmpeg.wasm does the whole job, slowly.

Four questions do most of the work. Does each output frame need to be exact? Will the file come from a live stream or from stored media? Does the job have to run on a phone or an older laptop, where WebAssembly speed may be the limit? And who will write and maintain the container code? Write the answers down before you pick a library, because each answer rules out a branch of the tree. A team that cannot maintain a muxer should prefer a library, and a team that needs frame control should accept the work of writing the packaging step.

Fig. 03
Pick the video stack from the job, not from habitA two-level decision tree. The first question asks whether each edit must land on exact frames. If yes, the second asks whether you will write the MP4 container yourself: if yes, use WebCodecs with a muxer, and if no, use FFmpeg.wasm, which is slow. If the answer to the first question is no, the next question asks whether a live canvas capture is enough. If so, use MediaRecorder with a disclosed limit. If not, use FFmpeg.wasm for a conversion.yesyesnonoyesno
Need exact frames for each edit?
Will you write the MP4 container?
WebCodecs with a muxer
FFmpeg.wasm: slow, does it all
Is a live canvas capture enough?
MediaRecorder
FFmpeg.wasm for a conversion
  1. Need exact frames for each edit?
    1. yes → Will you write the MP4 container?
      1. yes → WebCodecs with a muxer
      2. no → FFmpeg.wasm: slow, does it all
    2. no → Is a live canvas capture enough?
      1. yes → MediaRecorder
      2. no → FFmpeg.wasm for a conversion
Pick the video stack from the job, not from habit

For an editor, the first branch usually leads to WebCodecs, because preview and export both need frame control. The editor still needs a muxer and a reliable decoder, so the stack is a combination rather than a single API. FFmpeg.wasm is most useful for one-off conversions in a tool where speed is not critical. MediaRecorder is useful for capturing what a canvas shows, such as a quick preview recording, and it is poor at anything that must be frame accurate.

The licence question for FFmpeg builds#

Before you ship FFmpeg, read its licence. FFmpeg is licensed under the GNU Lesser General Public License, version 2.1 or later, but some of its optional parts and optimisations fall under the GPL, and using them brings the GPL over the whole project. The legal page names the configure options --enable-gpl and --enable-nonfree as the ones to avoid when you want the LGPL terms, and it names libx264 as a GPL library to keep out of a program that must stay LGPL. The LGPL checklist adds requirements such as linking dynamically, providing the matching source code, and giving attribution on download pages and in an about box.[2]

A practical checklist follows from the legal page. Decide early whether your product calls a shared FFmpeg library or ships FFmpeg inside its own build, because the obligations differ between those two cases. Record the configure line of every build you distribute, keep the matching source code available, and put the required notice on your download page and in the about box. Then have a lawyer review the result, since the legal page is explicit that it is not legal advice.

How the Azelify editor chooses its stack#

The Azelify editor uses WebCodecs in the browser for both decoding and encoding, through a demuxing and muxing library called mediabunny.[14] Decoding keeps a small number of warm decoders for the clips it plays, and the cap depends on the device tier, because hardware decoders are a scarce resource. Export builds the file from encoded chunks, as the export guide describes.

The server path uses a headless browser, which runs the same render routine. When that page can encode its frames with WebCodecs, it sends the encoded chunks onward. When it cannot, the frames travel as raw pixels and are encoded on the server by a software H.264 encoder. A separate server-side FFmpeg job finishes some browser exports, for example when the audio must change codec or the container must change, before the file is marked as done.

The editor does not use one API for everything, and the trade-off shows in its design. Frame-accurate preview and export need WebCodecs, because it is the route that hands the editor exact frames. Conversions that are rare and awkward are left to a server-side FFmpeg step, where a slow command does not block the person's screen. Each choice keeps the browser doing the work that depends on exact frames and the server doing the work that does not need to be interactive.

When to combine them in one product#

Most real products combine the tools rather than choosing one. A common pattern is WebCodecs for the frame-accurate edit and export in the browser, MediaRecorder for a quick capture of what the canvas shows, and FFmpeg on a server for occasional conversions such as turning a WebM into an MP4 with a different audio codec. Each tool then does the job it is best at. The combination also limits risk. If one path is slow or unsupported on a device, the other can take over with a clear message to the person.

A simple test helps when deciding whether to combine. List each user-visible action, name the tool that performs it, and ask what happens when that tool is unavailable. If the honest answer for an action is that the job fails and nothing explains why, add a fallback or a message before you add another tool to the stack.

Frequently asked questions#

Is ffmpeg.wasm faster than WebCodecs?#

Usually no. The ffmpeg.wasm FAQ says WebAssembly is still a lot slower than native code, and WebCodecs uses the browser's encoders, which are often hardware-assisted. The exception is a format the browser cannot decode or encode at all, where FFmpeg.wasm becomes the only path left, however slow it is.

Does MediaRecorder work with a canvas?#

Yes. The canvas captureStream method returns a MediaStream of the canvas contents, which MediaRecorder can record.[11] The recording runs in real time, so a 30-second clip takes about 30 seconds to capture.

Can I use WebCodecs in Firefox?#

Yes on desktop, from version 130 onward according to Can I Use.[13] Check the support table again before a release, because browser support changes with each version.

Why does ffmpeg.wasm need special headers?#

The multi-threaded build uses shared memory, and browsers allow shared memory only on pages that are cross-origin isolated. Serving the page with the right cross-origin headers makes it isolated.[12] Without those headers the single-thread build still works, but it is slower.

Which one should I learn first?#

Learn WebCodecs first if you are building an editor, because it gives you frames and chunks, the same units the rest of the editor uses. Learn MediaRecorder second, since it is short to use and handy for captures. Learn FFmpeg.wasm when you need a command you already know from the terminal.

Is MediaRecorder good enough for a video editor?#

For capturing, yes. For editing, usually no. An editor needs to address individual frames, seek to exact times and produce the same output on every run, and MediaRecorder offers none of those controls. Use it for quick recordings of a canvas, and use WebCodecs for the editing pipeline.

Where to go next#

For the packaging step that turns WebCodecs chunks into a file, read how to export MP4 from the browser. For the decoding side, which is the reverse of the encoder in this comparison, read how to decode video frames with WebCodecs.

Sources

  1. Wikipedia, “WebAssembly,” en.wikipedia.org (opens in a new tab) (accessed 2026-10-13). ↩

  2. ffmpeg.wasm, “ffmpeg.wasm,” github.com (opens in a new tab) (accessed 2026-10-13). ↩ ↩[2]

  3. Chrome for Developers, “Video processing with WebCodecs,” developer.chrome.com (opens in a new tab) (accessed 2026-10-13). ↩ ↩[2]

  4. W3C, “WebCodecs,” www.w3.org (opens in a new tab) (accessed 2026-10-13). ↩

  5. MDN Web Docs, “MediaRecorder,” developer.mozilla.org (opens in a new tab) (accessed 2026-10-13). ↩ ↩[2] ↩[3]

  6. W3C, “MediaStream Recording,” www.w3.org (opens in a new tab) (accessed 2026-10-13). ↩

  7. ffmpeg.wasm, “FAQ,” ffmpegwasm.netlify.app (opens in a new tab) (accessed 2026-10-13). ↩ ↩[2]

  8. ffmpeg.wasm, “Installation,” ffmpegwasm.netlify.app (opens in a new tab) (accessed 2026-10-13). ↩

  9. MDN Web Docs, “Using the WebCodecs API,” developer.mozilla.org (opens in a new tab) (accessed 2026-10-13). ↩

  10. MDN Web Docs, “HTMLCanvasElement: captureStream() method,” developer.mozilla.org (opens in a new tab) (accessed 2026-10-13). ↩ ↩[2]

  11. MDN Web Docs, “Window: crossOriginIsolated property,” developer.mozilla.org (opens in a new tab) (accessed 2026-10-13). ↩ ↩[2]

  12. Can I use, “WebCodecs API,” caniuse.com (opens in a new tab) (accessed 2026-10-13). ↩ ↩[2]

  13. Vanilagy, “Mediabunny,” github.com (opens in a new tab) (accessed 2026-10-13). ↩

Build a browser video editor

In this series

  1. 01How to build a video editor in the browser: the complete architecture
  2. 02Designing the project file for a web video editor: schema, versions and autosave
  3. 03Undo and redo in a web editor: the command pattern, explained from zero
  4. 04How a video editor timeline works: tracks, clips, trims and overlaps
  5. 05Importing video files in the browser: probing, codecs, proxies and storage
  6. 06WebCodecs tutorial: decoding video frames in the browser without a video tag
  7. 07Frame-accurate playback in JavaScript: clocks, requestAnimationFrame and A/V sync
  8. 08Compositing video layers with Canvas and WebGL: transforms, blends and effects
  9. 09Text and animated captions in a browser video editor: let CSS do the drawing
  10. 10Drag, resize and rotate handles for a canvas editor: building the transform gizmo
  11. 11Web Audio API for video editors: mixing tracks, drawing waveforms and loudness
  12. 12Exporting MP4 from the browser: WebCodecs encoding, muxing and server fallback
  13. 13Keyframe animation in a web editor: interpolation, easing curves and motion tracking
  14. 14How to test a video renderer: golden images, oracles and property-based tests
  15. 15FFmpeg.wasm vs WebCodecs vs MediaRecorder: choosing a browser video stack

Edit on a WebCodecs-era engine

Cut, preview and export a vertical video in one browser tab.

Open the studio

Keep reading

More in Browser video editor

All guides