---
layout: post
title: Recording Audio from the User
description: >
  Most browsers can get access to the user's microphone.
authors:
  - paulkinlan
date: 2016-08-23
updated: 2022-12-05
---

Many browsers now have the ability to access video and audio input from the
user. However, depending on the browser, it might be a full dynamic and inline
experience, or it could be delegated to another app on the user's device.

## Start simple and progressively

The easiest thing to do is simply ask the user for a pre-recorded file. Do this by creating a simple
file input element and adding an `accept` filter that indicates we can only accept audio files, and
a `capture` attribute that indicates we want to get it direct from the microphone.

```html
<input type="file" accept="audio/*" capture />
```

This method works on all platforms. On desktop, it will prompt the user to
upload a file from the file system (ignoring the `capture` attribute). In Safari
on iOS, it will open up the microphone app, allowing you to record audio and
then send it back to the web page; on Android, it will give the user the
choice of which app to use record the audio in before sending it back to the web
page.

Once the user has finished recording and they are back in the website, you
need to somehow get ahold of the file data. You can get quick access by
attaching an `onchange` event to the input element and then reading
the `files` property of the event object.

```html
<input type="file" accept="audio/*" capture id="recorder" />
<audio id="player" controls></audio>
  <script>
    const recorder = document.getElementById('recorder');
    const player = document.getElementById('player');

    recorder.addEventListener('change', function (e) {
      const file = e.target.files[0];
      const url = URL.createObjectURL(file);
      // Do something with the audio file.
      player.src = url;
    });
  </script>
</audio>
```

Once you have access to the file, you can do anything you want with it. For
example, you can:

- Attach it directly to an `<audio>` element so that you can play it
- Download it to the user's device
- Upload it to a server by attaching it to an `XMLHttpRequest`
- Pass it through the Web Audio API and apply filters on to it

Whilst using the input element method of getting access to audio data is
ubiquitous, it is the least appealing option. We really want to get access to
the microphone and provide a nice experience directly in the page.

## Access the microphone interactively

Modern browsers can have a direct line to the microphone allowing us to build
experiences that are fully integrated with the web page and the user will never
leave the browser.

### Acquire access to the microphone

We can directly access the Microphone by using an API in the WebRTC
specification called `getUserMedia()`. `getUserMedia()` will prompt the user for
access to their connected microphones and cameras.

If successful the API will return a `Stream` that will contain the data from either the camera or
the microphone, and we can then either attach it to an `<audio>` element, attach it to a WebRTC
stream, attach it to a Web Audio `AudioContext`, or save it using the `MediaRecorder` API.

To get data from the microphone, we just set `audio: true` in the constraints
object that is passed to the `getUserMedia()` API.

```html
<audio id="player" controls></audio>
<script>
  const player = document.getElementById('player');

  const handleSuccess = function (stream) {
    if (window.URL) {
      player.srcObject = stream;
    } else {
      player.src = stream;
    }
  };

  navigator.mediaDevices
    .getUserMedia({audio: true, video: false})
    .then(handleSuccess);
</script>
```

If you want to choose a particular microphone, you can first enumerate the available microphones.

```js
navigator.mediaDevices.enumerateDevices().then((devices) => {
  devices = devices.filter((d) => d.kind === 'audioinput');
});
```

You can then pass the `deviceId` that you wish to use when you call `getUserMedia`.

```js
navigator.mediaDevices.getUserMedia({
  audio: {
    deviceId: devices[0].deviceId,
  },
});
```

By itself, this isn't that useful. All we can do is take the audio data and play it back.

### Access the raw data from the microphone

To access the raw data from the microphone, we have to take the stream created by
`getUserMedia()` and then use the Web Audio API to process the data. The
Web Audio API is a simple API that takes input sources and connects those
sources to nodes which can process the audio data (adjust Gain etc.) and
ultimately to a speaker so that the user can hear it.

One of the nodes that you can connect is an `AudioWorkletNode`. This node gives
you the low-level capability for custom audio processing. The actual audio
processing happens in the `process()` callback method in the `AudioWorkletProcessor`.
Call this function to feed inputs and parameters and fetch outputs.

