Last active
August 8, 2025 16:39
-
-
Save ctralie/c9fc0b732961f1330e42d0d4066bdef7 to your computer and use it in GitHub Desktop.
Messy Prototype for Mp3 Audio Streaming To Server As Base64
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class AudioIOWorklet extends AudioWorkletProcessor { | |
constructor() { | |
super(); | |
this.port.postMessage({"action":"setSampleRate", "sampleRate":sampleRate}); | |
} | |
process(inputList, outputList, parameters) { | |
const input = inputList[0][0]; // First channel of the first stream | |
const samples = new Int16Array(input.length); | |
for (let i = 0; i < input.length; i++) { | |
samples[i] = Math.round(32767*input[i]); // We're expecting 16-bit signed integers for the samples | |
} | |
this.port.postMessage({"action":"inputQuanta", "samples":samples}); | |
return true; | |
} | |
} | |
registerProcessor("audio-io-worklet", AudioIOWorklet); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lame.min.js"></script> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> | |
</head> | |
<body> | |
<button type="button" onclick="startRecording()" id="startRecording">Start Recording</button> | |
<h4> | |
<p id="logArea"> | |
</p> | |
</h4> | |
<script type="text/javascript" src="recordmain.js"></script> | |
<script> | |
function startRecording() { | |
const startRecordingButton = document.getElementById("startRecording"); | |
const workletPath = "audioioworklet.js"; | |
const audioCtx = new AudioContext(); | |
recordAudio(audioCtx, workletPath); | |
} | |
</script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const SECONDS_TO_SEND = 4; // Send over every this many seconds | |
async function recordAudio(audioCtx, workletPath) { | |
// Step 1: Setup audio streamer | |
let stream; | |
let audioIOProcessor; | |
try { | |
stream = await navigator.mediaDevices.getUserMedia({audio:true}); | |
} catch (e) { | |
console.log("Error opening media device: " + e); | |
logArea.innerHTML = "<span style=\"color:red\">Error opening media device</span>"; | |
} | |
try { | |
console.log("workletPath", workletPath); | |
await audioCtx.audioWorklet.addModule(workletPath); | |
audioIOProcessor = new AudioWorkletNode(audioCtx, "audio-io-worklet"); | |
} catch(e) { | |
console.log("Error loading audio worklet processor: " + e); | |
logArea.innerHTML = "<span style=\"color:red\">Error loading audio worklet processor</span>"; | |
} | |
const source = audioCtx.createMediaStreamSource(stream); | |
source.connect(audioIOProcessor); | |
audioIOProcessor.connect(audioCtx.destination); | |
// Step 2: Setup mp3 audio encoder | |
let sampleRate = 48000; // This may be overwritten. Ideally, we can eventually convert down to 16k | |
let mp3Data = []; | |
let backlog = []; | |
let mp3Encoder = null; | |
let totalSamples = 0; | |
let sendingToServer = false; | |
// Step 3: Finish setting up worker that processes audio samples | |
audioIOProcessor.port.onmessage = function(event) { | |
if (event.data.action == "setSampleRate") { | |
sampleRate = event.data.sampleRate; | |
mp3Encoder = new lamejs.Mp3Encoder(1, sampleRate, 128); | |
mp3Data = []; | |
console.log("Setting up mp3 encoder at sample rate of", sampleRate); | |
} | |
else if (event.data.action == "inputQuanta") { | |
let samples = event.data.samples; | |
if (sendingToServer) { | |
backlog.push(samples); | |
} | |
else if (!(mp3Encoder === null)) { | |
// Step 1: Accumulate backlog of quanta that may have occurred while we were | |
// encoding and sending the last chunk of audio | |
if (backlog.length > 0) { | |
console.log("Pushing", backlog.length, "samples from backlog"); | |
for (let i = 0; i < backlog.length; i++) { | |
mp3Data.push(mp3Encoder.encodeBuffer(backlog[i])); | |
totalSamples += backlog[i].length; | |
} | |
backlog = []; | |
} | |
// Step 2: Accumulate this quantum | |
mp3Data.push(mp3Encoder.encodeBuffer(samples)); | |
totalSamples += samples.length; | |
// Step 3: If we've reached enough samples, encode the mp3 file | |
// in base64 and send to the server | |
if (totalSamples > SECONDS_TO_SEND*sampleRate) { | |
sendingToServer = true; | |
const end = mp3Encoder.flush(); | |
mp3Data.push(end); | |
const blob = new Blob(mp3Data, { type: 'audio/mp3' }); | |
const reader = new FileReader(); | |
reader.readAsDataURL(blob); | |
reader.onloadend = function() { | |
const base64data = reader.result; | |
const file = base64data.substr(base64data.indexOf(",")+1); | |
mp3Data = []; | |
totalSamples = 0; | |
sendingToServer = false; | |
$.ajax({ | |
url: '/process', | |
type: 'POST', | |
contentType: 'application/json', | |
data: JSON.stringify({'file': file, 'sr':sampleRate}), | |
error: function(error) { | |
console.log(error); | |
} | |
}); | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment