appendBuffer by chunks using a readable stream
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
- Tech stack
- javascript, typescript
- Domain
- audio-video-rtc
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
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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from gpac/mp4box.js
-
mp4box.js — 20-byte `sgpd` box with large `entry_count` causes unbounded loop and heap OOM abortOpen
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
Maintainers usually reply within 1 day
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 32/100
gpac/mp4box.js#551 · 1 comment ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 45/100
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 48/100
gpac/mp4box.js#528 · 2 comments ·
Maintainers usually reply within 1 day
-
bug
Difficulty 3/5 1-2 days Newbie friendliness 40/100
Maintainers usually reply within 1 day
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
pending close triaging
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
anomalyco/opencode#62582 · 1 comment ·
Maintainers usually reply within 1 day
-
feature request
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
callstack/react-native-paper#5189 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
live-codes/livecodes#1079 · 1 comment ·
Maintainers usually reply within 1 day
-
Add: ESP 2 ROOpenapproved check:passed streams:add
Difficulty 2/5 1-3 hours Newbie friendliness 66/100
Maintainers usually reply within 1 day