Check out [Enter Audio Worklet](https://developer.chrome.com/blog/audio-worklet/)
to learn more.

```html
<script>
  const handleSuccess = async function(stream) {
    const context = new AudioContext();
    const source = context.createMediaStreamSource(stream);

    await context.audioWorklet.addModule("processor.js");
    const worklet = new AudioWorkletNode(context, "worklet-processor");

    source.connect(worklet);
    worklet.connect(context.destination);
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess);
</script>
```

```js
// processor.js
class WorkletProcessor extends AudioWorkletProcessor {
  process(inputs, outputs, parameters) {
    // Do something with the data, e.g. convert it to WAV
    console.log(inputs);
    return true;
  }
}

registerProcessor("worklet-processor", WorkletProcessor);
```

The data that is held in the buffers is the raw data from the microphone and
you have a number of options with what you can do with that data:

- Upload it straight to the server
- Store it locally
- Convert it to a dedicated file format, such as WAV, and then save it to your
  servers or locally

### Save the data from the microphone

The easiest way to save the data from the microphone is to use the
`MediaRecorder` API.

The `MediaRecorder` API will take the stream created by `getUserMedia` and then
progressively save the data that is on the stream into your preferred
destination.

```html
<a id="download">Download</a>
<button id="stop">Stop</button>
<script>
  const downloadLink = document.getElementById('download');
  const stopButton = document.getElementById('stop');


  const handleSuccess = function(stream) {
    const options = {mimeType: 'audio/webm'};
    const recordedChunks = [];
    const mediaRecorder = new MediaRecorder(stream, options);

    mediaRecorder.addEventListener('dataavailable', function(e) {
      if (e.data.size > 0) recordedChunks.push(e.data);
    });

    mediaRecorder.addEventListener('stop', function() {
      downloadLink.href = URL.createObjectURL(new Blob(recordedChunks));
      downloadLink.download = 'acetest.wav';
    });

    stopButton.addEventListener('click', function() {
      mediaRecorder.stop();
    });

    mediaRecorder.start();
  };

  navigator.mediaDevices.getUserMedia({ audio: true, video: false })
      .then(handleSuccess);
</script>
```

In our case, we are saving the data directly into an array that we can later turn
into a `Blob`, which can be then used to save the data to our Web Server or directly
to the storage on the user's device.

### Ask permission to use microphone responsibly

If the user has not previously granted your site access to the microphone, then
the instant that you call `getUserMedia`, the browser will prompt the user to
grant your site permission to the microphone.

Users hate getting prompted for access to powerful devices on their machine and
they will frequently block the request, or they will ignore it if they don't
understand the context of which the prompt has been created. It is best practice
to only ask to access the microphone when first needed. Once the user has
granted access, they won't be asked again, however, if they reject access,
you can't ask the user for permission again.

{% Aside 'warning' %}
Asking for access to the microphone on page load will result in most of your users
rejecting access to the mic.
{% endAside %}

### Use the permissions API to check if you already have access

The `getUserMedia` API provides you with no knowledge of if you already have
access to the microphone. This presents you with a problem, to provide a nice UI
to get the user to grant you access to the microphone, you have to ask for
access to the microphone.

This can be solved in some browsers by using the Permission API. The
`navigator.permission` API allows you to query the state of the ability to
access specific API's without having to prompt again.

To query if you have access to the user's microphone, you can pass in
`{name: 'microphone'}` into the query method and it will return either:

- `granted` &mdash; the user has previously given you access to the microphone;
- `prompt` &mdash; the user has not given you access and will be prompted when
  you call `getUserMedia`;
- `denied` &mdash; the system or the user has explicitly blocked access to the
  microphone and you won't be able to get access to it.

And you can now quickly check to see if you need to alter your user
interface to accommodate the actions that the user needs to take.

```js
navigator.permissions.query({name: 'microphone'}).then(function (result) {
  if (result.state == 'granted') {
  } else if (result.state == 'prompt') {
  } else if (result.state == 'denied') {
  }
  result.onchange = function () {};
});
```

### Feedback {: #feedback }
