Get partial range of ReadableStream

825 views Asked by At

I want to return a specific byte range given a ReadableStream. It sounds easy but I can't find any way to skip through or read a specific amount of bytes from the stream since we can only read chunks. I would prefer to not store any data and just send the stream. It sounds like I could do this with a TransformStream and PipeThrough(). But I need some help wrapping my head around it.

Example: Given a ReadableStream of say 1000 bytes, I want to return another stream that starts at byte 300 and ends at byte 900.

I know this can easily be done with nodes createReadableStream(), but I need to run this in the browser so it cant use node.

2

There are 2 answers

0
Domenic On BEST ANSWER

Here's some skeleton code to get you started:

function byteRangeTransform(start, end) {
  let bytesSeen = 0;
  return new TransformStream({
    transform(chunk, controller) {
      const chunkStart = bytesSeen;
      const chunkEnd = bytesSeen + chunk.byteLength;
      bytesSeen += chunk.byteLength;
      
      // Six cases:

      // 1. Chunk entirely before start
      if (chunkEnd < start) {
        return;
      }
      
      // 2. Chunk starts before start, ends between start and end
      if (chunkStart < start && chunkEnd >= start && chunkEnd <= end) {
        const slice = /* TODO */;
        controller.enqueue(slice);
        return;
      }
      
      // 3. Chunk starts before start, ends after end
      if (chunkStart < start && chunkEnd > end) {
        // TODO
      }

      // 4. Chunk starts after start, ends between start and end
      // 5. Chunk starts after start, ends after end
      // 6. Chunk starts after end
    }
  });
}

const onlyInRange = originalReadable.pipeThrough(byteRangeTransform(300, 900));
0
Sơn Trần-Nguyễn On

Deno has recently added such TransformStream to its std library: https://deno.land/[email protected]/streams/buffer.ts?source#L247

class ByteSliceStream extends TransformStream<Uint8Array, Uint8Array> {
  #offsetStart = 0;
  #offsetEnd = 0;

  constructor(start = 0, end = Infinity) {
    super({
      start: () => {
        assert(start >= 0, "`start` must be greater than 0");
        end += 1;
      },
      transform: (chunk, controller) => {
        this.#offsetStart = this.#offsetEnd;
        this.#offsetEnd += chunk.byteLength;
        if (this.#offsetEnd > start) {
          if (this.#offsetStart < start) {
            chunk = chunk.slice(start - this.#offsetStart);
          }
          if (this.#offsetEnd >= end) {
            chunk = chunk.slice(0, chunk.byteLength - this.#offsetEnd + end);
            controller.enqueue(chunk);
            controller.terminate();
          } else {
            controller.enqueue(chunk);
          }
        }
      },
    });
  }
}