Record And Download Video In Your Browser Using Javascript

A simple way to record your video and audio on browser and download it using Javascript
Huy Ngo
Apr 06, 2021

Videos have become very popular in our daily activities on the internet. Do you wonder how difficult a website needs to do to support video? It was tough for companies to bring Media technologies to their tech product since it required knowledge from both Media and Software development. But things had changed in the past few years; when WebRTC was introduced as an open standard for browsers and mobile devices, software developers can easily use its APIs without the need for advanced Media knowledge.

This post will introduce a simple example of recording Video and Audio via your browsers, then play it back or download it to your local drive. The code will support most of the major browsers (Chrome, Firefox, Opera, Edge) except Safari.

I’ve put the code to an example using ReactJS at my github repos, but you can freely integrate the snippets on any Javascript Framework as your favorite.

The main work flow for the app is: initialize media stream > record media stream to blobs > combine recorded blobs into single video blob > play or download video > clean up.

Initialize Media Stream

The first part is to initialize a MediaStream. A stream can contain different tracks. In this sample, I request a stream contains video and audio from user’s device.

The initMediaStream function will request video and audio from user devices follow our spec in the constraints object. The browser may ask user to gain permission before returning a stream. If user doesn’t allow it, the code will throw an exception. I will handle exceptions at the caller and don’t need to worry about it here.

const initMediaStream = async () => {
  const constraints = {
    audio: {
      echoCancellation: { exact: true },
    },
    video: {
      with: 1280,
      height: 720,
    },
  };
  const stream = await navigator.mediaDevices.getUserMedia(
    constraints,
  );
  return stream;
};

Record MediaStream to Blobs

To record a MediaStream, we will need to use a MediaRecorder. It supports different video codecs. Currently, the latest one is vp9 for video and opus for audio. The function detectMimeType below is to detect which type of codec we’re going to use. We don’t have to specify mimeType, but setting the best one can help us have the best quality and size. As you might have noticed in the code, the output video type will be webm.

const detectMimeType = () => {
  const mimeTypes = [
    'video/webm;codecs=vp9,opus',
    'video/webm;codecs=vp8,opus',
    'video/webm',
  ];

  for (let mimeType of mimeTypes) {
    if (MediaRecorder.isTypeSupported(mimeType)) {
      return mimeType;
    }
  }

  return '';
};

Now it’s time to create a MediaRecorder to record our video. In the function beginRecord below, we will:

  • Initialize a stream (by calling initMediaStream as described in the previous section)
  • Create a MediaRecorder object, using the stream as input, and provide mimeType as detected by detectMimeType function.
  • Support two callbacks, onStreamReady and onFinished, so the caller can trigger some actions for those events, such as display video on screen during recording or capture recorded data into memory.
  • Return MediaRecorder object.

With this implementation, for every data received from the stream, the Media Recorder will put it into an array of Blob. The reason we need an array here is that the stream could be interrupted for many reasons. Every time it becomes available again, MediaRecorder will record a new Blob.

export const beginRecord = async (onStreamReady, onFinished) => {
  const stream = await initMediaStream();
  onStreamReady(stream);
  const options = { mimeType: detectMimeType() };
  const recordedBlobs = [];

  const mediaRecorder = new MediaRecorder(stream, options);
  mediaRecorder.ondataavailable = (event) => {
    if (event.data && event.data.size > 0) {
      recordedBlobs.push(event.data);
    }
  };
  mediaRecorder.onstop = () => {
    onFinished(recordedBlobs);
    stopMediaStream(stream);
  };

  mediaRecorder.start();

  return mediaRecorder;
};

Combine Blobs into a Single Video

Below is a simple snippet for you to combine an array of Blobs into a single Blob. The output object is ready to download or playback.

const combineBlobs = (recordedBlobs) => {
  return new Blob(recordedBlobs, { type: 'video/webm' });
};

Play And Download Video

To play or download a Video Blob, we need to create an URL for it.

const createBlobURL = (blob) => {
  const url = window.URL.createObjectURL(blob);
  return url;
};

To play the recorded blobs, we need to:

  • Combine those blobs into a single blog (combineBlobs)
  • Create an URL for it (createBlobURL)
  • Set the URL as the source of a video element on your website
  • Play the video element
export const playRecordedBlobs = (videoElement, recordedBlobs) => {
  const blob = combineBlobs(recordedBlobs);
  const url = createBlobURL(blob);

  stopPlaying(videoElement);

  videoElement.controls = true;
  videoElement.src = url;
  videoElement.play();
};

I use file-saver, a third-party library, to download the recorded Blob as in the function download below. You can also use plain Javascript code instead.

import FileSaver from 'file-saver';

export const download = (
  recordedBlobs,
  fileName = 'RecordedVideo.webm',
) => {
  const blob = combineBlobs(recordedBlobs);
  return FileSaver.saveAs(blob, fileName);
};

If you want to play stream directly in real-time, put it as a srcObject of a video element.

export const playStream = (videoElement, stream) => {
  stopPlaying(videoElement);

  videoElement.srcObject = stream;
  videoElement.play();
};

Clean Up

The clean-up steps are not mandatory but are recommended to do so, especially to stopMediaStream. Otherwise, your website will keep accessing video and audio on user’s device.

const stopMediaStream = async (stream) => {
  stream.getTracks().forEach((track) => track.stop());
};
export const stopPlaying = (videoElement) => {
  videoElement.pause();
  videoElement.src = null;
  videoElement.srcObject = null;
};

Putting Things Together

As described earlier in the post, I’ve put things together in a ReactJS app. The app has three buttons: Record, Play, and Download. These buttons have handlers, as described in the snippet below. You can customize it on your own to best fit with your JS framework.

Handler for Record button:

const btnRecord_onClick = async () => {
  try {
    if (!recorder) {
      const mediaRecorder = await beginRecord(
        (stream) =>
          playStream(recordingVideoEl.current, stream),
        (recordedBlobs) => setData(recordedBlobs),
      );
      setRecorder(mediaRecorder);
    } else {
      recorder.stop();
      stopPlaying(recordingVideoEl.current);

      setRecorder(undefined);
      setRecorded(true);
    }
  } catch (err) {
    console.error(err);
  }
}

Handler for Play button:

const btnPlay_onClick = () => {
  try {
    if (!playing) {
      setPlaying(true);
      playRecordedBlobs(playingVideoEl.current, data);
    } else {
      stopPlaying(playingVideoEl.current);
      setPlaying(false);
    }
  } catch (err) {
    console.error(err);
  }
}

Handler for Download button:

const btnDownload_onClick = () => {
  try {
    download(data);
  } catch (err) {
    console.error(err);
  }
}

Conclusion

In this post, I’ve given a small example of recording and downloading a Video using plain Javascript API. In my opinion, it’s pretty straightforward using built-in functions without the need for any other third-party API. While the script doesn’t support Safari yet, it does support all other major browsers and already covers a big partition of users.