{"version":3,"sources":["src/common.browser/PCMRecorder.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,qBAAa,WAAY,YAAW,SAAS;IACzC,OAAO,CAAC,kBAAkB,CAAkB;IAC5C,OAAO,CAAC,yBAAyB,CAAS;IAEnC,MAAM,+FAyFZ;IAEM,qBAAqB,kCAY3B;IAEM,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAG1C","file":"PCMRecorder.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport { RiffPcmEncoder, Stream } from \"../common/Exports\";\nimport { IRecorder } from \"./IRecorder\";\n\nexport class PcmRecorder implements IRecorder {\n    private privMediaResources: IMediaResources;\n    private privSpeechProcessorScript: string; // speech-processor.js Url\n\n    public record = (context: AudioContext, mediaStream: MediaStream, outputStream: Stream<ArrayBuffer>): void => {\n        const desiredSampleRate = 16000;\n\n        const scriptNode = (() => {\n            let bufferSize = 0;\n            try {\n                return context.createScriptProcessor(bufferSize, 1, 1);\n            } catch (error) {\n                // Webkit (<= version 31) requires a valid bufferSize.\n                bufferSize = 2048;\n                let audioSampleRate = context.sampleRate;\n                while (bufferSize < 16384 && audioSampleRate >= (2 * desiredSampleRate)) {\n                    bufferSize <<= 1;\n                    audioSampleRate >>= 1;\n                }\n                return context.createScriptProcessor(bufferSize, 1, 1);\n            }\n        })();\n\n        const waveStreamEncoder = new RiffPcmEncoder(context.sampleRate, desiredSampleRate);\n        let needHeader: boolean = true;\n        const that = this;\n        scriptNode.onaudioprocess = (event: AudioProcessingEvent) => {\n            const inputFrame = event.inputBuffer.getChannelData(0);\n\n            if (outputStream && !outputStream.isClosed) {\n                const waveFrame = waveStreamEncoder.encode(needHeader, inputFrame);\n                if (!!waveFrame) {\n                    outputStream.writeStreamChunk({\n                        buffer: waveFrame,\n                        isEnd: false,\n                        timeReceived: Date.now(),\n                    });\n                    needHeader = false;\n                }\n            }\n        };\n\n        const micInput = context.createMediaStreamSource(mediaStream);\n\n        // https://webaudio.github.io/web-audio-api/#audioworklet\n        // Using AudioWorklet to improve audio quality and avoid audio glitches due to blocking the UI thread\n\n        if (!!this.privSpeechProcessorScript && !!context.audioWorklet) {\n            context.audioWorklet\n                .addModule(this.privSpeechProcessorScript)\n                .then(() => {\n                    const workletNode = new AudioWorkletNode(context, \"speech-processor\");\n                    workletNode.port.onmessage = (ev: MessageEvent) => {\n                        const inputFrame: Float32Array = ev.data as Float32Array;\n\n                        if (outputStream && !outputStream.isClosed) {\n                            const waveFrame = waveStreamEncoder.encode(needHeader, inputFrame);\n                            if (!!waveFrame) {\n                                outputStream.writeStreamChunk({\n                                    buffer: waveFrame,\n                                    isEnd: false,\n                                    timeReceived: Date.now(),\n                                });\n                                needHeader = false;\n                            }\n                        }\n                    };\n                    micInput.connect(workletNode);\n                    workletNode.connect(context.destination);\n                    this.privMediaResources = {\n                        scriptProcessorNode: workletNode,\n                        source: micInput,\n                        stream: mediaStream,\n                    };\n                })\n                .catch(() => {\n                    micInput.connect(scriptNode);\n                    scriptNode.connect(context.destination);\n                    this.privMediaResources = {\n                        scriptProcessorNode: scriptNode,\n                        source: micInput,\n                        stream: mediaStream,\n                    };\n                });\n        } else {\n            micInput.connect(scriptNode);\n            scriptNode.connect(context.destination);\n            this.privMediaResources = {\n                scriptProcessorNode: scriptNode,\n                source: micInput,\n                stream: mediaStream,\n            };\n        }\n    }\n\n    public releaseMediaResources = (context: AudioContext): void => {\n        if (this.privMediaResources) {\n            if (this.privMediaResources.scriptProcessorNode) {\n                this.privMediaResources.scriptProcessorNode.disconnect(context.destination);\n                this.privMediaResources.scriptProcessorNode = null;\n            }\n            if (this.privMediaResources.source) {\n                this.privMediaResources.source.disconnect();\n                this.privMediaResources.stream.getTracks().forEach((track: any) => track.stop());\n                this.privMediaResources.source = null;\n            }\n        }\n    }\n\n    public setWorkletUrl(url: string): void {\n        this.privSpeechProcessorScript = url;\n    }\n}\n\ninterface IMediaResources {\n    source: MediaStreamAudioSourceNode;\n    scriptProcessorNode: ScriptProcessorNode | AudioWorkletNode;\n    stream: MediaStream;\n}\n"]}