JavaScript MediaRecorder API: Capture Audio and Video

The MediaRecorder API provides a native JavaScript interface for capturing real-time audio and video streams directly within web browsers without relying on external plugins. This article explains what the MediaRecorder interface is and details the step-by-step mechanism JavaScript uses to record MediaStream tracks, collect binary data chunks during recording, and assemble them into a final Blob for playback, download, or server upload.


What is the MediaRecorder API?

The MediaRecorder interface is a core component of the MediaStream Recording API. It allows developers to capture media generated by webcams, microphones, canvas elements, or screen sharing via navigator.mediaDevices.getUserMedia() or navigator.mediaDevices.getDisplayMedia().

Unlike older approaches that required sending raw canvas frames or WebAudio buffers to external encoders, MediaRecorder handles the encoding process natively in the browser, producing compressed formats such as WebM or MP4.


How Media Streams Become Blobs: The Step-by-Step Process

Capturing a stream and outputting it as a Blob follows a clear four-step lifecycle:

[Media Stream] ──> [MediaRecorder] ──> [dataavailable: chunks[]] ──> [stop: new Blob(chunks)]

1. Obtaining the MediaStream

First, the application requests access to audio and video inputs from the user. This returns a MediaStream object containing individual audio and video tracks.

const stream = await navigator.mediaDevices.getUserMedia({
  audio: true,
  video: true
});

2. Initializing the MediaRecorder

An instance of MediaRecorder is created by passing the active MediaStream and optional configuration options, such as the target MIME type and bitrate.

const options = { mimeType: 'video/webm; codecs=vp9' };
const mediaRecorder = new MediaRecorder(stream, options);

3. Collecting Data Chunks (ondataavailable)

When recording starts via mediaRecorder.start(), the recorder begins encoding data in the background. As encoded data becomes available, the dataavailable event fires, providing a Blob slice containing a portion of the recording. These chunks are stored in an array.

const recordedChunks = [];

mediaRecorder.ondataavailable = (event) => {
  if (event.data && event.data.size > 0) {
    recordedChunks.push(event.data);
  }
};

You can pass a time interval (in milliseconds) to mediaRecorder.start(1000) to trigger the dataavailable event periodically, or omit it to fire the event only when recording stops.

4. Assembling the Final Blob (onstop)

When mediaRecorder.stop() is called, the recorder finishes processing the remaining stream buffer. The stop event listener then combines the array of binary chunks into a single unified Blob.

mediaRecorder.onstop = () => {
  const completeBlob = new Blob(recordedChunks, { type: 'video/webm' });
  
  // Create a local URL for playback or download
  const videoURL = URL.createObjectURL(completeBlob);
  const videoElement = document.querySelector('video');
  videoElement.src = videoURL;
};

Practical Complete Example

async function recordStream(durationMs = 5000) {
  // 1. Get user media
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
  
  // 2. Instantiate recorder
  const recorder = new MediaRecorder(stream);
  const chunks = [];

  // 3. Collect chunks
  recorder.ondataavailable = (e) => chunks.push(e.data);

  // 4. Handle completion
  recorder.onstop = () => {
    const finalBlob = new Blob(chunks, { type: recorder.mimeType });
    console.log('Recording complete. Blob size:', finalBlob.size);
    
    // Stop all tracks to release camera/mic hardware
    stream.getTracks().forEach(track => track.stop());
  };

  // Start recording, record for specified duration, then stop
  recorder.start();
  setTimeout(() => recorder.stop(), durationMs);
}

Working with the Resulting Blob

Once the binary data is compiled into a Blob, it can be utilized in several ways: