Skip to content

API in Capabilities

WebCodecs API

Published

Limited availabilityNot supported in Firefox (Android), Firefox (Android)W3C

WebCodecs exposes the browser’s media codecs one frame at a time: VideoDecoder turns EncodedVideoChunk objects into VideoFrames, VideoEncoder does the reverse, and AudioDecoder and AudioEncoder do the same for EncodedAudioChunk and AudioData. Each codec is a queue with output and error callbacks rather than promise-returning methods, which is what lets a video editor, a conferencing client, or a cloud-gaming stream keep hardware acceleration while controlling timing per frame. Containers are out of scope: nothing in the API reads or writes MP4 or WebM, so a muxer or demuxer library sits on either side.

Chrome 94 and Edge 94 shipped all four codecs; Firefox 130 followed on desktop (Firefox for Android has none); Safari 16.4 added VideoDecoder and VideoEncoder and Safari 26 added AudioDecoder and AudioEncoder (BCD api.VideoDecoder, api.AudioDecoder). Every interface is available in dedicated workers, and that is where encoding loops belong so the main thread stays free.

const decoder = new VideoDecoder({ output, error })
decoder.configure(config)
decoder.decode(chunk)
decoder.flush()
decoder.reset()
decoder.close()
VideoDecoder.isConfigSupported(config)
const encoder = new VideoEncoder({ output, error })
encoder.configure(config)
encoder.encode(frame)
encoder.encode(frame, options)
encoder.flush()
VideoEncoder.isConfigSupported(config)

AudioDecoder and AudioEncoder have the same shape with decode(chunk) and encode(data). configure(), decode(), encode(), reset(), and close() return undefined and act on the codec’s control-message queue; flush() returns a promise that resolves once every queued message has produced its output; isConfigSupported() returns a promise for { supported, config } with the unrecognised members stripped. Attributes: state ("unconfigured", "configured", "closed"), decodeQueueSize or encodeQueueSize, and a dequeue event (Chrome 106) that fires when the queue shrinks. The interfaces are [SecureContext].

The constructor init has two required callbacks. configure() takes a config dictionary whose codec must be a fully specified string: "vp09.00.10.08", "avc1.42001E", "av01.0.04M.08", "opus", or "mp4a.40.2"; the short forms "vp9" and "h264" are rejected as invalid.

Dictionary Member Type Required Description
VideoDecoderInit / VideoEncoderInit output callback Yes Receives each VideoFrame (decoder) or EncodedVideoChunk plus EncodedVideoChunkMetadata (encoder).
same error WebCodecsErrorCallback Yes Receives the DOMException that closed the codec.
VideoDecoderConfig codec DOMString Yes Fully specified codec string.
VideoDecoderConfig description AllowSharedBufferSource No Codec-specific extradata such as the avcC box; required for H.264 in AVC format, absent for Annex B.
VideoDecoderConfig codedWidth, codedHeight unsigned long No Dimensions of the coded frames; both or neither.
VideoDecoderConfig displayAspectWidth, displayAspectHeight unsigned long No Display aspect ratio override.
VideoDecoderConfig colorSpace VideoColorSpaceInit No primaries, transfer, matrix, fullRange.
VideoDecoderConfig hardwareAcceleration HardwareAcceleration No, default "no-preference" "prefer-hardware" or "prefer-software".
VideoDecoderConfig optimizeForLatency boolean No Ask for minimal decode queue depth.
VideoDecoderConfig rotation, flip double, boolean No Orientation applied to output frames (Chrome 138).
VideoEncoderConfig codec, width, height Yes Codec string and the dimensions of every frame passed to encode().
VideoEncoderConfig displayWidth, displayHeight unsigned long No Display size signalled in the stream.
VideoEncoderConfig bitrate unsigned long long No Target bits per second.
VideoEncoderConfig framerate double No Expected frames per second, used for rate control.
VideoEncoderConfig bitrateMode VideoEncoderBitrateMode No, default "variable" "constant", "variable", or "quantizer".
VideoEncoderConfig latencyMode LatencyMode No, default "quality" "realtime" trades compression for low delay.
VideoEncoderConfig scalabilityMode DOMString No SVC mode such as "L1T2".
VideoEncoderConfig alpha AlphaOption No, default "discard" "keep" encodes an alpha channel where the codec allows it.
VideoEncoderConfig hardwareAcceleration, contentHint No As for the decoder; contentHint is "detail", "text", or "motion".
VideoEncoderEncodeOptions keyFrame boolean No, default false Force a key frame for this input.

