How do I record audio using the MediaStream API
The original link: www.sitepoint.com/mediastream…
The Media Capture and Streams API allows you to record audio from a user’s device and then retrieve the audio track. You can play the recorded audio directly or upload it to the server.
In this tutorial, we will create a website that uses the Media Streams API to allow users to record audio and upload it to the server for saving. Users can also view and play uploaded audio.
The source code is on Github.
Setting up a server
First we create a server using Node.js and Express. So, the first step is to download Node.js, if you don’t have it on your computer.
Create a directory:
Create a directory to hold the project, and then go to the directory
mkdir recording-tutorial
cd recording-tutorial
Copy the code
Initialize the project
Initialize the project with NPM:
npm init -y
Copy the code
The -y argument will create a package.json file with the default value
Install dependencies
Next, we rely on Express and Nodemon, whose role is to restart the server when the file is modified.
npm i express nodemon
Copy the code
Creating an Express Server
We’ll start by creating a simple server. Create the index.js file in the project root directory and enter the following
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}`);
});
Copy the code
The above code creates a server that runs on port 3000 (if port 3000 is not occupied) and uses the directory Public/Assets as a static resource server, where we’ll put JavaScript,CSS, images, etc.
Add a script
Finally, add a command called start in the scripts section of the package.json file:
"scripts": {
"start": "nodemon index.js"
},
Copy the code
Start the Web server Start the web server
Let’s test this by starting the server with the following command:
npm start
Copy the code
The server will start on port 3000. You can access it by typing localhost:3000 in the browser address bar, but you will receive the message “Cannot GET/” because we haven’t defined any routes yet.
Creating a Recording page
Next, we’ll create a home page that users can record and preview.
Create a public directory and create an index.html file in the public directory by typing the following:
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta >
<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>
Copy the code
The page will be laid out using Bootstrap 5. But for now, there is only one button on the page for users to record.
Notice that we’re using an image of the microphone. You can download images from Iconscout or you can use the GitHub Repository image above.
Download the image and name it “microphone. PNG” and place it under the public/ Assets /images directory.
Add the style
We put style in the index. The CSS, so to create a public/assets/CSS/index. The CSS file and type the following contents:
.record-button {
height: 8em;
width: 8em;
border-color: #f3f3f3 ! important;
}
.record-button:hover {
box-shadow: 0 .5rem 1rem rgba(0.0.0.15)! important;
}
Copy the code
Create routing
Finally, we add a route to index.js. Add the following code before app.listen:
app.get('/'.(req, res) = > {
res.sendFile(path.join(__dirname, 'public/index.html'));
});
Copy the code
If the server is not started, start the service with NPM start and then access localhost:3000 in your browser. The page is as follows:
Now the button doesn’t do anything, we need to bind a click event to record.
Create a public/assets/js/record. Js file add 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
Copy the code
Let’s initialize some variables that we’ll need later. Then create a record function that listens for events triggered by clicking a recordButton.
function record() {
//TODO start recording
}
recordButton.addEventListener('click', record);
Copy the code
Recording media
In order to start recording, we need to use mediaDevices. GetUserMedia () method.
This method allows us to capture and record audio and video streams as long as the user has given corresponding permissions on the site. GetUserMedia allows us to access the local input device.
GetUserMedia takes as a parameter a MediaStreamConstraints object that contains a set of constraints specifying the type of media to record. These constraints can be audio or video with Boolean values, such as {audio:true,video:true}.
If a value is false, the corresponding device is not accessed to record the corresponding media stream.
GetUserMedia returns a Promise. If the user agrees to record on the web, the Promise will resolve and accept a MediaStream object that holds the stream of audio and video we captured.
Capturing a media stream
To record a MediaStream using the MediaStream API, we need to use the MediaRecorder interface. We need to create a new interface object that accepts the MediaStream object in the constructor and allows us to easily control recording through its methods.
Inside the Record function, add the following code:
// Check whether the 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();
}
Copy the code
Browser support
We first check the navigator. MediaDevices and navigator. MediaDevices. Whether getUserMedia definition, since some browsers such as Internet Explorer, Chrome on Android does not support these objects.
In addition, using getUserMedia must be a secure site, which means either using HTTPS,file:// loaded pages, or from localhost. Otherwise, mediaDevices and getUserMedia will be undefined.
Start recording
If the condition is false (mediaDevices and getUserMedia are both supported), we first change the image of the record button to stop.png. You can download images from Iconscout or the GitHub Repository and place them in the public/ Assets /images directory.
Then, we check if mediaRecorder is null, which is the variable we defined at the beginning of the file.
If null, no recording is in progress. We then use getUserMedia to get an instance of MediaStream to start recording.
Pass {audio:true} as an argument, since we only need to record the audio.
The browser prompts the user to allow the microphone, and if the user does, the promise returned by getUserMedia will be fullfilled, and the code will execute.
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start();
mediaRecorder.ondataavailable = mediaRecorderDataAvailable;
mediaRecorder.onstop = mediaRecorderStop;
Copy the code
Above we create the MediaRecorder instance and assign the value to the MediaRecorder variable created earlier.
We pass the data received from getUserMedia to the constructor, and we use mediaRecorder.start() to start recording.
Finally, we bind event handlers for dataavailable and Stop, which we will create later.
We also added catch handling logic to prevent situations where the user is not allowed to use the microphone or other exceptions thrown.
To stop recording
The following code is executed when mediaRecorder is not null. If null, it means that one is recording and the user is ending it. So, we use the mediaRecorder.stop() method to end the recording.
} else {
//stop recording
mediaRecorder.stop();
}
Copy the code
Process media recording events
So far, our code has enabled the user to hit record and pause recording. The next. We’ll add event handling for dataavailable and Stop.
On data available
Dataavailable is triggered when recording is complete and when mediaRecorder.start() is called to start recording when the timeslice parameter is passed, which indicates how often the event is triggered. Passing timeslice allows the recording to be sharded and partitioned for data.
Create mediaRecorderDataAvailable function, it will handle dataavailable events, just will receive BlobEvent tracks of Blob parameters added to at the beginning of the file we define chunks in the array.
function mediaRecorderDataAvailable(e) {
chunks.push(e.data);
}
Copy the code
The chunk will be an array of tracks for user recordings.
On stop
Before creating a mediaRecorderStop that handles stop events, let’s first add the HTML element container that will hold recorded audio and the buttons Save and Discard.
Add the following to public/index.html, before the final tag.
<div class="recorded-audio-container mt-5 d-none flex-column justify-content-center align-items-center"
id="recordedAudioContainer">
<div class="actions mt-3">
<button class="btn btn-success rounded-pill" id="saveButton">Save</button>
<button class="btn btn-danger rounded-pill" id="discardButton">Discard</button>
</div>
</div>
Copy the code
Then, in the public/assets/js/record. The beginning of the js, add a variable, it will be # recordedAudioContainer elements of a node instance.
const recordedAudioContainer = document.getElementById('recordedAudioContainer');
Copy the code
Now we can implement mediaRecorderStop. This function first deletes previously recorded, unsaved audio elements, then creates a new audio media element, sets SRC to the Blob of the recorded stream, and displays the container.
function mediaRecorderStop () {
//check if there are any previous recordings and remove them
if (recordedAudioContainer.firstElementChild.tagName === 'AUDIO') {
recordedAudioContainer.firstElementChild.remove();
}
//create a new audio element that will hold the recorded audio
const audioElm = document.createElement('audio');
audioElm.setAttribute('controls'.' '); //add controls
//create the Blob from the chunks
audioBlob = new Blob(chunks, { type: 'audio/mp3' });
const audioURL = window.URL.createObjectURL(audioBlob);
audioElm.src = audioURL;
//show audio
recordedAudioContainer.insertBefore(audioElm, recordedAudioContainer.firstElementChild);
recordedAudioContainer.classList.add('d-flex');
recordedAudioContainer.classList.remove('d-none');
//reset to default
mediaRecorder = null;
chunks = [];
}
Copy the code
Finally, we need to reset the mediaRecorder and Chunks to their initial values to process the next recording. With this code, our site should be able to record audio, allowing the user to play the recorded audio when they stop recording.
The last thing we need to do is introduce record.js in index.html. Add “script” to the end of “body “.
<script src="/js/record.js"></script>
Copy the code
The test record
Now let’s test it. Go to Localhost :3000 in your browser and click the Record button. You will be asked to allow the site to use a microphone.
Make sure you load the site from a localhost or HTTPS server, even if you’re using a supported browser. Otherwise,MediaDevices
andgetUserMedia
Will not be available.
Click Permit. The microphone image will then change to a stopped image. At the same time, you will see a recording icon in the browser address bar. This indicates that the microphone is currently accessed by the website.
Try recording for a few seconds. Then click the stop button. The image of the button changes back to the image of the microphone, and two buttons –Save and Discard — are displayed below the audio player.
Next, we will implement click events for the Save and Discard buttons. The Save button should upload the audio to the server, while the Discard button should delete it.
Discard Click event processing
We’ll start by implementing an event handler for the Discard button. Clicking this button should first display a prompt to the user asking them to confirm whether they want to abandon the recording. If the user confirms, it will remove the audio player and hide the button.
Add the variable that holds the Discard button to the beginning of the following code
const discardAudioButton = document.getElementById('discardButton');
Copy the code
Add the following code to the end of the file:
function discardRecording () {
//show the user the prompt to confirm they want to discard
if (confirm('Are you sure you want to discard the recording? ')) {
//discard audio just recordedresetRecording(); }}function resetRecording () {
if (recordedAudioContainer.firstElementChild.tagName === 'AUDIO') {
//remove the audio
recordedAudioContainer.firstElementChild.remove();
//hide recordedAudioContainer
recordedAudioContainer.classList.add('d-none');
recordedAudioContainer.classList.remove('d-flex');
}
//reset audioBlob for the next recording
audioBlob = null;
}
//add the event listener to the button
discardAudioButton.addEventListener('click', discardRecording);
Copy the code
You can now try recording something and hit the Discard button. The audio player is removed and the buttons are hidden.
Uploading to the server
Save event handling
We will now implement the click handler for the Save button. When the user clicks the Save button, the handler uses the Fetch API to upload the audioBlob to the server.
If you are not familiar with the Fetch API, you can learn more about it in our “Introduction to the Fetch API” tutorial.
We create a uploads directory in the project root directory.
mkdir uploads
Copy the code
Then, at the beginning of record.js, add a variable to hold the “Save” button element.
const saveAudioButton = document.getElementById('saveButton');
Copy the code
Then, at the end of the file, add the following code:
function saveRecording () {
//the form data that will hold the Blob to upload
const formData = new FormData();
//add the Blob to formData
formData.append('audio', audioBlob, 'recording.mp3');
//send the request to the endpoint
fetch('/record', {
method: 'POST'.body: formData
})
.then((response) = > response.json())
.then(() = > {
alert("Your recording is saved");
//reset for next recording
resetRecording();
//TODO fetch recordings
})
.catch((err) = > {
console.error(err);
alert("An error occurred, please try again later");
//reset for next recordingresetRecording(); })}//add the event handler to the click event
saveAudioButton.addEventListener('click', saveRecording);
Copy the code
Note that once a recording has been uploaded, we use resetRecording to reset the audio for the next recording. Later, we’ll take all the recordings and show them to the user.
Create API
We now need to implement the corresponding interface to upload the audio to the Uploads directory.
To handle file uploads in Express, we will use the library Multer. It provides a middleware that handles file uploads.
We use NPM to install it.
npm i multer
Copy the code
Next, in index.js, add the following code at the beginning of the file
const fs = require('fs');
const multer = require('multer');
const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null.'uploads/');
},
filename(req, file, cb) {
const fileNameArr = file.originalname.split('. ');
cb(null.`The ${Date.now()}.${fileNameArr[fileNameArr.length - 1]}`); }});const upload = multer({ storage });
Copy the code
We declare storage with multer.diskStorage and configure it to store files under uploads, which we will save according to the current timestamp and extension.
Then, we declare an upload, which will be the middleware for uploading files.
Next, we want to make the files under the Uploads directory publicly accessible. Therefore, add the following to app.listen.
app.use(express.static('uploads'));
Copy the code
Finally, we’ll create the upload processing logic, all we need to do is use the Upload middleware to upload the audio and return a JSON response.
app.post('/record', upload.single('audio'), (req, res) = > res.json({ success: true }));
Copy the code
Upload Middleware will handle file uploads. We just need to pass the field name of the file we want to upload to upload.single.
Note that in general, you need to perform validation on Files and make sure that you are uploading the correct, expected file type. But for the sake of simplicity, we’ve omitted this point in this tutorial.
Test the upload
Let’s test that out. Go to localhost:3000 again in your browser, record something, and hit the Save button.
The request will be sent to the back end, the file will be uploaded, and a notification will be displayed to the user informing them that the recording has been saved.
You can check to see if the audio was uploaded successfully by checking the Uploads directory in your project root directory. You should find an MP3 audio file there.
Display all recordings
Create the interface
The last thing we need to do is show the user all the recordings so they can play them back.
First, we need to create an interface to get all the files. Add the following to app.listen in index.js:
app.get('/recordings'.(req, res) = > {
let files = fs.readdirSync(path.join(__dirname, 'uploads'));
files = files.filter((file) = > {
// check that the files are audio files
const fileNameArr = file.split('. ');
return fileNameArr[fileNameArr.length - 1= = ='mp3';
}).map((file) = > ` /${file}`);
return res.json({ success: true, files });
});
Copy the code
All we do is read the files in the Uploads directory, filter them, just get the MP3 files, and prefix each file name with a slash. Finally, we return a JSON object containing a list of files.
Adding a recording container
Next, we’ll add an HTML element that will be the container for the recording we want to present. At the end of the body, add the following before the record.js script.
<h2 class="mt-3">Saved Recordings</h2>
<div class="recordings row" id="recordings">
</div>
Copy the code
Obtain the uploaded file through the API
Also add a variable at the beginning of record.js to store the #recordings element.
const recordingsContainer = document.getElementById('recordings');
Copy the code
We’ll then add a fetchRecordings function that calls the interface we created earlier, and then render the data into an audio player via the createRecordingElement function.
We’ll also add a playRecording event listener that plays the click events of the audio button.
Add the following at the end of record.js.
function fetchRecordings () {
fetch('/recordings')
.then((response) = > response.json())
.then((response) = > {
if (response.success && response.files) {
//remove all previous recordings shown
recordingsContainer.innerHTML = ' ';
response.files.forEach((file) = > {
//create the recording element
const recordingElement = createRecordingElement(file);
//add it the the recordings container
recordingsContainer.appendChild(recordingElement);
})
}
})
.catch((err) = > console.error(err));
}
//create the recording element
function createRecordingElement (file) {
//container element
const recordingElement = document.createElement('div');
recordingElement.classList.add('col-lg-2'.'col'.'recording'.'mt-3');
//audio element
const audio = document.createElement('audio');
audio.src = file;
audio.onended = (e) = > {
//when the audio ends, change the image inside the button to play again
e.target.nextElementSibling.firstElementChild.src = 'images/play.png';
};
recordingElement.appendChild(audio);
//button element
const playButton = document.createElement('button');
playButton.classList.add('play-button'.'btn'.'border'.'shadow-sm'.'text-center'.'d-block'.'mx-auto');
//image element inside button
const playImage = document.createElement('img');
playImage.src = '/images/play.png';
playImage.classList.add('img-fluid');
playButton.appendChild(playImage);
//add event listener to the button to play the recording
playButton.addEventListener('click', playRecording);
recordingElement.appendChild(playButton);
//return the container element
return recordingElement;
}
function playRecording (e) {
let button = e.target;
if (button.tagName === 'IMG') {
//get parent button
button = button.parentElement;
}
//get audio sibling
const audio = button.previousElementSibling;
if (audio && audio.tagName === 'AUDIO') {
if (audio.paused) {
//if audio is paused, play it
audio.play();
//change the image inside the button to pause
button.firstElementChild.src = 'images/pause.png';
} else {
//if audio is playing, pause it
audio.pause();
//change the image inside the button to play
button.firstElementChild.src = 'images/play.png'; }}}Copy the code
Note that in playRecording, we use Audio. Paused to check if the audio is playing, and if the audio is not playing, Audio. Paused returns true.
We also used play and pause ICONS, which are displayed in each recording. You can get these ICONS from Iconscout or GitHub repositories.
When the page loads and a new recording is uploaded, we’ll call fetchRecordings.
Therefore, this function is called at the end of Record.js and in saveRecording’s Promise handler to replace the TODO annotation.
.then(() = > {
alert("Your recording is saved");
//reset for next recording
resetRecording();
//fetch recordings
fetchRecordings();
})
Copy the code
Add the style
The last thing we need to do is add some style to the element we are creating. In the public/assets/CSS/index. The CSS to add the following content.
.play-button:hover {
box-shadow: 0 .5rem 1rem rgba(0.0.0.15.)! important; } .play-button {height: 8em;
width: 8em;
background-color: #5084d2;
}
Copy the code
Test all features
Everything is ready. Open the localhost:3000 page in your browser, and if you uploaded any recordings before, you can see them now. You can also try uploading new recordings and see the list updated.
Users can now record their sounds and save or discard them. Users can also view all uploaded recordings and play them back.
conclusion
The MediaStream API allows us to add media capabilities to our users, such as recording audio. In addition, the MediaStream Web API allows us to record videos, screen captures, and more. Follow the information in this tutorial, along with tutorials from MDN and SitePoint, to add other media features to your site.