How to Record Audio Using the MediaStream API

How to Record Audio Using the MediaStream API

The Media Capture and Streams API (aka MediaStream API) allows you to record audio from a user’s microphone, then get the recorded audio or media elements as tracks. You can then either play these tracks straight after recording them, or upload the media to your server.

In this tutorial, we’ll create a website that will use the Media Streams API to allow the user to record something, then upload the recorded audio to the server to be saved. The user will also be able to see and play all the uploaded recordings.

You can find the full code for this tutorial in this GitHub Repository.

Setting Up The Server

We’ll first start by creating a Node.js and Express server. So firstly make sure to download and install Node.js if you don’t have it on your machine.

Create a directory

Create a new directory that will hold the project, and change to that directory:

mkdir recording-tutorial cd recording-tutorial 

Initialize the project

Then, initialize the project with npm:

npm init -y 

The option -y creates package.json with the default values.

Install the dependencies

Next, we’ll install Express for the server we’re creating and nodemon to restart the server when there are any changes:

npm i express nodemon 

Create the Express server

We can start now by creating a simple server. Create index.js in the root of the project with the following content:

const path = require('path'); const express = require('express'); const app = express(); const port = process.env.PORT || 3000; app.use(express.static('public/assets')); app.listen(port, () => { console.log(`App listening at http://localhost:${port}`); }); 

This creates a server that will run on port 3000 unless a port is set in the environment, and it exposes a directory public/assets — which we’ll create soon — that will hold JavaScript and CSS files and images.

Add a script

Finally, add a start script under scripts in package.json:

"scripts": { "start": "nodemon index.js" }, 

Start the web server

Let’s test our server. Run the following to start the server:

npm start 

And the server should start at port 3000. You can try accessing it on localhost:3000, but you’ll see a message saying “Cannot GET /” since we don’t have any routes defined yet.

Creating the Recording Page

Next, we’ll create the page that will be the main page of the website. The user will use this page to record and view and play recordings.

Create the public directory, and inside that create an index.html file with the following content:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Record</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We" crossorigin="anonymous"> <link href="/css/index.css" rel="stylesheet" /> </head> <body class="pt-5"> <div class="container"> <h1 class="text-center">Record Your Voice</h1> <div class="record-button-container text-center mt-5"> <button class="bg-transparent border btn record-button rounded-circle shadow-sm text-center" id="recordButton"> <img src="/images/microphone.png" alt="Record" class="img-fluid" /> </button> </div> </div> </body> </html> 

This page uses Bootstrap 5 for styling. For now, the page just shows a button that the user can use for recording.

Note that we’re using an image for the microphone. You can download the icon on Iconscout, or you can use the modified version in the GitHub repository.

Download the icon and place it inside public/assets/images with the name microphone.png.

Adding styles

We’re also linking the stylesheet index.css, so create an public/assets/css/index.css file with the following content:

.record-button { height: 8em; width: 8em; border-color: #f3f3f3 !important; } .record-button:hover { box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important; } 

Creating the route

Finally, we just need to add the new route in index.js. Add the following before app.listen:

app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public/index.html')); }); 

If the server isn’t already running, start the server with npm start. Then go to localhost:3000 in your browser. You’ll see a record button.

Record Page

The button, for now, does nothing. We’ll need to bind a click event that will trigger the recording.

Create a public/assets/js/record.js file with the following content:

//initialize elements we'll use const recordButton = document.getElementById('recordButton'); const recordButtonImage = recordButton.firstElementChild; let chunks = []; //will be used later to record audio let mediaRecorder = null; //will be used later to record audio let audioBlob = null; //the blob that will hold the recorded audio 

We’re initializing the variables we’ll use later. Then create a record function, which will be the event listener to the click event on recordButton:

function record() { //TODO start recording } recordButton.addEventListener('click', record); 

We’re also attaching this function as an event listener to the record button.

Media recording

In order to start recording, we’ll need to use the mediaDevices.getUserMedia() method.

This method allows us to obtain a stream and record the audio and/or video of the user only once the user provides permission for the website to do that. The getUserMedia method allows us to access local input devices.

getUserMedia accepts as a parameter an object of MediaStreamConstraints, which comprises a set of constraints that specify what are the expected media types in the stream we’ll obtain from getUserMedia. These constraints can be either audio and video with Boolean values.

If the value is false, it means we’re not interested in accessing this device or recording this media.

getUserMedia returns a promise. If the user allows the website to record, the promise’s fulfillment handler receives a MediaStream object which we can use to media capture video or audio streams of the user.

Media capture and streams

To use MediaStream API objects to capture media tracks, we need to use the MediaRecorder interface. We’ll need to create a new object of the interface which accepts the MediaStream object in the constructor and allows us to control the recording easily through its methods.

Inside the record function, add the following:

//check if browser supports getUserMedia if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { alert('Your browser does not support recording!'); return; } // browser supports getUserMedia // change image in button recordButtonImage.src = `/images/${mediaRecorder && mediaRecorder.state === 'recording' ? 'microphone' : 'stop'}.png`; if (!mediaRecorder) { // start recording navigator.mediaDevices.getUserMedia({ audio: true, }) .then((stream) => { mediaRecorder = new MediaRecorder(stream); mediaRecorder.start(); mediaRecorder.ondataavailable = mediaRecorderDataAvailable; mediaRecorder.onstop = mediaRecorderStop; }) .catch((err) => { alert(`The following error occurred: ${err}`); // change image in button recordButtonImage.src = '/images/microphone.png'; }); } else { // stop recording mediaRecorder.stop(); } 

Browser support

We’re first checking whether navigator.mediaDevices and navigator.mediaDevices.getUserMedia are defined, since there are browsers like Internet Explorer, Chrome on Android, or others that don’t support it.

Furthermore, using getUserMedia requires secure websites, which means either a page loaded using HTTPS, file://, or from localhost. So, if the page isn’t loaded securely, mediaDevices and getUserMedia will be undefined.

Start recording

If the condition is false (that is, both mediaDevices and getUserMedia are supported), we’re first changing the image of the recording button to stop.png, which you can download from Iconscout or the GitHub repository and place it in public/assets/images.

Then, we’re checking if mediaRecorder — which we defined at the beginning of the file — is or isn’t null.

If it’s null, it means there’s no ongoing recording. So, we get a MediaStream instance to start recording using getUserMedia.

We’re passing it an object with only the key audio and value true, as we’re just recording the audio.

This is where the browser prompts the user to allow the website to access the microphone. If the user allows it, the code inside the fulfillment handler will be executed:

mediaRecorder = new MediaRecorder(stream); mediaRecorder.start(); mediaRecorder.ondataavailable = mediaRecorderDataAvailable; mediaRecorder.onstop = mediaRecorderStop; 

Here we’re creating a new MediaRecorder, assigning it to mediaRecorder which we defined at the beginning of the file.

We’re passing the constructor the stream received from getUserMedia. Then, we’re starting the recording using mediaRecorder.start().

Finally, we’re binding event handlers (which we’ll create soon) to two events, dataavailable and stop.

We’ve also added a catch handler in case the user doesn’t allow the website to access the microphone or any other exception that might be thrown.

Stop recording

This all occurs if the mediaRecorder is not null. If it’s null, it means that there’s an ongoing recording and the user is ending it. So, we’re using the mediaRecorder.stop() method to stop the recording:

} else { //stop recording mediaRecorder.stop(); } 

Continue reading How to Record Audio Using the MediaStream API on SitePoint.