EncodedVideoChunk is constructed with { type: "key" | "delta", timestamp, duration, data }; VideoFrame from a canvas, image, or ArrayBuffer with { timestamp } and must be closed with frame.close() once consumed, or the pool of GPU-backed buffers runs dry.

Method Exception Condition
configure() TypeError The config is not valid: empty codec, only one of codedWidth and codedHeight, zero encoder width or height.
configure() InvalidStateError state is "closed".
configure() (async) NotSupportedError The browser cannot support the config; delivered to the error callback and the codec is closed.
decode() InvalidStateError state is not "configured".
decode() DataError A key chunk is required (first chunk after configure() or flush()) and chunk.type is "delta", or a chunk marked "key" is found not to be one.
decode() / encode() (async) EncodingError Decoding or encoding failed; delivered to error and the codec is closed.
encode() InvalidStateError state is not "configured".
encode() TypeError The VideoFrame or AudioData is detached (already closed).
encode() DataError The frame’s rotation or flip differs from the first frame encoded since configure().
flush() InvalidStateError Rejected promise when state is not "configured".
flush() (pending) AbortError reset() or close() was called before the flush completed.
reset(), close() InvalidStateError state is already "closed".
isConfigSupported() TypeError Rejected promise for an invalid config; an unsupported but valid config resolves with supported: false.

close() is final: the error callback is not invoked for the AbortError it uses internally, and a new instance is needed afterwards. reset() returns the codec to "unconfigured" and requires a new configure() and a key frame.

Each example feature-detects the codec interfaces and shows what runs without them. All three are meant to run inside a dedicated worker; the main-thread version differs only in where the frames come from.

Checking codec support before configuring, with a MediaRecorder fallback

Section titled “Checking codec support before configuring, with a MediaRecorder fallback”

isConfigSupported() answers without allocating a codec, so probe the preferred profile first and drop to MediaRecorder (which writes a container but hides individual frames) where WebCodecs is absent or the profile is unsupported.

async function pickEncoder(width, height) {
if (typeof VideoEncoder === 'undefined') return { kind: 'mediarecorder' };
const candidates = [
{ codec: 'av01.0.04M.08', width, height, bitrate: 2_000_000, framerate: 30 },
{ codec: 'vp09.00.10.08', width, height, bitrate: 2_000_000, framerate: 30 },
{ codec: 'avc1.42001E', width, height, bitrate: 2_000_000, framerate: 30, avc: { format: 'annexb' } },
];
for (const config of candidates) {
const { supported } = await VideoEncoder.isConfigSupported(config);
if (supported) return { kind: 'webcodecs', config };
}
return { kind: 'mediarecorder' };
}

The resolved config from isConfigSupported() is the dictionary with unknown members removed; pass that object to configure() rather than the original to avoid a later NotSupportedError.

Decoding demuxed chunks and surfacing the error callback

Section titled “Decoding demuxed chunks and surfacing the error callback”

A demuxer yields { type, timestamp, duration, data } records; wrap each in an EncodedVideoChunk and keep the first one a key frame. The error callback is the only place an EncodingError or NotSupportedError arrives, so route it to the UI instead of relying on exceptions.

function createDecoder(config, onFrame, onFatal) {
if (typeof VideoDecoder === 'undefined') {
onFatal(new Error('WebCodecs is not available; play through a <video> element instead.'));
return null;
}
const decoder = new VideoDecoder({
output: (frame) => {
onFrame(frame); // caller must call frame.close()
},
error: (err) => onFatal(err), // the decoder is closed once this fires
});
decoder.configure(config);
return decoder;
}
async function feed(decoder, records) {
for (const r of records) {
if (decoder.decodeQueueSize > 8) {
await new Promise((resolve) => decoder.addEventListener('dequeue', resolve, { once: true }));
}
decoder.decode(new EncodedVideoChunk(r));
}
await decoder.flush();
}

