{"version":3,"sources":["src/sdk/Audio/SpeakerAudioDestination.ts"],"names":[],"mappings":"AAGA,OAAO,EAIH,iBAAiB,EAEpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAmB3D;;;;;;GAMG;AACH,qBAAa,uBAAwB,YAAW,iBAAiB,EAAE,OAAO;IACtE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,UAAU,CAAwB;IAC1C,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,CAAC,eAAe,CAAc;IACrC,OAAO,CAAC,gBAAgB,CAAe;IACvC,OAAO,CAAC,mBAAmB,CAAkB;IAC7C,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,qBAAqB,CAAkB;IAC/C,OAAO,CAAC,qBAAqB,CAAkB;IAC/C,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,qBAAqB,CAA4B;IACzD,OAAO,CAAC,iBAAiB,CAAa;gBAEnB,kBAAkB,CAAC,EAAE,MAAM;IAMvC,EAAE,IAAI,MAAM;IAIZ,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAkBhF,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAiDlE,IAAW,MAAM,CAAC,MAAM,EAAE,iBAAiB,EA+C1C;IAED,IAAW,MAAM,IAAI,MAAM,CAE1B;IAED,IAAW,MAAM,CAAC,MAAM,EAAE,MAAM,EAI/B;IAEM,IAAI,IAAI,IAAI;IAMZ,MAAM,IAAI,IAAI;IAMrB,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED,IAAW,WAAW,IAAI,MAAM,CAK/B;IAEM,KAAK,IAAI,IAAI;IAOb,MAAM,CAAC,EAAE,CAAC,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAe5D,YAAY,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAExC,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAE7C,IAAW,aAAa,IAAI,gBAAgB,CAE3C;YAEa,kBAAkB;YAmBlB,2BAA2B;YAO3B,cAAc;IAiB5B,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,qBAAqB;CAGhC","file":"SpeakerAudioDestination.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport {\n    BackgroundEvent,\n    createNoDashGuid,\n    Events,\n    IAudioDestination,\n    INumberDictionary\n} from \"../../common/Exports.js\";\nimport { AudioStreamFormat, IPlayer } from \"../Exports.js\";\nimport { AudioOutputFormatImpl } from \"./AudioOutputFormat.js\";\nimport { PullAudioOutputStreamImpl } from \"./AudioOutputStream.js\";\nimport { AudioFormatTag } from \"./AudioStreamFormat.js\";\n\nconst MediaDurationPlaceholderSeconds = 60 * 30;\n\nconst AudioFormatToMimeType: INumberDictionary<string> = {\n    [AudioFormatTag.PCM]: \"audio/wav\",\n    [AudioFormatTag.MuLaw]: \"audio/x-wav\",\n    [AudioFormatTag.MP3]: \"audio/mpeg\",\n    [AudioFormatTag.OGG_OPUS]: \"audio/ogg\",\n    [AudioFormatTag.WEBM_OPUS]: \"audio/webm; codecs=opus\",\n    [AudioFormatTag.ALaw]: \"audio/x-wav\",\n    [AudioFormatTag.FLAC]: \"audio/flac\",\n    [AudioFormatTag.AMR_WB]: \"audio/amr-wb\",\n    [AudioFormatTag.G722]: \"audio/G722\",\n};\n\n/**\n * Represents the speaker playback audio destination, which only works in browser.\n * Note: the SDK will try to use <a href=\"https://www.w3.org/TR/media-source/\">Media Source Extensions</a> to play audio.\n * Mp3 format has better supports on Microsoft Edge, Chrome and Safari (desktop), so, it's better to specify mp3 format for playback.\n * @class SpeakerAudioDestination\n * Updated in version 1.17.0\n */\nexport class SpeakerAudioDestination implements IAudioDestination, IPlayer {\n    private readonly privId: string;\n    private privFormat: AudioOutputFormatImpl;\n    private privAudio: HTMLAudioElement;\n    private privMediaSource: MediaSource;\n    private privSourceBuffer: SourceBuffer;\n    private privPlaybackStarted: boolean = false;\n    private privAudioBuffer: ArrayBuffer[];\n    private privAppendingToBuffer: boolean = false;\n    private privMediaSourceOpened: boolean = false;\n    private privIsClosed: boolean;\n    private privIsPaused: boolean;\n    private privAudioOutputStream: PullAudioOutputStreamImpl;\n    private privBytesReceived: number = 0;\n\n    public constructor(audioDestinationId?: string) {\n        this.privId = audioDestinationId ? audioDestinationId : createNoDashGuid();\n        this.privIsPaused = false;\n        this.privIsClosed = false;\n    }\n\n    public id(): string {\n        return this.privId;\n    }\n\n    public write(buffer: ArrayBuffer, cb?: () => void, err?: (error: string) => void): void {\n        if (this.privAudioBuffer !== undefined) {\n            this.privAudioBuffer.push(buffer);\n            this.updateSourceBuffer().then((): void => {\n                if (!!cb) {\n                    cb();\n                }\n            }, (error: string): void => {\n                if (!!err) {\n                    err(error);\n                }\n            });\n        } else if (this.privAudioOutputStream !== undefined) {\n            this.privAudioOutputStream.write(buffer);\n            this.privBytesReceived += buffer.byteLength;\n        }\n    }\n\n    public close(cb?: () => void, err?: (error: string) => void): void {\n        this.privIsClosed = true;\n        if (this.privSourceBuffer !== undefined) {\n            this.handleSourceBufferUpdateEnd().then((): void => {\n                if (!!cb) {\n                    cb();\n                }\n            }, (error: string): void => {\n                if (!!err) {\n                    err(error);\n                }\n            });\n        } else if (this.privAudioOutputStream !== undefined && typeof window !== \"undefined\") {\n            if ((this.privFormat.formatTag === AudioFormatTag.PCM || this.privFormat.formatTag === AudioFormatTag.MuLaw\n                || this.privFormat.formatTag === AudioFormatTag.ALaw) && this.privFormat.hasHeader === false) {\n                // eslint-disable-next-line no-console\n                console.warn(\"Play back is not supported for raw PCM, mulaw or alaw format without header.\");\n                if (!!this.onAudioEnd) {\n                    this.onAudioEnd(this);\n                }\n            } else {\n                let receivedAudio = new ArrayBuffer(this.privBytesReceived);\n                this.privAudioOutputStream.read(receivedAudio).then((): void => {\n                    receivedAudio = this.privFormat.addHeader(receivedAudio);\n                    const audioBlob = new Blob([receivedAudio], { type: AudioFormatToMimeType[this.privFormat.formatTag] });\n                    this.privAudio.src = window.URL.createObjectURL(audioBlob);\n                    this.notifyPlayback().then((): void => {\n                        if (!!cb) {\n                            cb();\n                        }\n                    }, (error: string): void => {\n                        if (!!err) {\n                            err(error);\n                        }\n                    });\n                }, (error: string): void => {\n                    if (!!err) {\n                        err(error);\n                    }\n                });\n            }\n        } else {\n            // unsupported format, call onAudioEnd directly.\n            if (!!this.onAudioEnd) {\n                this.onAudioEnd(this);\n            }\n        }\n    }\n\n    public set format(format: AudioStreamFormat) {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n        if (typeof (AudioContext) !== \"undefined\" || (typeof (window) !== \"undefined\" && typeof ((window as any).webkitAudioContext) !== \"undefined\")) {\n            this.privFormat = format as AudioOutputFormatImpl;\n            const mimeType: string = AudioFormatToMimeType[this.privFormat.formatTag];\n            if (mimeType === undefined) {\n                // eslint-disable-next-line no-console\n                console.warn(\n                    `Unknown mimeType for format ${AudioFormatTag[this.privFormat.formatTag]}; playback is not supported.`);\n\n            } else if (typeof (MediaSource) !== \"undefined\" && MediaSource.isTypeSupported(mimeType)) {\n                this.privAudio = new Audio();\n                this.privAudioBuffer = [];\n                this.privMediaSource = new MediaSource();\n                this.privAudio.src = URL.createObjectURL(this.privMediaSource);\n                this.privAudio.load();\n                this.privMediaSource.onsourceopen = (): void => {\n                    this.privMediaSourceOpened = true;\n                    this.privMediaSource.duration = MediaDurationPlaceholderSeconds;\n                    this.privSourceBuffer = this.privMediaSource.addSourceBuffer(mimeType);\n                    this.privSourceBuffer.onupdate = (): void => {\n                        this.updateSourceBuffer().catch((reason: string): void => {\n                            Events.instance.onEvent(new BackgroundEvent(reason));\n                        });\n                    };\n                    this.privSourceBuffer.onupdateend = (): void => {\n                        this.handleSourceBufferUpdateEnd().catch((reason: string): void => {\n                            Events.instance.onEvent(new BackgroundEvent(reason));\n                        });\n                    };\n                    this.privSourceBuffer.onupdatestart = (): void => {\n                        this.privAppendingToBuffer = false;\n                    };\n                };\n                this.updateSourceBuffer().catch((reason: string): void => {\n                    Events.instance.onEvent(new BackgroundEvent(reason));\n                });\n\n            } else {\n                // eslint-disable-next-line no-console\n                console.warn(\n                    `Format ${AudioFormatTag[this.privFormat.formatTag]} could not be played by MSE, streaming playback is not enabled.`);\n                this.privAudioOutputStream = new PullAudioOutputStreamImpl();\n                this.privAudioOutputStream.format = this.privFormat;\n                this.privAudio = new Audio();\n            }\n        }\n    }\n\n    public get volume(): number {\n        return this.privAudio?.volume ?? -1;\n    }\n\n    public set volume(volume: number) {\n        if (!!this.privAudio) {\n            this.privAudio.volume = volume;\n        }\n    }\n\n    public mute(): void {\n        if (!!this.privAudio) {\n            this.privAudio.muted = true;\n        }\n    }\n\n    public unmute(): void {\n        if (!!this.privAudio) {\n            this.privAudio.muted = false;\n        }\n    }\n\n    public get isClosed(): boolean {\n        return this.privIsClosed;\n    }\n\n    public get currentTime(): number {\n        if (this.privAudio !== undefined) {\n            return this.privAudio.currentTime;\n        }\n        return -1;\n    }\n\n    public pause(): void {\n        if (!this.privIsPaused && this.privAudio !== undefined) {\n            this.privAudio.pause();\n            this.privIsPaused = true;\n        }\n    }\n\n    public resume(cb?: () => void, err?: (error: string) => void): void {\n        if (this.privIsPaused && this.privAudio !== undefined) {\n            this.privAudio.play().then((): void => {\n                if (!!cb) {\n                    cb();\n                }\n            }, (error: string): void => {\n                if (!!err) {\n                    err(error);\n                }\n            });\n            this.privIsPaused = false;\n        }\n    }\n\n    public onAudioStart: (sender: IPlayer) => void;\n\n    public onAudioEnd: (sender: IPlayer) => void;\n\n    public get internalAudio(): HTMLAudioElement {\n        return this.privAudio;\n    }\n\n    private async updateSourceBuffer(): Promise<void> {\n        if (this.privAudioBuffer !== undefined && (this.privAudioBuffer.length > 0) && this.sourceBufferAvailable()) {\n            this.privAppendingToBuffer = true;\n            const binary = this.privAudioBuffer.shift();\n            try {\n                this.privSourceBuffer.appendBuffer(binary);\n            } catch (error) {\n                this.privAudioBuffer.unshift(binary);\n                // eslint-disable-next-line no-console\n                console.log(\n                    \"buffer filled, pausing addition of binaries until space is made\");\n                return;\n            }\n            await this.notifyPlayback();\n        } else if (this.canEndStream()) {\n            await this.handleSourceBufferUpdateEnd();\n        }\n    }\n\n    private async handleSourceBufferUpdateEnd(): Promise<void> {\n        if (this.canEndStream() && this.sourceBufferAvailable()) {\n            this.privMediaSource.endOfStream();\n            await this.notifyPlayback();\n        }\n    }\n\n    private async notifyPlayback(): Promise<void> {\n        if (!this.privPlaybackStarted && this.privAudio !== undefined) {\n            this.privPlaybackStarted = true;\n            if (!!this.onAudioStart) {\n                this.onAudioStart(this);\n            }\n            this.privAudio.onended = (): void => {\n                if (!!this.onAudioEnd) {\n                    this.onAudioEnd(this);\n                }\n            };\n            if (!this.privIsPaused) {\n                await this.privAudio.play();\n            }\n        }\n    }\n\n    private canEndStream(): boolean {\n        return (this.isClosed && this.privSourceBuffer !== undefined && (this.privAudioBuffer.length === 0)\n            && this.privMediaSourceOpened && !this.privAppendingToBuffer && this.privMediaSource.readyState === \"open\");\n    }\n\n    private sourceBufferAvailable(): boolean {\n        return (this.privSourceBuffer !== undefined && !this.privSourceBuffer.updating);\n    }\n}\n"]}