An introduction to the MediaRecorder API
Tags: javascript, web
Published: 2019-06-03 20:40:55

---
title: An introduction to the MediaRecorder API
published: true
tags: javascript, web
canonical_url: https://www.twilio.com/blog/mediastream-recording-api
cover_image: https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/P8oWiUevnuMq5NOAO0Uve9Dq3D_Xv2XQAmpDVJKIEWfLyw.width-808.jpg
---

On the web we can [capture media streams from the user's camera, microphone](https://www.twilio.com/blog/2018/04/choosing-cameras-javascript-mediadevices-api.html) and [even desktop](https://www.twilio.com/blog/2018/05/screen-capture-in-microsoft-edge.html). We can use those media streams for [real time video chat over WebRTC](https://www.twilio.com/docs/video) and with the [MediaRecorder API](https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API) we can also record and save audio or video from our users directly in a web browser.

To explore the MediaRecorder API let's build a simple audio recorder app with just HTML, CSS and JavaScript.

## Getting started

To build this application all we need is a text editor and a [browser that supports the MediaRecorded API](https://caniuse.com/#feat=mediarecorder). At the time of writing, supported browsers include Firefox, Chrome and Opera. There is also work ongoing to bring this API to Edge and Safari.

To get started, create a folder to work in and save [this HTML file](https://github.com/philnash/web-assistant/blob/master/media-recorder/web-recorder-starter.html) and [this CSS file](https://github.com/philnash/web-assistant/blob/master/media-recorder/web-recorder-style.css) to give us something to start with. Make sure they are in the same folder and the CSS file is named `web-recorder-style.css`. Open the HTML file in your browser, you should see the following:

![The starter project for the web recorder. It has a heading, a button and a footer and it doesn&#39;t do anything yet.](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/oVQ9H3NH773crqhbdIBTdhkl0zYHy1PmnjSyn_3G5W14TF.width-500.png)

Now let's take a look at the MediaRecorder API.

## MediaRecorder API

To start with the MediaRecorder API, you need a MediaStream. You can either get one from a `<video>` or `<audio>` element or by calling `getUserMedia` to capture the user's camera and microphone. Once you have a stream you can initialise the `MediaRecorder` with it and you are ready to record.

During recording, the `MediaRecorder` object will emit `dataavailable` events with the recorded data as part of the event. We will listen for those events and collate the data chunks in an array. Once the recording is complete we'll tie the array of chunks back together in a `Blob` object. We can control the start and end of the recording by calling `start` and `stop` on the `MediaRecorder` object.

Let's see this in action.

### getUserMedia

We'll start by wiring up some of our UI and using the first button to get access to the user's microphone stream. Between the `<script>` tags at the bottom of the starter HTML you downloaded, start by registering an event to run after the content of the page has loaded and then gather the bits of UI that we will be using:

```html
<script>
  window.addEventListener('DOMContentLoaded', () => {
    const getMic = document.getElementById('mic');
    const recordButton = document.getElementById('record');
    const list = document.getElementById('recordings');

  });
</script>
```

Next, we'll check whether the browser supports the code we're writing. If it doesn't we'll display an error to the page instead.

```html
<script>
  window.addEventListener('DOMContentLoaded', () => {
    const getMic = document.getElementById('mic');
    const recordButton = document.getElementById('record');
    const list = document.getElementById('recordings');
    if ('MediaRecorder' in window) {
      // everything is good, let's go ahead
    } else {
      renderError("Sorry, your browser doesn't support the MediaRecorder API, so this demo will not work.");
    }
  });
</script>
```

For the `renderError` method we will replace the contents of the `<main>` element with the error message. Add this method after the event listener.

```javascript
    function renderError(message) {
      const main = document.querySelector('main');
      main.innerHTML = `<div class="error"><p>${message}</p></div>`;
    }
```

If we have access to the `MediaRecorder` then we now need to get access to the microphone to record from. For this we will use the [`getUserMedia` API](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia). We're not going to request access to the microphone straight away as that is a poor experience for any user. Instead, we will wait for the user to click the button to access the microphone, then ask.

```javascript
    if ('MediaRecorder' in window) {
      getMic.addEventListener('click', async () => {
        getMic.setAttribute('hidden', 'hidden');
        try {
          const stream = await navigator.mediaDevices.getUserMedia({
            audio: true,
            video: false
          });
          console.log(stream);
        } catch {
          renderError(
            'You denied access to the microphone so this demo will not work.'
          );
        }
      });
    } else {
```

Making a call to `navigator.mediaDevices.getUserMedia` returns a promise that resolves successfully if the user permits access to the media. Since we're using modern JavaScript, we can make that promise appear to be synchronous using `async/await`. We declare that the click handler is an `async` function and then when it comes to the call to `getUserMedia` we `await` the result and then carry on after.

The user might deny access to the microphone, which we'll handle by wrapping the call in a `try/catch` statement. Denial will cause the `catch` block to execute, and we'll use our `renderError` function again.

Save your file and open it in the browser. Click the _Get microphone_ button. You will get asked if you want to give access to the microphone and when you accept you'll see the resultant `MediaStream` logged to the console.

![If you open the browser console, press the "Get microphone" button and accept the permission, you will see a MediaStream object logged to the console.](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/ki7kNEUselR9FFmSiSTF7WYV46Tw_j4CCvkRt7xJ97Uf4D.width-500.png)

### Recording

Now we have access to the microphone, we can prepare our recorder. We'll store a couple of other variables that we'll need too. First the MIME type that we'll be working with, "audio/webm". This seems to be the most widely supported format that browsers will record to today. We'll also create an array called `chunks`, which we will use to store parts of the recording as it is created.

The [`MediaRecorder` is initialised](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder) with the media stream that we captured from the user's microphone and an object of options, of which we will pass the MIME type we defined earlier. Replace the `console.log` from earlier with:

```javascript
        try {
          const stream = await navigator.mediaDevices.getUserMedia({
            audio: true,
            video: false
          });
          const mimeType = 'audio/webm';
          let chunks = [];
          const recorder = new MediaRecorder(stream, { type: mimeType });
```

Now we've created our `MediaRecorder` we need to setup some event listeners for it. The recorder emits events for a number of different reasons. Many are to do with interaction with the recorder itself, so you can listen for events when it starts recording, pauses, resumes and stops. The most important event is the `dataavailable` event which is emitted periodically while the recorder is actively recording. The events contain a chunk of the recording, which we will push onto the `chunks` array we just created.

For our application we're going to listen to the `dataavailable` event collecting chunks and then when the `stop` event fires we'll gather all the chunks into a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) which we can then play with an `<audio>` element and reset the array of `chunks`.

```javascript
           const recorder = new MediaRecorder(stream, { type: mimeType });
           recorder.addEventListener('dataavailable', event => {
             if (typeof event.data === 'undefined') return;
               if (event.data.size === 0) return;
               chunks.push(event.data);
             });
           recorder.addEventListener('stop', () => {
             const recording = new Blob(chunks, {
               type: mimeType
             });
             renderRecording(recording, list);
             chunks = [];
           });
```

We'll implement the `renderRecording` method soon. We just have a little more work to do to enable a button to start and stop the recording.

We need to unhide the recording button, then when it is clicked either start the recording or stop it, depending on the state of the recorder itself. That code looks like this:

```javascript
           const recorder = new MediaRecorder(stream, { type: mimeType });
           recorder.addEventListener('dataavailable', event => {
             if (typeof event.data === 'undefined') return;
               if (event.data.size === 0) return;
               chunks.push(event.data);
             });
           recorder.addEventListener('stop', () => {
             const recording = new Blob(chunks, {
               type: mimeType
             });
             renderRecording(recording, list);
             chunks = [];
           });
```

To complete this little application we're going to render the recordings into `<audio>` elements and provide a download link so a user can save their recording to the desktop. The key here is that we can take the `Blob` we created and turn it into a URL using the `URL.createObjectURL` method. This URL can then be used as the `src` of an `<audio>` element and as the `href` of an anchor. To make the anchor download the file, we set the `download` attribute.

This function is mostly creating DOM elements and making a file name out of the time that the recording was made. Add it below your `renderError` function.

```javascript
  function renderRecording(blob, list) {
    const blobUrl = URL.createObjectURL(blob);
    const li = document.createElement('li');
    const audio = document.createElement('audio');
    const anchor = document.createElement('a');
    anchor.setAttribute('href', blobUrl);
    const now = new Date();
    anchor.setAttribute(
      'download',
      `recording-${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, '0')}-${now.getDay().toString().padStart(2, '0')}--${now.getHours().toString().padStart(2, '0')}-${now.getMinutes().toString().padStart(2, '0')}-${now.getSeconds().toString().padStart(2, '0')}.webm`
    );
    anchor.innerText = 'Download';
    audio.setAttribute('src', blobUrl);
    audio.setAttribute('controls', 'controls');
    li.appendChild(audio);
    li.appendChild(anchor);
    list.appendChild(li);
  }
```

![The completed application. This animation shows the full system, from getting the microphone to recording the audio, playing it back and downloading the recording.](https://s3.amazonaws.com/com.twilio.prod.twilio-docs/original_images/9dpCqwpYg0gxuPp8SGEy0WMYsZXkDr4_hi_R68Hrz37qNxuJzwTP2Balg71Ds4AfZEDdRLltwbIajw)

## Testing it out

Open the page in your web browser and click the _Get Microphone_ button. Accept the permissions dialog and then click to start recording. Record yourself a message and play it back from the page.

### WebM files

If you download one of your recordings, you may find you don't have a media player that's capable of playing a WebM file. [WebM is an open source format](https://en.wikipedia.org/wiki/WebM) for both audio and video, but it's mostly had support from browsers. If you have [VLC player](https://www.videolan.org/vlc/index.html) you can likely play the audio, otherwise you might want to convert it to an MP3 or WAV file using an online tool like [convertio](https://convertio.co/webm-mp3/) (or if you're feeling daring, with [ffmpeg](https://ffmpeg.org/) in your terminal).

## Your browser is now a recorder

The `MediaRecorder` API is a powerful new addition to browsers. In this post we've seen its ability to record audio, but it doesn't just stop there. Currently the application doesn't save the audio files, so a page refresh loses them. ou could save them using IndexedDB or send them to a server. You could also play around with the recording, imagine passing the audio through the Web Audio API before recording it. And if the WebM format isn't your cup of tea, you could always look into re-encoding the audio in the front end, though that's likely a job for WebAssembly (or your server…).

If you want to try out the code from this post you can check out a [live demo](https://web-recorder.glitch.me/). All the code is available in this [GitHub repo](https://github.com/philnash/web-assistant/tree/master/media-recorder) and you can [remix the project on](https://glitch.com/~web-recorder)[Glitch](https://glitch.com/~web-recorder) too.

Let me know what you think of the `MediaRecorder` API and what you could use it for. Hit up the comments below or drop me a line on Twitter at [@philnash](https://twitter.com/philnash).