The decodeQueueSize check keeps memory bounded; without it a fast demuxer queues the whole file before the first frame is decoded.

Encoding canvas frames in a worker and forcing periodic key frames

Section titled “Encoding canvas frames in a worker and forcing periodic key frames”

Capture frames from an OffscreenCanvas, request a key frame every 60 frames so a viewer can join mid-stream, and close each VideoFrame after encode() has taken it.

function startEncoder(canvas, config, onChunk) {
if (typeof VideoEncoder === 'undefined') return null;
const encoder = new VideoEncoder({
output: (chunk, meta) => onChunk(chunk, meta?.decoderConfig ?? null),
error: (err) => console.error(`${err.name}: ${err.message}`),
});
encoder.configure(config);
let index = 0;
return {
push(timestampMicros) {
const frame = new VideoFrame(canvas, { timestamp: timestampMicros });
encoder.encode(frame, { keyFrame: index % 60 === 0 });
frame.close();
index += 1;
},
async stop() {
await encoder.flush();
encoder.close();
},
};
}

meta.decoderConfig is present on the first output chunk and carries the description a decoder needs for AVC; store it with the stream header.

Specifications

SpecificationStatus
WebCodecs APIW3C
WebCodecs: VideoDecoder.configure() methodW3C
WebCodecs: VideoEncoder.encode() methodW3C
WebCodecs: codec processing modelW3C
  • Legend
  • Yes
  • Partial
  • Flag
  • No
  • Unknown
Browser / PlatformSupportVersionsConfidenceSourceNotes
Chrome (Desktop)Yes94highsource1
Chrome (Android)Yes94highsource23
Edge (Desktop)Yes94highsource45
Firefox (Desktop)Yes130highsource6
Firefox (Android)No—highsource78
Safari (macOS)Yes16.4highsource9
Safari (iOS)Yes16.4highsource1011
Samsung InternetYes17.0highsource1213
WebView (Android)Yes94highsource1415
Chrome (Desktop)Yes94highsource16
Chrome (Android)Yes94highsource1718
Edge (Desktop)Yes94highsource1920
Firefox (Desktop)Yes130highsource21
Firefox (Android)No—highsource2223
Safari (macOS)Yes26highsource24
Safari (iOS)Yes26highsource2526
Samsung InternetYes17.0highsource2728
WebView (Android)Yes94highsource2930
  1. Covers the `VideoDecoder` entry.
  2. Covers the `VideoDecoder` entry.
  3. Derived by browser-compat-data mirroring from Chrome.
  4. Covers the `VideoDecoder` entry.
  5. Derived by browser-compat-data mirroring from Chrome.
  6. Covers the `VideoDecoder` entry.
  7. Covers the `VideoDecoder` entry.
  8. No Firefox for Android support is recorded in browser-compat-data.
  9. Covers the `VideoDecoder` entry.
  10. Covers the `VideoDecoder` entry.
  11. Derived by browser-compat-data mirroring from Safari.
  12. Covers the `VideoDecoder` entry.
  13. Derived by browser-compat-data mirroring from Chrome Android.
  14. Covers the `VideoDecoder` entry.
  15. Derived by browser-compat-data mirroring from Chrome Android.
  16. Covers the `AudioDecoder` entry.
  17. Covers the `AudioDecoder` entry.
  18. Derived by browser-compat-data mirroring from Chrome.
  19. Covers the `AudioDecoder` entry.
  20. Derived by browser-compat-data mirroring from Chrome.
  21. Covers the `AudioDecoder` entry.
  22. Covers the `AudioDecoder` entry.
  23. No Firefox for Android support is recorded in browser-compat-data.
  24. Covers the `AudioDecoder` entry.
  25. Covers the `AudioDecoder` entry.
  26. Derived by browser-compat-data mirroring from Safari.
  27. Covers the `AudioDecoder` entry.
  28. Derived by browser-compat-data mirroring from Chrome Android.
  29. Covers the `AudioDecoder` entry.
  30. Derived by browser-compat-data mirroring from Chrome Android.

Source data: /compatibility/webcodecs.json · Global usage: 92 % (StatCounter 2026-05)

Source: spec · MDN · Last verified 2026-10-03 · Confidence: high (computed from sources)