Record Videos with MediaStream Recording API

Christian Nwamba

The MediaStream Recording API makes it possible to capture data generated by a MediaStream or HTMLMediaElement object for analysis, processing, or saving to disk. The API has just one interface called MediaRecorder. MediaRecorder gets the media data from a MediaStream and delivers it for processing. In this post, we’ll also learn the process of recording a stream. For better context, Nimbus, a google chrome extension makes use of the MediaStream Recording API to record video with audio and save the recording file to disk. In this post, we will be building an application that allows us record videos with JavaScript and MediaStream Recording API.

Pre-requisites

To flow along with this tutorial, you’ll need to have the following

  • JavaScript and React Knowledge
  • A code editor (preferably VS Code)
  • Live Server extension on your code editor

The complete code and demo is on Codesandbox.

Building the Media Recording app with JavaScript

Let’s start by creating a folder, i’ll call mine Video Recoding App. Open the folder on your code editor and create an index.html, main.js and styles.css files. Add these lines of code to the index.html:

1//index.html
2<!DOCTYPE html>
3<html lang="en">
4 <head>
5 <meta charset="UTF-8">
6 <meta http-equiv="X-UA-Compatible" content="IE=edge">
7 <meta name="viewport" content="width=device-width, initial-scale=1.0">
8 <title> Video Recording app</title>
9 <link rel="stylesheet" href="styles.css">
10 </head>
11
12 <body>
13 <div class="container">
14 <div class="preview">
15 <h2>Preview</h2>
16 <video id="preview" width="160" height="120" autoplay muted></video>
17 <button id="startButton" class="button">
18 Start
19 </button>
20 </div>
21 <div class="recorded">
22 <h2>Recording</h2>
23 <video id="recording" width="160" height="120" controls></video>
24 <button id="stopButton" class="button">
25 Stop
26 </button>
27 </div>
28 <button id="downloadButton" class="button">
29 Download
30 </button>
31 </div>
32 <div id="log"></div>
33
34 <script src="main.js"></script>
35 </body>
36</html>

Here, we have a container with two divs. The first div has the video tag with autoplay and muted set to true. There’s also a start button with an id of startButton. When you click on the button, it’ll request camera and audio access, then start recording with the video element. We’ll write the logic for this soon. The second div tag also has a video element but this video will be the recorded video. The controls attribute enable the video with all the media controls including play, pause, volume, e.t.c. We will use the stop button to stop recording the video. We also have a div with id of log, this will log information in the page.

Let’s go ahead and write the logic. Add these lines of code to your main.js

1//main.js
2const preview = document.getElementById("preview");
3const recording = document.getElementById("recording");
4const startButton = document.getElementById("startButton");
5const stopButton = document.getElementById("stopButton");
6const downloadButton = document.getElementById("downloadButton");
7const logElement = document.getElementById("log");
8const recordingTimeMS = 10000;

Here, we define some global variables. Most worthy to take note of is the recordingTimeMS variable, this is the recording time we set for our videos, so when it gets to 10seconds, the video automatically stops recording. Add these lines of code:

1function log(msg) {
2 logElement.innerHTML += msg + "\n";
3}
4
5function wait(delayInMS) {
6 return new Promise(resolve => setTimeout(resolve, delayInMS));
7}
8
9function formatBytes(bytes, decimals = 2) {
10 if (bytes === 0) return '0 Bytes';
11 const k = 1024;
12 const dm = decimals < 0 ? 0 : decimals;
13 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
14 const i = Math.floor(Math.log(bytes) / Math.log(k));
15 return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
16}

Here, we create 3 utility functions. First the log function, will log information to the webpage. The wait function will return a Promise which resolves once the specified number of milliseconds have elapsed. Finally, the formatBytes will convert the recorded size in bytes to kilobytes, megabytes, e.t.c.

Let’s move on to write the function that starts recording a video. Add these lines of code:

1//main.js
2
3function startRecording(stream, lengthInMS) {
4 let recorder = new MediaRecorder(stream);
5 let data = [];
6 recorder.ondataavailable = event => data.push(event.data);
7 recorder.start();
8 log(recorder.state + " for " + (lengthInMS / 1000) + " seconds...");
9 let stopped = new Promise((resolve, reject) => {
10 recorder.onstop = resolve;
11 recorder.onerror = event => reject(event.name);
12 });
13
14 let recorded = wait(lengthInMS).then(
15 () => recorder.state == "recording" && recorder.stop()
16 );
17
18 return Promise.all([
19 stopped,
20 recorded
21 ])
22 .then(() => data);
23}

Here, the startRecording() function takes two input parameters: a stream(which represents the MediaStream) to record from and the length in milliseconds of the recording to make. Next, we instantiate the MediaRecorder (remember this is the interface to MediaStream API) that will handle recording the input stream. Next, we assign an empty array to the data variable. Next, we set the ondataavailable event which is fired when the MediaRecorder delivers media data to our application for its use. It returns a Blob object that contains the data. recorder.start() starts the recording process. Next, we create a new Promise called stopped. The Promise resolves when the onstop event is triggered, and it’s rejected when the onerror event is called. Next, we create another Promise called recorded. It resolves when the assigned number of milliseconds elapses. Finally, we create a Promise that is fulfilled when the two Promises stopped and recorded resolves. On resolution, we output the data. Below, the startRecording() function, add these lines of code:

