mediabunny 1.52.3 → 1.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundles/mediabunny.cjs +966 -396
- package/dist/bundles/mediabunny.min.cjs +9 -9
- package/dist/bundles/mediabunny.min.mjs +9 -9
- package/dist/bundles/mediabunny.mjs +966 -396
- package/dist/bundles/mediabunny.node.cjs +966 -396
- package/dist/mediabunny.d.ts +26 -2
- package/dist/modules/src/adts/adts-muxer.d.ts.map +1 -1
- package/dist/modules/src/adts/adts-muxer.js +4 -1
- package/dist/modules/src/codec.d.ts +2 -2
- package/dist/modules/src/codec.d.ts.map +1 -1
- package/dist/modules/src/codec.js +10 -2
- package/dist/modules/src/conversion.js +2 -2
- package/dist/modules/src/encode.d.ts.map +1 -1
- package/dist/modules/src/encode.js +16 -4
- package/dist/modules/src/flac/flac-muxer.d.ts +1 -0
- package/dist/modules/src/flac/flac-muxer.d.ts.map +1 -1
- package/dist/modules/src/flac/flac-muxer.js +58 -34
- package/dist/modules/src/hls/hls-muxer.d.ts +17 -2
- package/dist/modules/src/hls/hls-muxer.d.ts.map +1 -1
- package/dist/modules/src/hls/hls-muxer.js +205 -97
- package/dist/modules/src/input.d.ts.map +1 -1
- package/dist/modules/src/input.js +4 -2
- package/dist/modules/src/isobmff/isobmff-boxes.d.ts +1 -1
- package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-boxes.js +6 -4
- package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +1 -0
- package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-demuxer.js +65 -54
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts +9 -3
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-muxer.js +125 -66
- package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-muxer.js +15 -3
- package/dist/modules/src/media-sink.d.ts.map +1 -1
- package/dist/modules/src/media-sink.js +3 -11
- package/dist/modules/src/media-source.d.ts.map +1 -1
- package/dist/modules/src/media-source.js +18 -6
- package/dist/modules/src/mp3/mp3-demuxer.d.ts +2 -0
- package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-demuxer.js +12 -0
- package/dist/modules/src/mp3/mp3-muxer.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-muxer.js +89 -11
- package/dist/modules/src/mp3/mp3-writer.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-writer.js +2 -1
- package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
- package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +4 -0
- package/dist/modules/src/mpeg-ts/mpeg-ts-muxer.js +2 -2
- package/dist/modules/src/ogg/ogg-muxer.d.ts.map +1 -1
- package/dist/modules/src/ogg/ogg-muxer.js +7 -1
- package/dist/modules/src/output-format.d.ts.map +1 -1
- package/dist/modules/src/output-format.js +13 -5
- package/dist/modules/src/output.d.ts +25 -1
- package/dist/modules/src/output.d.ts.map +1 -1
- package/dist/modules/src/output.js +24 -0
- package/dist/modules/src/sample.d.ts.map +1 -1
- package/dist/modules/src/sample.js +120 -55
- package/dist/modules/src/segmented-input.js +36 -8
- package/dist/modules/src/source.d.ts +2 -1
- package/dist/modules/src/source.d.ts.map +1 -1
- package/dist/modules/src/source.js +166 -52
- package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
- package/dist/modules/src/wave/wave-muxer.d.ts.map +1 -1
- package/dist/modules/src/wave/wave-muxer.js +15 -2
- package/package.json +1 -1
- package/src/adts/adts-muxer.ts +6 -1
- package/src/codec.ts +22 -2
- package/src/conversion.ts +2 -2
- package/src/encode.ts +14 -4
- package/src/flac/flac-muxer.ts +72 -39
- package/src/hls/hls-muxer.ts +261 -104
- package/src/input.ts +5 -2
- package/src/isobmff/isobmff-boxes.ts +8 -4
- package/src/isobmff/isobmff-demuxer.ts +70 -57
- package/src/isobmff/isobmff-muxer.ts +162 -76
- package/src/matroska/matroska-muxer.ts +29 -6
- package/src/media-sink.ts +3 -12
- package/src/media-source.ts +18 -6
- package/src/mp3/mp3-demuxer.ts +14 -0
- package/src/mp3/mp3-muxer.ts +105 -11
- package/src/mp3/mp3-writer.ts +3 -1
- package/src/mpeg-ts/mpeg-ts-demuxer.ts +5 -0
- package/src/mpeg-ts/mpeg-ts-muxer.ts +2 -2
- package/src/ogg/ogg-muxer.ts +8 -1
- package/src/output-format.ts +15 -5
- package/src/output.ts +48 -1
- package/src/sample.ts +160 -67
- package/src/segmented-input.ts +44 -8
- package/src/source.ts +208 -71
- package/src/wave/wave-muxer.ts +20 -2
|
@@ -2166,7 +2166,7 @@ var Mediabunny = (() => {
|
|
|
2166
2166
|
var HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/;
|
|
2167
2167
|
var VP9_CODEC_STRING_REGEX = /^vp09(?:\.\d{2}){3}(?:(?:\.\d{2}){5})?$/;
|
|
2168
2168
|
var AV1_CODEC_STRING_REGEX = /^av01\.\d\.\d{2}[MH]\.\d{2}(?:\.\d\.\d{3}\.\d{2}\.\d{2}\.\d{2}\.\d)?$/;
|
|
2169
|
-
var validateVideoChunkMetadata = (metadata) => {
|
|
2169
|
+
var validateVideoChunkMetadata = (metadata, trackCodec) => {
|
|
2170
2170
|
if (!metadata) {
|
|
2171
2171
|
throw new TypeError("Video chunk metadata must be provided.");
|
|
2172
2172
|
}
|
|
@@ -2285,6 +2285,11 @@ var Mediabunny = (() => {
|
|
|
2285
2285
|
);
|
|
2286
2286
|
}
|
|
2287
2287
|
}
|
|
2288
|
+
if (trackCodec !== null && inferCodecFromCodecString(metadata.decoderConfig.codec) !== trackCodec) {
|
|
2289
|
+
throw new TypeError(
|
|
2290
|
+
`Video chunk metadata decoder configuration codec string '${metadata.decoderConfig.codec}' does not fit to the track codec '${trackCodec}'.`
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2288
2293
|
};
|
|
2289
2294
|
var VALID_AUDIO_CODEC_STRING_PREFIXES = [
|
|
2290
2295
|
"mp4a",
|
|
@@ -2298,7 +2303,7 @@ var Mediabunny = (() => {
|
|
|
2298
2303
|
"ac-3",
|
|
2299
2304
|
"ec-3"
|
|
2300
2305
|
];
|
|
2301
|
-
var validateAudioChunkMetadata = (metadata) => {
|
|
2306
|
+
var validateAudioChunkMetadata = (metadata, trackCodec) => {
|
|
2302
2307
|
if (!metadata) {
|
|
2303
2308
|
throw new TypeError("Audio chunk metadata must be provided.");
|
|
2304
2309
|
}
|
|
@@ -2392,6 +2397,11 @@ var Mediabunny = (() => {
|
|
|
2392
2397
|
);
|
|
2393
2398
|
}
|
|
2394
2399
|
}
|
|
2400
|
+
if (trackCodec !== null && inferCodecFromCodecString(metadata.decoderConfig.codec) !== trackCodec) {
|
|
2401
|
+
throw new TypeError(
|
|
2402
|
+
`Audio chunk metadata decoder configuration codec string '${metadata.decoderConfig.codec}' does not fit to the track codec '${trackCodec}'.`
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2395
2405
|
};
|
|
2396
2406
|
var validateSubtitleMetadata = (metadata) => {
|
|
2397
2407
|
if (!metadata) {
|
|
@@ -5579,6 +5589,7 @@ var Mediabunny = (() => {
|
|
|
5579
5589
|
return this.metadataPromise ??= (async () => {
|
|
5580
5590
|
let currentPos = 0;
|
|
5581
5591
|
let lookForMfraBox = false;
|
|
5592
|
+
let foundMovieBoxes = false;
|
|
5582
5593
|
while (true) {
|
|
5583
5594
|
let slice = this.reader.requestSliceRange(currentPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
|
|
5584
5595
|
if (slice instanceof Promise) slice = await slice;
|
|
@@ -5602,6 +5613,7 @@ var Mediabunny = (() => {
|
|
|
5602
5613
|
track.editListOffset -= Math.round(previousSegmentDurationsInSeconds * track.timescale);
|
|
5603
5614
|
}
|
|
5604
5615
|
lookForMfraBox = this.isFragmented && this.reader.fileSize !== null && this.reader.fileSize > startPos + boxInfo.totalSize;
|
|
5616
|
+
foundMovieBoxes = true;
|
|
5605
5617
|
break;
|
|
5606
5618
|
} else if (boxInfo.name === "moof") {
|
|
5607
5619
|
if (!this.input._initInput) {
|
|
@@ -5609,61 +5621,16 @@ var Mediabunny = (() => {
|
|
|
5609
5621
|
'"moof" box encountered with no "moov" box present; this file is likely a Segment as described in ISO/IEC 14496-12 Section 8.16. A separate init file that contains a "moov" box is required to read this file, please provide it using InputOptions.initInput.'
|
|
5610
5622
|
);
|
|
5611
5623
|
}
|
|
5612
|
-
|
|
5613
|
-
if (initDemuxer.constructor !== _IsobmffDemuxer) {
|
|
5614
|
-
throw new Error("Init input must match the input's format.");
|
|
5615
|
-
}
|
|
5616
|
-
await initDemuxer.readMetadata();
|
|
5617
|
-
this.movieTimescale = initDemuxer.movieTimescale;
|
|
5618
|
-
this.movieDurationInTimescale = initDemuxer.movieDurationInTimescale;
|
|
5619
|
-
this.metadataTags = initDemuxer.metadataTags;
|
|
5620
|
-
this.isFragmented = true;
|
|
5621
|
-
this.fragmentTrackDefaults = initDemuxer.fragmentTrackDefaults;
|
|
5622
|
-
this.psshBoxes = initDemuxer.psshBoxes;
|
|
5623
|
-
for (const foreignTrack of initDemuxer.tracks) {
|
|
5624
|
-
const track = {
|
|
5625
|
-
id: foreignTrack.id,
|
|
5626
|
-
demuxer: this,
|
|
5627
|
-
trackBacking: null,
|
|
5628
|
-
disposition: foreignTrack.disposition,
|
|
5629
|
-
timescale: foreignTrack.timescale,
|
|
5630
|
-
durationInMediaTimescale: foreignTrack.durationInMediaTimescale,
|
|
5631
|
-
durationInMovieTimescale: foreignTrack.durationInMovieTimescale,
|
|
5632
|
-
rotation: foreignTrack.rotation,
|
|
5633
|
-
internalCodecId: foreignTrack.internalCodecId,
|
|
5634
|
-
name: foreignTrack.name,
|
|
5635
|
-
languageCode: foreignTrack.languageCode,
|
|
5636
|
-
sampleTableByteOffset: null,
|
|
5637
|
-
sampleTable: null,
|
|
5638
|
-
fragmentLookupTable: [],
|
|
5639
|
-
currentFragmentState: null,
|
|
5640
|
-
fragmentPositionCache: [],
|
|
5641
|
-
editListPreviousSegmentDurations: foreignTrack.editListPreviousSegmentDurations,
|
|
5642
|
-
editListOffset: foreignTrack.editListOffset,
|
|
5643
|
-
encryptionInfo: foreignTrack.encryptionInfo,
|
|
5644
|
-
encryptionAuxInfo: null,
|
|
5645
|
-
frmaCodecString: null,
|
|
5646
|
-
info: foreignTrack.info
|
|
5647
|
-
};
|
|
5648
|
-
if (foreignTrack.trackBacking) {
|
|
5649
|
-
assert(track.info);
|
|
5650
|
-
if (track.info.type === "video" && track.info.width !== -1) {
|
|
5651
|
-
const videoTrack = track;
|
|
5652
|
-
track.trackBacking = new IsobmffVideoTrackBacking(videoTrack);
|
|
5653
|
-
this.tracks.push(track);
|
|
5654
|
-
} else if (track.info.type === "audio" && track.info.numberOfChannels !== -1) {
|
|
5655
|
-
const audioTrack = track;
|
|
5656
|
-
track.trackBacking = new IsobmffAudioTrackBacking(audioTrack);
|
|
5657
|
-
this.tracks.push(track);
|
|
5658
|
-
}
|
|
5659
|
-
} else {
|
|
5660
|
-
}
|
|
5661
|
-
}
|
|
5624
|
+
await this.copyMetadataFromInitInput(this.input._initInput);
|
|
5662
5625
|
lookForMfraBox = false;
|
|
5626
|
+
foundMovieBoxes = true;
|
|
5663
5627
|
break;
|
|
5664
5628
|
}
|
|
5665
5629
|
currentPos = startPos + boxInfo.totalSize;
|
|
5666
5630
|
}
|
|
5631
|
+
if (!foundMovieBoxes && this.input._initInput) {
|
|
5632
|
+
await this.copyMetadataFromInitInput(this.input._initInput);
|
|
5633
|
+
}
|
|
5667
5634
|
if (lookForMfraBox) {
|
|
5668
5635
|
assert(this.reader.fileSize !== null);
|
|
5669
5636
|
let lastWordSlice = this.reader.requestSlice(this.reader.fileSize - 4, 4);
|
|
@@ -5692,6 +5659,58 @@ var Mediabunny = (() => {
|
|
|
5692
5659
|
}
|
|
5693
5660
|
})();
|
|
5694
5661
|
}
|
|
5662
|
+
async copyMetadataFromInitInput(initInput) {
|
|
5663
|
+
const initDemuxer = await initInput._getDemuxer();
|
|
5664
|
+
if (initDemuxer.constructor !== _IsobmffDemuxer) {
|
|
5665
|
+
throw new Error("Init input must match the input's format.");
|
|
5666
|
+
}
|
|
5667
|
+
await initDemuxer.readMetadata();
|
|
5668
|
+
this.movieTimescale = initDemuxer.movieTimescale;
|
|
5669
|
+
this.movieDurationInTimescale = initDemuxer.movieDurationInTimescale;
|
|
5670
|
+
this.metadataTags = initDemuxer.metadataTags;
|
|
5671
|
+
this.isFragmented = true;
|
|
5672
|
+
this.fragmentTrackDefaults = initDemuxer.fragmentTrackDefaults;
|
|
5673
|
+
this.psshBoxes = initDemuxer.psshBoxes;
|
|
5674
|
+
for (const foreignTrack of initDemuxer.tracks) {
|
|
5675
|
+
const track = {
|
|
5676
|
+
id: foreignTrack.id,
|
|
5677
|
+
demuxer: this,
|
|
5678
|
+
trackBacking: null,
|
|
5679
|
+
disposition: foreignTrack.disposition,
|
|
5680
|
+
timescale: foreignTrack.timescale,
|
|
5681
|
+
durationInMediaTimescale: foreignTrack.durationInMediaTimescale,
|
|
5682
|
+
durationInMovieTimescale: foreignTrack.durationInMovieTimescale,
|
|
5683
|
+
rotation: foreignTrack.rotation,
|
|
5684
|
+
internalCodecId: foreignTrack.internalCodecId,
|
|
5685
|
+
name: foreignTrack.name,
|
|
5686
|
+
languageCode: foreignTrack.languageCode,
|
|
5687
|
+
sampleTableByteOffset: null,
|
|
5688
|
+
sampleTable: null,
|
|
5689
|
+
fragmentLookupTable: [],
|
|
5690
|
+
currentFragmentState: null,
|
|
5691
|
+
fragmentPositionCache: [],
|
|
5692
|
+
editListPreviousSegmentDurations: foreignTrack.editListPreviousSegmentDurations,
|
|
5693
|
+
editListOffset: foreignTrack.editListOffset,
|
|
5694
|
+
encryptionInfo: foreignTrack.encryptionInfo,
|
|
5695
|
+
encryptionAuxInfo: null,
|
|
5696
|
+
frmaCodecString: null,
|
|
5697
|
+
info: foreignTrack.info
|
|
5698
|
+
};
|
|
5699
|
+
if (foreignTrack.trackBacking) {
|
|
5700
|
+
assert(track.info);
|
|
5701
|
+
if (track.info.type === "video" && track.info.width !== -1) {
|
|
5702
|
+
const videoTrack = track;
|
|
5703
|
+
track.trackBacking = new IsobmffVideoTrackBacking(videoTrack);
|
|
5704
|
+
this.tracks.push(track);
|
|
5705
|
+
} else if (track.info.type === "audio" && track.info.numberOfChannels !== -1) {
|
|
5706
|
+
const audioTrack = track;
|
|
5707
|
+
track.trackBacking = new IsobmffAudioTrackBacking(audioTrack);
|
|
5708
|
+
this.tracks.push(track);
|
|
5709
|
+
}
|
|
5710
|
+
} else {
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5713
|
+
}
|
|
5695
5714
|
getSampleTableForTrack(internalTrack) {
|
|
5696
5715
|
if (internalTrack.sampleTable) {
|
|
5697
5716
|
return internalTrack.sampleTable;
|
|
@@ -11102,6 +11121,8 @@ var Mediabunny = (() => {
|
|
|
11102
11121
|
this.metadataPromise = null;
|
|
11103
11122
|
this.firstFrameHeader = null;
|
|
11104
11123
|
this.firstFrameHeaderPos = null;
|
|
11124
|
+
this.xingFrameHeader = null;
|
|
11125
|
+
this.xingFrameHeaderPos = null;
|
|
11105
11126
|
this.loadedSamples = [];
|
|
11106
11127
|
// All samples from the start of the file to lastLoadedPos
|
|
11107
11128
|
this.metadataTags = null;
|
|
@@ -11118,6 +11139,10 @@ var Mediabunny = (() => {
|
|
|
11118
11139
|
while (!this.firstFrameHeader && !this.lastSampleLoaded) {
|
|
11119
11140
|
await this.advanceReader();
|
|
11120
11141
|
}
|
|
11142
|
+
if (!this.firstFrameHeader && this.xingFrameHeader) {
|
|
11143
|
+
this.firstFrameHeader = this.xingFrameHeader;
|
|
11144
|
+
this.firstFrameHeaderPos = this.xingFrameHeaderPos;
|
|
11145
|
+
}
|
|
11121
11146
|
if (!this.firstFrameHeader) {
|
|
11122
11147
|
throw new Error("No valid MP3 frame found.");
|
|
11123
11148
|
}
|
|
@@ -11159,6 +11184,10 @@ var Mediabunny = (() => {
|
|
|
11159
11184
|
const word = readU32Be(slice);
|
|
11160
11185
|
const isXing = word === XING || word === INFO;
|
|
11161
11186
|
if (isXing) {
|
|
11187
|
+
if (!this.xingFrameHeader) {
|
|
11188
|
+
this.xingFrameHeader = header;
|
|
11189
|
+
this.xingFrameHeaderPos = result.startPos;
|
|
11190
|
+
}
|
|
11162
11191
|
if (!this.xingData) {
|
|
11163
11192
|
let xingDataSlice = this.reader.requestSlice(result.startPos + xingOffset + 4, 12);
|
|
11164
11193
|
if (xingDataSlice instanceof Promise) xingDataSlice = await xingDataSlice;
|
|
@@ -14157,6 +14186,9 @@ var Mediabunny = (() => {
|
|
|
14157
14186
|
throw new Error("No Program Map Table found in the file.");
|
|
14158
14187
|
}
|
|
14159
14188
|
for (const stream of this.elementaryStreams) {
|
|
14189
|
+
if (!stream.initialized) {
|
|
14190
|
+
continue;
|
|
14191
|
+
}
|
|
14160
14192
|
if (stream.info.type === "video") {
|
|
14161
14193
|
this.trackBackingEntries.push(
|
|
14162
14194
|
new MpegTsVideoTrackBacking(stream)
|
|
@@ -15619,6 +15651,7 @@ var Mediabunny = (() => {
|
|
|
15619
15651
|
this.packetInfos = /* @__PURE__ */ new WeakMap();
|
|
15620
15652
|
this.hydrationPromise = null;
|
|
15621
15653
|
this.firstInputTrack = null;
|
|
15654
|
+
this.firstSegment = null;
|
|
15622
15655
|
this.segmentedInput = segmentedInput;
|
|
15623
15656
|
this.decl = decl;
|
|
15624
15657
|
this.number = number;
|
|
@@ -15629,13 +15662,22 @@ var Mediabunny = (() => {
|
|
|
15629
15662
|
if (!this.segmentedInput.firstSegment) {
|
|
15630
15663
|
throw new Error("Missing first segment, can't retrieve track.");
|
|
15631
15664
|
}
|
|
15632
|
-
|
|
15633
|
-
|
|
15634
|
-
|
|
15665
|
+
let currentSegment = this.segmentedInput.firstSegment;
|
|
15666
|
+
let track = null;
|
|
15667
|
+
while (currentSegment) {
|
|
15668
|
+
const input = this.segmentedInput.getInputForSegment(currentSegment);
|
|
15669
|
+
const inputTracks = await input.getTracks();
|
|
15670
|
+
track = inputTracks.find((x) => x.type === this.decl.type && x.number === this.number) ?? null;
|
|
15671
|
+
if (track) {
|
|
15672
|
+
break;
|
|
15673
|
+
}
|
|
15674
|
+
currentSegment = await this.segmentedInput.getNextSegment(currentSegment, {});
|
|
15675
|
+
}
|
|
15635
15676
|
if (!track) {
|
|
15636
15677
|
throw new Error("No matching track found in underlying media data.");
|
|
15637
15678
|
}
|
|
15638
15679
|
this.firstInputTrack = track;
|
|
15680
|
+
this.firstSegment = currentSegment;
|
|
15639
15681
|
})();
|
|
15640
15682
|
}
|
|
15641
15683
|
getId() {
|
|
@@ -15724,13 +15766,28 @@ var Mediabunny = (() => {
|
|
|
15724
15766
|
}
|
|
15725
15767
|
async getFirstPacket(options) {
|
|
15726
15768
|
await this.hydrate();
|
|
15727
|
-
assert(this.segmentedInput.firstSegment);
|
|
15728
15769
|
assert(this.firstInputTrack);
|
|
15729
|
-
|
|
15730
|
-
|
|
15731
|
-
|
|
15770
|
+
assert(this.firstSegment);
|
|
15771
|
+
let currentTrack = this.firstInputTrack;
|
|
15772
|
+
let currentSegment = this.firstSegment;
|
|
15773
|
+
while (true) {
|
|
15774
|
+
if (currentTrack) {
|
|
15775
|
+
const packet = await currentTrack._backing.getFirstPacket(options);
|
|
15776
|
+
if (packet) {
|
|
15777
|
+
return this.createAdjustedPacket(packet, currentSegment, currentTrack);
|
|
15778
|
+
}
|
|
15779
|
+
}
|
|
15780
|
+
currentSegment = await this.segmentedInput.getNextSegment(currentSegment, {
|
|
15781
|
+
skipLiveWait: options.skipLiveWait
|
|
15782
|
+
});
|
|
15783
|
+
if (!currentSegment) {
|
|
15784
|
+
break;
|
|
15785
|
+
}
|
|
15786
|
+
const nextInput = this.segmentedInput.getInputForSegment(currentSegment);
|
|
15787
|
+
const nextTracks = await nextInput.getTracks();
|
|
15788
|
+
currentTrack = nextTracks.find((t) => t.type === this.firstInputTrack.type && t.number === this.firstInputTrack.number) ?? null;
|
|
15732
15789
|
}
|
|
15733
|
-
return
|
|
15790
|
+
return null;
|
|
15734
15791
|
}
|
|
15735
15792
|
getNextPacket(packet, options) {
|
|
15736
15793
|
return this._getNextInternal(packet, options, false);
|
|
@@ -16291,6 +16348,12 @@ var Mediabunny = (() => {
|
|
|
16291
16348
|
* @internal
|
|
16292
16349
|
*/
|
|
16293
16350
|
this._fileSizeDetermined = false;
|
|
16351
|
+
/**
|
|
16352
|
+
* When the server doesn't support range requests, we abandon the orchestrator and instead defer to an internal
|
|
16353
|
+
* ReadableStreamSource wrapping the response body, which pulls new data only when reads demand it.
|
|
16354
|
+
* @internal
|
|
16355
|
+
*/
|
|
16356
|
+
this._sequentialBacking = null;
|
|
16294
16357
|
this._url = url2;
|
|
16295
16358
|
this._options = options;
|
|
16296
16359
|
this._getRetryDelay = options.getRetryDelay ?? DEFAULT_RETRY_DELAY;
|
|
@@ -16334,7 +16397,7 @@ var Mediabunny = (() => {
|
|
|
16334
16397
|
if (!this._fileSizeDetermined) {
|
|
16335
16398
|
return this._length !== null ? this._length : void 0;
|
|
16336
16399
|
}
|
|
16337
|
-
const baseSize = this._orchestrator.fileSize;
|
|
16400
|
+
const baseSize = this._sequentialBacking ? this._sequentialBacking._endIndex : this._orchestrator.fileSize;
|
|
16338
16401
|
if (baseSize === null) {
|
|
16339
16402
|
return this._length !== null ? this._length : null;
|
|
16340
16403
|
}
|
|
@@ -16346,7 +16409,7 @@ var Mediabunny = (() => {
|
|
|
16346
16409
|
return null;
|
|
16347
16410
|
}
|
|
16348
16411
|
const offset = this._offset;
|
|
16349
|
-
const result = this._orchestrator.read(
|
|
16412
|
+
const result = this._sequentialBacking ? this._sequentialBacking._read(offset + start, offset + end) : this._orchestrator.read(
|
|
16350
16413
|
offset + start,
|
|
16351
16414
|
offset + end,
|
|
16352
16415
|
Math.max(offset + minReadPosition, offset),
|
|
@@ -16400,11 +16463,21 @@ var Mediabunny = (() => {
|
|
|
16400
16463
|
}
|
|
16401
16464
|
const contentLength = response.headers.get("Content-Length");
|
|
16402
16465
|
if (contentLength) {
|
|
16403
|
-
|
|
16466
|
+
const basePos = response.status === 206 ? worker.currentPos : 0;
|
|
16467
|
+
this._orchestrator.supplyFileSize(basePos + Number(contentLength));
|
|
16404
16468
|
}
|
|
16405
16469
|
}
|
|
16406
16470
|
this._fileSizeDetermined = true;
|
|
16471
|
+
if (!response.body) {
|
|
16472
|
+
throw new Error(
|
|
16473
|
+
"Missing HTTP response body stream. The used fetch function must provide the response body as a ReadableStream."
|
|
16474
|
+
);
|
|
16475
|
+
}
|
|
16407
16476
|
if (response.status !== 206) {
|
|
16477
|
+
if (this._sequentialBacking) {
|
|
16478
|
+
void response.body.cancel();
|
|
16479
|
+
return;
|
|
16480
|
+
}
|
|
16408
16481
|
if (!this._usedForHls) {
|
|
16409
16482
|
const url2 = new URL(
|
|
16410
16483
|
this._url instanceof Request ? this._url.url : this._url,
|
|
@@ -16413,26 +16486,14 @@ var Mediabunny = (() => {
|
|
|
16413
16486
|
if (url2.origin !== "null" && !(url2.pathname.endsWith(".m3u8") || url2.pathname.endsWith(".m3u"))) {
|
|
16414
16487
|
if (!warnedOrigins.has(url2.origin)) {
|
|
16415
16488
|
Logging._warn(
|
|
16416
|
-
`HTTP server (origin ${url2.origin}) did not respond to a range request with 206 Partial Content, meaning the
|
|
16489
|
+
`HTTP server (origin ${url2.origin}) did not respond to a range request with 206 Partial Content, meaning the resource will now be streamed sequentially, with old data being evicted from the cache. Reads into evicted regions will throw. To enable efficient media file streaming across a network, please make sure your server supports range requests. Alternatively, set maxCacheSize to Infinity in the UrlSource options to keep the entire resource in memory.`
|
|
16417
16490
|
);
|
|
16418
16491
|
warnedOrigins.add(url2.origin);
|
|
16419
16492
|
}
|
|
16420
16493
|
}
|
|
16421
16494
|
}
|
|
16422
|
-
|
|
16423
|
-
|
|
16424
|
-
if (this._orchestrator.fileSize !== null) {
|
|
16425
|
-
worker.targetPos = this._orchestrator.fileSize;
|
|
16426
|
-
} else {
|
|
16427
|
-
worker.targetPos = Infinity;
|
|
16428
|
-
worker.strictTarget = false;
|
|
16429
|
-
}
|
|
16430
|
-
this._orchestrator.consolidateEverythingIntoOneWorker(worker);
|
|
16431
|
-
}
|
|
16432
|
-
if (!response.body) {
|
|
16433
|
-
throw new Error(
|
|
16434
|
-
"Missing HTTP response body stream. The used fetch function must provide the response body as a ReadableStream."
|
|
16435
|
-
);
|
|
16495
|
+
this._transitionToSequentialMode(response.body);
|
|
16496
|
+
return;
|
|
16436
16497
|
}
|
|
16437
16498
|
const reader = response.body.getReader();
|
|
16438
16499
|
while (true) {
|
|
@@ -16479,8 +16540,119 @@ var Mediabunny = (() => {
|
|
|
16479
16540
|
}
|
|
16480
16541
|
}
|
|
16481
16542
|
/** @internal */
|
|
16543
|
+
_transitionToSequentialMode(body) {
|
|
16544
|
+
let currentReader = body.getReader();
|
|
16545
|
+
let streamPosition = 0;
|
|
16546
|
+
let skipRemaining = 0;
|
|
16547
|
+
const wrappedStream = new ReadableStream({
|
|
16548
|
+
pull: async (controller) => {
|
|
16549
|
+
while (true) {
|
|
16550
|
+
let readResult;
|
|
16551
|
+
try {
|
|
16552
|
+
readResult = await currentReader.read();
|
|
16553
|
+
} catch (error) {
|
|
16554
|
+
if (this._disposed) {
|
|
16555
|
+
throw error;
|
|
16556
|
+
}
|
|
16557
|
+
const retryDelayInSeconds = this._getRetryDelay(1, error, this._url);
|
|
16558
|
+
if (retryDelayInSeconds === null) {
|
|
16559
|
+
throw error;
|
|
16560
|
+
}
|
|
16561
|
+
Logging._error("Error while reading response stream. Attempting to resume.", error);
|
|
16562
|
+
await wait(1e3 * retryDelayInSeconds);
|
|
16563
|
+
const newResponse = await retriedFetch(
|
|
16564
|
+
this._options.fetchFn ?? fetch,
|
|
16565
|
+
this._url,
|
|
16566
|
+
mergeRequestInit(this._requestInit, {
|
|
16567
|
+
headers: {
|
|
16568
|
+
// Who knows, maybe the server honors range requests this time
|
|
16569
|
+
Range: `bytes=${streamPosition}-`
|
|
16570
|
+
}
|
|
16571
|
+
}),
|
|
16572
|
+
this._getRetryDelay,
|
|
16573
|
+
() => this._disposed
|
|
16574
|
+
);
|
|
16575
|
+
if (!newResponse.ok) {
|
|
16576
|
+
throw new Error(
|
|
16577
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
16578
|
+
`Error fetching ${String(this._url)}: ${newResponse.status} ${newResponse.statusText}`
|
|
16579
|
+
);
|
|
16580
|
+
}
|
|
16581
|
+
if (!newResponse.body) {
|
|
16582
|
+
throw new Error(
|
|
16583
|
+
"Missing HTTP response body stream. The used fetch function must provide the response body as a ReadableStream."
|
|
16584
|
+
);
|
|
16585
|
+
}
|
|
16586
|
+
currentReader = newResponse.body.getReader();
|
|
16587
|
+
skipRemaining = newResponse.status === 206 ? 0 : streamPosition;
|
|
16588
|
+
continue;
|
|
16589
|
+
}
|
|
16590
|
+
if (readResult.done) {
|
|
16591
|
+
controller.close();
|
|
16592
|
+
return;
|
|
16593
|
+
}
|
|
16594
|
+
let chunk = readResult.value;
|
|
16595
|
+
if (skipRemaining > 0) {
|
|
16596
|
+
const skippedAmount = Math.min(skipRemaining, chunk.length);
|
|
16597
|
+
skipRemaining -= skippedAmount;
|
|
16598
|
+
chunk = chunk.subarray(skippedAmount);
|
|
16599
|
+
}
|
|
16600
|
+
if (chunk.length === 0) {
|
|
16601
|
+
continue;
|
|
16602
|
+
}
|
|
16603
|
+
streamPosition += chunk.length;
|
|
16604
|
+
controller.enqueue(chunk);
|
|
16605
|
+
return;
|
|
16606
|
+
}
|
|
16607
|
+
},
|
|
16608
|
+
cancel: () => currentReader.cancel()
|
|
16609
|
+
});
|
|
16610
|
+
const backing = new ReadableStreamSource(wrappedStream, {
|
|
16611
|
+
maxCacheSize: this._orchestrator.options.maxCacheSize
|
|
16612
|
+
});
|
|
16613
|
+
backing._endIndex = this._orchestrator.fileSize;
|
|
16614
|
+
backing._cacheMissErrorMessage = "Attempted to read data from an already-evicted part of the cache. Because the HTTP server did not honor the range request, data can only be read sequentially, with old data being evicted from the cache. To fix this issue, either ensure your server responds to range requests with 206 Partial Content, or set maxCacheSize to Infinity in the UrlSource options. Note that the latter will store the entire file in the cache if needed, no matter how large.";
|
|
16615
|
+
backing.on("read", ({ start, end }) => this._dispatchRead(start, end));
|
|
16616
|
+
this._sequentialBacking = backing;
|
|
16617
|
+
const uniqueSlices = /* @__PURE__ */ new Set();
|
|
16618
|
+
for (const otherWorker of this._orchestrator.workers) {
|
|
16619
|
+
for (const slice of otherWorker.pendingSlices) {
|
|
16620
|
+
uniqueSlices.add(slice);
|
|
16621
|
+
}
|
|
16622
|
+
otherWorker.aborted = true;
|
|
16623
|
+
otherWorker.pendingSlices.length = 0;
|
|
16624
|
+
}
|
|
16625
|
+
for (const queuedRead of this._orchestrator.queuedReads) {
|
|
16626
|
+
for (const slice of queuedRead.pendingSlices) {
|
|
16627
|
+
uniqueSlices.add(slice);
|
|
16628
|
+
}
|
|
16629
|
+
}
|
|
16630
|
+
this._orchestrator.workers.length = 0;
|
|
16631
|
+
this._orchestrator.queuedReads.length = 0;
|
|
16632
|
+
for (const slice of uniqueSlices) {
|
|
16633
|
+
const result = backing._read(slice.start, slice.start + slice.bytes.length);
|
|
16634
|
+
if (result instanceof Promise) {
|
|
16635
|
+
result.then((readResult) => {
|
|
16636
|
+
if (readResult) {
|
|
16637
|
+
assert(readResult.offset === slice.start);
|
|
16638
|
+
slice.resolve(readResult.bytes);
|
|
16639
|
+
} else {
|
|
16640
|
+
slice.resolve(null);
|
|
16641
|
+
}
|
|
16642
|
+
}, (error) => slice.reject(error));
|
|
16643
|
+
} else {
|
|
16644
|
+
assert(result === null);
|
|
16645
|
+
slice.resolve(null);
|
|
16646
|
+
}
|
|
16647
|
+
}
|
|
16648
|
+
}
|
|
16649
|
+
/** @internal */
|
|
16482
16650
|
_dispose() {
|
|
16483
16651
|
this._orchestrator.dispose();
|
|
16652
|
+
if (this._sequentialBacking) {
|
|
16653
|
+
this._sequentialBacking._disposed = true;
|
|
16654
|
+
this._sequentialBacking._dispose();
|
|
16655
|
+
}
|
|
16484
16656
|
}
|
|
16485
16657
|
};
|
|
16486
16658
|
var BYTE_RANGE_REGEX = /^bytes=(\d+)-(\d*)$/;
|
|
@@ -16699,6 +16871,11 @@ var Mediabunny = (() => {
|
|
|
16699
16871
|
this._endIndex = null;
|
|
16700
16872
|
/** @internal */
|
|
16701
16873
|
this._pulling = false;
|
|
16874
|
+
/**
|
|
16875
|
+
* Overridable for internal use.
|
|
16876
|
+
* @internal
|
|
16877
|
+
*/
|
|
16878
|
+
this._cacheMissErrorMessage = "Attempted to read data from an already-evicted part of the cache. With ReadableStreamSource, you must access the data more sequentially or increase the size of its cache.";
|
|
16702
16879
|
this._stream = stream;
|
|
16703
16880
|
this._maxCacheSize = options.maxCacheSize ?? 32 * 2 ** 20;
|
|
16704
16881
|
}
|
|
@@ -16778,9 +16955,7 @@ var Mediabunny = (() => {
|
|
|
16778
16955
|
}
|
|
16779
16956
|
/** @internal */
|
|
16780
16957
|
_throwDueToCacheMiss() {
|
|
16781
|
-
throw new Error(
|
|
16782
|
-
"Read is before the cached region. With ReadableStreamSource, you must access the data more sequentially or increase the size of its cache."
|
|
16783
|
-
);
|
|
16958
|
+
throw new Error(this._cacheMissErrorMessage);
|
|
16784
16959
|
}
|
|
16785
16960
|
/** @internal */
|
|
16786
16961
|
async _pull() {
|
|
@@ -17170,30 +17345,6 @@ var Mediabunny = (() => {
|
|
|
17170
17345
|
}
|
|
17171
17346
|
});
|
|
17172
17347
|
}
|
|
17173
|
-
consolidateEverythingIntoOneWorker(worker) {
|
|
17174
|
-
const uniqueSlices = new Set(worker.pendingSlices);
|
|
17175
|
-
for (let i = 0; i < this.workers.length; i++) {
|
|
17176
|
-
const otherWorker = this.workers[i];
|
|
17177
|
-
if (otherWorker === worker) {
|
|
17178
|
-
continue;
|
|
17179
|
-
}
|
|
17180
|
-
for (const slice of otherWorker.pendingSlices) {
|
|
17181
|
-
uniqueSlices.add(slice);
|
|
17182
|
-
}
|
|
17183
|
-
otherWorker.aborted = true;
|
|
17184
|
-
otherWorker.pendingSlices.length = 0;
|
|
17185
|
-
this.workers.splice(i, 1);
|
|
17186
|
-
i--;
|
|
17187
|
-
}
|
|
17188
|
-
for (let i = 0; i < this.queuedReads.length; i++) {
|
|
17189
|
-
const queuedRead = this.queuedReads[i];
|
|
17190
|
-
for (const slice of queuedRead.pendingSlices) {
|
|
17191
|
-
uniqueSlices.add(slice);
|
|
17192
|
-
}
|
|
17193
|
-
}
|
|
17194
|
-
worker.pendingSlices = [...uniqueSlices];
|
|
17195
|
-
this.queuedReads.length = 0;
|
|
17196
|
-
}
|
|
17197
17348
|
/** Called by a worker when it has read some data. */
|
|
17198
17349
|
supplyWorkerData(worker, bytes2) {
|
|
17199
17350
|
assert(!worker.aborted);
|
|
@@ -19504,12 +19655,22 @@ var Mediabunny = (() => {
|
|
|
19504
19655
|
if (init.duration !== void 0 && (!Number.isFinite(init.duration) || init.duration < 0)) {
|
|
19505
19656
|
throw new TypeError("init.duration, when provided, must be a non-negative number.");
|
|
19506
19657
|
}
|
|
19658
|
+
if (init.visibleRect !== void 0) {
|
|
19659
|
+
validateRectangle(init.visibleRect, "init.visibleRect");
|
|
19660
|
+
}
|
|
19507
19661
|
if (typeof VideoFrame !== "undefined") {
|
|
19508
19662
|
return new _VideoSample(
|
|
19509
19663
|
new VideoFrame(data, {
|
|
19510
19664
|
timestamp: Math.trunc(init.timestamp * SECOND_TO_MICROSECOND_FACTOR),
|
|
19511
19665
|
// Drag 0 to undefined
|
|
19512
|
-
duration: Math.trunc((init.duration ?? 0) * SECOND_TO_MICROSECOND_FACTOR) || void 0
|
|
19666
|
+
duration: Math.trunc((init.duration ?? 0) * SECOND_TO_MICROSECOND_FACTOR) || void 0,
|
|
19667
|
+
// WebCodecs wants DOMRectInit
|
|
19668
|
+
visibleRect: init.visibleRect && {
|
|
19669
|
+
x: init.visibleRect.left,
|
|
19670
|
+
y: init.visibleRect.top,
|
|
19671
|
+
width: init.visibleRect.width,
|
|
19672
|
+
height: init.visibleRect.height
|
|
19673
|
+
}
|
|
19513
19674
|
}),
|
|
19514
19675
|
init
|
|
19515
19676
|
);
|
|
@@ -19529,7 +19690,8 @@ var Mediabunny = (() => {
|
|
|
19529
19690
|
if (!width || !height) {
|
|
19530
19691
|
throw new TypeError("Could not determine dimensions.");
|
|
19531
19692
|
}
|
|
19532
|
-
const
|
|
19693
|
+
const visibleRect = init.visibleRect ?? { left: 0, top: 0, width, height };
|
|
19694
|
+
const canvas = new OffscreenCanvas(visibleRect.width, visibleRect.height);
|
|
19533
19695
|
const context = canvas.getContext("2d", {
|
|
19534
19696
|
alpha: isFirefox(),
|
|
19535
19697
|
// Firefox has VideoFrame glitches with opaque canvases
|
|
@@ -19540,13 +19702,13 @@ var Mediabunny = (() => {
|
|
|
19540
19702
|
"OffscreenCanvas must have support for the '2d' context in order to create a VideoSample from this data."
|
|
19541
19703
|
);
|
|
19542
19704
|
}
|
|
19543
|
-
context.drawImage(data,
|
|
19705
|
+
context.drawImage(data, -visibleRect.left, -visibleRect.top);
|
|
19544
19706
|
this._data = canvas;
|
|
19545
19707
|
this._layout = null;
|
|
19546
19708
|
this.format = "RGBX";
|
|
19547
|
-
this.visibleRect = { left: 0, top: 0, width, height };
|
|
19548
|
-
this.squarePixelWidth = width;
|
|
19549
|
-
this.squarePixelHeight = height;
|
|
19709
|
+
this.visibleRect = { left: 0, top: 0, width: visibleRect.width, height: visibleRect.height };
|
|
19710
|
+
this.squarePixelWidth = visibleRect.width;
|
|
19711
|
+
this.squarePixelHeight = visibleRect.height;
|
|
19550
19712
|
this.rotation = init.rotation ?? 0;
|
|
19551
19713
|
this.timestamp = init.timestamp;
|
|
19552
19714
|
this.duration = init.duration ?? 0;
|
|
@@ -20123,6 +20285,61 @@ var Mediabunny = (() => {
|
|
|
20123
20285
|
}
|
|
20124
20286
|
return { sx, sy, sWidth, sHeight };
|
|
20125
20287
|
}
|
|
20288
|
+
/**
|
|
20289
|
+
* Draws the sample onto the target canvas with fit behavior, manually mipmapping on strong downscales for quality.
|
|
20290
|
+
* @internal
|
|
20291
|
+
*/
|
|
20292
|
+
_drawWithFitAndMipmapping(targetCanvas, targetContext, options) {
|
|
20293
|
+
const targetWidth = targetCanvas.width;
|
|
20294
|
+
const targetHeight = targetCanvas.height;
|
|
20295
|
+
const [rotatedWidth, rotatedHeight] = options.rotation % 180 === 0 ? [this.squarePixelWidth, this.squarePixelHeight] : [this.squarePixelHeight, this.squarePixelWidth];
|
|
20296
|
+
const sourceWidth = options.crop ? options.crop.width : rotatedWidth;
|
|
20297
|
+
const sourceHeight = options.crop ? options.crop.height : rotatedHeight;
|
|
20298
|
+
let mipLevels = 0;
|
|
20299
|
+
if (2 * targetWidth < sourceWidth && 2 * targetHeight < sourceHeight) {
|
|
20300
|
+
mipLevels = Math.floor(Math.log2(
|
|
20301
|
+
Math.min(sourceWidth / targetWidth, sourceHeight / targetHeight)
|
|
20302
|
+
));
|
|
20303
|
+
}
|
|
20304
|
+
const drawWidth = targetWidth * 2 ** mipLevels;
|
|
20305
|
+
const drawHeight = targetHeight * 2 ** mipLevels;
|
|
20306
|
+
const { canvas, context, isNew } = mipLevels > 0 ? getTransformationCanvas(drawWidth, drawHeight) : { canvas: targetCanvas, context: targetContext, isNew: options.targetIsFresh };
|
|
20307
|
+
context.imageSmoothingQuality = "high";
|
|
20308
|
+
if (options.fillBlack) {
|
|
20309
|
+
context.fillStyle = "black";
|
|
20310
|
+
context.fillRect(0, 0, drawWidth, drawHeight);
|
|
20311
|
+
} else if (!isNew) {
|
|
20312
|
+
context.clearRect(0, 0, drawWidth, drawHeight);
|
|
20313
|
+
}
|
|
20314
|
+
this.drawWithFit(context, {
|
|
20315
|
+
fit: options.fit,
|
|
20316
|
+
rotation: options.rotation,
|
|
20317
|
+
crop: options.crop
|
|
20318
|
+
});
|
|
20319
|
+
context.globalCompositeOperation = "copy";
|
|
20320
|
+
for (let i = mipLevels; i > 1; i--) {
|
|
20321
|
+
const levelWidth = targetWidth * 2 ** i;
|
|
20322
|
+
const levelHeight = targetHeight * 2 ** i;
|
|
20323
|
+
context.drawImage(canvas, 0, 0, levelWidth, levelHeight, 0, 0, levelWidth / 2, levelHeight / 2);
|
|
20324
|
+
}
|
|
20325
|
+
context.globalCompositeOperation = "source-over";
|
|
20326
|
+
if (mipLevels > 0) {
|
|
20327
|
+
targetContext.imageSmoothingQuality = "high";
|
|
20328
|
+
targetContext.globalCompositeOperation = "copy";
|
|
20329
|
+
targetContext.drawImage(
|
|
20330
|
+
canvas,
|
|
20331
|
+
0,
|
|
20332
|
+
0,
|
|
20333
|
+
2 * targetWidth,
|
|
20334
|
+
2 * targetHeight,
|
|
20335
|
+
0,
|
|
20336
|
+
0,
|
|
20337
|
+
targetWidth,
|
|
20338
|
+
targetHeight
|
|
20339
|
+
);
|
|
20340
|
+
targetContext.globalCompositeOperation = "source-over";
|
|
20341
|
+
}
|
|
20342
|
+
}
|
|
20126
20343
|
/**
|
|
20127
20344
|
* Converts this video sample to a
|
|
20128
20345
|
* [`CanvasImageSource`](https://udn.realityripple.com/docs/Web/API/CanvasImageSource) for drawing to a canvas.
|
|
@@ -20226,56 +20443,13 @@ var Mediabunny = (() => {
|
|
|
20226
20443
|
return result;
|
|
20227
20444
|
}
|
|
20228
20445
|
}
|
|
20229
|
-
|
|
20230
|
-
|
|
20231
|
-
for (const entry of transformationCanvasCache) {
|
|
20232
|
-
if (entry.canvas.width === description.width && entry.canvas.height === description.height) {
|
|
20233
|
-
canvas = entry.canvas;
|
|
20234
|
-
entry.age = transformationCanvasCacheNextAge++;
|
|
20235
|
-
break;
|
|
20236
|
-
}
|
|
20237
|
-
}
|
|
20238
|
-
if (canvas === null) {
|
|
20239
|
-
if (typeof OffscreenCanvas !== "undefined") {
|
|
20240
|
-
canvas = new OffscreenCanvas(description.width, description.height);
|
|
20241
|
-
} else {
|
|
20242
|
-
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
20243
|
-
throw new Error(
|
|
20244
|
-
"Cannot transform VideoSamples in this environment. Either run in an environment with OffscreenCanvas or HTMLCanvasElement, or supply a custom VideoSample transformer using registerVideoSampleTransformer()."
|
|
20245
|
-
);
|
|
20246
|
-
}
|
|
20247
|
-
canvas = document.createElement("canvas");
|
|
20248
|
-
canvas.width = description.width;
|
|
20249
|
-
canvas.height = description.height;
|
|
20250
|
-
}
|
|
20251
|
-
canvasIsNew = true;
|
|
20252
|
-
if (transformationCanvasCache.length >= TRANSFORMATION_CANVAS_CACHE_MAX_SIZE) {
|
|
20253
|
-
transformationCanvasCache.splice(arrayArgmin(transformationCanvasCache, (x) => x.age), 1);
|
|
20254
|
-
}
|
|
20255
|
-
transformationCanvasCache.push({
|
|
20256
|
-
canvas,
|
|
20257
|
-
age: transformationCanvasCacheNextAge++
|
|
20258
|
-
});
|
|
20259
|
-
}
|
|
20260
|
-
const context = canvas.getContext("2d", {
|
|
20261
|
-
alpha: true
|
|
20262
|
-
});
|
|
20263
|
-
if (!context) {
|
|
20264
|
-
throw new Error(
|
|
20265
|
-
"The '2d' canvas context is required to transform VideoSamples. Register a custom transformer using registerVideoSampleTransformer to work around this limitation."
|
|
20266
|
-
);
|
|
20267
|
-
}
|
|
20268
|
-
context.imageSmoothingQuality = "high";
|
|
20269
|
-
if (description.alpha === "discard") {
|
|
20270
|
-
context.fillStyle = "black";
|
|
20271
|
-
context.fillRect(0, 0, description.width, description.height);
|
|
20272
|
-
} else if (!canvasIsNew) {
|
|
20273
|
-
context.clearRect(0, 0, description.width, description.height);
|
|
20274
|
-
}
|
|
20275
|
-
this.drawWithFit(context, {
|
|
20446
|
+
const { canvas, context, isNew } = getTransformationCanvas(description.width, description.height);
|
|
20447
|
+
this._drawWithFitAndMipmapping(canvas, context, {
|
|
20276
20448
|
fit: description.fit,
|
|
20277
20449
|
rotation: description.rotation,
|
|
20278
|
-
crop: description.crop
|
|
20450
|
+
crop: description.crop,
|
|
20451
|
+
targetIsFresh: isNew,
|
|
20452
|
+
fillBlack: description.alpha === "discard"
|
|
20279
20453
|
});
|
|
20280
20454
|
return new _VideoSample(canvas, {
|
|
20281
20455
|
timestamp: this.timestamp,
|
|
@@ -20327,6 +20501,45 @@ var Mediabunny = (() => {
|
|
|
20327
20501
|
var TRANSFORMATION_CANVAS_CACHE_MAX_SIZE = 3;
|
|
20328
20502
|
var transformationCanvasCache = [];
|
|
20329
20503
|
var transformationCanvasCacheNextAge = 0;
|
|
20504
|
+
var getTransformationCanvas = (width, height) => {
|
|
20505
|
+
for (const entry of transformationCanvasCache) {
|
|
20506
|
+
if (entry.canvas.width === width && entry.canvas.height === height) {
|
|
20507
|
+
entry.age = transformationCanvasCacheNextAge++;
|
|
20508
|
+
return { canvas: entry.canvas, context: entry.context, isNew: false };
|
|
20509
|
+
}
|
|
20510
|
+
}
|
|
20511
|
+
let canvas;
|
|
20512
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
20513
|
+
canvas = new OffscreenCanvas(width, height);
|
|
20514
|
+
} else {
|
|
20515
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
20516
|
+
throw new Error(
|
|
20517
|
+
"Cannot transform VideoSamples in this environment. Either run in an environment with OffscreenCanvas or HTMLCanvasElement, or supply a custom VideoSample transformer using registerVideoSampleTransformer()."
|
|
20518
|
+
);
|
|
20519
|
+
}
|
|
20520
|
+
canvas = document.createElement("canvas");
|
|
20521
|
+
canvas.width = width;
|
|
20522
|
+
canvas.height = height;
|
|
20523
|
+
}
|
|
20524
|
+
const context = canvas.getContext("2d", {
|
|
20525
|
+
alpha: true,
|
|
20526
|
+
willReadFrequently: false
|
|
20527
|
+
});
|
|
20528
|
+
if (!context) {
|
|
20529
|
+
throw new Error(
|
|
20530
|
+
"The '2d' canvas context is required to transform VideoSamples. Register a custom transformer using registerVideoSampleTransformer to work around this limitation."
|
|
20531
|
+
);
|
|
20532
|
+
}
|
|
20533
|
+
if (transformationCanvasCache.length >= TRANSFORMATION_CANVAS_CACHE_MAX_SIZE) {
|
|
20534
|
+
transformationCanvasCache.splice(arrayArgmin(transformationCanvasCache, (x) => x.age), 1);
|
|
20535
|
+
}
|
|
20536
|
+
transformationCanvasCache.push({
|
|
20537
|
+
canvas,
|
|
20538
|
+
context,
|
|
20539
|
+
age: transformationCanvasCacheNextAge++
|
|
20540
|
+
});
|
|
20541
|
+
return { canvas, context, isNew: true };
|
|
20542
|
+
};
|
|
20330
20543
|
var VideoSampleColorSpace = class {
|
|
20331
20544
|
/** Creates a new VideoSampleColorSpace. */
|
|
20332
20545
|
constructor(init) {
|
|
@@ -21894,8 +22107,12 @@ var Mediabunny = (() => {
|
|
|
21894
22107
|
return false;
|
|
21895
22108
|
}
|
|
21896
22109
|
for (const { config, quantizer } of candidates) {
|
|
21897
|
-
|
|
21898
|
-
|
|
22110
|
+
try {
|
|
22111
|
+
const support = await VideoEncoder.isConfigSupported(config);
|
|
22112
|
+
if (!support.supported) {
|
|
22113
|
+
continue;
|
|
22114
|
+
}
|
|
22115
|
+
} catch {
|
|
21899
22116
|
continue;
|
|
21900
22117
|
}
|
|
21901
22118
|
if (!isFirefox()) {
|
|
@@ -21987,8 +22204,12 @@ var Mediabunny = (() => {
|
|
|
21987
22204
|
if (typeof AudioEncoder === "undefined") {
|
|
21988
22205
|
return false;
|
|
21989
22206
|
}
|
|
21990
|
-
|
|
21991
|
-
|
|
22207
|
+
try {
|
|
22208
|
+
const support = await AudioEncoder.isConfigSupported(encoderConfig);
|
|
22209
|
+
return support.supported === true;
|
|
22210
|
+
} catch {
|
|
22211
|
+
return false;
|
|
22212
|
+
}
|
|
21992
22213
|
})();
|
|
21993
22214
|
canEncodeAudioMemo.set(key, promise);
|
|
21994
22215
|
return promise;
|
|
@@ -23521,19 +23742,12 @@ var Mediabunny = (() => {
|
|
|
23521
23742
|
// Firefox has VideoFrame glitches with opaque canvases
|
|
23522
23743
|
});
|
|
23523
23744
|
assert(context);
|
|
23524
|
-
|
|
23525
|
-
if (!canvasIsNew) {
|
|
23526
|
-
if (!this._alpha && isFirefox()) {
|
|
23527
|
-
context.fillStyle = "black";
|
|
23528
|
-
context.fillRect(0, 0, width, height);
|
|
23529
|
-
} else {
|
|
23530
|
-
context.clearRect(0, 0, width, height);
|
|
23531
|
-
}
|
|
23532
|
-
}
|
|
23533
|
-
sample.drawWithFit(context, {
|
|
23745
|
+
sample._drawWithFitAndMipmapping(canvas, context, {
|
|
23534
23746
|
fit: this._fit,
|
|
23535
23747
|
rotation: this._rotation,
|
|
23536
|
-
crop: this._crop
|
|
23748
|
+
crop: this._crop,
|
|
23749
|
+
targetIsFresh: canvasIsNew,
|
|
23750
|
+
fillBlack: !this._alpha && isFirefox()
|
|
23537
23751
|
});
|
|
23538
23752
|
const result = {
|
|
23539
23753
|
canvas,
|
|
@@ -24971,8 +25185,9 @@ var Mediabunny = (() => {
|
|
|
24971
25185
|
if (filtered.length === 0) {
|
|
24972
25186
|
return 0;
|
|
24973
25187
|
}
|
|
24974
|
-
const
|
|
24975
|
-
|
|
25188
|
+
const firstPackets = await Promise.all(filtered.map((x) => x._backing.getFirstPacket({ metadataOnly: true })));
|
|
25189
|
+
const result = Math.min(...firstPackets.map((x) => x?.timestamp ?? Infinity));
|
|
25190
|
+
return result === Infinity ? 0 : result;
|
|
24976
25191
|
}
|
|
24977
25192
|
/**
|
|
24978
25193
|
* Computes the duration of the input file, in seconds. More precisely, returns the largest end timestamp among
|
|
@@ -26478,7 +26693,7 @@ var Mediabunny = (() => {
|
|
|
26478
26693
|
try {
|
|
26479
26694
|
this.validateTimestamp(track, packet.timestamp, packet.type === "key");
|
|
26480
26695
|
if (this.inputIsAdts === null) {
|
|
26481
|
-
validateAudioChunkMetadata(meta);
|
|
26696
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
26482
26697
|
const description = meta?.decoderConfig?.description;
|
|
26483
26698
|
this.inputIsAdts = !description;
|
|
26484
26699
|
if (!this.inputIsAdts) {
|
|
@@ -26518,6 +26733,9 @@ var Mediabunny = (() => {
|
|
|
26518
26733
|
}
|
|
26519
26734
|
async finalize() {
|
|
26520
26735
|
const release = await this.mutex.acquire();
|
|
26736
|
+
if (this.inputIsAdts === null) {
|
|
26737
|
+
throw new Error("Cannot finalize an empty ADTS file: not a single packet was added.");
|
|
26738
|
+
}
|
|
26521
26739
|
release();
|
|
26522
26740
|
}
|
|
26523
26741
|
};
|
|
@@ -26541,8 +26759,46 @@ var Mediabunny = (() => {
|
|
|
26541
26759
|
const release = await this.mutex.acquire();
|
|
26542
26760
|
this.writer = await this.output._getRootWriter(!!this.format._options.appendOnly);
|
|
26543
26761
|
this.writer.write(FLAC_HEADER);
|
|
26762
|
+
const track = this.output.tracks[0];
|
|
26763
|
+
assert(track?.isAudioTrack());
|
|
26764
|
+
if (track.metadata.decoderConfig) {
|
|
26765
|
+
validateAudioChunkMetadata({ decoderConfig: track.metadata.decoderConfig }, track.source._codec);
|
|
26766
|
+
this.applyDecoderConfig(track.metadata.decoderConfig);
|
|
26767
|
+
}
|
|
26544
26768
|
release();
|
|
26545
26769
|
}
|
|
26770
|
+
applyDecoderConfig(decoderConfig) {
|
|
26771
|
+
assert(decoderConfig.description);
|
|
26772
|
+
this.sampleRate = decoderConfig.sampleRate;
|
|
26773
|
+
this.channels = decoderConfig.numberOfChannels;
|
|
26774
|
+
const descriptionBitstream = new Bitstream(
|
|
26775
|
+
toUint8Array(decoderConfig.description)
|
|
26776
|
+
);
|
|
26777
|
+
descriptionBitstream.skipBits(103 + 64);
|
|
26778
|
+
this.bitsPerSample = descriptionBitstream.readBits(5) + 1;
|
|
26779
|
+
if (this.format._options.appendOnly) {
|
|
26780
|
+
this.writeHeader({
|
|
26781
|
+
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
|
|
26782
|
+
// Per RFC 9639, min/max block sizes can be looser than
|
|
26783
|
+
// actual values, so we use the full valid range (16–65535).
|
|
26784
|
+
// "The actual max block size MAY be smaller than what's
|
|
26785
|
+
// listed, and the actual min (excluding last block) MAY be
|
|
26786
|
+
// larger. This is because the encoder has to write these
|
|
26787
|
+
// fields before receiving any input audio data and cannot
|
|
26788
|
+
// know beforehand what block sizes it will use."
|
|
26789
|
+
minimumBlockSize: 16,
|
|
26790
|
+
maximumBlockSize: 65535,
|
|
26791
|
+
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
|
|
26792
|
+
// "A value of 0 signifies that the value is not known."
|
|
26793
|
+
minimumFrameSize: 0,
|
|
26794
|
+
maximumFrameSize: 0,
|
|
26795
|
+
sampleRate: this.sampleRate,
|
|
26796
|
+
channels: this.channels,
|
|
26797
|
+
bitsPerSample: this.bitsPerSample,
|
|
26798
|
+
totalSamples: 0
|
|
26799
|
+
});
|
|
26800
|
+
}
|
|
26801
|
+
}
|
|
26546
26802
|
writeHeader({
|
|
26547
26803
|
bitsPerSample,
|
|
26548
26804
|
minimumBlockSize,
|
|
@@ -26647,40 +26903,10 @@ var Mediabunny = (() => {
|
|
|
26647
26903
|
packet.type === "key"
|
|
26648
26904
|
);
|
|
26649
26905
|
if (this.sampleRate === null) {
|
|
26650
|
-
validateAudioChunkMetadata(meta);
|
|
26906
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
26651
26907
|
assert(meta);
|
|
26652
26908
|
assert(meta.decoderConfig);
|
|
26653
|
-
|
|
26654
|
-
this.sampleRate = meta.decoderConfig.sampleRate;
|
|
26655
|
-
this.channels = meta.decoderConfig.numberOfChannels;
|
|
26656
|
-
const descriptionBitstream = new Bitstream(
|
|
26657
|
-
toUint8Array(meta.decoderConfig.description)
|
|
26658
|
-
);
|
|
26659
|
-
descriptionBitstream.skipBits(103 + 64);
|
|
26660
|
-
const bitsPerSample = descriptionBitstream.readBits(5) + 1;
|
|
26661
|
-
this.bitsPerSample = bitsPerSample;
|
|
26662
|
-
if (this.format._options.appendOnly) {
|
|
26663
|
-
this.writeHeader({
|
|
26664
|
-
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
|
|
26665
|
-
// Per RFC 9639, min/max block sizes can be looser than
|
|
26666
|
-
// actual values, so we use the full valid range (16–65535).
|
|
26667
|
-
// "The actual max block size MAY be smaller than what's
|
|
26668
|
-
// listed, and the actual min (excluding last block) MAY be
|
|
26669
|
-
// larger. This is because the encoder has to write these
|
|
26670
|
-
// fields before receiving any input audio data and cannot
|
|
26671
|
-
// know beforehand what block sizes it will use."
|
|
26672
|
-
minimumBlockSize: 16,
|
|
26673
|
-
maximumBlockSize: 65535,
|
|
26674
|
-
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
|
|
26675
|
-
// "A value of 0 signifies that the value is not known."
|
|
26676
|
-
minimumFrameSize: 0,
|
|
26677
|
-
maximumFrameSize: 0,
|
|
26678
|
-
sampleRate: this.sampleRate,
|
|
26679
|
-
channels: this.channels,
|
|
26680
|
-
bitsPerSample: this.bitsPerSample,
|
|
26681
|
-
totalSamples: 0
|
|
26682
|
-
});
|
|
26683
|
-
}
|
|
26909
|
+
this.applyDecoderConfig(meta.decoderConfig);
|
|
26684
26910
|
}
|
|
26685
26911
|
if (!this.metadataWritten) {
|
|
26686
26912
|
this.writeVorbisCommentAndPictureBlock();
|
|
@@ -26714,6 +26940,14 @@ var Mediabunny = (() => {
|
|
|
26714
26940
|
}
|
|
26715
26941
|
async finalize() {
|
|
26716
26942
|
const release = await this.mutex.acquire();
|
|
26943
|
+
if (this.sampleRate === null) {
|
|
26944
|
+
throw new Error(
|
|
26945
|
+
"Cannot finalize an empty FLAC file: no packets were added and the track specified no decoderConfig in its metadata, so there's no telling what the file should look like."
|
|
26946
|
+
);
|
|
26947
|
+
}
|
|
26948
|
+
if (!this.metadataWritten) {
|
|
26949
|
+
this.writeVorbisCommentAndPictureBlock();
|
|
26950
|
+
}
|
|
26717
26951
|
if (!this.format._options.appendOnly) {
|
|
26718
26952
|
let minimumBlockSize = Infinity;
|
|
26719
26953
|
let maximumBlockSize = 0;
|
|
@@ -26731,7 +26965,12 @@ var Mediabunny = (() => {
|
|
|
26731
26965
|
}
|
|
26732
26966
|
minimumBlockSize = Math.min(minimumBlockSize, this.blockSizes[i]);
|
|
26733
26967
|
}
|
|
26734
|
-
|
|
26968
|
+
if (this.blockSizes.length === 0) {
|
|
26969
|
+
minimumBlockSize = 16;
|
|
26970
|
+
maximumBlockSize = 65535;
|
|
26971
|
+
minimumFrameSize = 0;
|
|
26972
|
+
maximumFrameSize = 0;
|
|
26973
|
+
}
|
|
26735
26974
|
assert(this.channels !== null);
|
|
26736
26975
|
assert(this.bitsPerSample !== null);
|
|
26737
26976
|
this.writer.seek(4);
|
|
@@ -27711,7 +27950,8 @@ var Mediabunny = (() => {
|
|
|
27711
27950
|
]);
|
|
27712
27951
|
};
|
|
27713
27952
|
var dac3 = (trackData) => {
|
|
27714
|
-
|
|
27953
|
+
assert(trackData.info.primingPacket);
|
|
27954
|
+
const frameInfo = parseAc3SyncFrame(trackData.info.primingPacket.data);
|
|
27715
27955
|
if (!frameInfo) {
|
|
27716
27956
|
throw new Error(
|
|
27717
27957
|
"Couldn't extract AC-3 frame info from the audio packet. Ensure the packets contain valid AC-3 sync frames (as specified in ETSI TS 102 366)."
|
|
@@ -27729,7 +27969,8 @@ var Mediabunny = (() => {
|
|
|
27729
27969
|
return box("dac3", [...bytes2]);
|
|
27730
27970
|
};
|
|
27731
27971
|
var dec3 = (trackData) => {
|
|
27732
|
-
|
|
27972
|
+
assert(trackData.info.primingPacket);
|
|
27973
|
+
const frameInfo = parseEac3SyncFrame(trackData.info.primingPacket.data);
|
|
27733
27974
|
if (!frameInfo) {
|
|
27734
27975
|
throw new Error(
|
|
27735
27976
|
"Couldn't extract E-AC-3 frame info from the audio packet. Ensure the packets contain valid E-AC-3 sync frames (as specified in ETSI TS 102 366)."
|
|
@@ -28030,7 +28271,7 @@ var Mediabunny = (() => {
|
|
|
28030
28271
|
mfro()
|
|
28031
28272
|
]);
|
|
28032
28273
|
};
|
|
28033
|
-
var tfra = (trackData
|
|
28274
|
+
var tfra = (trackData) => {
|
|
28034
28275
|
const version = 1;
|
|
28035
28276
|
return fullBox("tfra", version, 0, [
|
|
28036
28277
|
u32(trackData.track.id),
|
|
@@ -28044,7 +28285,7 @@ var Mediabunny = (() => {
|
|
|
28044
28285
|
// Time (in presentation time)
|
|
28045
28286
|
u64(chunk.moofOffset),
|
|
28046
28287
|
// moof offset
|
|
28047
|
-
u32(
|
|
28288
|
+
u32(chunk.trafIndex + 1),
|
|
28048
28289
|
// traf number
|
|
28049
28290
|
u32(1),
|
|
28050
28291
|
// trun number
|
|
@@ -29213,6 +29454,7 @@ var Mediabunny = (() => {
|
|
|
29213
29454
|
this.allTracksKnown = promiseWithResolvers();
|
|
29214
29455
|
this.creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET;
|
|
29215
29456
|
this.finalizedChunks = [];
|
|
29457
|
+
this.wroteFragmentedHeader = false;
|
|
29216
29458
|
this.nextFragmentNumber = 1;
|
|
29217
29459
|
// Only relevant for fragmented files, to make sure new fragments start with the highest timestamp seen so far
|
|
29218
29460
|
this.maxWrittenTimestamp = -Infinity;
|
|
@@ -29220,17 +29462,18 @@ var Mediabunny = (() => {
|
|
|
29220
29462
|
this.maxWrittenEndTimestamp = -Infinity;
|
|
29221
29463
|
this.segmentHeaderSize = null;
|
|
29222
29464
|
this.format = format;
|
|
29465
|
+
this.formatOptions = { ...format._options };
|
|
29223
29466
|
this.isQuickTime = format instanceof MovOutputFormat;
|
|
29224
29467
|
this.isCmaf = format instanceof CmafOutputFormat;
|
|
29225
|
-
this.minimumFragmentDuration =
|
|
29468
|
+
this.minimumFragmentDuration = this.formatOptions.minimumFragmentDuration ?? (format instanceof CmafOutputFormat ? Infinity : 1);
|
|
29226
29469
|
this.auxWriter.start();
|
|
29227
29470
|
}
|
|
29228
29471
|
async start() {
|
|
29229
29472
|
const release = await this.mutex.acquire();
|
|
29230
29473
|
if (!this.isCmaf) {
|
|
29231
|
-
this.writer = await this.output._getRootWriter((target) => this.
|
|
29474
|
+
this.writer = await this.output._getRootWriter((target) => this.formatOptions.fastStart !== void 0 ? this.formatOptions.fastStart === "fragmented" : target instanceof BufferTarget);
|
|
29232
29475
|
this.boxWriter = new IsobmffBoxWriter(this.writer);
|
|
29233
|
-
this.fastStart = this.
|
|
29476
|
+
this.fastStart = this.formatOptions.fastStart ?? (this.writer.target instanceof BufferTarget ? "in-memory" : false);
|
|
29234
29477
|
this.isFragmented = this.fastStart === "fragmented";
|
|
29235
29478
|
} else {
|
|
29236
29479
|
this.fastStart = "fragmented";
|
|
@@ -29252,7 +29495,7 @@ var Mediabunny = (() => {
|
|
|
29252
29495
|
{
|
|
29253
29496
|
const boxWriter = this.initBoxWriter ?? this.boxWriter;
|
|
29254
29497
|
assert(boxWriter);
|
|
29255
|
-
if (this.
|
|
29498
|
+
if (this.formatOptions.onFtyp) {
|
|
29256
29499
|
boxWriter.writer.startTrackingWrites();
|
|
29257
29500
|
}
|
|
29258
29501
|
boxWriter.writeBox(ftyp({
|
|
@@ -29261,9 +29504,9 @@ var Mediabunny = (() => {
|
|
|
29261
29504
|
fragmented: this.isFragmented,
|
|
29262
29505
|
cmaf: this.isCmaf
|
|
29263
29506
|
}));
|
|
29264
|
-
if (this.
|
|
29507
|
+
if (this.formatOptions.onFtyp) {
|
|
29265
29508
|
const { data, start } = boxWriter.writer.stopTrackingWrites();
|
|
29266
|
-
this.
|
|
29509
|
+
this.formatOptions.onFtyp(data, start);
|
|
29267
29510
|
}
|
|
29268
29511
|
this.ftypSize = boxWriter.writer.getPos();
|
|
29269
29512
|
if (this.isCmaf) {
|
|
@@ -29283,13 +29526,28 @@ var Mediabunny = (() => {
|
|
|
29283
29526
|
} else {
|
|
29284
29527
|
assert(this.writer);
|
|
29285
29528
|
assert(this.boxWriter);
|
|
29286
|
-
if (this.
|
|
29529
|
+
if (this.formatOptions.onMdat) {
|
|
29287
29530
|
this.writer.startTrackingWrites();
|
|
29288
29531
|
}
|
|
29289
29532
|
this.mdat = mdat(true);
|
|
29290
29533
|
this.boxWriter.writeBox(this.mdat);
|
|
29291
29534
|
}
|
|
29292
29535
|
await this.writer?.flush();
|
|
29536
|
+
for (const track of this.output.tracks) {
|
|
29537
|
+
if (track.isVideoTrack() && track.metadata.decoderConfig) {
|
|
29538
|
+
this.getVideoTrackData(
|
|
29539
|
+
track,
|
|
29540
|
+
track.metadata.primingPacket ?? null,
|
|
29541
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
29542
|
+
);
|
|
29543
|
+
} else if (track.isAudioTrack() && track.metadata.decoderConfig) {
|
|
29544
|
+
this.getAudioTrackData(
|
|
29545
|
+
track,
|
|
29546
|
+
track.metadata.primingPacket ?? null,
|
|
29547
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
29548
|
+
);
|
|
29549
|
+
}
|
|
29550
|
+
}
|
|
29293
29551
|
release();
|
|
29294
29552
|
}
|
|
29295
29553
|
allTracksAreKnown() {
|
|
@@ -29326,7 +29584,7 @@ var Mediabunny = (() => {
|
|
|
29326
29584
|
if (existingTrackData) {
|
|
29327
29585
|
return existingTrackData;
|
|
29328
29586
|
}
|
|
29329
|
-
validateVideoChunkMetadata(meta);
|
|
29587
|
+
validateVideoChunkMetadata(meta, track.source._codec);
|
|
29330
29588
|
assert(meta);
|
|
29331
29589
|
assert(meta.decoderConfig);
|
|
29332
29590
|
const decoderConfig = { ...meta.decoderConfig };
|
|
@@ -29334,6 +29592,9 @@ var Mediabunny = (() => {
|
|
|
29334
29592
|
assert(decoderConfig.codedHeight !== void 0);
|
|
29335
29593
|
let requiresAnnexBTransformation = false;
|
|
29336
29594
|
if (track.source._codec === "avc" && !decoderConfig.description) {
|
|
29595
|
+
if (!packet) {
|
|
29596
|
+
throw new Error("No AVC description provided; you must therefore provide a priming packet.");
|
|
29597
|
+
}
|
|
29337
29598
|
const decoderConfigurationRecord = extractAvcDecoderConfigurationRecord(packet.data);
|
|
29338
29599
|
if (!decoderConfigurationRecord) {
|
|
29339
29600
|
throw new Error(
|
|
@@ -29343,6 +29604,9 @@ var Mediabunny = (() => {
|
|
|
29343
29604
|
decoderConfig.description = serializeAvcDecoderConfigurationRecord(decoderConfigurationRecord);
|
|
29344
29605
|
requiresAnnexBTransformation = true;
|
|
29345
29606
|
} else if (track.source._codec === "hevc" && !decoderConfig.description) {
|
|
29607
|
+
if (!packet) {
|
|
29608
|
+
throw new Error("No HEVC description provided; you must therefore provide a priming packet.");
|
|
29609
|
+
}
|
|
29346
29610
|
const decoderConfigurationRecord = extractHevcDecoderConfigurationRecord(packet.data);
|
|
29347
29611
|
if (!decoderConfigurationRecord) {
|
|
29348
29612
|
throw new Error(
|
|
@@ -29401,12 +29665,15 @@ var Mediabunny = (() => {
|
|
|
29401
29665
|
if (existingTrackData) {
|
|
29402
29666
|
return existingTrackData;
|
|
29403
29667
|
}
|
|
29404
|
-
validateAudioChunkMetadata(meta);
|
|
29668
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
29405
29669
|
assert(meta);
|
|
29406
29670
|
assert(meta.decoderConfig);
|
|
29407
29671
|
const decoderConfig = { ...meta.decoderConfig };
|
|
29408
29672
|
let requiresAdtsStripping = false;
|
|
29409
29673
|
if (track.source._codec === "aac" && !decoderConfig.description) {
|
|
29674
|
+
if (!packet) {
|
|
29675
|
+
throw new Error("No AAC description provided; you must therefore provide a priming packet.");
|
|
29676
|
+
}
|
|
29410
29677
|
const adtsFrame = readAdtsFrameHeader(FileSlice4.tempFromBytes(packet.data));
|
|
29411
29678
|
if (!adtsFrame) {
|
|
29412
29679
|
throw new Error(
|
|
@@ -29425,6 +29692,11 @@ var Mediabunny = (() => {
|
|
|
29425
29692
|
});
|
|
29426
29693
|
requiresAdtsStripping = true;
|
|
29427
29694
|
}
|
|
29695
|
+
if (track.source._codec === "ac3" || track.source._codec === "eac3") {
|
|
29696
|
+
if (!packet) {
|
|
29697
|
+
throw new Error("AC-3/E-AC-3 require a priming packet.");
|
|
29698
|
+
}
|
|
29699
|
+
}
|
|
29428
29700
|
const newTrackData = {
|
|
29429
29701
|
muxer: this,
|
|
29430
29702
|
track,
|
|
@@ -29436,7 +29708,7 @@ var Mediabunny = (() => {
|
|
|
29436
29708
|
requiresPcmTransformation: !this.isFragmented && PCM_AUDIO_CODECS.includes(track.source._codec),
|
|
29437
29709
|
expectedNextPcmPacketTimestamp: null,
|
|
29438
29710
|
requiresAdtsStripping,
|
|
29439
|
-
|
|
29711
|
+
primingPacket: packet
|
|
29440
29712
|
},
|
|
29441
29713
|
timescale: decoderConfig.sampleRate,
|
|
29442
29714
|
samples: [],
|
|
@@ -29865,7 +30137,8 @@ var Mediabunny = (() => {
|
|
|
29865
30137
|
startTimestamp: sample.timestamp,
|
|
29866
30138
|
samples: [],
|
|
29867
30139
|
offset: null,
|
|
29868
|
-
moofOffset: null
|
|
30140
|
+
moofOffset: null,
|
|
30141
|
+
trafIndex: null
|
|
29869
30142
|
};
|
|
29870
30143
|
}
|
|
29871
30144
|
assert(trackData.currentChunk);
|
|
@@ -29932,19 +30205,19 @@ var Mediabunny = (() => {
|
|
|
29932
30205
|
}
|
|
29933
30206
|
async finalizeFragment(flushWriter = !this.isCmaf) {
|
|
29934
30207
|
assert(this.isFragmented);
|
|
29935
|
-
|
|
29936
|
-
|
|
30208
|
+
if (!this.wroteFragmentedHeader) {
|
|
30209
|
+
this.wroteFragmentedHeader = true;
|
|
29937
30210
|
const boxWriter = this.initBoxWriter ?? this.boxWriter;
|
|
29938
30211
|
assert(boxWriter);
|
|
29939
|
-
if (this.
|
|
30212
|
+
if (this.formatOptions.onMoov) {
|
|
29940
30213
|
boxWriter.writer.startTrackingWrites();
|
|
29941
30214
|
}
|
|
29942
30215
|
this.ensureOneEnabledTrack();
|
|
29943
30216
|
const movieBox = moov(this);
|
|
29944
30217
|
boxWriter.writeBox(movieBox);
|
|
29945
|
-
if (this.
|
|
30218
|
+
if (this.formatOptions.onMoov) {
|
|
29946
30219
|
const { data, start } = boxWriter.writer.stopTrackingWrites();
|
|
29947
|
-
this.
|
|
30220
|
+
this.formatOptions.onMoov(data, start);
|
|
29948
30221
|
}
|
|
29949
30222
|
if (this.isCmaf) {
|
|
29950
30223
|
assert(this.initWriter);
|
|
@@ -29961,14 +30234,23 @@ var Mediabunny = (() => {
|
|
|
29961
30234
|
assert(this.writer);
|
|
29962
30235
|
assert(this.boxWriter);
|
|
29963
30236
|
const tracksInFragment = this.trackDatas.filter((x) => x.currentChunk);
|
|
30237
|
+
if (tracksInFragment.length === 0) {
|
|
30238
|
+
if (flushWriter) {
|
|
30239
|
+
await this.writer.flush();
|
|
30240
|
+
}
|
|
30241
|
+
return;
|
|
30242
|
+
}
|
|
30243
|
+
const fragmentNumber = this.nextFragmentNumber++;
|
|
29964
30244
|
const moofBox = moof(fragmentNumber, tracksInFragment);
|
|
29965
30245
|
const moofOffset = this.writer.getPos();
|
|
29966
30246
|
const mdatStartPos = moofOffset + this.boxWriter.measureBox(moofBox);
|
|
29967
30247
|
let currentPos = mdatStartPos + MIN_BOX_HEADER_SIZE;
|
|
29968
30248
|
let fragmentStartTimestamp = Infinity;
|
|
29969
|
-
for (
|
|
30249
|
+
for (let i = 0; i < tracksInFragment.length; i++) {
|
|
30250
|
+
const trackData = tracksInFragment[i];
|
|
29970
30251
|
trackData.currentChunk.offset = currentPos;
|
|
29971
30252
|
trackData.currentChunk.moofOffset = moofOffset;
|
|
30253
|
+
trackData.currentChunk.trafIndex = i;
|
|
29972
30254
|
for (const sample of trackData.currentChunk.samples) {
|
|
29973
30255
|
currentPos += sample.size;
|
|
29974
30256
|
}
|
|
@@ -29981,17 +30263,17 @@ var Mediabunny = (() => {
|
|
|
29981
30263
|
trackData.currentChunk.offset += MAX_BOX_HEADER_SIZE - MIN_BOX_HEADER_SIZE;
|
|
29982
30264
|
}
|
|
29983
30265
|
}
|
|
29984
|
-
if (this.
|
|
30266
|
+
if (this.formatOptions.onMoof) {
|
|
29985
30267
|
this.writer.startTrackingWrites();
|
|
29986
30268
|
}
|
|
29987
30269
|
const newMoofBox = moof(fragmentNumber, tracksInFragment);
|
|
29988
30270
|
this.boxWriter.writeBox(newMoofBox);
|
|
29989
|
-
if (this.
|
|
30271
|
+
if (this.formatOptions.onMoof) {
|
|
29990
30272
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
29991
|
-
this.
|
|
30273
|
+
this.formatOptions.onMoof(data, start, fragmentStartTimestamp);
|
|
29992
30274
|
}
|
|
29993
30275
|
assert(this.writer.getPos() === mdatStartPos);
|
|
29994
|
-
if (this.
|
|
30276
|
+
if (this.formatOptions.onMdat) {
|
|
29995
30277
|
this.writer.startTrackingWrites();
|
|
29996
30278
|
}
|
|
29997
30279
|
const mdatBox = mdat(needsLargeMdatSize);
|
|
@@ -30004,9 +30286,9 @@ var Mediabunny = (() => {
|
|
|
30004
30286
|
sample.data = null;
|
|
30005
30287
|
}
|
|
30006
30288
|
}
|
|
30007
|
-
if (this.
|
|
30289
|
+
if (this.formatOptions.onMdat) {
|
|
30008
30290
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30009
|
-
this.
|
|
30291
|
+
this.formatOptions.onMdat(data, start);
|
|
30010
30292
|
}
|
|
30011
30293
|
for (const trackData of tracksInFragment) {
|
|
30012
30294
|
trackData.finalizedChunks.push(trackData.currentChunk);
|
|
@@ -30018,33 +30300,36 @@ var Mediabunny = (() => {
|
|
|
30018
30300
|
}
|
|
30019
30301
|
}
|
|
30020
30302
|
async registerSampleFastStartReserve(trackData, sample) {
|
|
30021
|
-
assert(this.writer);
|
|
30022
|
-
assert(this.boxWriter);
|
|
30023
30303
|
if (this.allTracksAreKnown()) {
|
|
30024
30304
|
if (!this.mdat) {
|
|
30025
|
-
this.
|
|
30026
|
-
const moovBox = moov(this);
|
|
30027
|
-
const moovSize = this.boxWriter.measureBox(moovBox);
|
|
30028
|
-
const reservedSize = moovSize + this.computeSampleTableSizeUpperBound() + 4096;
|
|
30029
|
-
assert(this.ftypSize !== null);
|
|
30030
|
-
this.writer.seek(this.ftypSize + reservedSize);
|
|
30031
|
-
if (this.format._options.onMdat) {
|
|
30032
|
-
this.writer.startTrackingWrites();
|
|
30033
|
-
}
|
|
30034
|
-
this.mdat = mdat(true);
|
|
30035
|
-
this.boxWriter.writeBox(this.mdat);
|
|
30036
|
-
for (const trackData2 of this.trackDatas) {
|
|
30037
|
-
for (const sample2 of trackData2.sampleQueue) {
|
|
30038
|
-
await this.addSampleToTrack(trackData2, sample2);
|
|
30039
|
-
}
|
|
30040
|
-
trackData2.sampleQueue.length = 0;
|
|
30041
|
-
}
|
|
30305
|
+
await this.createFastStartReserveMdat();
|
|
30042
30306
|
}
|
|
30043
30307
|
await this.addSampleToTrack(trackData, sample);
|
|
30044
30308
|
} else {
|
|
30045
30309
|
trackData.sampleQueue.push(sample);
|
|
30046
30310
|
}
|
|
30047
30311
|
}
|
|
30312
|
+
async createFastStartReserveMdat() {
|
|
30313
|
+
assert(this.writer);
|
|
30314
|
+
assert(this.boxWriter);
|
|
30315
|
+
this.ensureOneEnabledTrack();
|
|
30316
|
+
const moovBox = moov(this);
|
|
30317
|
+
const moovSize = this.boxWriter.measureBox(moovBox);
|
|
30318
|
+
const reservedSize = moovSize + this.computeSampleTableSizeUpperBound() + 4096;
|
|
30319
|
+
assert(this.ftypSize !== null);
|
|
30320
|
+
this.writer.seek(this.ftypSize + reservedSize);
|
|
30321
|
+
if (this.formatOptions.onMdat) {
|
|
30322
|
+
this.writer.startTrackingWrites();
|
|
30323
|
+
}
|
|
30324
|
+
this.mdat = mdat(true);
|
|
30325
|
+
this.boxWriter.writeBox(this.mdat);
|
|
30326
|
+
for (const trackData of this.trackDatas) {
|
|
30327
|
+
for (const sample of trackData.sampleQueue) {
|
|
30328
|
+
await this.addSampleToTrack(trackData, sample);
|
|
30329
|
+
}
|
|
30330
|
+
trackData.sampleQueue.length = 0;
|
|
30331
|
+
}
|
|
30332
|
+
}
|
|
30048
30333
|
computeSampleTableSizeUpperBound() {
|
|
30049
30334
|
assert(this.fastStart === "reserve");
|
|
30050
30335
|
let upperBound = 0;
|
|
@@ -30095,11 +30380,31 @@ var Mediabunny = (() => {
|
|
|
30095
30380
|
}
|
|
30096
30381
|
}
|
|
30097
30382
|
}
|
|
30383
|
+
/** Internal function for external callers who want to full control fragment boundaries. */
|
|
30384
|
+
async forceFragmentFinalization() {
|
|
30385
|
+
assert(this.isFragmented);
|
|
30386
|
+
const release = await this.mutex.acquire();
|
|
30387
|
+
try {
|
|
30388
|
+
for (const trackData of this.trackDatas) {
|
|
30389
|
+
if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") {
|
|
30390
|
+
await this.processWebVTTCues(trackData, Infinity);
|
|
30391
|
+
}
|
|
30392
|
+
this.processTimestamps(trackData);
|
|
30393
|
+
}
|
|
30394
|
+
await this.interleaveSamples(true);
|
|
30395
|
+
await this.finalizeFragment();
|
|
30396
|
+
} finally {
|
|
30397
|
+
release();
|
|
30398
|
+
}
|
|
30399
|
+
}
|
|
30098
30400
|
/** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */
|
|
30099
30401
|
async finalize() {
|
|
30100
30402
|
const release = await this.mutex.acquire();
|
|
30101
30403
|
this.allTracksKnown.resolve();
|
|
30102
30404
|
this.ensureOneEnabledTrack();
|
|
30405
|
+
if (!this.mdat && this.fastStart === "reserve") {
|
|
30406
|
+
await this.createFastStartReserveMdat();
|
|
30407
|
+
}
|
|
30103
30408
|
for (const trackData of this.trackDatas) {
|
|
30104
30409
|
trackData.closed = true;
|
|
30105
30410
|
if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") {
|
|
@@ -30113,11 +30418,12 @@ var Mediabunny = (() => {
|
|
|
30113
30418
|
} else {
|
|
30114
30419
|
for (const trackData of this.trackDatas) {
|
|
30115
30420
|
await this.finalizeCurrentChunk(trackData);
|
|
30116
|
-
|
|
30117
|
-
|
|
30118
|
-
|
|
30119
|
-
|
|
30120
|
-
|
|
30421
|
+
if (trackData.startTimestampOffset !== null) {
|
|
30422
|
+
for (let i = 0; i < trackData.samples.length; i++) {
|
|
30423
|
+
const sample = trackData.samples[i];
|
|
30424
|
+
sample.timestamp -= trackData.startTimestampOffset;
|
|
30425
|
+
sample.decodeTimestamp -= trackData.startTimestampOffset;
|
|
30426
|
+
}
|
|
30121
30427
|
}
|
|
30122
30428
|
}
|
|
30123
30429
|
}
|
|
@@ -30142,16 +30448,16 @@ var Mediabunny = (() => {
|
|
|
30142
30448
|
if (currentChunkPos < 2 ** 32) break;
|
|
30143
30449
|
if (mdatSize >= 2 ** 32) this.mdat.largeSize = true;
|
|
30144
30450
|
}
|
|
30145
|
-
if (this.
|
|
30451
|
+
if (this.formatOptions.onMoov) {
|
|
30146
30452
|
this.writer.startTrackingWrites();
|
|
30147
30453
|
}
|
|
30148
30454
|
const movieBox = moov(this);
|
|
30149
30455
|
this.boxWriter.writeBox(movieBox);
|
|
30150
|
-
if (this.
|
|
30456
|
+
if (this.formatOptions.onMoov) {
|
|
30151
30457
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30152
|
-
this.
|
|
30458
|
+
this.formatOptions.onMoov(data, start);
|
|
30153
30459
|
}
|
|
30154
|
-
if (this.
|
|
30460
|
+
if (this.formatOptions.onMdat) {
|
|
30155
30461
|
this.writer.startTrackingWrites();
|
|
30156
30462
|
}
|
|
30157
30463
|
this.mdat.size = mdatSize;
|
|
@@ -30163,9 +30469,9 @@ var Mediabunny = (() => {
|
|
|
30163
30469
|
sample.data = null;
|
|
30164
30470
|
}
|
|
30165
30471
|
}
|
|
30166
|
-
if (this.
|
|
30472
|
+
if (this.formatOptions.onMdat) {
|
|
30167
30473
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30168
|
-
this.
|
|
30474
|
+
this.formatOptions.onMdat(data, start);
|
|
30169
30475
|
}
|
|
30170
30476
|
} else if (this.isFragmented) {
|
|
30171
30477
|
if (this.isCmaf) {
|
|
@@ -30189,29 +30495,29 @@ var Mediabunny = (() => {
|
|
|
30189
30495
|
this.mdat.size = mdatSize;
|
|
30190
30496
|
this.mdat.largeSize = mdatSize >= 2 ** 32;
|
|
30191
30497
|
this.boxWriter.patchBox(this.mdat);
|
|
30192
|
-
if (this.
|
|
30498
|
+
if (this.formatOptions.onMdat) {
|
|
30193
30499
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30194
|
-
this.
|
|
30500
|
+
this.formatOptions.onMdat(data, start);
|
|
30195
30501
|
}
|
|
30196
30502
|
const movieBox = moov(this);
|
|
30197
30503
|
if (this.fastStart === "reserve") {
|
|
30198
30504
|
assert(this.ftypSize !== null);
|
|
30199
30505
|
this.writer.seek(this.ftypSize);
|
|
30200
|
-
if (this.
|
|
30506
|
+
if (this.formatOptions.onMoov) {
|
|
30201
30507
|
this.writer.startTrackingWrites();
|
|
30202
30508
|
}
|
|
30203
30509
|
this.boxWriter.writeBox(movieBox);
|
|
30204
30510
|
const remainingSpace = this.boxWriter.offsets.get(this.mdat) - this.writer.getPos();
|
|
30205
30511
|
this.boxWriter.writeBox(free(remainingSpace));
|
|
30206
30512
|
} else {
|
|
30207
|
-
if (this.
|
|
30513
|
+
if (this.formatOptions.onMoov) {
|
|
30208
30514
|
this.writer.startTrackingWrites();
|
|
30209
30515
|
}
|
|
30210
30516
|
this.boxWriter.writeBox(movieBox);
|
|
30211
30517
|
}
|
|
30212
|
-
if (this.
|
|
30518
|
+
if (this.formatOptions.onMoov) {
|
|
30213
30519
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30214
|
-
this.
|
|
30520
|
+
this.formatOptions.onMoov(data, start);
|
|
30215
30521
|
}
|
|
30216
30522
|
}
|
|
30217
30523
|
release();
|
|
@@ -30258,6 +30564,21 @@ var Mediabunny = (() => {
|
|
|
30258
30564
|
this.createSegmentInfo();
|
|
30259
30565
|
this.createCues();
|
|
30260
30566
|
await this.writer.flush();
|
|
30567
|
+
for (const track of this.output.tracks) {
|
|
30568
|
+
if (track.isVideoTrack() && track.metadata.decoderConfig) {
|
|
30569
|
+
this.getVideoTrackData(
|
|
30570
|
+
track,
|
|
30571
|
+
track.metadata.primingPacket ?? null,
|
|
30572
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
30573
|
+
);
|
|
30574
|
+
} else if (track.isAudioTrack() && track.metadata.decoderConfig) {
|
|
30575
|
+
this.getAudioTrackData(
|
|
30576
|
+
track,
|
|
30577
|
+
track.metadata.primingPacket ?? null,
|
|
30578
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
30579
|
+
);
|
|
30580
|
+
}
|
|
30581
|
+
}
|
|
30261
30582
|
release();
|
|
30262
30583
|
}
|
|
30263
30584
|
writeEBMLHeader() {
|
|
@@ -30722,7 +31043,7 @@ var Mediabunny = (() => {
|
|
|
30722
31043
|
if (existingTrackData) {
|
|
30723
31044
|
return existingTrackData;
|
|
30724
31045
|
}
|
|
30725
|
-
validateVideoChunkMetadata(meta);
|
|
31046
|
+
validateVideoChunkMetadata(meta, track.source._codec);
|
|
30726
31047
|
assert(meta);
|
|
30727
31048
|
assert(meta.decoderConfig);
|
|
30728
31049
|
assert(meta.decoderConfig.codedWidth !== void 0);
|
|
@@ -30741,8 +31062,7 @@ var Mediabunny = (() => {
|
|
|
30741
31062
|
height: meta.decoderConfig.codedHeight,
|
|
30742
31063
|
aspectRatio,
|
|
30743
31064
|
decoderConfig: meta.decoderConfig,
|
|
30744
|
-
alphaMode: !!packet.sideData.alpha
|
|
30745
|
-
// The first packet determines if this track has alpha or not
|
|
31065
|
+
alphaMode: packet ? !!packet.sideData.alpha : null
|
|
30746
31066
|
},
|
|
30747
31067
|
chunkQueue: [],
|
|
30748
31068
|
lastWrittenMsTimestamp: null,
|
|
@@ -30772,12 +31092,15 @@ var Mediabunny = (() => {
|
|
|
30772
31092
|
if (existingTrackData) {
|
|
30773
31093
|
return existingTrackData;
|
|
30774
31094
|
}
|
|
30775
|
-
validateAudioChunkMetadata(meta);
|
|
31095
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
30776
31096
|
assert(meta);
|
|
30777
31097
|
assert(meta.decoderConfig);
|
|
30778
31098
|
const decoderConfig = { ...meta.decoderConfig };
|
|
30779
31099
|
let requiresAdtsStripping = false;
|
|
30780
31100
|
if (track.source._codec === "aac" && !decoderConfig.description) {
|
|
31101
|
+
if (!packet) {
|
|
31102
|
+
throw new Error("No AAC description provided; you must therefore provide a priming packet.");
|
|
31103
|
+
}
|
|
30781
31104
|
const adtsFrame = readAdtsFrameHeader(FileSlice4.tempFromBytes(packet.data));
|
|
30782
31105
|
if (!adtsFrame) {
|
|
30783
31106
|
throw new Error(
|
|
@@ -30847,6 +31170,7 @@ var Mediabunny = (() => {
|
|
|
30847
31170
|
const release = await this.mutex.acquire();
|
|
30848
31171
|
try {
|
|
30849
31172
|
const trackData = this.getVideoTrackData(track, packet, meta);
|
|
31173
|
+
trackData.info.alphaMode ??= !!packet.sideData.alpha;
|
|
30850
31174
|
let packetData = packet.data;
|
|
30851
31175
|
if (track.source._codec === "prores") {
|
|
30852
31176
|
if (packetData.byteLength < 8) {
|
|
@@ -31237,7 +31561,7 @@ ${cue.notes ?? ""}`;
|
|
|
31237
31561
|
data.sampleRate,
|
|
31238
31562
|
padding
|
|
31239
31563
|
);
|
|
31240
|
-
this.writer.
|
|
31564
|
+
this.writer.write(new Uint8Array(startPos + frameSize - this.writer.getPos()));
|
|
31241
31565
|
}
|
|
31242
31566
|
};
|
|
31243
31567
|
|
|
@@ -31322,22 +31646,95 @@ ${cue.notes ?? ""}`;
|
|
|
31322
31646
|
throw new Error("MP3 does not support subtitles.");
|
|
31323
31647
|
}
|
|
31324
31648
|
async finalize() {
|
|
31325
|
-
if (!this.xingFrameData || this.xingFramePos === null) {
|
|
31326
|
-
return;
|
|
31327
|
-
}
|
|
31328
31649
|
const release = await this.mutex.acquire();
|
|
31650
|
+
if (!this.xingFrameData && this.format._options.xingHeader === false) {
|
|
31651
|
+
throw new Error(
|
|
31652
|
+
"Cannot finalize an empty MP3 file: not a single packet was added and the Xing header is disabled, so there's no frame we could write."
|
|
31653
|
+
);
|
|
31654
|
+
}
|
|
31655
|
+
if (!this.xingFrameData) {
|
|
31656
|
+
const track = this.output.tracks[0];
|
|
31657
|
+
assert(track?.isAudioTrack());
|
|
31658
|
+
const primingPacket = track.metadata.primingPacket;
|
|
31659
|
+
if (primingPacket) {
|
|
31660
|
+
const view2 = toDataView(primingPacket.data);
|
|
31661
|
+
if (view2.byteLength < 4) {
|
|
31662
|
+
throw new Error("Invalid MP3 header in priming packet.");
|
|
31663
|
+
}
|
|
31664
|
+
const word = view2.getUint32(0, false);
|
|
31665
|
+
const header = readMp3FrameHeader(word, null).header;
|
|
31666
|
+
if (!header) {
|
|
31667
|
+
throw new Error("Invalid MP3 header in priming packet.");
|
|
31668
|
+
}
|
|
31669
|
+
this.xingFrameData = {
|
|
31670
|
+
mpegVersionId: header.mpegVersionId,
|
|
31671
|
+
layer: header.layer,
|
|
31672
|
+
frequencyIndex: header.frequencyIndex,
|
|
31673
|
+
sampleRate: header.sampleRate,
|
|
31674
|
+
channel: header.channel,
|
|
31675
|
+
modeExtension: header.modeExtension,
|
|
31676
|
+
copyright: header.copyright,
|
|
31677
|
+
original: header.original,
|
|
31678
|
+
emphasis: header.emphasis,
|
|
31679
|
+
frameCount: null,
|
|
31680
|
+
fileSize: null,
|
|
31681
|
+
toc: null
|
|
31682
|
+
};
|
|
31683
|
+
} else if (track.metadata.decoderConfig) {
|
|
31684
|
+
const { sampleRate, numberOfChannels } = track.metadata.decoderConfig;
|
|
31685
|
+
const mpegVersionIds = [3, 2, 0];
|
|
31686
|
+
let mpegVersionId = null;
|
|
31687
|
+
let frequencyIndex = -1;
|
|
31688
|
+
for (let i = 0; i < mpegVersionIds.length; i++) {
|
|
31689
|
+
frequencyIndex = SAMPLING_RATES.indexOf(sampleRate << i);
|
|
31690
|
+
if (frequencyIndex !== -1) {
|
|
31691
|
+
mpegVersionId = mpegVersionIds[i];
|
|
31692
|
+
break;
|
|
31693
|
+
}
|
|
31694
|
+
}
|
|
31695
|
+
if (mpegVersionId === null) {
|
|
31696
|
+
throw new Error(`${sampleRate} Hz is not a valid MP3 sample rate.`);
|
|
31697
|
+
}
|
|
31698
|
+
this.xingFrameData = {
|
|
31699
|
+
mpegVersionId,
|
|
31700
|
+
layer: 1,
|
|
31701
|
+
// Layer III
|
|
31702
|
+
frequencyIndex,
|
|
31703
|
+
sampleRate,
|
|
31704
|
+
channel: numberOfChannels === 1 ? 3 : 0,
|
|
31705
|
+
// 3 = single channel, 0 = stereo
|
|
31706
|
+
modeExtension: 0,
|
|
31707
|
+
copyright: 0,
|
|
31708
|
+
original: 0,
|
|
31709
|
+
emphasis: 0,
|
|
31710
|
+
frameCount: null,
|
|
31711
|
+
fileSize: null,
|
|
31712
|
+
toc: null
|
|
31713
|
+
};
|
|
31714
|
+
} else {
|
|
31715
|
+
throw new Error(
|
|
31716
|
+
"Cannot finalize an empty MP3 file: no packets were added and the track specified neither a decoderConfig nor a primingPacket in its metadata, so there's no telling what the file should look like."
|
|
31717
|
+
);
|
|
31718
|
+
}
|
|
31719
|
+
this.xingFramePos = this.writer.getPos();
|
|
31720
|
+
this.mp3Writer.writeXingFrame(this.xingFrameData);
|
|
31721
|
+
this.frameCount++;
|
|
31722
|
+
}
|
|
31723
|
+
assert(this.xingFramePos !== null);
|
|
31329
31724
|
const endPos = this.writer.getPos();
|
|
31330
31725
|
const audioDataEndPos = endPos - this.xingFramePos;
|
|
31331
31726
|
this.writer.seek(this.xingFramePos);
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31727
|
+
if (this.framePositions.length > 0) {
|
|
31728
|
+
const toc = new Uint8Array(100);
|
|
31729
|
+
for (let i = 0; i < 100; i++) {
|
|
31730
|
+
const index = Math.floor(this.framePositions.length * (i / 100));
|
|
31731
|
+
const byteOffset = this.framePositions[index] - this.xingFramePos;
|
|
31732
|
+
toc[i] = 256 * (byteOffset / audioDataEndPos);
|
|
31733
|
+
}
|
|
31734
|
+
this.xingFrameData.toc = toc;
|
|
31337
31735
|
}
|
|
31338
31736
|
this.xingFrameData.frameCount = this.frameCount;
|
|
31339
31737
|
this.xingFrameData.fileSize = audioDataEndPos;
|
|
31340
|
-
this.xingFrameData.toc = toc;
|
|
31341
31738
|
if (this.format._options.onXingFrame) {
|
|
31342
31739
|
this.writer.startTrackingWrites();
|
|
31343
31740
|
}
|
|
@@ -31365,6 +31762,12 @@ ${cue.notes ?? ""}`;
|
|
|
31365
31762
|
async start() {
|
|
31366
31763
|
const release = await this.mutex.acquire();
|
|
31367
31764
|
this.writer = await this.output._getRootWriter(true);
|
|
31765
|
+
for (const track of this.output.tracks) {
|
|
31766
|
+
assert(track.isAudioTrack());
|
|
31767
|
+
if (track.metadata.decoderConfig) {
|
|
31768
|
+
this.getTrackData(track, { decoderConfig: track.metadata.decoderConfig });
|
|
31769
|
+
}
|
|
31770
|
+
}
|
|
31368
31771
|
release();
|
|
31369
31772
|
}
|
|
31370
31773
|
async getMimeType() {
|
|
@@ -31386,7 +31789,7 @@ ${cue.notes ?? ""}`;
|
|
|
31386
31789
|
serialNumber = Math.floor(2 ** 32 * Math.random());
|
|
31387
31790
|
} while (this.trackDatas.some((td) => td.serialNumber === serialNumber));
|
|
31388
31791
|
assert(track.source._codec === "vorbis" || track.source._codec === "opus");
|
|
31389
|
-
validateAudioChunkMetadata(meta);
|
|
31792
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
31390
31793
|
assert(meta);
|
|
31391
31794
|
assert(meta.decoderConfig);
|
|
31392
31795
|
const newTrackData = {
|
|
@@ -31738,7 +32141,7 @@ ${cue.notes ?? ""}`;
|
|
|
31738
32141
|
if (existingTrackData) {
|
|
31739
32142
|
return existingTrackData;
|
|
31740
32143
|
}
|
|
31741
|
-
validateVideoChunkMetadata(meta);
|
|
32144
|
+
validateVideoChunkMetadata(meta, track.source._codec);
|
|
31742
32145
|
assert(meta?.decoderConfig);
|
|
31743
32146
|
const codec = track.source._codec;
|
|
31744
32147
|
assert(codec === "avc" || codec === "hevc");
|
|
@@ -31773,7 +32176,7 @@ ${cue.notes ?? ""}`;
|
|
|
31773
32176
|
if (existingTrackData) {
|
|
31774
32177
|
return existingTrackData;
|
|
31775
32178
|
}
|
|
31776
|
-
validateAudioChunkMetadata(meta);
|
|
32179
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
31777
32180
|
assert(meta?.decoderConfig);
|
|
31778
32181
|
const codec = track.source._codec;
|
|
31779
32182
|
assert(codec === "aac" || codec === "mp3" || codec === "ac3" || codec === "eac3");
|
|
@@ -32325,6 +32728,14 @@ ${cue.notes ?? ""}`;
|
|
|
32325
32728
|
const release = await this.mutex.acquire();
|
|
32326
32729
|
this.writer = await this.output._getRootWriter(false);
|
|
32327
32730
|
this.riffWriter = new RiffWriter(this.writer);
|
|
32731
|
+
const track = this.output.tracks[0];
|
|
32732
|
+
assert(track?.isAudioTrack());
|
|
32733
|
+
if (track.metadata.decoderConfig) {
|
|
32734
|
+
validateAudioChunkMetadata({ decoderConfig: track.metadata.decoderConfig }, track.source._codec);
|
|
32735
|
+
this.writeHeader(track, track.metadata.decoderConfig);
|
|
32736
|
+
this.sampleRate = track.metadata.decoderConfig.sampleRate;
|
|
32737
|
+
this.headerWritten = true;
|
|
32738
|
+
}
|
|
32328
32739
|
release();
|
|
32329
32740
|
}
|
|
32330
32741
|
async getMimeType() {
|
|
@@ -32337,7 +32748,7 @@ ${cue.notes ?? ""}`;
|
|
|
32337
32748
|
const release = await this.mutex.acquire();
|
|
32338
32749
|
try {
|
|
32339
32750
|
if (!this.headerWritten) {
|
|
32340
|
-
validateAudioChunkMetadata(meta);
|
|
32751
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
32341
32752
|
assert(meta);
|
|
32342
32753
|
assert(meta.decoderConfig);
|
|
32343
32754
|
this.writeHeader(track, meta.decoderConfig);
|
|
@@ -32561,6 +32972,11 @@ ${cue.notes ?? ""}`;
|
|
|
32561
32972
|
}
|
|
32562
32973
|
async finalize() {
|
|
32563
32974
|
const release = await this.mutex.acquire();
|
|
32975
|
+
if (!this.headerWritten) {
|
|
32976
|
+
throw new Error(
|
|
32977
|
+
"Cannot finalize an empty WAVE file: no packets were added and the track specified no decoderConfig in its metadata, so there's no telling what the file should look like."
|
|
32978
|
+
);
|
|
32979
|
+
}
|
|
32564
32980
|
const endPos = this.writer.getPos();
|
|
32565
32981
|
if (this.isRf64) {
|
|
32566
32982
|
assert(this.ds64RiffSizePos !== null);
|
|
@@ -33235,10 +33651,13 @@ ${cue.notes ?? ""}`;
|
|
|
33235
33651
|
`The dimensions ${candidateConfig.width}x${candidateConfig.height} are not supported for codec '${this.encodingConfig.codec}'; both width and height must be even numbers. Make sure to round your dimensions to the nearest even number.`
|
|
33236
33652
|
);
|
|
33237
33653
|
}
|
|
33238
|
-
|
|
33239
|
-
|
|
33240
|
-
|
|
33241
|
-
|
|
33654
|
+
try {
|
|
33655
|
+
const support = await VideoEncoder.isConfigSupported(candidateConfig);
|
|
33656
|
+
if (support.supported) {
|
|
33657
|
+
selected = candidate;
|
|
33658
|
+
break;
|
|
33659
|
+
}
|
|
33660
|
+
} catch {
|
|
33242
33661
|
}
|
|
33243
33662
|
}
|
|
33244
33663
|
if (!selected) {
|
|
@@ -34294,8 +34713,14 @@ ${cue.notes ?? ""}`;
|
|
|
34294
34713
|
if (typeof AudioEncoder === "undefined") {
|
|
34295
34714
|
throw new Error("AudioEncoder is not supported by this browser.");
|
|
34296
34715
|
}
|
|
34297
|
-
|
|
34298
|
-
|
|
34716
|
+
let supported;
|
|
34717
|
+
try {
|
|
34718
|
+
const support = await AudioEncoder.isConfigSupported(encoderConfig);
|
|
34719
|
+
supported = support.supported ?? false;
|
|
34720
|
+
} catch {
|
|
34721
|
+
supported = false;
|
|
34722
|
+
}
|
|
34723
|
+
if (!supported) {
|
|
34299
34724
|
throw new Error(
|
|
34300
34725
|
`This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps, ${encoderConfig.numberOfChannels} channels, ${encoderConfig.sampleRate} Hz) is not supported by this browser. Consider using another codec or changing your audio parameters.`
|
|
34301
34726
|
);
|
|
@@ -35177,6 +35602,21 @@ ${cue.notes ?? ""}`;
|
|
|
35177
35602
|
references: variant.linkedGroup ? this.playlistDeclarations.filter((x) => x.groupId === variant.linkedGroup.name) : []
|
|
35178
35603
|
});
|
|
35179
35604
|
}
|
|
35605
|
+
for (const track of this.output.tracks) {
|
|
35606
|
+
if (track.isVideoTrack() && track.metadata.decoderConfig) {
|
|
35607
|
+
this.getVideoTrackData(
|
|
35608
|
+
track,
|
|
35609
|
+
track.metadata.primingPacket ?? null,
|
|
35610
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
35611
|
+
);
|
|
35612
|
+
} else if (track.isAudioTrack() && track.metadata.decoderConfig) {
|
|
35613
|
+
this.getAudioTrackData(
|
|
35614
|
+
track,
|
|
35615
|
+
track.metadata.primingPacket ?? null,
|
|
35616
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
35617
|
+
);
|
|
35618
|
+
}
|
|
35619
|
+
}
|
|
35180
35620
|
release();
|
|
35181
35621
|
}
|
|
35182
35622
|
async getMimeType() {
|
|
@@ -35205,12 +35645,12 @@ ${cue.notes ?? ""}`;
|
|
|
35205
35645
|
release();
|
|
35206
35646
|
}
|
|
35207
35647
|
}
|
|
35208
|
-
getVideoTrackData(track, meta) {
|
|
35648
|
+
getVideoTrackData(track, packet, meta) {
|
|
35209
35649
|
let trackData = this.trackDatas.find((x) => x.track === track);
|
|
35210
35650
|
if (trackData) {
|
|
35211
35651
|
return trackData;
|
|
35212
35652
|
}
|
|
35213
|
-
validateVideoChunkMetadata(meta);
|
|
35653
|
+
validateVideoChunkMetadata(meta, track.source._codec);
|
|
35214
35654
|
assert(meta);
|
|
35215
35655
|
assert(meta?.decoderConfig);
|
|
35216
35656
|
const playlists = this.playlists.filter((x) => x.tracks.includes(track));
|
|
@@ -35222,18 +35662,19 @@ ${cue.notes ?? ""}`;
|
|
|
35222
35662
|
closed: false,
|
|
35223
35663
|
info: {
|
|
35224
35664
|
type: "video",
|
|
35225
|
-
decoderConfig: meta.decoderConfig
|
|
35665
|
+
decoderConfig: meta.decoderConfig,
|
|
35666
|
+
primingPacket: packet
|
|
35226
35667
|
}
|
|
35227
35668
|
};
|
|
35228
35669
|
this.trackDatas.push(trackData);
|
|
35229
35670
|
return trackData;
|
|
35230
35671
|
}
|
|
35231
|
-
getAudioTrackData(track, meta) {
|
|
35672
|
+
getAudioTrackData(track, packet, meta) {
|
|
35232
35673
|
let trackData = this.trackDatas.find((x) => x.track === track);
|
|
35233
35674
|
if (trackData) {
|
|
35234
35675
|
return trackData;
|
|
35235
35676
|
}
|
|
35236
|
-
validateAudioChunkMetadata(meta);
|
|
35677
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
35237
35678
|
assert(meta);
|
|
35238
35679
|
assert(meta?.decoderConfig);
|
|
35239
35680
|
const playlists = this.playlists.filter((x) => x.tracks.includes(track));
|
|
@@ -35245,14 +35686,15 @@ ${cue.notes ?? ""}`;
|
|
|
35245
35686
|
closed: false,
|
|
35246
35687
|
info: {
|
|
35247
35688
|
type: "audio",
|
|
35248
|
-
decoderConfig: meta.decoderConfig
|
|
35689
|
+
decoderConfig: meta.decoderConfig,
|
|
35690
|
+
primingPacket: packet
|
|
35249
35691
|
}
|
|
35250
35692
|
};
|
|
35251
35693
|
this.trackDatas.push(trackData);
|
|
35252
35694
|
return trackData;
|
|
35253
35695
|
}
|
|
35254
35696
|
async addEncodedVideoPacket(track, packet, meta) {
|
|
35255
|
-
const trackData = this.getVideoTrackData(track, meta);
|
|
35697
|
+
const trackData = this.getVideoTrackData(track, packet, meta);
|
|
35256
35698
|
const playlist = trackData.playlist;
|
|
35257
35699
|
const release = await playlist.mutex.acquire();
|
|
35258
35700
|
try {
|
|
@@ -35272,7 +35714,7 @@ ${cue.notes ?? ""}`;
|
|
|
35272
35714
|
}
|
|
35273
35715
|
}
|
|
35274
35716
|
async addEncodedAudioPacket(track, packet, meta) {
|
|
35275
|
-
const trackData = this.getAudioTrackData(track, meta);
|
|
35717
|
+
const trackData = this.getAudioTrackData(track, packet, meta);
|
|
35276
35718
|
const playlist = trackData.playlist;
|
|
35277
35719
|
const release = await playlist.mutex.acquire();
|
|
35278
35720
|
try {
|
|
@@ -35299,11 +35741,13 @@ ${cue.notes ?? ""}`;
|
|
|
35299
35741
|
if (!this.allTracksAreKnown(playlist)) {
|
|
35300
35742
|
return;
|
|
35301
35743
|
}
|
|
35744
|
+
const trackDatas = this.trackDatas.filter((x) => playlist.tracks.includes(x.track));
|
|
35302
35745
|
if (playlist.currentSegmentStartTimestamp === null) {
|
|
35303
|
-
|
|
35746
|
+
if (trackDatas.every((x) => x.closed)) {
|
|
35747
|
+
await this.onPlaylistDone(playlist);
|
|
35748
|
+
}
|
|
35304
35749
|
return;
|
|
35305
35750
|
}
|
|
35306
|
-
const trackDatas = this.trackDatas.filter((x) => playlist.tracks.includes(x.track));
|
|
35307
35751
|
const videoTrack = trackDatas.find((x) => x.info.type === "video");
|
|
35308
35752
|
const audioTrack = trackDatas.find((x) => x.info.type === "audio");
|
|
35309
35753
|
while (true) {
|
|
@@ -35422,12 +35866,68 @@ ${cue.notes ?? ""}`;
|
|
|
35422
35866
|
isRoot: false,
|
|
35423
35867
|
mimeType: playlist.segmentFormat.mimeType
|
|
35424
35868
|
});
|
|
35425
|
-
|
|
35869
|
+
let fragmentedIsobmffOutput = null;
|
|
35870
|
+
if (playlist.segmentFormat._isFragmentedIsobmff()) {
|
|
35871
|
+
fragmentedIsobmffOutput = {
|
|
35872
|
+
output: new Output({
|
|
35873
|
+
format: playlist.segmentFormat,
|
|
35874
|
+
target
|
|
35875
|
+
}),
|
|
35876
|
+
videoSource: null,
|
|
35877
|
+
audioSource: null,
|
|
35878
|
+
firstMoofPosition: null,
|
|
35879
|
+
currentFileSize: 0
|
|
35880
|
+
};
|
|
35881
|
+
target.on("write", ({ end }) => {
|
|
35882
|
+
fragmentedIsobmffOutput.currentFileSize = Math.max(
|
|
35883
|
+
fragmentedIsobmffOutput.currentFileSize,
|
|
35884
|
+
end
|
|
35885
|
+
);
|
|
35886
|
+
});
|
|
35887
|
+
const muxer = fragmentedIsobmffOutput.output._muxer;
|
|
35888
|
+
muxer.minimumFragmentDuration = Infinity;
|
|
35889
|
+
const originalOnMoof = muxer.formatOptions.onMoof;
|
|
35890
|
+
muxer.formatOptions.onMoof = (data, position, timestamp) => {
|
|
35891
|
+
fragmentedIsobmffOutput.firstMoofPosition = position;
|
|
35892
|
+
originalOnMoof?.(data, position, timestamp);
|
|
35893
|
+
muxer.formatOptions.onMoof = originalOnMoof;
|
|
35894
|
+
};
|
|
35895
|
+
if (videoTrack) {
|
|
35896
|
+
fragmentedIsobmffOutput.videoSource = new EncodedVideoPacketSource(
|
|
35897
|
+
videoTrack.track.source._codec
|
|
35898
|
+
);
|
|
35899
|
+
fragmentedIsobmffOutput.output.addVideoTrack(
|
|
35900
|
+
fragmentedIsobmffOutput.videoSource,
|
|
35901
|
+
{
|
|
35902
|
+
...videoTrack.track.metadata,
|
|
35903
|
+
decoderConfig: videoTrack.info.decoderConfig,
|
|
35904
|
+
primingPacket: videoTrack.info.primingPacket ?? void 0
|
|
35905
|
+
}
|
|
35906
|
+
);
|
|
35907
|
+
}
|
|
35908
|
+
if (audioTrack) {
|
|
35909
|
+
fragmentedIsobmffOutput.audioSource = new EncodedAudioPacketSource(
|
|
35910
|
+
audioTrack.track.source._codec
|
|
35911
|
+
);
|
|
35912
|
+
fragmentedIsobmffOutput.output.addAudioTrack(
|
|
35913
|
+
fragmentedIsobmffOutput.audioSource,
|
|
35914
|
+
{
|
|
35915
|
+
...audioTrack.track.metadata,
|
|
35916
|
+
decoderConfig: audioTrack.info.decoderConfig,
|
|
35917
|
+
primingPacket: audioTrack.info.primingPacket ?? void 0
|
|
35918
|
+
}
|
|
35919
|
+
);
|
|
35920
|
+
}
|
|
35921
|
+
await fragmentedIsobmffOutput.output.start();
|
|
35922
|
+
} else {
|
|
35923
|
+
target._start();
|
|
35924
|
+
}
|
|
35426
35925
|
playlist.singleFile = {
|
|
35427
35926
|
target,
|
|
35428
35927
|
path: relativeSegmentPath,
|
|
35429
35928
|
nextOffset: 0,
|
|
35430
|
-
info: segmentInfo2
|
|
35929
|
+
info: segmentInfo2,
|
|
35930
|
+
fragmentedIsobmffOutput
|
|
35431
35931
|
};
|
|
35432
35932
|
} else {
|
|
35433
35933
|
relativeSegmentPath = playlist.singleFile.path;
|
|
@@ -35450,95 +35950,114 @@ ${cue.notes ?? ""}`;
|
|
|
35450
35950
|
}
|
|
35451
35951
|
let segmentSize = 0;
|
|
35452
35952
|
let outputTarget = null;
|
|
35453
|
-
|
|
35454
|
-
|
|
35455
|
-
|
|
35456
|
-
|
|
35457
|
-
|
|
35458
|
-
|
|
35459
|
-
|
|
35460
|
-
|
|
35461
|
-
|
|
35462
|
-
|
|
35953
|
+
let maxEndTimestamp = -Infinity;
|
|
35954
|
+
let output = null;
|
|
35955
|
+
let videoSource = null;
|
|
35956
|
+
let audioSource = null;
|
|
35957
|
+
try {
|
|
35958
|
+
if (playlist.singleFile?.fragmentedIsobmffOutput) {
|
|
35959
|
+
output = playlist.singleFile.fragmentedIsobmffOutput.output;
|
|
35960
|
+
videoSource = playlist.singleFile.fragmentedIsobmffOutput.videoSource;
|
|
35961
|
+
audioSource = playlist.singleFile.fragmentedIsobmffOutput.audioSource;
|
|
35962
|
+
} else {
|
|
35963
|
+
output = new Output({
|
|
35964
|
+
format: playlist.segmentFormat,
|
|
35965
|
+
target: new PathedTarget(
|
|
35966
|
+
fullSegmentPath,
|
|
35967
|
+
async (request) => {
|
|
35968
|
+
const proxiedRequest = {
|
|
35969
|
+
...request,
|
|
35970
|
+
isRoot: false
|
|
35971
|
+
};
|
|
35972
|
+
if (request.isRoot) {
|
|
35973
|
+
if (playlist.singleFile) {
|
|
35974
|
+
const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset);
|
|
35975
|
+
slice.on("write", ({ end }) => segmentSize = Math.max(segmentSize, end));
|
|
35976
|
+
return slice;
|
|
35977
|
+
} else {
|
|
35978
|
+
const target = await this.output._getTarget(proxiedRequest);
|
|
35979
|
+
outputTarget = target;
|
|
35980
|
+
target.on("write", ({ end }) => segmentSize = Math.max(segmentSize, end));
|
|
35981
|
+
return target;
|
|
35982
|
+
}
|
|
35983
|
+
}
|
|
35984
|
+
return this.output._getTarget(proxiedRequest);
|
|
35985
|
+
}
|
|
35986
|
+
),
|
|
35987
|
+
initTarget: async () => {
|
|
35988
|
+
if (playlist.initSegment) {
|
|
35989
|
+
return new NullTarget();
|
|
35990
|
+
}
|
|
35463
35991
|
if (playlist.singleFile) {
|
|
35992
|
+
playlist.initSegment = {
|
|
35993
|
+
path: playlist.singleFile.path,
|
|
35994
|
+
duration: 0,
|
|
35995
|
+
timestamp: 0,
|
|
35996
|
+
byteSize: 0,
|
|
35997
|
+
byteOffset: 0,
|
|
35998
|
+
info: null
|
|
35999
|
+
};
|
|
35464
36000
|
const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset);
|
|
35465
|
-
slice.on("write", ({ end }) =>
|
|
36001
|
+
slice.on("write", ({ end }) => {
|
|
36002
|
+
playlist.initSegment.byteSize = Math.max(playlist.initSegment.byteSize, end);
|
|
36003
|
+
});
|
|
36004
|
+
slice.on("finalized", () => {
|
|
36005
|
+
playlist.singleFile.nextOffset = playlist.initSegment.byteSize;
|
|
36006
|
+
});
|
|
35466
36007
|
return slice;
|
|
35467
36008
|
} else {
|
|
35468
|
-
const
|
|
35469
|
-
|
|
35470
|
-
|
|
36009
|
+
const playlistInfo = toPlaylistInfo(playlist);
|
|
36010
|
+
const initPath = await this.getInitPath(playlistInfo);
|
|
36011
|
+
validateInitPath(initPath);
|
|
36012
|
+
playlist.initSegment = {
|
|
36013
|
+
path: initPath,
|
|
36014
|
+
duration: 0,
|
|
36015
|
+
timestamp: 0,
|
|
36016
|
+
byteSize: 0,
|
|
36017
|
+
byteOffset: null,
|
|
36018
|
+
info: null
|
|
36019
|
+
};
|
|
36020
|
+
const fullInitPath = joinPaths(
|
|
36021
|
+
joinPaths(pathedTarget.rootPath, playlist.path),
|
|
36022
|
+
initPath
|
|
36023
|
+
);
|
|
36024
|
+
const target = await this.output._getTarget({
|
|
36025
|
+
path: fullInitPath,
|
|
36026
|
+
isRoot: false,
|
|
36027
|
+
mimeType: playlist.segmentFormat.mimeType
|
|
36028
|
+
});
|
|
36029
|
+
target.on("write", ({ end }) => {
|
|
36030
|
+
playlist.initSegment.byteSize = Math.max(playlist.initSegment.byteSize, end);
|
|
36031
|
+
});
|
|
36032
|
+
target.on("finalized", () => {
|
|
36033
|
+
this.format._options.onInit?.(target, playlistInfo);
|
|
36034
|
+
});
|
|
35471
36035
|
return target;
|
|
35472
36036
|
}
|
|
35473
36037
|
}
|
|
35474
|
-
|
|
35475
|
-
|
|
35476
|
-
|
|
35477
|
-
|
|
35478
|
-
if (playlist.initSegment) {
|
|
35479
|
-
return new NullTarget();
|
|
35480
|
-
}
|
|
35481
|
-
if (playlist.singleFile) {
|
|
35482
|
-
playlist.initSegment = {
|
|
35483
|
-
path: playlist.singleFile.path,
|
|
35484
|
-
duration: 0,
|
|
35485
|
-
timestamp: 0,
|
|
35486
|
-
byteSize: 0,
|
|
35487
|
-
byteOffset: 0,
|
|
35488
|
-
info: null
|
|
35489
|
-
};
|
|
35490
|
-
const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset);
|
|
35491
|
-
slice.on("write", ({ end }) => {
|
|
35492
|
-
playlist.initSegment.byteSize = Math.max(playlist.initSegment.byteSize, end);
|
|
35493
|
-
});
|
|
35494
|
-
slice.on("finalized", () => {
|
|
35495
|
-
playlist.singleFile.nextOffset = playlist.initSegment.byteSize;
|
|
35496
|
-
});
|
|
35497
|
-
return slice;
|
|
35498
|
-
} else {
|
|
35499
|
-
const playlistInfo = toPlaylistInfo(playlist);
|
|
35500
|
-
const initPath = await this.getInitPath(playlistInfo);
|
|
35501
|
-
validateInitPath(initPath);
|
|
35502
|
-
playlist.initSegment = {
|
|
35503
|
-
path: initPath,
|
|
35504
|
-
duration: 0,
|
|
35505
|
-
timestamp: 0,
|
|
35506
|
-
byteSize: 0,
|
|
35507
|
-
byteOffset: null,
|
|
35508
|
-
info: null
|
|
35509
|
-
};
|
|
35510
|
-
const fullInitPath = joinPaths(
|
|
35511
|
-
joinPaths(pathedTarget.rootPath, playlist.path),
|
|
35512
|
-
initPath
|
|
36038
|
+
});
|
|
36039
|
+
if (videoTrack) {
|
|
36040
|
+
videoSource = new EncodedVideoPacketSource(
|
|
36041
|
+
videoTrack.track.source._codec
|
|
35513
36042
|
);
|
|
35514
|
-
|
|
35515
|
-
|
|
35516
|
-
|
|
35517
|
-
|
|
36043
|
+
output.addVideoTrack(videoSource, {
|
|
36044
|
+
...videoTrack.track.metadata,
|
|
36045
|
+
decoderConfig: videoTrack.info.decoderConfig,
|
|
36046
|
+
primingPacket: videoTrack.info.primingPacket ?? void 0
|
|
35518
36047
|
});
|
|
35519
|
-
|
|
35520
|
-
|
|
35521
|
-
|
|
35522
|
-
|
|
35523
|
-
|
|
36048
|
+
}
|
|
36049
|
+
if (audioTrack) {
|
|
36050
|
+
audioSource = new EncodedAudioPacketSource(
|
|
36051
|
+
audioTrack.track.source._codec
|
|
36052
|
+
);
|
|
36053
|
+
output.addAudioTrack(audioSource, {
|
|
36054
|
+
...audioTrack.track.metadata,
|
|
36055
|
+
decoderConfig: audioTrack.info.decoderConfig,
|
|
36056
|
+
primingPacket: audioTrack.info.primingPacket ?? void 0
|
|
35524
36057
|
});
|
|
35525
|
-
return target;
|
|
35526
36058
|
}
|
|
36059
|
+
await output.start();
|
|
35527
36060
|
}
|
|
35528
|
-
});
|
|
35529
|
-
let maxEndTimestamp = -Infinity;
|
|
35530
|
-
try {
|
|
35531
|
-
let videoSource = null;
|
|
35532
|
-
let audioSource = null;
|
|
35533
|
-
if (videoTrack) {
|
|
35534
|
-
videoSource = new EncodedVideoPacketSource(videoTrack.track.source._codec);
|
|
35535
|
-
output.addVideoTrack(videoSource, videoTrack.track.metadata);
|
|
35536
|
-
}
|
|
35537
|
-
if (audioTrack) {
|
|
35538
|
-
audioSource = new EncodedAudioPacketSource(audioTrack.track.source._codec);
|
|
35539
|
-
output.addAudioTrack(audioSource, audioTrack.track.metadata);
|
|
35540
|
-
}
|
|
35541
|
-
await output.start();
|
|
35542
36061
|
if (videoTrack) {
|
|
35543
36062
|
assert(videoSource);
|
|
35544
36063
|
const meta = { decoderConfig: videoTrack.info.decoderConfig };
|
|
@@ -35557,9 +36076,26 @@ ${cue.notes ?? ""}`;
|
|
|
35557
36076
|
maxEndTimestamp = Math.max(maxEndTimestamp, packet.timestamp + packet.duration);
|
|
35558
36077
|
}
|
|
35559
36078
|
}
|
|
35560
|
-
|
|
36079
|
+
if (playlist.singleFile?.fragmentedIsobmffOutput) {
|
|
36080
|
+
const muxer = playlist.singleFile.fragmentedIsobmffOutput.output._muxer;
|
|
36081
|
+
await muxer.forceFragmentFinalization();
|
|
36082
|
+
if (playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition !== null && !playlist.initSegment) {
|
|
36083
|
+
playlist.initSegment = {
|
|
36084
|
+
path: playlist.singleFile.path,
|
|
36085
|
+
duration: 0,
|
|
36086
|
+
timestamp: 0,
|
|
36087
|
+
byteSize: playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition,
|
|
36088
|
+
byteOffset: 0,
|
|
36089
|
+
info: null
|
|
36090
|
+
};
|
|
36091
|
+
playlist.singleFile.nextOffset = playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition;
|
|
36092
|
+
}
|
|
36093
|
+
segmentSize = playlist.singleFile.fragmentedIsobmffOutput.currentFileSize - playlist.singleFile.nextOffset;
|
|
36094
|
+
} else {
|
|
36095
|
+
await output.finalize();
|
|
36096
|
+
}
|
|
35561
36097
|
} catch (e) {
|
|
35562
|
-
await output
|
|
36098
|
+
await output?.cancel();
|
|
35563
36099
|
throw e;
|
|
35564
36100
|
}
|
|
35565
36101
|
if (segmentInfo) {
|
|
@@ -35617,8 +36153,12 @@ ${cue.notes ?? ""}`;
|
|
|
35617
36153
|
assert(!playlist.done);
|
|
35618
36154
|
playlist.done = true;
|
|
35619
36155
|
if (playlist.singleFile) {
|
|
35620
|
-
|
|
35621
|
-
|
|
36156
|
+
if (playlist.singleFile.fragmentedIsobmffOutput) {
|
|
36157
|
+
await playlist.singleFile.fragmentedIsobmffOutput.output.finalize();
|
|
36158
|
+
} else {
|
|
36159
|
+
await playlist.singleFile.target._flush();
|
|
36160
|
+
await playlist.singleFile.target._finalize();
|
|
36161
|
+
}
|
|
35622
36162
|
this.format._options.onSegment?.(playlist.singleFile.target, playlist.singleFile.info);
|
|
35623
36163
|
}
|
|
35624
36164
|
await this.writePlaylist(playlist);
|
|
@@ -35938,6 +36478,10 @@ ${cue.notes ?? ""}`;
|
|
|
35938
36478
|
_codecUnsupportedHint(codec) {
|
|
35939
36479
|
return "";
|
|
35940
36480
|
}
|
|
36481
|
+
/** @internal */
|
|
36482
|
+
_isFragmentedIsobmff() {
|
|
36483
|
+
return false;
|
|
36484
|
+
}
|
|
35941
36485
|
};
|
|
35942
36486
|
var IsobmffOutputFormat2 = class extends OutputFormat {
|
|
35943
36487
|
/** Internal constructor. */
|
|
@@ -35979,7 +36523,7 @@ ${cue.notes ?? ""}`;
|
|
|
35979
36523
|
video: { min: 0, max },
|
|
35980
36524
|
audio: { min: 0, max },
|
|
35981
36525
|
subtitle: { min: 0, max },
|
|
35982
|
-
total: { min:
|
|
36526
|
+
total: { min: 0, max }
|
|
35983
36527
|
};
|
|
35984
36528
|
}
|
|
35985
36529
|
get supportsVideoRotationMetadata() {
|
|
@@ -35992,6 +36536,10 @@ ${cue.notes ?? ""}`;
|
|
|
35992
36536
|
_createMuxer(output) {
|
|
35993
36537
|
return new IsobmffMuxer2(output, this);
|
|
35994
36538
|
}
|
|
36539
|
+
/** @internal */
|
|
36540
|
+
_isFragmentedIsobmff() {
|
|
36541
|
+
return this._options.fastStart === "fragmented";
|
|
36542
|
+
}
|
|
35995
36543
|
};
|
|
35996
36544
|
var Mp4OutputFormat = class extends IsobmffOutputFormat2 {
|
|
35997
36545
|
/** Creates a new {@link Mp4OutputFormat} configured with the specified `options`. */
|
|
@@ -36135,7 +36683,7 @@ ${cue.notes ?? ""}`;
|
|
|
36135
36683
|
video: { min: 0, max },
|
|
36136
36684
|
audio: { min: 0, max },
|
|
36137
36685
|
subtitle: { min: 0, max },
|
|
36138
|
-
total: { min:
|
|
36686
|
+
total: { min: 0, max }
|
|
36139
36687
|
};
|
|
36140
36688
|
}
|
|
36141
36689
|
get fileExtension() {
|
|
@@ -36319,7 +36867,7 @@ ${cue.notes ?? ""}`;
|
|
|
36319
36867
|
video: { min: 0, max: 0 },
|
|
36320
36868
|
audio: { min: 0, max },
|
|
36321
36869
|
subtitle: { min: 0, max: 0 },
|
|
36322
|
-
total: { min:
|
|
36870
|
+
total: { min: 0, max }
|
|
36323
36871
|
};
|
|
36324
36872
|
}
|
|
36325
36873
|
get fileExtension() {
|
|
@@ -36456,7 +37004,7 @@ ${cue.notes ?? ""}`;
|
|
|
36456
37004
|
video: { min: 0, max: maxVideo },
|
|
36457
37005
|
audio: { min: 0, max: maxAudio },
|
|
36458
37006
|
subtitle: { min: 0, max: 0 },
|
|
36459
|
-
total: { min:
|
|
37007
|
+
total: { min: 0, max: maxTotal }
|
|
36460
37008
|
};
|
|
36461
37009
|
}
|
|
36462
37010
|
get fileExtension() {
|
|
@@ -36561,7 +37109,7 @@ ${cue.notes ?? ""}`;
|
|
|
36561
37109
|
audio: { min: 0, max: supportsAudio ? Infinity : 0 },
|
|
36562
37110
|
subtitle: { min: 0, max: 0 },
|
|
36563
37111
|
// Currently disabled
|
|
36564
|
-
total: { min:
|
|
37112
|
+
total: { min: 0, max: Infinity }
|
|
36565
37113
|
};
|
|
36566
37114
|
}
|
|
36567
37115
|
get supportsVideoRotationMetadata() {
|
|
@@ -36892,6 +37440,17 @@ ${cue.notes ?? ""}`;
|
|
|
36892
37440
|
`Invalid video frame rate: ${metadata.frameRate}. Must be a positive number.`
|
|
36893
37441
|
);
|
|
36894
37442
|
}
|
|
37443
|
+
if (metadata.decoderConfig !== void 0) {
|
|
37444
|
+
validateVideoChunkMetadata({ decoderConfig: metadata.decoderConfig }, source._codec);
|
|
37445
|
+
}
|
|
37446
|
+
if (metadata.primingPacket !== void 0) {
|
|
37447
|
+
if (!(metadata.primingPacket instanceof EncodedPacket)) {
|
|
37448
|
+
throw new TypeError("metadata.primingPacket, when provided, must be an EncodedPacket.");
|
|
37449
|
+
}
|
|
37450
|
+
if (metadata.decoderConfig === void 0) {
|
|
37451
|
+
throw new TypeError("metadata.primingPacket can only be provided alongside metadata.decoderConfig.");
|
|
37452
|
+
}
|
|
37453
|
+
}
|
|
36895
37454
|
const metadataCopy = { ...metadata };
|
|
36896
37455
|
metadataCopy.group ??= this.defaultTrackGroup;
|
|
36897
37456
|
return this._addTrack(new OutputVideoTrack2(
|
|
@@ -36907,6 +37466,17 @@ ${cue.notes ?? ""}`;
|
|
|
36907
37466
|
throw new TypeError("source must be an AudioSource.");
|
|
36908
37467
|
}
|
|
36909
37468
|
validateBaseTrackMetadata(metadata);
|
|
37469
|
+
if (metadata.decoderConfig !== void 0) {
|
|
37470
|
+
validateAudioChunkMetadata({ decoderConfig: metadata.decoderConfig }, source._codec);
|
|
37471
|
+
}
|
|
37472
|
+
if (metadata.primingPacket !== void 0) {
|
|
37473
|
+
if (!(metadata.primingPacket instanceof EncodedPacket)) {
|
|
37474
|
+
throw new TypeError("metadata.primingPacket, when provided, must be an EncodedPacket.");
|
|
37475
|
+
}
|
|
37476
|
+
if (metadata.decoderConfig === void 0) {
|
|
37477
|
+
throw new TypeError("metadata.primingPacket can only be provided alongside metadata.decoderConfig.");
|
|
37478
|
+
}
|
|
37479
|
+
}
|
|
36910
37480
|
const metadataCopy = { ...metadata };
|
|
36911
37481
|
metadataCopy.group ??= this.defaultTrackGroup;
|
|
36912
37482
|
return this._addTrack(new OutputAudioTrack2(
|
|
@@ -37597,7 +38167,7 @@ ${cue.notes ?? ""}`;
|
|
|
37597
38167
|
this.output.setMetadataTags(outputTags);
|
|
37598
38168
|
}
|
|
37599
38169
|
if (!this._composable) {
|
|
37600
|
-
this.isValid = this.output.hasEnoughTracks();
|
|
38170
|
+
this.isValid = this.output.hasEnoughTracks() && this.output.tracks.length > 0;
|
|
37601
38171
|
} else {
|
|
37602
38172
|
this.isValid = true;
|
|
37603
38173
|
}
|