@hahnpro/ms-speech-sdk
Version:
Microsoft Cognitive Services Speech SDK for JavaScript
1 lines • 13.3 kB
Source Map (JSON)
{"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"]}