A Developer’s Guide to MediaRecorder Error Handling

If you've spent any time building a browser-based video or audio recording solution on top of the MediaStream Recording API and it's MediaRecorder object, you already know that getting a stream from getUserMedia() or getDisplayMedia() is only half the battle. The other half is dealing with everything that can go wrong once you actually try to record it.

At Pipe, we've been running a browser-based recording client in production for years, handling audio recording, video recording, and screen capture across a huge range of devices, browsers, and operating systems. We've processed millions of recordings created through the MediaStream Recording API. As a result, we've hit pretty much every possible MediaRecorder error. Some of them are obvious, some are hard to deal with.

This article covers the six errors we've encountered, what they actually mean in practice, and how to handle them.

A quick note on where these errors surface

Unlike getUserMedia() errors, which are thrown synchronously as rejected promises, MediaRecorder errors can show up in two different places depending on when they occur:

  • Errors thrown at construction (new MediaRecorder(stream, options)) behave like regular exceptions, they propagate immediately.
  • Errors thrown at start() or during an active recording session are delivered asynchronously via the onerror event (or addEventListener('error', ...)).

That distinction matters a lot for how you structure your error handling, and it's where many bugs can arise.

The errors

1. InvalidStateError

What it means: You called MediaRecorder.start() while the recorder was already running or paused.

This one is almost always a code bug rather than a user or environment problem. It happens when you call start() twice without stopping in between, or if you're not tracking the recorder state carefully enough and hit a race condition. For example, a button click handler fires before a previous recording session has fully stopped.

The MDN docs are pretty clear on this: start() must only be called when the recorder is in the inactive state. You can check mediaRecorder.state before calling it, which should return "inactive", "recording", or "paused".

if (mediaRecorder.state === "inactive") {
    mediaRecorder.start();
} else {
    console.warn("Recorder is already active:", mediaRecorder.state);
}

In practice, we saw this most often with edge cases in our UI, such as rapid double-clicks on a record button or situations where a stop-and-restart sequence doesn't wait for the onstop callback to fire before calling start() again. Guard your start() calls carefully.

2. SecurityError

What it means: The MediaStream being recorded is configured to disallow recording.

It's conceptually the MediaRecorder-side sibling of getUserMedia()'s NotAllowedError. The difference is when it surfaces: NotAllowedError fires when you're trying to get the stream; SecurityError fires when you're trying to record it.

The MDN docs also note it can arrive as an error event mid-recording if security conditions change after recording has already started, for example, if a track's source changes in a way that makes it non-recordable.

In practice, we can replicate this on Chrome with an iframe with the sandbox attribute set (Chrome will log SecurityError Invalid security origin to the console). There's no automatic recovery possible. We show a descriptive error message inside our recording client and ask the user to check their browser permissions.

3. NotSupportedError

What it means: This one can come from two completely different situations, and that's what makes it tricky.

  • At construction: if you pass a mimeType to new MediaRecorder(stream, { mimeType: "video/mp4; codecs=h264" }) that the browser doesn't support, it throws a NotSupportedError immediately. You're supposed to check MediaRecorder.isTypeSupported(mimeType) before constructing, but that only tells you whether the browser claims to support it (more on that when we get to EncodingError).
  • At start() : if the MediaStream being recorded is inactive, or if one or more of its tracks can't be recorded with the current configuration, you get a NotSupportedError at start time instead.

It's worth noting that a document-level NotSupportedError from getUserMedia() , thrown when the browser itself doesn't support the API, uses the exact same error name, which adds to the confusion.

MDN constructor reference | MDN start() reference

In practice: A NotSupportedError at construction due to an unsupported MIME type is really the same class of problem as EncodingError . The codec doesn't work, but it surfaces earlier and with a different error name. We treat them similarly: fall back to a known-good codec. The key difference is that NotSupportedError at construction is at least "honest". The browser is telling you upfront it can't do it. EncodingError (and a separate class of NotSupportedError) hits you when you start recording.

4. InvalidModificationError

What it means: The track composition of the MediaStream changed while recording was active. A track was added or removed dynamically from the stream. The MediaRecorder doesn't support this, it locks in the stream's track structure when recording starts.

Per the spec, this will be surfaced as an error event on the recorder.

In practice: This is fairly uncommon in straightforward recording scenarios, but it can happen if you're doing more advanced stream manipulation, for example, dynamically switching between camera inputs by adding and removing tracks from a stream, or mixing streams from multiple sources. The fix is to stop the recorder, modify the stream, and start a new MediaRecorder instance with the updated stream rather than trying to modify the stream while the recorder is running.

