The basics
Audio processing revolves around the concept of a stream. A stream can have a number of sources, but for this article we'll be using the system microphone.
System peripherals are necessarily subject to strict security. Nobody wants to have their private conversations secretly recorded by a rogue web site. Hence, the microphone and camera can only be used if the script that uses them is hosted on the same machine, or a secure web site served over HTTPS://
Similarly, the recorded media must be saved to the hosting server, or downloaded to the local computer through the familiar download dialog box.
Accessing the microphone
Browsers provide the navigator.mediaDevices
object which Javascript can use to request access to system devices. You can check for its availability with (if (navigator.mediDevices){...}
A call to navigator.mediaDevices.getUserMedia()
returns a promise that resolves to a media stream that we can use to record Audio. It's this call that raises the prompt that the user sees asking for permission to use the microphone or camera. A constraints object controls what permissions are requested.
The use of await
allows us to run the code as if it is synchronous, and wait for the user to grant permission.
let constraints = {audio:true, video:false};
let stream = await navigator.mediaDevices.getUserMedia(constraints);
Setting up the recorder
Now that we have a stream, we can record it. Just call the MediaRecorder constructor with the stream as the first parameter, and supply any further options in an options object as the second parameter.
let mediaOptions = {
mimetype:'audio/mpeg'
};
let mRec = new MediaRecorder(stream, mediaOptions);
Recording
We've set up our MediaRecorder. Now we need to start it and save the data it records. The demonstration code I've included uses the dataavailable
event to save the recording periodically, before assembling it into a complete Blob when the recording finishes. The frequency with which this happens is set when the recorder is started.
the data is provided to the event handler in the data
property of the event object. We can just push that into an array for later handling.
let chunks = [];
mRec.addEventListener('dataavailable', (e)=>{
console.log('Data available');
chunks.push(e.data);
});
At the end of the recording
When the recording stops the MediaRecorder fires the stop
event, where we can assemble the saved chunks into a completed Blob, and offer the user a version to save
mRec.addEventListener('stop', ()=>{
console.log('Recording stopped');
recordedSound = new Blob(chunks, {type:mRec.mimeType});
let dURL = URL.createObjectURL(recordedSound);
let downloadLink = document.createElement('a');
downloadLink.href = dURL;
downloadLink.download = "demo.mp3";
downloadLink.style.display= "none";
document.querySelector('body').append(downloadLink);
downloadLink.click();
});
The complete code
To make all this work we need to add a button to start recording, and wrap everything in an immediately-invoke function expression (IIFE) to prevent any pollution of the global workspace.
<!DOCTYPE html>
<html lang="en">
<head
<meta charset="UTF-8"
<title>Demo audio recorder</title>
</head>
<body>
<input type="button" id="recordButton" value="Record">
<script>
(function(){
"use strict"
let recordedSound;
let chunks = [];
let mRec;
let stream;
// Wait for a click on the record button
document.getElementById('recordButton').addEventListener('click',async function(){
console.log("Record button clicked");
// If this is the first time in, set up the Media Recorder.
if (!mRec) {
// Check that the MediaRecorder interface is available/
if (!navigator.mediaDevices || !MediaRecorder) {
alert('mediaDevices not supported.');
return;
}
console.log("Media recorder available");
stream = await navigator.mediaDevices.getUserMedia({audio: true, video: false});
mRec = new MediaRecorder(stream);
// Handle the dataavailable event - store the available data in an array
mRec.addEventListener('dataavailable', (e) => {
console.log('Data available');
chunks.push(e.data);
});
/** Handle the stop event.
* Reset the record button,
* Assemble the stored audio data into blob
* Create an (invisible) <a> element to action the download
* Click the <a> element to initiate the download.
*/
mRec.addEventListener('stop', () => {
console.log('Recording stopped');
document.getElementById("recordButton").value = "Record";
// Assemble the stored chunks into a blob and clear the saved data
recordedSound = new Blob(chunks);
chunks = [];
// Package up the blob and present it for download
let dURL = URL.createObjectURL(recordedSound);
let downloadLink = document.createElement('a');
downloadLink.href = dURL;
downloadLink.download = "demo.webm";
downloadLink.style.display = "none";
document.querySelector('body').append(downloadLink);
downloadLink.click();
});
}
// change the button label
this.value = 'Recording';
// set a five second timer to limit the recording length
setTimeout(()=>{mRec.stop()}, 5000 );
// Start recording in 100ms chunks
mRec.start(100);
});
})();
</script>
</body>
</html>
Try it!
Click the button to record five seconds of audio and download it
Get the code!
The download includes this demo along with a more complete demonstration with Start, Stop, Play and Save functionality. The code was developed and tested in current versions of Firefox, Chrome and Edge. Internet Explorer does not support the Media APIs