Compare commits

..
2 Commits
Author SHA1 Message Date
Vanilagy bb9554df60 Handle ISOBMFF boxes with no size 2025-08-20 15:12:25 +02:00
Vanilagy f4b97c1731 Make mfra box lookup more resilient 2025-08-20 14:33:26 +02:00
9 changed files with 59 additions and 19 deletions
+1 -3
View File
@@ -24,9 +24,7 @@
chunked: true, chunked: true,
chunkSize: 2**20 chunkSize: 2**20
}); });
const outputFormat = new Mediabunny.AdtsOutputFormat({ const outputFormat = new Mediabunny.Mp4OutputFormat({});
onFrame: console.log
});
const button = document.createElement('button'); const button = document.createElement('button');
button.textContent = 'Cancel'; button.textContent = 'Cancel';
+2
View File
@@ -21,6 +21,8 @@
for await (const packet of sink.packets()) { for await (const packet of sink.packets()) {
console.log(packet); console.log(packet);
//if (packet.timestamp > 135) break;
} }
/* /*
+6 -6
View File
@@ -1,12 +1,12 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"version": "1.9.1", "version": "1.9.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mediabunny", "name": "mediabunny",
"version": "1.9.1", "version": "1.9.3",
"license": "MPL-2.0", "license": "MPL-2.0",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@@ -5900,9 +5900,9 @@
} }
}, },
"node_modules/mediabunny": { "node_modules/mediabunny": {
"version": "1.9.0", "version": "1.9.2",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.9.0.tgz", "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.9.2.tgz",
"integrity": "sha512-pNviaYemNRjrIbx1K1+Q1xcCbD1PRVp6i1+0GXJf5NPVeHUVJfJY2xkhtBjbd4LpbqCTqRoSPYxcrOmIRt14bw==", "integrity": "sha512-U3RKZVvU2O7vXVtPf6Q+4XNulkV6fdVOVYMm7V2YTr6PqyqrvUl1eUCTtcrmQESgRi9sFZeXkUR8ln5TS48M2g==",
"license": "MPL-2.0", "license": "MPL-2.0",
"peer": true, "peer": true,
"workspaces": [ "workspaces": [
@@ -9017,7 +9017,7 @@
}, },
"packages/mp3-encoder": { "packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"version": "1.9.1", "version": "1.9.3",
"license": "MPL-2.0", "license": "MPL-2.0",
"devDependencies": { "devDependencies": {
"@types/emscripten": "^1.40.1" "@types/emscripten": "^1.40.1"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.9.1", "version": "1.9.3",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.9.1", "version": "1.9.3",
"description": "MP3 encoder extension for Mediabunny, based on LAME.", "description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs", "main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs", "module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
+2
View File
@@ -50,6 +50,8 @@ export class AdtsMuxer extends Muxer {
const release = await this.mutex.acquire(); const release = await this.mutex.acquire();
try { try {
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
if (!this.audioSpecificConfig) { if (!this.audioSpecificConfig) {
validateAudioChunkMetadata(meta); validateAudioChunkMetadata(meta);
+39 -7
View File
@@ -257,6 +257,9 @@ export class IsobmffDemuxer extends Demuxer {
); );
const startPos = this.metadataReader.pos; const startPos = this.metadataReader.pos;
const boxInfo = this.metadataReader.readBoxHeader(); const boxInfo = this.metadataReader.readBoxHeader();
if (!boxInfo) {
break;
}
if (boxInfo.name === 'ftyp') { if (boxInfo.name === 'ftyp') {
const majorBrand = this.metadataReader.readAscii(4); const majorBrand = this.metadataReader.readAscii(4);
@@ -291,14 +294,20 @@ export class IsobmffDemuxer extends Demuxer {
const lastWord = this.metadataReader.readU32(); const lastWord = this.metadataReader.readU32();
const potentialMfraPos = sourceSize - lastWord; const potentialMfraPos = sourceSize - lastWord;
if (potentialMfraPos >= 0 && potentialMfraPos < sourceSize) { if (potentialMfraPos >= 0 && potentialMfraPos <= sourceSize - MAX_BOX_HEADER_SIZE) {
await this.metadataReader.reader.loadRange(potentialMfraPos, sourceSize); // Load the header and a bit more, likely covering the entire box
await this.metadataReader.reader.loadRange(potentialMfraPos, potentialMfraPos + 2 ** 16);
this.metadataReader.pos = potentialMfraPos; this.metadataReader.pos = potentialMfraPos;
const boxInfo = this.metadataReader.readBoxHeader(); const boxInfo = this.metadataReader.readBoxHeader();
if (boxInfo.name === 'mfra') { if (boxInfo && boxInfo.name === 'mfra') {
// We found the mfra box, allowing for much better random access. Let's parse it: // We found the mfra box, allowing for much better random access. Let's parse it.
await this.metadataReader.reader.loadRange(
potentialMfraPos,
potentialMfraPos + boxInfo.totalSize,
);
this.readContiguousBoxes(boxInfo.contentSize); this.readContiguousBoxes(boxInfo.contentSize);
} }
} }
@@ -456,7 +465,7 @@ export class IsobmffDemuxer extends Demuxer {
); );
const moofBoxInfo = this.metadataReader.readBoxHeader(); const moofBoxInfo = this.metadataReader.readBoxHeader();
assert(moofBoxInfo.name === 'moof'); assert(moofBoxInfo?.name === 'moof');
const contentStart = this.metadataReader.pos; const contentStart = this.metadataReader.pos;
await this.metadataReader.reader.loadRange(contentStart, contentStart + moofBoxInfo.contentSize); await this.metadataReader.reader.loadRange(contentStart, contentStart + moofBoxInfo.contentSize);
@@ -502,7 +511,7 @@ export class IsobmffDemuxer extends Demuxer {
let nextFragmentIsFirstFragment = this.metadataReader.pos === 0; let nextFragmentIsFirstFragment = this.metadataReader.pos === 0;
while (this.metadataReader.pos < startPos) { while (this.metadataReader.pos <= startPos - MIN_BOX_HEADER_SIZE) {
if (currentFragment?.nextFragment) { if (currentFragment?.nextFragment) {
currentFragment = currentFragment.nextFragment; currentFragment = currentFragment.nextFragment;
this.metadataReader.pos = currentFragment.moofOffset + currentFragment.moofSize; this.metadataReader.pos = currentFragment.moofOffset + currentFragment.moofSize;
@@ -513,6 +522,9 @@ export class IsobmffDemuxer extends Demuxer {
); );
const startPos = this.metadataReader.pos; const startPos = this.metadataReader.pos;
const boxInfo = this.metadataReader.readBoxHeader(); const boxInfo = this.metadataReader.readBoxHeader();
if (!boxInfo) {
break;
}
if (boxInfo.name === 'moof') { if (boxInfo.name === 'moof') {
const index = binarySearchExact(this.fragments, startPos, x => x.moofOffset); const index = binarySearchExact(this.fragments, startPos, x => x.moofOffset);
@@ -562,13 +574,21 @@ export class IsobmffDemuxer extends Demuxer {
const startIndex = this.metadataReader.pos; const startIndex = this.metadataReader.pos;
while (this.metadataReader.pos - startIndex <= totalSize - MIN_BOX_HEADER_SIZE) { while (this.metadataReader.pos - startIndex <= totalSize - MIN_BOX_HEADER_SIZE) {
this.traverseBox(); const foundBox = this.traverseBox();
if (!foundBox) {
break;
}
} }
} }
traverseBox() { traverseBox() {
const startPos = this.metadataReader.pos; const startPos = this.metadataReader.pos;
const boxInfo = this.metadataReader.readBoxHeader(); const boxInfo = this.metadataReader.readBoxHeader();
if (!boxInfo) {
return false;
}
const boxEndPos = startPos + boxInfo.totalSize; const boxEndPos = startPos + boxInfo.totalSize;
switch (boxInfo.name) { switch (boxInfo.name) {
@@ -819,6 +839,10 @@ export class IsobmffDemuxer extends Demuxer {
for (let i = 0; i < entries; i++) { for (let i = 0; i < entries; i++) {
const startPos = this.metadataReader.pos; const startPos = this.metadataReader.pos;
const sampleBoxInfo = this.metadataReader.readBoxHeader(); const sampleBoxInfo = this.metadataReader.readBoxHeader();
if (!sampleBoxInfo) {
break;
}
const lowercaseBoxName = sampleBoxInfo.name.toLowerCase(); const lowercaseBoxName = sampleBoxInfo.name.toLowerCase();
if (track.info.type === 'video') { if (track.info.type === 'video') {
@@ -1898,6 +1922,7 @@ export class IsobmffDemuxer extends Demuxer {
} }
this.metadataReader.pos = boxEndPos; this.metadataReader.pos = boxEndPos;
return true;
} }
} }
@@ -2318,8 +2343,12 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
/** Looks for a packet in the fragments while trying to load as few fragments as possible to retrieve it. */ /** Looks for a packet in the fragments while trying to load as few fragments as possible to retrieve it. */
private async performFragmentedLookup( private async performFragmentedLookup(
// This function returns the best-matching sample that is currently loaded. Based on this information, we know
// which fragments we need to load to find the actual match.
getBestMatch: () => { fragmentIndex: number; sampleIndex: number; correctSampleFound: boolean }, getBestMatch: () => { fragmentIndex: number; sampleIndex: number; correctSampleFound: boolean },
// The timestamp with which we can search the lookup table
searchTimestamp: number, searchTimestamp: number,
// The timestamp for which we know the correct sample will not come after it
latestTimestamp: number, latestTimestamp: number,
options: PacketRetrievalOptions, options: PacketRetrievalOptions,
): Promise<EncodedPacket | null> { ): Promise<EncodedPacket | null> {
@@ -2391,6 +2420,9 @@ abstract class IsobmffTrackBacking implements InputTrackBacking {
await metadataReader.reader.loadRange(metadataReader.pos, metadataReader.pos + MAX_BOX_HEADER_SIZE); await metadataReader.reader.loadRange(metadataReader.pos, metadataReader.pos + MAX_BOX_HEADER_SIZE);
const startPos = metadataReader.pos; const startPos = metadataReader.pos;
const boxInfo = metadataReader.readBoxHeader(); const boxInfo = metadataReader.readBoxHeader();
if (!boxInfo) {
break;
}
if (boxInfo.name === 'moof') { if (boxInfo.name === 'moof') {
const index = binarySearchExact(demuxer.fragments, startPos, x => x.moofOffset); const index = binarySearchExact(demuxer.fragments, startPos, x => x.moofOffset);
+6 -1
View File
@@ -132,6 +132,11 @@ export class IsobmffReader {
headerSize = 16; headerSize = 16;
} }
return { name, totalSize, headerSize, contentSize: totalSize - headerSize }; const contentSize = totalSize - headerSize;
if (contentSize < 0) {
return null; // Hardly a box is it
}
return { name, totalSize, headerSize, contentSize };
} }
} }
+1
View File
@@ -760,6 +760,7 @@ export class MatroskaDemuxer extends Demuxer {
while (reader.pos - startIndex <= totalSize - MIN_HEADER_SIZE) { while (reader.pos - startIndex <= totalSize - MIN_HEADER_SIZE) {
const foundElement = this.traverseElement(reader); const foundElement = this.traverseElement(reader);
if (!foundElement) { if (!foundElement) {
break; break;
} }