UNPKG

@voice-ping/cognitive-services-speech

Version:

VoicePing Cognitive Services Speech SDK for JavaScript forked from Microsoft

1 lines 9.34 kB
{"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"]}