{"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"]}