{"version":3,"sources":["src/sdk/Audio/SpeakerAudioDestination.ts"],"names":[],"mappings":"AAIA,OAAO,EAEH,iBAAiB,EAEpB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAYxD;;;;;;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,GAAG,IAAI;IAUhC,KAAK,IAAI,IAAI;IAgBpB,IAAI,MAAM,CAAC,MAAM,EAAE,iBAAiB,EAuCnC;IAED,IAAW,QAAQ,IAAI,OAAO,CAE7B;IAED,IAAW,WAAW,IAAI,MAAM,CAK/B;IAEM,KAAK,IAAI,IAAI;IAOb,MAAM,IAAI,IAAI;IAOd,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAE7C,IAAW,aAAa,IAAI,gBAAgB,CAE3C;IAED,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,cAAc;IActB,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 { SynthesisAdapterBase } from \"../../common.speech/Exports\";\nimport {\n    createNoDashGuid,\n    IAudioDestination,\n    INumberDictionary\n} from \"../../common/Exports\";\nimport { AudioStreamFormat, IPlayer } from \"../Exports\";\nimport { AudioFormatTag, AudioOutputFormatImpl } from \"./AudioOutputFormat\";\nimport { PullAudioOutputStreamImpl } from \"./AudioOutputStream\";\n\nconst MediaDurationPlaceholderSeconds = 60 * 30;\n\nconst AudioFormatToMimeType: INumberDictionary<string> = {\n    [AudioFormatTag.PCM]: \"audio/wav\",\n    [AudioFormatTag.MP3]: \"audio/mpeg\",\n    [AudioFormatTag.Opus]: \"audio/ogg\",\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.12.1\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): void {\n        if (this.privAudioBuffer !== undefined) {\n            this.privAudioBuffer.push(buffer);\n            this.updateSourceBuffer();\n        } else if (this.privAudioOutputStream !== undefined) {\n            this.privAudioOutputStream.write(buffer);\n            this.privBytesReceived += buffer.byteLength;\n        }\n    }\n\n    public close(): void {\n        this.privIsClosed = true;\n        if (this.privSourceBuffer !== undefined) {\n            this.handleSourceBufferUpdateEnd();\n        } else if (this.privAudioOutputStream !== undefined) {\n            let receivedAudio = new ArrayBuffer(this.privBytesReceived);\n            this.privAudioOutputStream.read(receivedAudio);\n            if (this.privFormat.hasHeader) {\n                receivedAudio = SynthesisAdapterBase.addHeader(receivedAudio, this.privFormat);\n            }\n            const audioBlob = new Blob([receivedAudio], { type: AudioFormatToMimeType[this.privFormat.formatTag] });\n            this.privAudio.src = window.URL.createObjectURL(audioBlob);\n            this.notifyPlayback();\n        }\n    }\n\n    set format(format: AudioStreamFormat) {\n        if (typeof (AudioContext) !== \"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                // tslint:disable-next-line:no-console\n                console.warn(\n                    `Unknown mimeType for format ${AudioFormatTag[this.privFormat.formatTag]}.`);\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 = (event: Event): void => {\n                    this.privMediaSourceOpened = true;\n                    this.privMediaSource.duration = MediaDurationPlaceholderSeconds;\n                    this.privSourceBuffer = this.privMediaSource.addSourceBuffer(mimeType);\n                    this.privSourceBuffer.onupdate = (_: Event) => {\n                        this.updateSourceBuffer();\n                    };\n                    this.privSourceBuffer.onupdateend = (_: Event) => {\n                        this.handleSourceBufferUpdateEnd();\n                    };\n                    this.privSourceBuffer.onupdatestart = (_: Event) => {\n                        this.privAppendingToBuffer = false;\n                    };\n                };\n                this.updateSourceBuffer();\n            } else {\n                // tslint: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 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(): void {\n        if (this.privIsPaused && this.privAudio !== undefined) {\n            this.privAudio.play();\n            this.privIsPaused = false;\n        }\n    }\n\n    public onAudioEnd: (sender: IPlayer) => void;\n\n    public get internalAudio(): HTMLAudioElement {\n        return this.privAudio;\n    }\n\n    private updateSourceBuffer(): 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                // tslint:disable-next-line:no-console\n                console.log(\n                    \"buffer filled, pausing addition of binaries until space is made\");\n                return;\n            }\n            this.notifyPlayback();\n        } else if (this.canEndStream()) {\n            this.handleSourceBufferUpdateEnd();\n        }\n    }\n\n    private handleSourceBufferUpdateEnd(): void {\n        if (this.canEndStream() && this.sourceBufferAvailable()) {\n            this.privMediaSource.endOfStream();\n            this.notifyPlayback();\n        }\n    }\n\n    private notifyPlayback(): void {\n        if (!this.privPlaybackStarted && this.privAudio !== undefined) {\n            this.privAudio.onended = (): void => {\n                if (!!this.onAudioEnd) {\n                    this.onAudioEnd(this);\n                }\n            };\n            if (!this.privIsPaused) {\n                this.privAudio.play();\n            }\n            this.privPlaybackStarted = true;\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"]}