5. EncodingError

What it means: The recorder accepted your codec configuration, passed the isTypeSupported() check, constructed without errors, and then failed at runtime when actual media frames started coming in. The hardware or software encoder can't handle the codec, resolution, or bitrate you asked for.

This is the most painful MediaRecorder error we've dealt with, because isTypeSupported() gives you a false positive. The browser thinks it can encode this, but the actual encoder disagrees once it sees real data.

Per the spec, it's listed as a possible error name in the onerror event. In practice, Chrome delivers it with one of a few specific error messages:

  • "Video encoding failed." : the older Chrome message
  • "Encoder initialization failed." : typically seen during screen recording
  • "The given encoder configuration is not supported by the encoder." : the newer Chrome message

We've hit this error twice in recent times. The first time was during cross-device testing on an older Samsung Galaxy A8 running Android 9. We were trying to record video with H.264, isTypeSupported() came back true, but as soon as we called start() the recorder threw an EncodingError. No H.264 support, despite the browser claiming otherwise through isTypeSupported.

The second time was when we tested 5k screen recording with getDisplayMedia() on an M4 Mac Mini and on a 13 inch M1 MacBook (both connected to a 5k monitor). Everything worked fine at normal resolutions, but on a 5k display the encoder would silently fail to initialize and throw EncodingError right at the start of recording.

Our solution: We implemented an automatic codec fallback in our recording client. We listen for EncodingError and check the error message against the known Chrome strings above. If it matches, and we haven't already fallen back, we switch to VP8 and retry. Without this retry mechanism, a large number of devices will silently fail to record video using H.264, and you'll have no idea why. The behavior depends on whether any data has already been recorded:

  • If no data was captured yet (the error hit at initialization, before any frames were encoded): we suppress the normal stop handler, reset state back to idle, and immediately call start() again with VP8. The user barely notices anything happened.
  • If data was already recorded (the encoder failed mid-session): we stop the recording gracefully and save what we have, rather than discarding it. Restarting mid-session with a different codec would produce an incompatible file, so a clean stop is the right call here.

The VP8 fallback covers the vast majority of cases. If we're already on VP8 and still getting an encoding error, we fall through to showing the user an error message. At that point something more fundamental is wrong.

The EncodingError fallback: Checking for those specific Chrome error message strings is the most important piece of custom logic here.

6. UnknownError

What it means: Something went wrong that doesn't fit any of the other categories and isn't security-related. The spec's catch-all. Per the MDN docs, it's reserved for errors that are genuinely unclassifiable.

In practice: We've seen this very rarely, and when we have, it's been impossible to reproduce reliably. It tends to appear on unusual device/browser combinations where the browser itself seems to be having a bad day. There's no meaningful recovery you can implement for a true UnknownError . The best you can do is surface a clear message to the user and log as much context as possible (device, browser, OS, media constraints, mimeType) so you can investigate.

Handling all of these in code

You can setup error handling for all six errors in your code. The key is listening on the onerror event for runtime errors, and wrapping the MediaRecorder constructor in a try/catch for errors that surface at construction.

Our audio, video and screen recording client already does it.

Summary

Error When it surfaces Recovery possible?
InvalidStateError start() called in wrong state Yes: guard start() calls
SecurityError Stream not allowed to be recorded No: surface to user
NotSupportedError Construction or start() Sometimes: fall back to supported codec
InvalidModificationError Mid-recording, stream track changed Yes: stop, modify stream, restart
EncodingError Mid-recording, encoder failure Often: fall back to VP8
UnknownError Anytime No: log and surface to user

Most of these errors you can handle gracefully with a combination of defensive coding and a good fallback codec strategy. The spec defines them clearly, but browser behavior, especially around EncodingError and the false positives from isTypeSupported() , doesn't always match the theory. Test on real devices, especially older Android phones and high-resolution displays, and don't trust isTypeSupported() as your only safety net.

If you'd rather not deal with any of this yourself, the Pipe recording client bundles with the Pipe Platform handles it for you, including the codec fallback, cross-browser normalization, and error recovery, so your users just get a recorder that works.

A Developer’s Guide to MediaRecorder Error Handling
Share this
IT TAKES 1 MINUTE
Sign up for a 14 Day Trial

With our 14 days (336 hours) trial you can add audio, video and screen + camera recording to your website today and explore Pipe for 2 weeks