web-recorder-style.css
. Open the HTML file in your browser, you should see the following:<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.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.<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:<script> window.addEventListener('DOMContentLoaded', () => { const getMic = document.getElementById('mic'); const recordButton = document.getElementById('record'); const list = document.getElementById('recordings'); }); </script>
<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>
renderError
method we will replace the contents of the <main>
element with the error message. Add this method after the event listener.function renderError(message) { const main = document.querySelector('main'); main.innerHTML = `<div class="error"><p>${message}</p></div>`; }
MediaRecorder
then we now need to get access to the microphone to record from. For this we will use the getUserMedia
API. 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.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 {
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.try/catch
statement. Denial will cause the catch
block to execute, and we'll use our renderError
function again.MediaStream
logged to the console.chunks
, which we will use to store parts of the recording as it is created.MediaRecorder
is initialised 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:try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); const mimeType = 'audio/webm'; let chunks = []; const recorder = new MediaRecorder(stream, { type: mimeType });
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.dataavailable
event collecting chunks and then when the stop
event fires we'll gather all the chunks into a Blob
which we can then play with an <audio>
element and reset the array of chunks
.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 = []; });
renderRecording
method soon. We just have a little more work to do to enable a button to start and stop the recording.chunks = []; }); recordButton.removeAttribute('hidden'); recordButton.addEventListener('click', () => { if (recorder.state === 'inactive') { recorder.start(); recordButton.innerText = 'Stop'; } else { recorder.stop(); recordButton.innerText = 'Record'; } });
<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.renderError
function.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); }
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…).MediaRecorder
API and what you could use it for. Hit up the comments below or drop me a line on Twitter at @philnash.