UNPKG

@inworld/web-core

Version:
81 lines (80 loc) 3.31 kB
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;