appendBuffer by chunks using a readable stream

Open
#387 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
15/100
Issue type
Feature
Clarity
Needs clarification
Activity status
Stale

Research direction

This is a usage question, not a scoped change: no comments, no linked PR, no reproduction repository, and nothing filed for almost three years. Start from the public appendBuffer entry point in src/mp4box.js and the segmentation API (setSegmentOptions, initializeSegmentation, onSegment) to see what fileStart/offset discipline the library expects, then compare it with the reporter's 10 MB batching loop and their MediaSource SourceBuffer appends. 'Done' would be a minimal readable-stream reproduction, or a documented streaming example if maintainers decide the API should accept chunks directly.

Written by the indexing model from the issue text.

Description

question

Hello,
I'm having problems playing video files from a readable stream. I did a simple test using the Fetch API and it works fine, but in my case I'm dealing with large encrypted files that are decrypted on the fly using rcloneInstance.File.createReadStream(fetchStreamFactory(vid_URL)) from rclone-js , which returns a readable stream. I think the issue might be how I'm handling the appending of the arrayBuffer. Looking at the logs I can see that the file info is successfully extracted and the MIMECodec is also correctly identified, but the video does not play.

My code:


async function playFile(vid_URL) {
  const mp4boxfile = MP4Box.createFile();
  const mediaSource = new MediaSource();
  let mimeCodec;
  let sourceBuffer;

  mp4boxfile.onReady = function(info) {
    console.log(info)
    let videpCoded = info.videoTracks[0].codec
    let trackCodec = info.audioTracks[0].codec
    mimeCodec = `video/mp4; codecs="${trackCodec} , ${videpCoded}"`

    const videoTracks = info.tracks.filter(track => track.type === "video");
    console.log('Video tracks filtered', { videoTracks });
    var trackIndex = 0

    if (videoTracks[trackIndex]) {
      const track = videoTracks[trackIndex];
      // start segmentation
      console.log('Segment options set', { trackId: track.id, nbSamples: 1000 });
      const options = { nbSamples: 1000}
      mp4boxfile.setSegmentOptions(track.id, options)
      const initSegs = mp4boxfile.initializeSegmentation()
      sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);
      sourceBuffer.addEventListener("updateend", onInitAppended);

      initSegs.forEach((initSeg) => {
            console.log('Appended initialization segment', { initSeg }, initSeg);
            sourceBuffer.appendBuffer(initSeg.buffer);
      });
      mp4boxfile.start();
    }; 
  }

  mp4boxfile.onSegment = function (id, user, buffer) {
    console.log("Received segment on track "+id+" for object "+user+" with a length of "+buffer.byteLength);
    console.log('Received segment', { trackId: id, bufferByteLength: buffer.byteLength });
    if (sourceBuffer) {
      if (!sourceBuffer.updating) {
        sourceBuffer.appendBuffer(buffer);
        console.log('Appended media segment to SourceBuffer', { buffer });
      }
    }
  };

  function onSourceClose(e) {
    console.log("MediaSource closed!", "MSE closed");
  }

  function onInitAppended(e) {
    console.log('updateend event after appending init segment', { sourceBuffer });
    console.log(mediaSource.readyState, "open", "MSE opened after init append")
  }

  async function onSourceOpen(e) {
    const opts = {
      start: 0,
      end: undefined, 
      chunkSize: 1024 * 1024 * 4, 
    }
    
    // returns a readable stream
    const decryptedStream = await rcloneInstance.File.createReadStream(fetchStreamFactory(vid_URL), opts)

    const CHUNK_SIZE_THRESHOLD = 1024 * 1024 * 10;
    
    let offset = 0;
    let accumulatedBuffers = [];

    // process chunks
    for await (const chunk of decryptedStream) {
      const arrayBuffer = chunk;
      accumulatedBuffers.push(arrayBuffer);

      // Check if accumulated buffers size exceeds the threshold
      const accumulatedSize = accumulatedBuffers.reduce((acc, buf) => acc + buf.byteLength, 0);
      if (accumulatedSize >= CHUNK_SIZE_THRESHOLD) {
          // Concatenate accumulated buffers
          const concatenatedBuffer = concatenateArrayBuffers(accumulatedBuffers);
          console.log("concatenatedBuffer : " + concatenatedBuffer.byteLength)

          concatenatedBuffer.fileStart = offset;
          offset += concatenatedBuffer.byteLength; // update offset
          mp4boxfile.appendBuffer(concatenatedBuffer);
          console.log('Appended ArrayBuffer to MP4Box file', { offset: offset });
          // Reset accumulated buffers and offset
          accumulatedBuffers = [];
      }
    }

    // Append any remaining buffers
    if (accumulatedBuffers.length > 0) {
      const concatenatedBuffer = concatenateArrayBuffers(accumulatedBuffers);
      concatenatedBuffer.fileStart = offset;
      offset += concatenatedBuffer.byteLength;
      mp4boxfile.appendBuffer(concatenatedBuffer);
    }

  }

  mediaSource.addEventListener("sourceopen", onSourceOpen);
  mediaSource.addEventListener("sourceclose", onSourceClose);
  const blobUrl = URL.createObjectURL(mediaSource);
  setSrc(blobUrl); // sets URL for <video src={src} controls />
};

function concatenateArrayBuffers(chunks) {
  const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
  const concatenatedBuffer = new Uint8Array(totalLength);
  let offset = 0;
  for (const chunk of chunks) {
      concatenatedBuffer.set(chunk, offset);
      offset += chunk.length;
  }
  return concatenatedBuffer.buffer;
}
Dominant language
TypeScript
Stars
2.5k
Forks
390
Avg merge
1d 17h
Merged PRs (30d)
8

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from gpac/mp4box.js

All issues in gpac/mp4box.js

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.