Record Audio in JS and upload as wav or mp3 file to your backend
My startup lets kids bring their colored templates to life on a screen. Building a digital world by uploading colored templates on paper is already a highlight in itself, but we wanted to go further and give kids additionally the chance to record a voice for their animals.
The problem
I am writing this post because it took me quite some time to find a working solution without having to use too many full blown recording libraries. The part to record audio with the MediaRecorder API is simple, but my uploaded audio files seemed to be unplayable or corrupted. The reason for that is that browsers don’t record audio in mp3 or wav, but in webm (at least Chrome).
What we will do:
- Record audio as wav
- Convert wav to mp3
- Upload audio file to server
- Save file on local disk or S3
Audio conversion to wav
To ultimately convert your recording to an mp3 file, I had to convert it first into the wav format. For that, I used the MediaRecorder library from chrisguttandin/extendable-media-recorder which is an extendable drop-in replacement for the native MediaRecorder.
Install it with:
npm install extendable-media-recorder
Recording the Audio
There are plenty of guides out there on how to use getUserMedia to record audio. I am gonna keep it simple and cover the crucial parts to have a working mp3 or wav audio file.
import {MediaRecorder, register} from 'extendable-media-recorder';
import {connect} from 'extendable-media-recorder-wav-encoder';
let mediaRecorder = null;
let audioBlobs = [];
let capturedStream = null;
// Register the extendable-media-recorder-wav-encoder
async connect() {
await register(await connect());
}
// Starts recording audio
function startRecording() {
return navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
}
}).then(stream => {
audioBlobs = [];
capturedStream = stream;
// Use the extended MediaRecorder library
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/wav'
});
// Add audio blobs while recording
mediaRecorder.addEventListener('dataavailable', event => {
audioBlobs.push(event.data);
});
mediaRecorder.start();
}).catch((e) => {
console.error(e);
});
}
And the stop recording function:
function stopRecording() {
return new Promise(resolve => {
if (!mediaRecorder) {
resolve(null);
return;
}
mediaRecorder.addEventListener('stop', () => {
const mimeType = mediaRecorder.mimeType;
const audioBlob = new Blob(audioBlobs, { type: mimeType });
if (capturedStream) {
capturedStream.getTracks().forEach(track => track.stop());
}
resolve(audioBlob);
});
mediaRecorder.stop();
});
}
If you wanted to let the user play the audio in the browser you can do so by:
playAudio(audioBlob) {
if (audioBlob) {
const audio = new Audio();
audio.src = URL.createObjectURL(audioBlob);
audio.play();
}
}
Wav to mp3
For converting the wav to mp3 I used the lamejs library:
Install
npm install @breezystack/lamejs
Now we can create a function convertWavToMp3 and pass the recorded audioBlob, to obtain an mp3 blob.
import * as lamejs from '@breezystack/lamejs';
convertWavToMp3(wavBlob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function () {
const arrayBuffer = this.result;
// Create a WAV decoder
// @ts-expect-error - No idea
const wavDecoder = lamejs.WavHeader.readHeader(new DataView(arrayBuffer));
// Get the WAV audio data as an array of samples
const wavSamples = new Int16Array(arrayBuffer as ArrayBuffer, wavDecoder.dataOffset, wavDecoder.dataLen / 2);
// Create an MP3 encoder
const mp3Encoder = new lamejs.Mp3Encoder(wavDecoder.channels, wavDecoder.sampleRate, 128);
// Encode the WAV samples to MP3
const mp3Buffer = mp3Encoder.encodeBuffer(wavSamples);
// Finalize the MP3 encoding
const mp3Data = mp3Encoder.flush();
// Combine the MP3 header and data into a new ArrayBuffer
const mp3BufferWithHeader = new Uint8Array(mp3Buffer.length + mp3Data.length);
mp3BufferWithHeader.set(mp3Buffer, 0);
mp3BufferWithHeader.set(mp3Data, mp3Buffer.length);
// Create a Blob from the ArrayBuffer
const mp3Blob = new Blob([mp3BufferWithHeader], { type: 'audio/mp3' });
resolve(mp3Blob);
};
reader.onerror = function (error) {
reject(error);
};
// Read the input blob as an ArrayBuffer
reader.readAsArrayBuffer(wavBlob);
});
}
Upload file
Uploading a file is rather the easy part and should be pretty much self explanatory by showing you the code:
/**
* Uploads audio blob to your server
* @params {Blob} audioBlob - The audio blob data
* @params {string} fileType - 'mp3' or 'wav'
* @return {Promise<object>)
*/
function uploadBlob(audioBlob, fileType) {
const formData = new FormData();
formData.append('audio_data', audioBlob, 'file');
formData.append('type', fileType || 'mp3');
// Your server endpoint to upload audio:
const apiUrl = "http://localhost:3000/upload/audio";
const response = await fetch(apiUrl, {
method: 'POST',
cache: 'no-cache',
body: formData
});
return response.json();
}
All together
Quick example on how to use all the functions together:
// Init
await connect();
// User clicks record button
startRecording();
// User clicks the stop button or a defined timeout
const wavAudioBlob = await stopRecording();
// For fun: play it
playAudio(wavAudioBlob);
// Convert to mp3
// NOTE: mp3 only worked for me on Chrome and Firefox
// Safari didn't seem to like it, so I uploaded .wav on Safari
const mp3Blob = await convertWavToMp3(wavAudioBlob);
// Upload blob to server
const response = await uploadBlob(mp3Blob, 'mp3');
You may want to use try/catch and check some of the vars for null.
Saving the file — Flask
After you POSTed the audio file to your endpoint, you will want to save it. I will show you how I saved the file locally or to a S3 bucket in python Flask, but it should be similar in other languages like NodeJS, PHP etc.
def uploadAudio(request):
# Get params
audio_file = request.files.get('audio_data')
file_type = request.form.get("type", "mp3")
# You may want to create a uuid for your filenames
filename = "myAudioFile." + file_type
# Save it on your local disk
target_path = ("your/local/dir/%s" % filename)
audio_file.save(target_path)
# Or: Save file on AWS S3
session = boto3.Session(""" API CREDENTIALS """)
s3 = session.resource('s3')
bucket = s3.Bucket("your-bucket-name")
destination_dir = "audiofiles/"
response = bucket.upload_fileobj(audio_file, destination_dir, ExtraArgs={
"ContentType": "audio/" + file_type
})
Please comment if you find bugs or have improvements!