DEV Community

Cover image for Converting audio to MP3 in the browser - Javascript tutorial
Tiago S. P Rodrigues
Tiago S. P Rodrigues

Posted on

Converting audio to MP3 in the browser - Javascript tutorial

This week is my son's first birthday. My wife downloaded over 60 children's songs for the party and asked me to convert them to MP3, as her speaker didn't support the m4a format she was using.

My first thought was to open any online conversion site, upload the songs, and wait for the conversion. But most sites only allow you to convert a maximum of 2 to 5 files on their free plans.

Of course, I could simply pay for one of these sites. But like any good programmer who likes to reinvent the wheel, I decided to create the converter myself.

As a web developer, I was curious if this would be possible directly in the browser. After some research, I found the Lame.js library, which allows you to encode MP3 files.

In just under a morning, I had created a simple page that converted all the files to MP3 in a matter of minutes. I liked the result so much that I even bought a domain, did some improvements and put it online: https://mp3converter.cloud

MP3 Converter website print

In this short tutorial, I'd like to show you how simple it is to take an audio file and convert it to MP3 using this small library.

Firstly, download the lame.all.js file from the repository and put it inside your project's folder.

Then, create a simple HTML page with a file input and a button to start the conversion:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Audio to MP3 Converter</title> <script src="lame.all.js"></script> </head> <body> <h1>Convert Audio to MP3</h1> <input type="file" id="audioFile" accept="audio/*"> <button onclick="convertToMP3()">Convert</button> </body> 
Enter fullscreen mode Exit fullscreen mode

Now lets implement the *convertToMP3 * method. Firstly, we read the audio file from the input:

async function convertToMP3() { const fileInput = document.getElementById('audioFile'); if (!fileInput.files[0]) return alert('Select a file first.'); const file = fileInput.files[0]; // ... rest of the code } 
Enter fullscreen mode Exit fullscreen mode

Then we decode the file to a Buffer using the AudioContext api:

const buffer = await file.arrayBuffer(); const audioCtx = new AudioContext(); const audioBuffer = await audioCtx.decodeAudioData(buffer); 
Enter fullscreen mode Exit fullscreen mode

Now we convert the data to the Lame.js accepted format (from Float to Int16):

const channels = Math.min(audioBuffer.numberOfChannels, 2); const sampleRate = audioBuffer.sampleRate; const int16Data = []; for (let ch = 0; ch < channels; ch++) { const floatData = audioBuffer.getChannelData(ch); const intData = new Int16Array(floatData.length); for (let i = 0; i < floatData.length; i++) { intData[i] = Math.max(-32768, Math.min(32767, floatData[i] * 32767)); } int16Data.push(intData); } 
Enter fullscreen mode Exit fullscreen mode

With that, it is now possible to generate the final mp3 file using Lame:

const encoder = new lamejs.Mp3Encoder(channels, sampleRate, 96); const mp3Data = []; const blockSize = 1152; // Better to use multiples of 576 const length = int16Data[0].length; for (let i = 0; i < length; i += blockSize) { const leftChunk = int16Data[0].subarray(i, i + blockSize); let mp3buf; if (channels === 2) { const rightChunk = int16Data[1].subarray(i, i + blockSize); mp3buf = encoder.encodeBuffer(leftChunk, rightChunk); } else { mp3buf = encoder.encodeBuffer(leftChunk); } if (mp3buf.length > 0) { mp3Data.push(mp3buf); } } const finalMp3 = encoder.flush(); if (finalMp3.length > 0) { mp3Data.push(finalMp3); } 
Enter fullscreen mode Exit fullscreen mode

And finally, download the generated file:

const blob = new Blob(mp3Data, { type: 'audio/mp3' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = file.name.replace(/\.[^/.]+$/, '') + '.mp3'; a.click(); URL.revokeObjectURL(url); 
Enter fullscreen mode Exit fullscreen mode

If you are interested in the full code, here it is:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Audio to MP3 Converter</title> <script src="lame.all.js"></script> </head> <body> <h1>Convert Audio to MP3</h1> <input type="file" id="audioFile" accept="audio/*"> <button onclick="convertToMP3()">Convert</button> <script> async function convertToMP3() { const fileInput = document.getElementById('audioFile'); if (!fileInput.files[0]) return alert('Select a file first.'); const file = fileInput.files[0]; const buffer = await file.arrayBuffer(); const audioCtx = new AudioContext(); const audioBuffer = await audioCtx.decodeAudioData(buffer); const channels = Math.min(audioBuffer.numberOfChannels, 2); const sampleRate = audioBuffer.sampleRate; const int16Data = []; for (let ch = 0; ch < channels; ch++) { const floatData = audioBuffer.getChannelData(ch); const intData = new Int16Array(floatData.length); for (let i = 0; i < floatData.length; i++) { intData[i] = Math.max(-32768, Math.min(32767, floatData[i] * 32767)); } int16Data.push(intData); } const encoder = new lamejs.Mp3Encoder(channels, sampleRate, 96); const mp3Data = []; const blockSize = 1152; const length = int16Data[0].length; for (let i = 0; i < length; i += blockSize) { const leftChunk = int16Data[0].subarray(i, i + blockSize); let mp3buf; if (channels === 2) { const rightChunk = int16Data[1].subarray(i, i + blockSize); mp3buf = encoder.encodeBuffer(leftChunk, rightChunk); } else { mp3buf = encoder.encodeBuffer(leftChunk); } if (mp3buf.length > 0) { mp3Data.push(mp3buf); } } const finalMp3 = encoder.flush(); if (finalMp3.length > 0) { mp3Data.push(finalMp3); } const blob = new Blob(mp3Data, { type: 'audio/mp3' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = file.name.replace(/\.[^/.]+$/, '') + '.mp3'; a.click(); URL.revokeObjectURL(url); } </script> </body> 
Enter fullscreen mode Exit fullscreen mode

And that's basically it. In my case, I implemented some improvements, such as using web workers to perform the conversion faster (which increases the conversion speed at least 5x on my machine).

I hope this short article was helpful. Cheers!

Top comments (0)