UNPKG

@euirim/microsoft-cognitiveservices-speech-sdk

Version:
1 lines 6.76 kB
{"version":3,"sources":["src/common.browser/ReplayableAudioNode.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EACH,gBAAgB,EAChB,YAAY,EACZ,OAAO,EAEV,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,mBAAoB,YAAW,gBAAgB;IACxD,OAAO,CAAC,aAAa,CAAmB;IACxC,OAAO,CAAC,UAAU,CAAwB;IAC1C,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,oBAAoB,CAAa;IACzC,OAAO,CAAC,qBAAqB,CAAa;IAC1C,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,yBAAyB,CAAa;gBAE3B,WAAW,EAAE,gBAAgB,EAAE,MAAM,EAAE,qBAAqB;IAKxE,EAAE,eAER;IAMM,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IA6C1C,MAAM,IAAI,IAAI;IAKd,MAAM,IAAI,IAAI;IAUd,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAoBnC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAgBlD","file":"ReplayableAudioNode.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT license.\n\nimport { AudioStreamFormatImpl } from \"../../src/sdk/Audio/AudioStreamFormat\";\nimport {\n IAudioStreamNode,\n IStreamChunk,\n Promise,\n PromiseHelper,\n} from \"../common/Exports\";\n\nexport class ReplayableAudioNode implements IAudioStreamNode {\n private privAudioNode: IAudioStreamNode;\n private privFormat: AudioStreamFormatImpl;\n private privBuffers: BufferEntry[] = [];\n private privReplayOffset: number = 0;\n private privLastShrinkOffset: number = 0;\n private privBufferStartOffset: number = 0;\n private privBufferSerial: number = 0;\n private privBufferedBytes: number = 0;\n private privReplay: boolean = false;\n private privLastChunkAcquiredTime: number = 0;\n\n public constructor(audioSource: IAudioStreamNode, format: AudioStreamFormatImpl) {\n this.privAudioNode = audioSource;\n this.privFormat = format;\n }\n\n public id = (): string => {\n return this.privAudioNode.id();\n }\n\n // Reads and returns the next chunk of audio buffer.\n // If replay of existing buffers are needed, read() will first seek and replay\n // existing content, and upoin completion it will read new content from the underlying\n // audio node, saving that content into the replayable buffers.\n public read(): Promise<IStreamChunk<ArrayBuffer>> {\n // if there is a replay request to honor.\n if (!!this.privReplay && this.privBuffers.length !== 0) {\n // Find the start point in the buffers.\n // Offsets are in 100ns increments.\n // So how many bytes do we need to seek to get the right offset?\n const offsetToSeek: number = this.privReplayOffset - this.privBufferStartOffset;\n\n let bytesToSeek: number = Math.round(offsetToSeek * this.privFormat.avgBytesPerSec * 1e-7);\n if (0 !== (bytesToSeek % 2)) {\n bytesToSeek++;\n }\n\n let i: number = 0;\n\n while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].chunk.buffer.byteLength) {\n bytesToSeek -= this.privBuffers[i++].chunk.buffer.byteLength;\n }\n\n const retVal: ArrayBuffer = this.privBuffers[i].chunk.buffer.slice(bytesToSeek);\n\n this.privReplayOffset += (retVal.byteLength / this.privFormat.avgBytesPerSec) * 1e+7;\n\n // If we've reached the end of the buffers, stop replaying.\n if (i === this.privBuffers.length - 1) {\n this.privReplay = false;\n }\n\n return PromiseHelper.fromResult<IStreamChunk<ArrayBuffer>>({\n buffer: retVal,\n isEnd: false,\n timeReceived: this.privBuffers[i].chunk.timeReceived,\n });\n }\n\n return this.privAudioNode.read()\n .onSuccessContinueWith((result: IStreamChunk<ArrayBuffer>) => {\n if (result.buffer) {\n this.privBuffers.push(new BufferEntry(result, this.privBufferSerial++, this.privBufferedBytes));\n this.privBufferedBytes += result.buffer.byteLength;\n }\n return result;\n });\n }\n\n public detach(): void {\n this.privAudioNode.detach();\n this.privBuffers = undefined;\n }\n\n public replay(): void {\n if (this.privBuffers && 0 !== this.privBuffers.length) {\n this.privReplay = true;\n this.privReplayOffset = this.privLastShrinkOffset;\n }\n }\n\n // Shrinks the existing audio buffers to start at the new offset, or at the\n // beginning of the buffer closest to the requested offset.\n // A replay request will start from the last shrink point.\n public shrinkBuffers(offset: number): void {\n this.privLastShrinkOffset = offset;\n\n // Find the start point in the buffers.\n // Offsets are in 100ns increments.\n // So how many bytes do we need to seek to get the right offset?\n const offsetToSeek: number = offset - this.privBufferStartOffset;\n\n let bytesToSeek: number = Math.round(offsetToSeek * this.privFormat.avgBytesPerSec * 1e-7);\n\n let i: number = 0;\n\n while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].chunk.buffer.byteLength) {\n bytesToSeek -= this.privBuffers[i++].chunk.buffer.byteLength;\n }\n this.privBufferStartOffset = Math.round(offset - ((bytesToSeek / this.privFormat.avgBytesPerSec) * 1e+7));\n this.privBuffers = this.privBuffers.slice(i);\n }\n\n // Finds the time a buffer of audio was first seen by offset.\n public findTimeAtOffset(offset: number): number {\n if (offset < this.privBufferStartOffset) {\n return 0;\n }\n\n for (const value of this.privBuffers) {\n const startOffset: number = (value.byteOffset / this.privFormat.avgBytesPerSec) * 1e7;\n const endOffset: number = startOffset + ((value.chunk.buffer.byteLength / this.privFormat.avgBytesPerSec) * 1e7);\n\n if (offset >= startOffset && offset <= endOffset) {\n return value.chunk.timeReceived;\n }\n }\n\n return 0;\n }\n}\n\n// Primary use of this class is to help debugging problems with the replay\n// code. If the memory cost of alloc / dealloc gets too much, drop it and just use\n// the ArrayBuffer directly.\n// tslint:disable-next-line:max-classes-per-file\nclass BufferEntry {\n public chunk: IStreamChunk<ArrayBuffer>;\n public serial: number;\n public byteOffset: number;\n\n public constructor(chunk: IStreamChunk<ArrayBuffer>, serial: number, byteOffset: number) {\n this.chunk = chunk;\n this.serial = serial;\n this.byteOffset = byteOffset;\n }\n}\n"]}