{"version":3,"sources":["src/common.browser/ReplayableAudioNode.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,gBAAgB,EAChB,YAAY,EACf,MAAM,sBAAsB,CAAC;AAE9B,qBAAa,mBAAoB,YAAW,gBAAgB;IACxD,OAAO,CAAC,aAAa,CAAmB;IACxC,OAAO,CAAC,kBAAkB,CAAS;IACnC,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,cAAc,EAAE,MAAM;IAKjE,EAAE,IAAI,MAAM;IAQZ,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;IA+C1C,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvB,MAAM,IAAI,IAAI;IAUd,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAwBnC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;CAgBlD","file":"ReplayableAudioNode.d.ts","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved.\r\n// Licensed under the MIT license.\r\n\r\n// eslint-disable-next-line max-classes-per-file\r\nimport {\r\n    IAudioStreamNode,\r\n    IStreamChunk,\r\n} from \"../common/Exports.js\";\r\n\r\nexport class ReplayableAudioNode implements IAudioStreamNode {\r\n    private privAudioNode: IAudioStreamNode;\r\n    private privBytesPerSecond: number;\r\n    private privBuffers: BufferEntry[] = [];\r\n    private privReplayOffset: number = 0;\r\n    private privLastShrinkOffset: number = 0;\r\n    private privBufferStartOffset: number = 0;\r\n    private privBufferSerial: number = 0;\r\n    private privBufferedBytes: number = 0;\r\n    private privReplay: boolean = false;\r\n    private privLastChunkAcquiredTime: number = 0;\r\n\r\n    public constructor(audioSource: IAudioStreamNode, bytesPerSecond: number) {\r\n        this.privAudioNode = audioSource;\r\n        this.privBytesPerSecond = bytesPerSecond;\r\n    }\r\n\r\n    public id(): string {\r\n        return this.privAudioNode.id();\r\n    }\r\n\r\n    // Reads and returns the next chunk of audio buffer.\r\n    // If replay of existing buffers are needed, read() will first seek and replay\r\n    // existing content, and upoin completion it will read new content from the underlying\r\n    // audio node, saving that content into the replayable buffers.\r\n    public read(): Promise<IStreamChunk<ArrayBuffer>> {\r\n        // if there is a replay request to honor.\r\n        if (!!this.privReplay && this.privBuffers.length !== 0) {\r\n            // Find the start point in the buffers.\r\n            // Offsets are in 100ns increments.\r\n            // So how many bytes do we need to seek to get the right offset?\r\n            const offsetToSeek: number = this.privReplayOffset - this.privBufferStartOffset;\r\n\r\n            let bytesToSeek: number = Math.round(offsetToSeek * this.privBytesPerSecond * 1e-7);\r\n            if (0 !== (bytesToSeek % 2)) {\r\n                bytesToSeek++;\r\n            }\r\n\r\n            let i: number = 0;\r\n\r\n            while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].chunk.buffer.byteLength) {\r\n                bytesToSeek -= this.privBuffers[i++].chunk.buffer.byteLength;\r\n            }\r\n\r\n            if (i < this.privBuffers.length) {\r\n                const retVal: ArrayBuffer = this.privBuffers[i].chunk.buffer.slice(bytesToSeek);\r\n\r\n                this.privReplayOffset += (retVal.byteLength / this.privBytesPerSecond) * 1e+7;\r\n\r\n                // If we've reached the end of the buffers, stop replaying.\r\n                if (i === this.privBuffers.length - 1) {\r\n                    this.privReplay = false;\r\n                }\r\n\r\n                return Promise.resolve<IStreamChunk<ArrayBuffer>>({\r\n                    buffer: retVal,\r\n                    isEnd: false,\r\n                    timeReceived: this.privBuffers[i].chunk.timeReceived,\r\n                });\r\n            }\r\n        }\r\n\r\n        return this.privAudioNode.read()\r\n            .then((result: IStreamChunk<ArrayBuffer>): IStreamChunk<ArrayBuffer> => {\r\n                if (result && result.buffer && this.privBuffers) {\r\n                    this.privBuffers.push(new BufferEntry(result, this.privBufferSerial++, this.privBufferedBytes));\r\n                    this.privBufferedBytes += result.buffer.byteLength;\r\n                }\r\n                return result;\r\n            });\r\n    }\r\n\r\n    public detach(): Promise<void> {\r\n        this.privBuffers = undefined;\r\n        return this.privAudioNode.detach();\r\n    }\r\n\r\n    public replay(): void {\r\n        if (this.privBuffers && 0 !== this.privBuffers.length) {\r\n            this.privReplay = true;\r\n            this.privReplayOffset = this.privLastShrinkOffset;\r\n        }\r\n    }\r\n\r\n    // Shrinks the existing audio buffers to start at the new offset, or at the\r\n    // beginning of the buffer closest to the requested offset.\r\n    // A replay request will start from the last shrink point.\r\n    public shrinkBuffers(offset: number): void {\r\n        if (this.privBuffers === undefined || this.privBuffers.length === 0) {\r\n            return;\r\n        }\r\n\r\n        this.privLastShrinkOffset = offset;\r\n\r\n        // Find the start point in the buffers.\r\n        // Offsets are in 100ns increments.\r\n        // So how many bytes do we need to seek to get the right offset?\r\n        const offsetToSeek: number = offset - this.privBufferStartOffset;\r\n\r\n        let bytesToSeek: number = Math.round(offsetToSeek * this.privBytesPerSecond * 1e-7);\r\n\r\n        let i: number = 0;\r\n\r\n        while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].chunk.buffer.byteLength) {\r\n            bytesToSeek -= this.privBuffers[i++].chunk.buffer.byteLength;\r\n        }\r\n        this.privBufferStartOffset = Math.round(offset - ((bytesToSeek / this.privBytesPerSecond) * 1e+7));\r\n        this.privBuffers = this.privBuffers.slice(i);\r\n    }\r\n\r\n    // Finds the time a buffer of audio was first seen by offset.\r\n    public findTimeAtOffset(offset: number): number {\r\n        if (offset < this.privBufferStartOffset || this.privBuffers === undefined) {\r\n            return 0;\r\n        }\r\n\r\n        for (const value of this.privBuffers) {\r\n            const startOffset: number = (value.byteOffset / this.privBytesPerSecond) * 1e7;\r\n            const endOffset: number = startOffset + ((value.chunk.buffer.byteLength / this.privBytesPerSecond) * 1e7);\r\n\r\n            if (offset >= startOffset && offset <= endOffset) {\r\n                return value.chunk.timeReceived;\r\n            }\r\n        }\r\n\r\n        return 0;\r\n    }\r\n}\r\n\r\n// Primary use of this class is to help debugging problems with the replay\r\n// code. If the memory cost of alloc / dealloc gets too much, drop it and just use\r\n// the ArrayBuffer directly.\r\nclass BufferEntry {\r\n    public chunk: IStreamChunk<ArrayBuffer>;\r\n    public serial: number;\r\n    public byteOffset: number;\r\n\r\n    public constructor(chunk: IStreamChunk<ArrayBuffer>, serial: number, byteOffset: number) {\r\n        this.chunk = chunk;\r\n        this.serial = serial;\r\n        this.byteOffset = byteOffset;\r\n    }\r\n}\r\n"]}