Recording audio directly in web browsers has become increasingly important for modern web applications. Whether you're building a voice messaging system, a podcast platform, or a language learning app, the ability to capture and handle audio is essential. In this post, I'll show you how to implement audio recording, playback, and uploading using vanilla JavaScript.
Prerequisites
Before we dive in, make sure you understand:
- Basic HTML and JavaScript
- Async/await and Promises in JavaScript
- Basic understanding of Web APIs
Understanding the MediaRecorder API
The MediaRecorder API is a key component for recording audio in the browser. It provides a clean interface to capture media streams and create recordings. Here's how it works:
- First, we request access to the user's microphone
- Then we create a MediaRecorder instance with the audio stream
- Finally, we collect the recorded data and convert it into a usable format
Implementation
Let's build a complete audio recorder with these features:
- Record audio from the microphone
- Preview the recording
- Convert to MP3 format
- Upload to a server
HTML Structure
First, let's create a simple interface:
<!DOCTYPE html> <html> <head> <title>Audio Recorder</title> <style> .container { max-width: 600px; margin: 0 auto; padding: 20px; } .controls { margin: 20px 0; } button { margin: 5px; padding: 10px 20px; } #audioPreview { width: 100%; margin: 20px 0; } </style> </head> <body> <div class="container"> <h1>Audio Recorder</h1> <div class="controls"> <button id="recordButton">Start Recording</button> <button id="stopButton" disabled>Stop Recording</button> <button id="uploadButton" disabled>Upload Recording</button> </div> <audio id="audioPreview" controls></audio> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.1/lame.min.js"></script> <script src="audio-recorder.js"></script> </body> </html>
JavaScript Implementation
Here's our complete audio-recorder.js:
let mediaRecorder; let audioChunks = []; document.getElementById('recordButton').addEventListener('click', startRecording); document.getElementById('stopButton').addEventListener('click', stopRecording); document.getElementById('uploadButton').addEventListener('click', uploadRecording); async function startRecording() { audioChunks = []; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm' }); mediaRecorder.ondataavailable = (event) => { audioChunks.push(event.data); }; mediaRecorder.onstop = async () => { const audioBlob = await convertToMp3(new Blob(audioChunks, { type: 'audio/webm' })); const audioUrl = URL.createObjectURL(audioBlob); document.getElementById('audioPreview').src = audioUrl; }; mediaRecorder.start(); document.getElementById('recordButton').disabled = true; document.getElementById('stopButton').disabled = false; document.getElementById('uploadButton').disabled = true; } catch (err) { console.error('Error accessing microphone:', err); alert('Error accessing microphone. Please ensure you have granted permission.'); } } function stopRecording() { mediaRecorder.stop(); mediaRecorder.stream.getTracks().forEach(track => track.stop()); document.getElementById('recordButton').disabled = false; document.getElementById('stopButton').disabled = true; document.getElementById('uploadButton').disabled = false; } async function convertToMp3(blob) { const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const arrayBuffer = await blob.arrayBuffer(); const audioBuffer = await audioContext.decodeAudioData(arrayBuffer); const mp3Encoder = new lamejs.Mp3Encoder(1, audioBuffer.sampleRate, 128); const samples = new Int16Array(audioBuffer.length); const channel = audioBuffer.getChannelData(0); // Convert Float32Array to Int16Array for (let i = 0; i < channel.length; i++) { samples[i] = channel[i] * 0x7FFF; } // Encode to MP3 const mp3Data = []; const blockSize = 1152; for (let i = 0; i < samples.length; i += blockSize) { const sampleChunk = samples.subarray(i, i + blockSize); const mp3buf = mp3Encoder.encodeBuffer(sampleChunk); if (mp3buf.length > 0) { mp3Data.push(mp3buf); } } const mp3buf = mp3Encoder.flush(); if (mp3buf.length > 0) { mp3Data.push(mp3buf); } return new Blob(mp3Data, { type: 'audio/mp3' }); } async function uploadRecording() { try { const audioBlob = await convertToMp3(new Blob(audioChunks, { type: 'audio/webm' })); const formData = new FormData(); formData.append('audio', audioBlob, 'recording.mp3'); const response = await fetch('/upload-audio', { method: 'POST', body: formData }); const result = await response.json(); if (response.ok) { alert('Recording uploaded successfully!'); document.getElementById('uploadButton').disabled = true; } else { alert('Error uploading recording: ' + result.error); } } catch (err) { console.error('Error uploading recording:', err); alert('Error uploading recording. Please try again.'); } }
How It Works
Let's break down the key components:
1. Recording Audio
The recording process begins when the user clicks the "Start Recording" button. We use navigator.mediaDevices.getUserMedia() to access the microphone and create a MediaRecorder instance. The recorder stores audio chunks as they become available.
2. Converting to MP3
We use the lame.js library to convert the WebM audio to MP3 format. This involves:
- Converting the audio blob to an ArrayBuffer
- Decoding the audio data
- Converting the float audio data to 16-bit integers
- Encoding the data to MP3 format
3. Uploading
The upload process uses the Fetch API to send the MP3 file to the server using FormData. This makes it easy to handle on the server side, regardless of the backend technology you're using.
Browser Compatibility
This implementation works in all modern browsers. However, keep in mind:
- Safari has some limitations with the MediaRecorder API
- Older browsers might not support some features
- Mobile browsers might handle audio permissions differently
Security Considerations
When implementing audio recording:
- Always request user permission before accessing the microphone
- Use secure connections (HTTPS) for uploading
- Implement proper server-side validation
- Consider implementing file size limits
- Handle user permissions appropriately
Enhancements You Could Add
Here are some ways to enhance this basic implementation:
- Add recording duration display
- Implement pause/resume functionality
- Add audio visualization
- Include recording quality options
- Add progress indicators for upload
- Implement error handling for various network conditions
Conclusion
Recording and handling audio in web applications is now easier than ever with modern Web APIs. This implementation provides a solid foundation that you can build upon for your specific use case. Remember to consider browser compatibility and security implications when deploying to production.
The complete code is available in the examples above. Feel free to modify and enhance it for your specific needs!