@inworld/web-core
Version:
81 lines (80 loc) • 3.31 kB
JavaScript
export class GrpcAudioRecorder {
constructor() {
this.currentMediaStream = null;
this.processor = null;
this.leftChannel = [];
this.recordingLength = 0;
this.interval = null;
this.listener = null;
this.onAudioProcess = (e) => {
const samples = e.inputBuffer.getChannelData(0);
// we clone the samples to not loose them
this.leftChannel.push(new Float32Array(samples));
this.recordingLength += GrpcAudioRecorder.BUFFER_SIZE_BYTES;
};
this.intervalFunction = (listener) => {
const PCM32fSamples = this.mergeBuffers(this.leftChannel, this.recordingLength);
// reset "buffer" on each iteration
this.leftChannel = [];
this.recordingLength = 0;
// convert to Int16 (we are working in LINEAR16 currently)
const PCM16iSamples = Int16Array.from(PCM32fSamples, (k) => 32767 * Math.min(1, k));
listener(this.arrayBufferToBase64(PCM16iSamples.buffer));
};
}
stopConvertion() {
var _a;
if (!this.currentMediaStream) {
return;
}
this.currentMediaStream.getTracks().forEach((track) => track.stop());
if (this.interval) {
clearInterval(this.interval);
}
(_a = this.processor) === null || _a === void 0 ? void 0 : _a.disconnect();
this.leftChannel = [];
this.recordingLength = 0;
this.interval = null;
}
isRecording() {
return this.interval != null;
}
// Consumes stream that is coming out of local webrtc loopback and converts it to the messages for the server.
async startConvertion(stream, listener) {
this.listener = listener;
const context = new AudioContext({
sampleRate: GrpcAudioRecorder.SAMPLE_RATE_HZ,
});
const source = context.createMediaStreamSource(stream);
// need to keep track of this two in order to properly disconnect later on;
this.currentMediaStream = stream;
this.processor = context.createScriptProcessor(GrpcAudioRecorder.BUFFER_SIZE_BYTES,
/* input channels = */ 1,
/* output channels = */ 1);
source.connect(this.processor);
this.processor.connect(context.destination);
this.processor.onaudioprocess = this.onAudioProcess;
this.interval = setInterval(() => this.intervalFunction(this.listener), GrpcAudioRecorder.INTERVAL_TIMEOUT_MS);
}
mergeBuffers(channelBuffer, recordingLength) {
const result = new Float32Array(recordingLength);
let offset = 0;
for (let i = 0; i < channelBuffer.length; i++) {
result.set(channelBuffer[i], offset);
offset += channelBuffer[i].length;
}
return Array.prototype.slice.call(result);
}
arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
const length = bytes.byteLength;
for (let i = 0; i < length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
}
GrpcAudioRecorder.SAMPLE_RATE_HZ = 16000;
GrpcAudioRecorder.BUFFER_SIZE_BYTES = 2048;
GrpcAudioRecorder.INTERVAL_TIMEOUT_MS = 200;