@euirim/microsoft-cognitiveservices-speech-sdk
Version:
Microsoft Cognitive Services Speech SDK for JavaScript
115 lines (113 loc) • 5.04 kB
JavaScript
// 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