UNPKG

microsoft-cognitiveservices-speech-sdk

Version:
1 lines 7.78 kB
{"version":3,"sources":["src/sdk/Audio/BaseAudioPlayer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAIlD;;;;GAIG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,qBAAqB,CAAU;IACvC,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;OAIG;gBACgB,WAAW,CAAC,EAAE,iBAAiB;IAOlD;;;OAGG;IACI,eAAe,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAkBvG;;OAEG;IACI,SAAS,CAAC,EAAE,CAAC,EAAE,MAAM,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAkBtE,OAAO,CAAC,IAAI;IAKZ,OAAO,CAAC,wBAAwB;IAUhC,OAAO,CAAC,kBAAkB;IAY1B,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,iBAAiB;YAmCX,SAAS;CAY1B","file":"BaseAudioPlayer.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\r\n// Licensed under the MIT license.\r\n\r\nimport { InvalidOperationError } from \"../../common/Error.js\";\r\nimport { AudioStreamFormat } from \"../Exports.js\";\r\nimport { AudioStreamFormatImpl } from \"./AudioStreamFormat.js\";\r\n\r\ntype AudioDataTypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array;\r\n/**\r\n * Base audio player class\r\n * TODO: Plays only PCM for now.\r\n * @class\r\n */\r\nexport class BaseAudioPlayer {\r\n\r\n private audioContext: AudioContext = null;\r\n private gainNode: GainNode = null;\r\n private audioFormat: AudioStreamFormatImpl;\r\n private autoUpdateBufferTimer: any = 0;\r\n private samples: Float32Array;\r\n private startTime: number;\r\n\r\n /**\r\n * Creates and initializes an instance of this class.\r\n * @constructor\r\n * @param {AudioStreamFormat} audioFormat audio stream format recognized by the player.\r\n */\r\n public constructor(audioFormat?: AudioStreamFormat) {\r\n if (audioFormat === undefined) {\r\n audioFormat = AudioStreamFormat.getDefaultInputFormat();\r\n }\r\n this.init(audioFormat);\r\n }\r\n\r\n /**\r\n * play Audio sample\r\n * @param newAudioData audio data to be played.\r\n */\r\n public playAudioSample(newAudioData: ArrayBuffer, cb?: () => void, err?: (error: string) => void): void {\r\n try {\r\n this.ensureInitializedContext();\r\n const audioData = this.formatAudioData(newAudioData);\r\n const newSamplesData = new Float32Array(this.samples.length + audioData.length);\r\n newSamplesData.set(this.samples, 0);\r\n newSamplesData.set(audioData, this.samples.length);\r\n this.samples = newSamplesData;\r\n if (!!cb) {\r\n cb();\r\n }\r\n } catch (e) {\r\n if (!!err) {\r\n err(e as string);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * stops audio and clears the buffers\r\n */\r\n public stopAudio(cb?: () => void, err?: (error: string) => void): void {\r\n if (this.audioContext !== null) {\r\n this.samples = new Float32Array();\r\n // eslint-disable-next-line @typescript-eslint/no-unsafe-argument\r\n clearInterval(this.autoUpdateBufferTimer);\r\n this.audioContext.close().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.audioContext = null;\r\n }\r\n }\r\n\r\n private init(audioFormat: AudioStreamFormat): void {\r\n this.audioFormat = audioFormat as AudioStreamFormatImpl;\r\n this.samples = new Float32Array();\r\n }\r\n\r\n private ensureInitializedContext(): void {\r\n if (this.audioContext === null) {\r\n this.createAudioContext();\r\n const timerPeriod = 200;\r\n this.autoUpdateBufferTimer = setInterval((): void => {\r\n this.updateAudioBuffer();\r\n }, timerPeriod);\r\n }\r\n }\r\n\r\n private createAudioContext(): void {\r\n // new ((window as any).AudioContext || (window as any).webkitAudioContext)();\r\n this.audioContext = AudioStreamFormatImpl.getAudioContext();\r\n\r\n // TODO: Various examples shows this gain node, it does not seem to be needed unless we plan\r\n // to control the volume, not likely\r\n this.gainNode = this.audioContext.createGain();\r\n this.gainNode.gain.value = 1;\r\n this.gainNode.connect(this.audioContext.destination);\r\n this.startTime = this.audioContext.currentTime;\r\n }\r\n\r\n private formatAudioData(audioData: ArrayBuffer): Float32Array {\r\n switch (this.audioFormat.bitsPerSample) {\r\n case 8:\r\n return this.formatArrayBuffer(new Int8Array(audioData), 128);\r\n case 16:\r\n return this.formatArrayBuffer(new Int16Array(audioData), 32768);\r\n case 32:\r\n return this.formatArrayBuffer(new Int32Array(audioData), 2147483648);\r\n default:\r\n throw new InvalidOperationError(\"Only WAVE_FORMAT_PCM (8/16/32 bps) format supported at this time\");\r\n }\r\n }\r\n\r\n private formatArrayBuffer(audioData: AudioDataTypedArray, maxValue: number): Float32Array {\r\n const float32Data = new Float32Array(audioData.length);\r\n for (let i = 0; i < audioData.length; i++) {\r\n float32Data[i] = audioData[i] / maxValue;\r\n }\r\n return float32Data;\r\n }\r\n\r\n private updateAudioBuffer(): void {\r\n if (this.samples.length === 0) {\r\n return;\r\n }\r\n\r\n const channelCount = this.audioFormat.channels;\r\n const bufferSource = this.audioContext.createBufferSource();\r\n const frameCount = this.samples.length / channelCount;\r\n const audioBuffer = this.audioContext.createBuffer(channelCount, frameCount, this.audioFormat.samplesPerSec);\r\n\r\n // TODO: Should we do the conversion in the pushAudioSample instead?\r\n for (let channel = 0; channel < channelCount; channel++) {\r\n // Fill in individual channel data\r\n let channelOffset = channel;\r\n const audioData = audioBuffer.getChannelData(channel);\r\n for (let i = 0; i < this.samples.length; i++, channelOffset += channelCount) {\r\n audioData[i] = this.samples[channelOffset];\r\n }\r\n }\r\n\r\n if (this.startTime < this.audioContext.currentTime) {\r\n this.startTime = this.audioContext.currentTime;\r\n }\r\n\r\n bufferSource.buffer = audioBuffer;\r\n bufferSource.connect(this.gainNode);\r\n bufferSource.start(this.startTime);\r\n\r\n // Make sure we play the next sample after the current one.\r\n this.startTime += audioBuffer.duration;\r\n\r\n // Clear the samples for the next pushed data.\r\n this.samples = new Float32Array();\r\n }\r\n\r\n private async playAudio(audioData: ArrayBuffer): Promise<void> {\r\n if (this.audioContext === null) {\r\n this.createAudioContext();\r\n }\r\n const source: AudioBufferSourceNode = this.audioContext.createBufferSource();\r\n const destination: AudioDestinationNode = this.audioContext.destination;\r\n await this.audioContext.decodeAudioData(audioData, (newBuffer: AudioBuffer): void => {\r\n source.buffer = newBuffer;\r\n source.connect(destination);\r\n source.start(0);\r\n });\r\n }\r\n}\r\n"]}