UNPKG

microsoft-cognitiveservices-speech-sdk

Version:
1 lines 13.9 kB
{"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.\r\n// Licensed under the MIT license.\r\n\r\nimport {\r\n BackgroundEvent,\r\n createNoDashGuid,\r\n Events,\r\n IAudioDestination,\r\n INumberDictionary\r\n} from \"../../common/Exports.js\";\r\nimport { AudioStreamFormat, IPlayer } from \"../Exports.js\";\r\nimport { AudioOutputFormatImpl } from \"./AudioOutputFormat.js\";\r\nimport { PullAudioOutputStreamImpl } from \"./AudioOutputStream.js\";\r\nimport { AudioFormatTag } from \"./AudioStreamFormat.js\";\r\n\r\nconst MediaDurationPlaceholderSeconds = 60 * 30;\r\n\r\nconst AudioFormatToMimeType: INumberDictionary<string> = {\r\n [AudioFormatTag.PCM]: \"audio/wav\",\r\n [AudioFormatTag.MuLaw]: \"audio/x-wav\",\r\n [AudioFormatTag.MP3]: \"audio/mpeg\",\r\n [AudioFormatTag.OGG_OPUS]: \"audio/ogg\",\r\n [AudioFormatTag.WEBM_OPUS]: \"audio/webm; codecs=opus\",\r\n [AudioFormatTag.ALaw]: \"audio/x-wav\",\r\n [AudioFormatTag.FLAC]: \"audio/flac\",\r\n [AudioFormatTag.AMR_WB]: \"audio/amr-wb\",\r\n [AudioFormatTag.G722]: \"audio/G722\",\r\n};\r\n\r\n/**\r\n * Represents the speaker playback audio destination, which only works in browser.\r\n * Note: the SDK will try to use <a href=\"https://www.w3.org/TR/media-source/\">Media Source Extensions</a> to play audio.\r\n * Mp3 format has better supports on Microsoft Edge, Chrome and Safari (desktop), so, it's better to specify mp3 format for playback.\r\n * @class SpeakerAudioDestination\r\n * Updated in version 1.17.0\r\n */\r\nexport class SpeakerAudioDestination implements IAudioDestination, IPlayer {\r\n private readonly privId: string;\r\n private privFormat: AudioOutputFormatImpl;\r\n private privAudio: HTMLAudioElement;\r\n private privMediaSource: MediaSource;\r\n private privSourceBuffer: SourceBuffer;\r\n private privPlaybackStarted: boolean = false;\r\n private privAudioBuffer: ArrayBuffer[];\r\n private privAppendingToBuffer: boolean = false;\r\n private privMediaSourceOpened: boolean = false;\r\n private privIsClosed: boolean;\r\n private privIsPaused: boolean;\r\n private privAudioOutputStream: PullAudioOutputStreamImpl;\r\n private privBytesReceived: number = 0;\r\n\r\n public constructor(audioDestinationId?: string) {\r\n this.privId = audioDestinationId ? audioDestinationId : createNoDashGuid();\r\n this.privIsPaused = false;\r\n this.privIsClosed = false;\r\n }\r\n\r\n public id(): string {\r\n return this.privId;\r\n }\r\n\r\n public write(buffer: ArrayBuffer, cb?: () => void, err?: (error: string) => void): void {\r\n if (this.privAudioBuffer !== undefined) {\r\n this.privAudioBuffer.push(buffer);\r\n this.updateSourceBuffer().then((): void => {\r\n if (!!cb) {\r\n cb();\r\n }\r\n }, (error: string): void => {\r\n if (!!err) {\r\n err(error);\r\n }\r\n });\r\n } else if (this.privAudioOutputStream !== undefined) {\r\n this.privAudioOutputStream.write(buffer);\r\n this.privBytesReceived += buffer.byteLength;\r\n }\r\n }\r\n\r\n public close(cb?: () => void, err?: (error: string) => void): void {\r\n this.privIsClosed = true;\r\n if (this.privSourceBuffer !== undefined) {\r\n this.handleSourceBufferUpdateEnd().then((): void => {\r\n if (!!cb) {\r\n cb();\r\n }\r\n }, (error: string): void => {\r\n if (!!err) {\r\n err(error);\r\n }\r\n });\r\n } else if (this.privAudioOutputStream !== undefined && typeof window !== \"undefined\") {\r\n if ((this.privFormat.formatTag === AudioFormatTag.PCM || this.privFormat.formatTag === AudioFormatTag.MuLaw\r\n || this.privFormat.formatTag === AudioFormatTag.ALaw) && this.privFormat.hasHeader === false) {\r\n // eslint-disable-next-line no-console\r\n console.warn(\"Play back is not supported for raw PCM, mulaw or alaw format without header.\");\r\n if (!!this.onAudioEnd) {\r\n this.onAudioEnd(this);\r\n }\r\n } else {\r\n let receivedAudio = new ArrayBuffer(this.privBytesReceived);\r\n this.privAudioOutputStream.read(receivedAudio).then((): void => {\r\n receivedAudio = this.privFormat.addHeader(receivedAudio);\r\n const audioBlob = new Blob([receivedAudio], { type: AudioFormatToMimeType[this.privFormat.formatTag] });\r\n this.privAudio.src = window.URL.createObjectURL(audioBlob);\r\n this.notifyPlayback().then((): void => {\r\n if (!!cb) {\r\n cb();\r\n }\r\n }, (error: string): void => {\r\n if (!!err) {\r\n err(error);\r\n }\r\n });\r\n }, (error: string): void => {\r\n if (!!err) {\r\n err(error);\r\n }\r\n });\r\n }\r\n } else {\r\n // unsupported format, call onAudioEnd directly.\r\n if (!!this.onAudioEnd) {\r\n this.onAudioEnd(this);\r\n }\r\n }\r\n }\r\n\r\n public set format(format: AudioStreamFormat) {\r\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\r\n if (typeof (AudioContext) !== \"undefined\" || (typeof (window) !== \"undefined\" && typeof ((window as any).webkitAudioContext) !== \"undefined\")) {\r\n this.privFormat = format as AudioOutputFormatImpl;\r\n const mimeType: string = AudioFormatToMimeType[this.privFormat.formatTag];\r\n if (mimeType === undefined) {\r\n // eslint-disable-next-line no-console\r\n console.warn(\r\n `Unknown mimeType for format ${AudioFormatTag[this.privFormat.formatTag]}; playback is not supported.`);\r\n\r\n } else if (typeof (MediaSource) !== \"undefined\" && MediaSource.isTypeSupported(mimeType)) {\r\n this.privAudio = new Audio();\r\n this.privAudioBuffer = [];\r\n this.privMediaSource = new MediaSource();\r\n this.privAudio.src = URL.createObjectURL(this.privMediaSource);\r\n this.privAudio.load();\r\n this.privMediaSource.onsourceopen = (): void => {\r\n this.privMediaSourceOpened = true;\r\n this.privMediaSource.duration = MediaDurationPlaceholderSeconds;\r\n this.privSourceBuffer = this.privMediaSource.addSourceBuffer(mimeType);\r\n this.privSourceBuffer.onupdate = (): void => {\r\n this.updateSourceBuffer().catch((reason: string): void => {\r\n Events.instance.onEvent(new BackgroundEvent(reason));\r\n });\r\n };\r\n this.privSourceBuffer.onupdateend = (): void => {\r\n this.handleSourceBufferUpdateEnd().catch((reason: string): void => {\r\n Events.instance.onEvent(new BackgroundEvent(reason));\r\n });\r\n };\r\n this.privSourceBuffer.onupdatestart = (): void => {\r\n this.privAppendingToBuffer = false;\r\n };\r\n };\r\n this.updateSourceBuffer().catch((reason: string): void => {\r\n Events.instance.onEvent(new BackgroundEvent(reason));\r\n });\r\n\r\n } else {\r\n // eslint-disable-next-line no-console\r\n console.warn(\r\n `Format ${AudioFormatTag[this.privFormat.formatTag]} could not be played by MSE, streaming playback is not enabled.`);\r\n this.privAudioOutputStream = new PullAudioOutputStreamImpl();\r\n this.privAudioOutputStream.format = this.privFormat;\r\n this.privAudio = new Audio();\r\n }\r\n }\r\n }\r\n\r\n public get volume(): number {\r\n return this.privAudio?.volume ?? -1;\r\n }\r\n\r\n public set volume(volume: number) {\r\n if (!!this.privAudio) {\r\n this.privAudio.volume = volume;\r\n }\r\n }\r\n\r\n public mute(): void {\r\n if (!!this.privAudio) {\r\n this.privAudio.muted = true;\r\n }\r\n }\r\n\r\n public unmute(): void {\r\n if (!!this.privAudio) {\r\n this.privAudio.muted = false;\r\n }\r\n }\r\n\r\n public get isClosed(): boolean {\r\n return this.privIsClosed;\r\n }\r\n\r\n public get currentTime(): number {\r\n if (this.privAudio !== undefined) {\r\n return this.privAudio.currentTime;\r\n }\r\n return -1;\r\n }\r\n\r\n public pause(): void {\r\n if (!this.privIsPaused && this.privAudio !== undefined) {\r\n this.privAudio.pause();\r\n this.privIsPaused = true;\r\n }\r\n }\r\n\r\n public resume(cb?: () => void, err?: (error: string) => void): void {\r\n if (this.privIsPaused && this.privAudio !== undefined) {\r\n this.privAudio.play().then((): void => {\r\n if (!!cb) {\r\n cb();\r\n }\r\n }, (error: string): void => {\r\n if (!!err) {\r\n err(error);\r\n }\r\n });\r\n this.privIsPaused = false;\r\n }\r\n }\r\n\r\n public onAudioStart: (sender: IPlayer) => void;\r\n\r\n public onAudioEnd: (sender: IPlayer) => void;\r\n\r\n public get internalAudio(): HTMLAudioElement {\r\n return this.privAudio;\r\n }\r\n\r\n private async updateSourceBuffer(): Promise<void> {\r\n if (this.privAudioBuffer !== undefined && (this.privAudioBuffer.length > 0) && this.sourceBufferAvailable()) {\r\n this.privAppendingToBuffer = true;\r\n const binary = this.privAudioBuffer.shift();\r\n try {\r\n this.privSourceBuffer.appendBuffer(binary);\r\n } catch (error) {\r\n this.privAudioBuffer.unshift(binary);\r\n // eslint-disable-next-line no-console\r\n console.log(\r\n \"buffer filled, pausing addition of binaries until space is made\");\r\n return;\r\n }\r\n await this.notifyPlayback();\r\n } else if (this.canEndStream()) {\r\n await this.handleSourceBufferUpdateEnd();\r\n }\r\n }\r\n\r\n private async handleSourceBufferUpdateEnd(): Promise<void> {\r\n if (this.canEndStream() && this.sourceBufferAvailable()) {\r\n this.privMediaSource.endOfStream();\r\n await this.notifyPlayback();\r\n }\r\n }\r\n\r\n private async notifyPlayback(): Promise<void> {\r\n if (!this.privPlaybackStarted && this.privAudio !== undefined) {\r\n this.privPlaybackStarted = true;\r\n if (!!this.onAudioStart) {\r\n this.onAudioStart(this);\r\n }\r\n this.privAudio.onended = (): void => {\r\n if (!!this.onAudioEnd) {\r\n this.onAudioEnd(this);\r\n }\r\n };\r\n if (!this.privIsPaused) {\r\n await this.privAudio.play();\r\n }\r\n }\r\n }\r\n\r\n private canEndStream(): boolean {\r\n return (this.isClosed && this.privSourceBuffer !== undefined && (this.privAudioBuffer.length === 0)\r\n && this.privMediaSourceOpened && !this.privAppendingToBuffer && this.privMediaSource.readyState === \"open\");\r\n }\r\n\r\n private sourceBufferAvailable(): boolean {\r\n return (this.privSourceBuffer !== undefined && !this.privSourceBuffer.updating);\r\n }\r\n}\r\n"]}