{"version":3,"sources":["src/sdk/Audio/BaseAudioPlayer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAIlD;;;;GAIG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,qBAAqB,CAAU;IACvC,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;OAIG;gBACgB,WAAW,CAAC,EAAE,iBAAiB;IAOlD;;;OAGG;IACI,eAAe,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAkBvG;;OAEG;IACI,SAAS,CAAC,EAAE,CAAC,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAkBtE,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,iBAAiB;YAmCX,SAAS;CAY1B","file":"BaseAudioPlayer.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport { InvalidOperationError } from \"../../common/Error.js\";\nimport { AudioStreamFormat } from \"../Exports.js\";\nimport { AudioStreamFormatImpl } from \"./AudioStreamFormat.js\";\n\ntype AudioDataTypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array;\n/**\n * Base audio player class\n * TODO: Plays only PCM for now.\n * @class\n */\nexport class BaseAudioPlayer {\n\n    private audioContext: AudioContext = null;\n    private gainNode: GainNode = null;\n    private audioFormat: AudioStreamFormatImpl;\n    private autoUpdateBufferTimer: any = 0;\n    private samples: Float32Array;\n    private startTime: number;\n\n    /**\n     * Creates and initializes an instance of this class.\n     * @constructor\n     * @param {AudioStreamFormat} audioFormat audio stream format recognized by the player.\n     */\n    public constructor(audioFormat?: AudioStreamFormat) {\n        if (audioFormat === undefined) {\n            audioFormat = AudioStreamFormat.getDefaultInputFormat();\n        }\n        this.init(audioFormat);\n    }\n\n    /**\n     * play Audio sample\n     * @param newAudioData audio data to be played.\n     */\n    public playAudioSample(newAudioData: ArrayBuffer, cb?: () => void, err?: (error: string) => void): void {\n        try {\n            this.ensureInitializedContext();\n            const audioData = this.formatAudioData(newAudioData);\n            const newSamplesData = new Float32Array(this.samples.length + audioData.length);\n            newSamplesData.set(this.samples, 0);\n            newSamplesData.set(audioData, this.samples.length);\n            this.samples = newSamplesData;\n            if (!!cb) {\n                cb();\n            }\n        } catch (e) {\n            if (!!err) {\n                err(e as string);\n            }\n        }\n    }\n\n    /**\n     * stops audio and clears the buffers\n     */\n    public stopAudio(cb?: () => void, err?: (error: string) => void): void {\n        if (this.audioContext !== null) {\n            this.samples = new Float32Array();\n            // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\n            clearInterval(this.autoUpdateBufferTimer);\n            this.audioContext.close().then((): void => {\n                if (!!cb) {\n                    cb();\n                }\n            }, (error: string): void => {\n                if (!!err) {\n                    err(error);\n                }\n            });\n            this.audioContext = null;\n        }\n    }\n\n    private init(audioFormat: AudioStreamFormat): void {\n        this.audioFormat = audioFormat as AudioStreamFormatImpl;\n        this.samples = new Float32Array();\n    }\n\n    private ensureInitializedContext(): void {\n        if (this.audioContext === null) {\n            this.createAudioContext();\n            const timerPeriod = 200;\n            this.autoUpdateBufferTimer = setInterval((): void => {\n                this.updateAudioBuffer();\n            }, timerPeriod);\n        }\n    }\n\n    private createAudioContext(): void {\n        // new ((window as any).AudioContext || (window as any).webkitAudioContext)();\n        this.audioContext = AudioStreamFormatImpl.getAudioContext();\n\n        // TODO: Various examples shows this gain node, it does not seem to be needed unless we plan\n        // to control the volume, not likely\n        this.gainNode = this.audioContext.createGain();\n        this.gainNode.gain.value = 1;\n        this.gainNode.connect(this.audioContext.destination);\n        this.startTime = this.audioContext.currentTime;\n    }\n\n    private formatAudioData(audioData: ArrayBuffer): Float32Array {\n        switch (this.audioFormat.bitsPerSample) {\n            case 8:\n                return this.formatArrayBuffer(new Int8Array(audioData), 128);\n            case 16:\n                return this.formatArrayBuffer(new Int16Array(audioData), 32768);\n            case 32:\n                return this.formatArrayBuffer(new Int32Array(audioData), 2147483648);\n            default:\n                throw new InvalidOperationError(\"Only WAVE_FORMAT_PCM (8/16/32 bps) format supported at this time\");\n        }\n    }\n\n    private formatArrayBuffer(audioData: AudioDataTypedArray, maxValue: number): Float32Array {\n        const float32Data = new Float32Array(audioData.length);\n        for (let i = 0; i < audioData.length; i++) {\n            float32Data[i] = audioData[i] / maxValue;\n        }\n        return float32Data;\n    }\n\n    private updateAudioBuffer(): void {\n        if (this.samples.length === 0) {\n            return;\n        }\n\n        const channelCount = this.audioFormat.channels;\n        const bufferSource = this.audioContext.createBufferSource();\n        const frameCount = this.samples.length / channelCount;\n        const audioBuffer = this.audioContext.createBuffer(channelCount, frameCount, this.audioFormat.samplesPerSec);\n\n        // TODO: Should we do the conversion in the pushAudioSample instead?\n        for (let channel = 0; channel < channelCount; channel++) {\n            // Fill in individual channel data\n            let channelOffset = channel;\n            const audioData = audioBuffer.getChannelData(channel);\n            for (let i = 0; i < this.samples.length; i++, channelOffset += channelCount) {\n                audioData[i] = this.samples[channelOffset];\n            }\n        }\n\n        if (this.startTime < this.audioContext.currentTime) {\n            this.startTime = this.audioContext.currentTime;\n        }\n\n        bufferSource.buffer = audioBuffer;\n        bufferSource.connect(this.gainNode);\n        bufferSource.start(this.startTime);\n\n        // Make sure we play the next sample after the current one.\n        this.startTime += audioBuffer.duration;\n\n        // Clear the samples for the next pushed data.\n        this.samples = new Float32Array();\n    }\n\n    private async playAudio(audioData: ArrayBuffer): Promise<void> {\n        if (this.audioContext === null) {\n            this.createAudioContext();\n        }\n        const source: AudioBufferSourceNode = this.audioContext.createBufferSource();\n        const destination: AudioDestinationNode = this.audioContext.destination;\n        await this.audioContext.decodeAudioData(audioData, (newBuffer: AudioBuffer): void => {\n            source.buffer = newBuffer;\n            source.connect(destination);\n            source.start(0);\n        });\n    }\n}\n"]}