/** * @license * Copyright 2022-2026 Matter.js Authors * SPDX-License-Identifier: Apache-2.0 */ import { Bytes, DataReader } from "#util/index.js"; /** * A stream reader that reads bytes from a stream and provides a way to skip bytes. */ export class BytesStreamReader { #streamReader: ReadableStreamDefaultReader; #dataReader?: DataReader; #streamEndReached = false; constructor(streamReader: ReadableStreamDefaultReader) { this.#streamReader = streamReader; } async *read(chunkSize: number) { while (true) { if (this.#streamEndReached) { // End of the stream already reached, so there is no more data // This case can happen when bytes were skipped return; } // No data or not enough data available, read more data from the stream if (this.#dataReader === undefined || this.#dataReader.remainingBytesCount < chunkSize) { const { value, done } = await this.#streamReader.read(); if (value === undefined || done) { // We are done and have no more data to read, send out the rest if there is any if (this.#dataReader !== undefined) { yield this.#dataReader.remainingBytes; } break; } if (this.#dataReader !== undefined) { this.#dataReader = new DataReader(Bytes.concat(this.#dataReader.remainingBytes, value)); } else { this.#dataReader = new DataReader(value); } } // If we have enough data available, just return the next chunk if (this.#dataReader !== undefined && this.#dataReader.remainingBytesCount >= chunkSize) { yield this.#dataReader.readByteArray(chunkSize); if (this.#dataReader !== undefined && this.#dataReader.remainingBytesCount === 0) { this.#dataReader = undefined; } } } } /** * Tries to skip the desired number of bytes, returns the actual number of skipped bytes. * This skip() method should be called when no read() chunk is also awaited! */ async skip(desiredSkippedBytes: number): Promise { if (this.#streamEndReached) { // End of the stream already reached, so there is no more data // This case can happen when bytes were skipped return 0; } // We have enough data to just skip the bytes if (this.#dataReader !== undefined && this.#dataReader.remainingBytesCount >= desiredSkippedBytes) { this.#dataReader.offset = this.#dataReader.offset + desiredSkippedBytes; if (this.#dataReader.remainingBytesCount === 0) { this.#dataReader = undefined; } return desiredSkippedBytes; } // Otherwise, loop through the stream until we have enough data to skip let skippedBytes = desiredSkippedBytes - (this.#dataReader?.remainingBytesCount ?? 0); this.#dataReader = undefined; while (true) { const { value, done } = await this.#streamReader.read(); if (value === undefined || done) { this.#streamEndReached = true; // No more data to skip, return the number of bytes we could skip return desiredSkippedBytes - skippedBytes; } if (value.byteLength <= skippedBytes) { // The current chunk is too small (or exactly the necessary length), handle that case skippedBytes -= value.byteLength; if (skippedBytes === 0) { return desiredSkippedBytes; } continue; } // We got more data than needed, so remember partial data this.#dataReader = new DataReader(value); this.#dataReader.offset = skippedBytes; return desiredSkippedBytes; } } }