1//main.js
2function stop(stream) {
3 stream.getTracks().forEach(track => track.stop());
4}

This function stops the input media from recording. If the camera is on, it turns it off too.

When you click on the Start button what happens? add these lines of code to implement the functionality:

1//main.js
2
3startButton.addEventListener("click", function () {
4 navigator.mediaDevices.getUserMedia({
5 video: true,
6 audio: true
7 }).then(stream => {
8 preview.srcObject = stream;
9 downloadButton.href = stream;
10 preview.captureStream = preview.captureStream || preview.mozCaptureStream;
11 return new Promise(resolve => preview.onplaying = resolve);
12 }).then(() => startRecording(preview.captureStream(), recordingTimeMS))
13 .then(recordedChunks => {
14 let recordedBlob = new Blob(recordedChunks, { type: "video/webm" });
15 recording.src = URL.createObjectURL(recordedBlob);
16 downloadButton.href = recording.src;
17 downloadButton.download = "RecordedVideo.webm";
18 log(`Your video is ${formatBytes(recordedBlob.size)}`);
19 console.log(recordedBlob.size)
20 })
21 .catch(log);
22}, false);

Let’s go through the moving parts of this code snippet. First, we get permissions from the user to have access to audio, and video using naviagator.mediaDevices.getUserMedia. The getUserMedia returns a Promise, on resolution, we assign the input stream to preview <video> srcObject. This allows the video captured by the camera to be displayed in the <video id=``"``preview``"``> box. After that, a new Promise resolves when the preview video starts to play. When the video starts to play, we invoke the startsRecording() and pass two arguments; the preview video stream as the source media to be recorded, and recordingTimeMS as the number of milliseconds of media to record. Next, we merge the recordedChunks (which is the array of media data Blobs) with mimeType of video/webm. We then set the src attribute of the recorded video to URL.createObjectURL and pass the recordedBlob as argument. [URL.createObjectURL()](https://docs.w3cub.com/dom/url/createobjecturl) is used to create an URL that references the blob. We assign the newly created URL to the href attribute of the download button. Then, we set the download attribute of the download button to download a RecordedVideo.webm file whenever we click on the button.

Finally, let’s add the functionality for when we click on the stop button. Add these lines of code:

1//main.js
2stopButton.addEventListener("click", function () {
3 stop(preview.srcObject);
4}, false);

Go ahead to add these lines of css code in your styles.css

1//styles.css
2@import url('https://fonts.googleapis.com/css2?family=Karla:wght@300;400;500&display=swap');
3
4body {
5 width: 1200px;
6 margin: auto;
7 font-family: 'Karla', sans-serif;
8 background: #9ebded;
9}
10
11.container {
12 display: grid;
13 grid-template-columns: 1fr 1fr;
14 grid-gap: 2rem;
15 place-content: center;
16 height: 100vh;
17 background: #9ebded;
18}
19
20.preview, .recorded {
21 margin: auto;
22}
23
24video {
25 width: 100%;
26 height: 100%;
27}
28
29button, .button {
30 padding: 0.5rem 3rem;
31 background: #fff;
32 border: none;
33 font-size: 1rem;
34 cursor: pointer;
35}
36
37#startButton, h2 {
38 margin-left: 2rem
39}
40
41#downloadButton {
42 margin: auto;
43}
44
45#log {
46 color: #fff;
47 margin-bottom: 3rem;
48}

Awesome! Now start your app by running Live Server on your code editor. Navigate to your browser, you should have something like this:

https://www.dropbox.com/s/29tmoy3shhxuk0m/videoRecord.webm?dl=0

I had already given permission to audio and video so i didn’t get the prompt in this video.

Building the Media Recording app with React

Let’s go ahead and implement the recording app with React. We will be using a React library [**use-screen-recorder**](https://github.com/ishan-chhabra/use-screen-recorder) that wraps the MediaStream Recording API nicely into a Hook.

1//javascript
2
3import * as React from "react";
4import useScreenRecorder from "use-screen-recorder";
5
6export default function MediaController() {
7 const {
8 blobUrl,
9 pauseRecording,
10 resetRecording,
11 resumeRecording,
12 startRecording,
13 status,
14 stopRecording,
15 } = useScreenRecorder();
16
17 return (
18 <div>
19 <video src={blobUrl} controls autoplay />
20 <small>Status: {status}</small>
21 <button onClick={startRecording}>Start Recording</button>
22 <button onClick={stopRecording}>Stop Recording</button>
23 <button onClick={pauseRecording}>Pause Recording</button>
24 <button onClick={resumeRecording}>Resume Recording</button>
25 <button onClick={resetRecording}>Reset Recording</button>
26 </div>
27 );
28};

Check this example to see how to use this hook with React.

Conclusion

In this tutorial, we learned about the MediaStream Recording API, and we went on to build a JavaScript media recorder application using the API. We also got to see how to implement similar functionality in React using the use-screen-recorder hook. I hope you’ve learned something new from this.

Further Reading

Happy Coding!

Christian Nwamba

Developer Advocate at AWS

A software engineer and developer advocate. I love to research and talk about web technologies and how to delight customers with them.