UNPKG

@euirim/microsoft-cognitiveservices-speech-sdk

Version:
115 lines (113 loc) 5.04 kB
// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. import { PromiseHelper, } from "../common/Exports"; export class ReplayableAudioNode { constructor(audioSource, format) { this.privBuffers = []; this.privReplayOffset = 0; this.privLastShrinkOffset = 0; this.privBufferStartOffset = 0; this.privBufferSerial = 0; this.privBufferedBytes = 0; this.privReplay = false; this.privLastChunkAcquiredTime = 0; this.id = () => { return this.privAudioNode.id(); }; this.privAudioNode = audioSource; this.privFormat = format; } // Reads and returns the next chunk of audio buffer. // If replay of existing buffers are needed, read() will first seek and replay // existing content, and upoin completion it will read new content from the underlying // audio node, saving that content into the replayable buffers. read() { // if there is a replay request to honor. if (!!this.privReplay && this.privBuffers.length !== 0) { // Find the start point in the buffers. // Offsets are in 100ns increments. // So how many bytes do we need to seek to get the right offset? const offsetToSeek = this.privReplayOffset - this.privBufferStartOffset; let bytesToSeek = Math.round(offsetToSeek * this.privFormat.avgBytesPerSec * 1e-7); if (0 !== (bytesToSeek % 2)) { bytesToSeek++; } let i = 0; while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].chunk.buffer.byteLength) { bytesToSeek -= this.privBuffers[i++].chunk.buffer.byteLength; } const retVal = this.privBuffers[i].chunk.buffer.slice(bytesToSeek); this.privReplayOffset += (retVal.byteLength / this.privFormat.avgBytesPerSec) * 1e+7; // If we've reached the end of the buffers, stop replaying. if (i === this.privBuffers.length - 1) { this.privReplay = false; } return PromiseHelper.fromResult({ buffer: retVal, isEnd: false, timeReceived: this.privBuffers[i].chunk.timeReceived, }); } return this.privAudioNode.read() .onSuccessContinueWith((result) => { if (result.buffer) { this.privBuffers.push(new BufferEntry(result, this.privBufferSerial++, this.privBufferedBytes)); this.privBufferedBytes += result.buffer.byteLength; } return result; }); } detach() { this.privAudioNode.detach(); this.privBuffers = undefined; } replay() { if (this.privBuffers && 0 !== this.privBuffers.length) { this.privReplay = true; this.privReplayOffset = this.privLastShrinkOffset; } } // Shrinks the existing audio buffers to start at the new offset, or at the // beginning of the buffer closest to the requested offset. // A replay request will start from the last shrink point. shrinkBuffers(offset) { this.privLastShrinkOffset = offset; // Find the start point in the buffers. // Offsets are in 100ns increments. // So how many bytes do we need to seek to get the right offset? const offsetToSeek = offset - this.privBufferStartOffset; let bytesToSeek = Math.round(offsetToSeek * this.privFormat.avgBytesPerSec * 1e-7); let i = 0; while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].chunk.buffer.byteLength) { bytesToSeek -= this.privBuffers[i++].chunk.buffer.byteLength; } this.privBufferStartOffset = Math.round(offset - ((bytesToSeek / this.privFormat.avgBytesPerSec) * 1e+7)); this.privBuffers = this.privBuffers.slice(i); } // Finds the time a buffer of audio was first seen by offset. findTimeAtOffset(offset) { if (offset < this.privBufferStartOffset) { return 0; } for (const value of this.privBuffers) { const startOffset = (value.byteOffset / this.privFormat.avgBytesPerSec) * 1e7; const endOffset = startOffset + ((value.chunk.buffer.byteLength / this.privFormat.avgBytesPerSec) * 1e7); if (offset >= startOffset && offset <= endOffset) { return value.chunk.timeReceived; } } return 0; } } // Primary use of this class is to help debugging problems with the replay // code. If the memory cost of alloc / dealloc gets too much, drop it and just use // the ArrayBuffer directly. // tslint:disable-next-line:max-classes-per-file class BufferEntry { constructor(chunk, serial, byteOffset) { this.chunk = chunk; this.serial = serial; this.byteOffset = byteOffset; } } //# sourceMappingURL=ReplayableAudioNode.js.map