Skip to content

Instantly share code, notes, and snippets.

@ctralie
Last active August 8, 2025 16:39
Show Gist options
  • Save ctralie/c9fc0b732961f1330e42d0d4066bdef7 to your computer and use it in GitHub Desktop.
Save ctralie/c9fc0b732961f1330e42d0d4066bdef7 to your computer and use it in GitHub Desktop.
Messy Prototype for Mp3 Audio Streaming To Server As Base64
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);
<!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>
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