mediabunny 1.52.3 → 1.53.1
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 +974 -397
- package/dist/bundles/mediabunny.min.cjs +9 -9
- package/dist/bundles/mediabunny.min.mjs +9 -9
- package/dist/bundles/mediabunny.mjs +974 -397
- package/dist/bundles/mediabunny.node.cjs +974 -397
- 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/target.d.ts.map +1 -1
- package/dist/modules/src/target.js +11 -2
- 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/target.ts +12 -2
- 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
|
|
@@ -28968,9 +29209,16 @@ var Mediabunny = (() => {
|
|
|
28968
29209
|
if (this._monotonicity === true && position !== this._lastFlushEnd) {
|
|
28969
29210
|
throw new Error("Internal error: Monotonicity violation.");
|
|
28970
29211
|
}
|
|
29212
|
+
const isPartialView = section.start !== 0 || section.end !== chunk.data.byteLength;
|
|
29213
|
+
let data;
|
|
29214
|
+
if (isPartialView && isWebKit()) {
|
|
29215
|
+
data = chunk.data.slice(section.start, section.end);
|
|
29216
|
+
} else {
|
|
29217
|
+
data = chunk.data.subarray(section.start, section.end);
|
|
29218
|
+
}
|
|
28971
29219
|
void this._streamWriter.write({
|
|
28972
29220
|
type: "write",
|
|
28973
|
-
data
|
|
29221
|
+
data,
|
|
28974
29222
|
position
|
|
28975
29223
|
}).catch((error) => {
|
|
28976
29224
|
this._writeError ??= error;
|
|
@@ -29213,6 +29461,7 @@ var Mediabunny = (() => {
|
|
|
29213
29461
|
this.allTracksKnown = promiseWithResolvers();
|
|
29214
29462
|
this.creationTime = Math.floor(Date.now() / 1e3) + TIMESTAMP_OFFSET;
|
|
29215
29463
|
this.finalizedChunks = [];
|
|
29464
|
+
this.wroteFragmentedHeader = false;
|
|
29216
29465
|
this.nextFragmentNumber = 1;
|
|
29217
29466
|
// Only relevant for fragmented files, to make sure new fragments start with the highest timestamp seen so far
|
|
29218
29467
|
this.maxWrittenTimestamp = -Infinity;
|
|
@@ -29220,17 +29469,18 @@ var Mediabunny = (() => {
|
|
|
29220
29469
|
this.maxWrittenEndTimestamp = -Infinity;
|
|
29221
29470
|
this.segmentHeaderSize = null;
|
|
29222
29471
|
this.format = format;
|
|
29472
|
+
this.formatOptions = { ...format._options };
|
|
29223
29473
|
this.isQuickTime = format instanceof MovOutputFormat;
|
|
29224
29474
|
this.isCmaf = format instanceof CmafOutputFormat;
|
|
29225
|
-
this.minimumFragmentDuration =
|
|
29475
|
+
this.minimumFragmentDuration = this.formatOptions.minimumFragmentDuration ?? (format instanceof CmafOutputFormat ? Infinity : 1);
|
|
29226
29476
|
this.auxWriter.start();
|
|
29227
29477
|
}
|
|
29228
29478
|
async start() {
|
|
29229
29479
|
const release = await this.mutex.acquire();
|
|
29230
29480
|
if (!this.isCmaf) {
|
|
29231
|
-
this.writer = await this.output._getRootWriter((target) => this.
|
|
29481
|
+
this.writer = await this.output._getRootWriter((target) => this.formatOptions.fastStart !== void 0 ? this.formatOptions.fastStart === "fragmented" : target instanceof BufferTarget);
|
|
29232
29482
|
this.boxWriter = new IsobmffBoxWriter(this.writer);
|
|
29233
|
-
this.fastStart = this.
|
|
29483
|
+
this.fastStart = this.formatOptions.fastStart ?? (this.writer.target instanceof BufferTarget ? "in-memory" : false);
|
|
29234
29484
|
this.isFragmented = this.fastStart === "fragmented";
|
|
29235
29485
|
} else {
|
|
29236
29486
|
this.fastStart = "fragmented";
|
|
@@ -29252,7 +29502,7 @@ var Mediabunny = (() => {
|
|
|
29252
29502
|
{
|
|
29253
29503
|
const boxWriter = this.initBoxWriter ?? this.boxWriter;
|
|
29254
29504
|
assert(boxWriter);
|
|
29255
|
-
if (this.
|
|
29505
|
+
if (this.formatOptions.onFtyp) {
|
|
29256
29506
|
boxWriter.writer.startTrackingWrites();
|
|
29257
29507
|
}
|
|
29258
29508
|
boxWriter.writeBox(ftyp({
|
|
@@ -29261,9 +29511,9 @@ var Mediabunny = (() => {
|
|
|
29261
29511
|
fragmented: this.isFragmented,
|
|
29262
29512
|
cmaf: this.isCmaf
|
|
29263
29513
|
}));
|
|
29264
|
-
if (this.
|
|
29514
|
+
if (this.formatOptions.onFtyp) {
|
|
29265
29515
|
const { data, start } = boxWriter.writer.stopTrackingWrites();
|
|
29266
|
-
this.
|
|
29516
|
+
this.formatOptions.onFtyp(data, start);
|
|
29267
29517
|
}
|
|
29268
29518
|
this.ftypSize = boxWriter.writer.getPos();
|
|
29269
29519
|
if (this.isCmaf) {
|
|
@@ -29283,13 +29533,28 @@ var Mediabunny = (() => {
|
|
|
29283
29533
|
} else {
|
|
29284
29534
|
assert(this.writer);
|
|
29285
29535
|
assert(this.boxWriter);
|
|
29286
|
-
if (this.
|
|
29536
|
+
if (this.formatOptions.onMdat) {
|
|
29287
29537
|
this.writer.startTrackingWrites();
|
|
29288
29538
|
}
|
|
29289
29539
|
this.mdat = mdat(true);
|
|
29290
29540
|
this.boxWriter.writeBox(this.mdat);
|
|
29291
29541
|
}
|
|
29292
29542
|
await this.writer?.flush();
|
|
29543
|
+
for (const track of this.output.tracks) {
|
|
29544
|
+
if (track.isVideoTrack() && track.metadata.decoderConfig) {
|
|
29545
|
+
this.getVideoTrackData(
|
|
29546
|
+
track,
|
|
29547
|
+
track.metadata.primingPacket ?? null,
|
|
29548
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
29549
|
+
);
|
|
29550
|
+
} else if (track.isAudioTrack() && track.metadata.decoderConfig) {
|
|
29551
|
+
this.getAudioTrackData(
|
|
29552
|
+
track,
|
|
29553
|
+
track.metadata.primingPacket ?? null,
|
|
29554
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
29555
|
+
);
|
|
29556
|
+
}
|
|
29557
|
+
}
|
|
29293
29558
|
release();
|
|
29294
29559
|
}
|
|
29295
29560
|
allTracksAreKnown() {
|
|
@@ -29326,7 +29591,7 @@ var Mediabunny = (() => {
|
|
|
29326
29591
|
if (existingTrackData) {
|
|
29327
29592
|
return existingTrackData;
|
|
29328
29593
|
}
|
|
29329
|
-
validateVideoChunkMetadata(meta);
|
|
29594
|
+
validateVideoChunkMetadata(meta, track.source._codec);
|
|
29330
29595
|
assert(meta);
|
|
29331
29596
|
assert(meta.decoderConfig);
|
|
29332
29597
|
const decoderConfig = { ...meta.decoderConfig };
|
|
@@ -29334,6 +29599,9 @@ var Mediabunny = (() => {
|
|
|
29334
29599
|
assert(decoderConfig.codedHeight !== void 0);
|
|
29335
29600
|
let requiresAnnexBTransformation = false;
|
|
29336
29601
|
if (track.source._codec === "avc" && !decoderConfig.description) {
|
|
29602
|
+
if (!packet) {
|
|
29603
|
+
throw new Error("No AVC description provided; you must therefore provide a priming packet.");
|
|
29604
|
+
}
|
|
29337
29605
|
const decoderConfigurationRecord = extractAvcDecoderConfigurationRecord(packet.data);
|
|
29338
29606
|
if (!decoderConfigurationRecord) {
|
|
29339
29607
|
throw new Error(
|
|
@@ -29343,6 +29611,9 @@ var Mediabunny = (() => {
|
|
|
29343
29611
|
decoderConfig.description = serializeAvcDecoderConfigurationRecord(decoderConfigurationRecord);
|
|
29344
29612
|
requiresAnnexBTransformation = true;
|
|
29345
29613
|
} else if (track.source._codec === "hevc" && !decoderConfig.description) {
|
|
29614
|
+
if (!packet) {
|
|
29615
|
+
throw new Error("No HEVC description provided; you must therefore provide a priming packet.");
|
|
29616
|
+
}
|
|
29346
29617
|
const decoderConfigurationRecord = extractHevcDecoderConfigurationRecord(packet.data);
|
|
29347
29618
|
if (!decoderConfigurationRecord) {
|
|
29348
29619
|
throw new Error(
|
|
@@ -29401,12 +29672,15 @@ var Mediabunny = (() => {
|
|
|
29401
29672
|
if (existingTrackData) {
|
|
29402
29673
|
return existingTrackData;
|
|
29403
29674
|
}
|
|
29404
|
-
validateAudioChunkMetadata(meta);
|
|
29675
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
29405
29676
|
assert(meta);
|
|
29406
29677
|
assert(meta.decoderConfig);
|
|
29407
29678
|
const decoderConfig = { ...meta.decoderConfig };
|
|
29408
29679
|
let requiresAdtsStripping = false;
|
|
29409
29680
|
if (track.source._codec === "aac" && !decoderConfig.description) {
|
|
29681
|
+
if (!packet) {
|
|
29682
|
+
throw new Error("No AAC description provided; you must therefore provide a priming packet.");
|
|
29683
|
+
}
|
|
29410
29684
|
const adtsFrame = readAdtsFrameHeader(FileSlice4.tempFromBytes(packet.data));
|
|
29411
29685
|
if (!adtsFrame) {
|
|
29412
29686
|
throw new Error(
|
|
@@ -29425,6 +29699,11 @@ var Mediabunny = (() => {
|
|
|
29425
29699
|
});
|
|
29426
29700
|
requiresAdtsStripping = true;
|
|
29427
29701
|
}
|
|
29702
|
+
if (track.source._codec === "ac3" || track.source._codec === "eac3") {
|
|
29703
|
+
if (!packet) {
|
|
29704
|
+
throw new Error("AC-3/E-AC-3 require a priming packet.");
|
|
29705
|
+
}
|
|
29706
|
+
}
|
|
29428
29707
|
const newTrackData = {
|
|
29429
29708
|
muxer: this,
|
|
29430
29709
|
track,
|
|
@@ -29436,7 +29715,7 @@ var Mediabunny = (() => {
|
|
|
29436
29715
|
requiresPcmTransformation: !this.isFragmented && PCM_AUDIO_CODECS.includes(track.source._codec),
|
|
29437
29716
|
expectedNextPcmPacketTimestamp: null,
|
|
29438
29717
|
requiresAdtsStripping,
|
|
29439
|
-
|
|
29718
|
+
primingPacket: packet
|
|
29440
29719
|
},
|
|
29441
29720
|
timescale: decoderConfig.sampleRate,
|
|
29442
29721
|
samples: [],
|
|
@@ -29865,7 +30144,8 @@ var Mediabunny = (() => {
|
|
|
29865
30144
|
startTimestamp: sample.timestamp,
|
|
29866
30145
|
samples: [],
|
|
29867
30146
|
offset: null,
|
|
29868
|
-
moofOffset: null
|
|
30147
|
+
moofOffset: null,
|
|
30148
|
+
trafIndex: null
|
|
29869
30149
|
};
|
|
29870
30150
|
}
|
|
29871
30151
|
assert(trackData.currentChunk);
|
|
@@ -29932,19 +30212,19 @@ var Mediabunny = (() => {
|
|
|
29932
30212
|
}
|
|
29933
30213
|
async finalizeFragment(flushWriter = !this.isCmaf) {
|
|
29934
30214
|
assert(this.isFragmented);
|
|
29935
|
-
|
|
29936
|
-
|
|
30215
|
+
if (!this.wroteFragmentedHeader) {
|
|
30216
|
+
this.wroteFragmentedHeader = true;
|
|
29937
30217
|
const boxWriter = this.initBoxWriter ?? this.boxWriter;
|
|
29938
30218
|
assert(boxWriter);
|
|
29939
|
-
if (this.
|
|
30219
|
+
if (this.formatOptions.onMoov) {
|
|
29940
30220
|
boxWriter.writer.startTrackingWrites();
|
|
29941
30221
|
}
|
|
29942
30222
|
this.ensureOneEnabledTrack();
|
|
29943
30223
|
const movieBox = moov(this);
|
|
29944
30224
|
boxWriter.writeBox(movieBox);
|
|
29945
|
-
if (this.
|
|
30225
|
+
if (this.formatOptions.onMoov) {
|
|
29946
30226
|
const { data, start } = boxWriter.writer.stopTrackingWrites();
|
|
29947
|
-
this.
|
|
30227
|
+
this.formatOptions.onMoov(data, start);
|
|
29948
30228
|
}
|
|
29949
30229
|
if (this.isCmaf) {
|
|
29950
30230
|
assert(this.initWriter);
|
|
@@ -29961,14 +30241,23 @@ var Mediabunny = (() => {
|
|
|
29961
30241
|
assert(this.writer);
|
|
29962
30242
|
assert(this.boxWriter);
|
|
29963
30243
|
const tracksInFragment = this.trackDatas.filter((x) => x.currentChunk);
|
|
30244
|
+
if (tracksInFragment.length === 0) {
|
|
30245
|
+
if (flushWriter) {
|
|
30246
|
+
await this.writer.flush();
|
|
30247
|
+
}
|
|
30248
|
+
return;
|
|
30249
|
+
}
|
|
30250
|
+
const fragmentNumber = this.nextFragmentNumber++;
|
|
29964
30251
|
const moofBox = moof(fragmentNumber, tracksInFragment);
|
|
29965
30252
|
const moofOffset = this.writer.getPos();
|
|
29966
30253
|
const mdatStartPos = moofOffset + this.boxWriter.measureBox(moofBox);
|
|
29967
30254
|
let currentPos = mdatStartPos + MIN_BOX_HEADER_SIZE;
|
|
29968
30255
|
let fragmentStartTimestamp = Infinity;
|
|
29969
|
-
for (
|
|
30256
|
+
for (let i = 0; i < tracksInFragment.length; i++) {
|
|
30257
|
+
const trackData = tracksInFragment[i];
|
|
29970
30258
|
trackData.currentChunk.offset = currentPos;
|
|
29971
30259
|
trackData.currentChunk.moofOffset = moofOffset;
|
|
30260
|
+
trackData.currentChunk.trafIndex = i;
|
|
29972
30261
|
for (const sample of trackData.currentChunk.samples) {
|
|
29973
30262
|
currentPos += sample.size;
|
|
29974
30263
|
}
|
|
@@ -29981,17 +30270,17 @@ var Mediabunny = (() => {
|
|
|
29981
30270
|
trackData.currentChunk.offset += MAX_BOX_HEADER_SIZE - MIN_BOX_HEADER_SIZE;
|
|
29982
30271
|
}
|
|
29983
30272
|
}
|
|
29984
|
-
if (this.
|
|
30273
|
+
if (this.formatOptions.onMoof) {
|
|
29985
30274
|
this.writer.startTrackingWrites();
|
|
29986
30275
|
}
|
|
29987
30276
|
const newMoofBox = moof(fragmentNumber, tracksInFragment);
|
|
29988
30277
|
this.boxWriter.writeBox(newMoofBox);
|
|
29989
|
-
if (this.
|
|
30278
|
+
if (this.formatOptions.onMoof) {
|
|
29990
30279
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
29991
|
-
this.
|
|
30280
|
+
this.formatOptions.onMoof(data, start, fragmentStartTimestamp);
|
|
29992
30281
|
}
|
|
29993
30282
|
assert(this.writer.getPos() === mdatStartPos);
|
|
29994
|
-
if (this.
|
|
30283
|
+
if (this.formatOptions.onMdat) {
|
|
29995
30284
|
this.writer.startTrackingWrites();
|
|
29996
30285
|
}
|
|
29997
30286
|
const mdatBox = mdat(needsLargeMdatSize);
|
|
@@ -30004,9 +30293,9 @@ var Mediabunny = (() => {
|
|
|
30004
30293
|
sample.data = null;
|
|
30005
30294
|
}
|
|
30006
30295
|
}
|
|
30007
|
-
if (this.
|
|
30296
|
+
if (this.formatOptions.onMdat) {
|
|
30008
30297
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30009
|
-
this.
|
|
30298
|
+
this.formatOptions.onMdat(data, start);
|
|
30010
30299
|
}
|
|
30011
30300
|
for (const trackData of tracksInFragment) {
|
|
30012
30301
|
trackData.finalizedChunks.push(trackData.currentChunk);
|
|
@@ -30018,33 +30307,36 @@ var Mediabunny = (() => {
|
|
|
30018
30307
|
}
|
|
30019
30308
|
}
|
|
30020
30309
|
async registerSampleFastStartReserve(trackData, sample) {
|
|
30021
|
-
assert(this.writer);
|
|
30022
|
-
assert(this.boxWriter);
|
|
30023
30310
|
if (this.allTracksAreKnown()) {
|
|
30024
30311
|
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
|
-
}
|
|
30312
|
+
await this.createFastStartReserveMdat();
|
|
30042
30313
|
}
|
|
30043
30314
|
await this.addSampleToTrack(trackData, sample);
|
|
30044
30315
|
} else {
|
|
30045
30316
|
trackData.sampleQueue.push(sample);
|
|
30046
30317
|
}
|
|
30047
30318
|
}
|
|
30319
|
+
async createFastStartReserveMdat() {
|
|
30320
|
+
assert(this.writer);
|
|
30321
|
+
assert(this.boxWriter);
|
|
30322
|
+
this.ensureOneEnabledTrack();
|
|
30323
|
+
const moovBox = moov(this);
|
|
30324
|
+
const moovSize = this.boxWriter.measureBox(moovBox);
|
|
30325
|
+
const reservedSize = moovSize + this.computeSampleTableSizeUpperBound() + 4096;
|
|
30326
|
+
assert(this.ftypSize !== null);
|
|
30327
|
+
this.writer.seek(this.ftypSize + reservedSize);
|
|
30328
|
+
if (this.formatOptions.onMdat) {
|
|
30329
|
+
this.writer.startTrackingWrites();
|
|
30330
|
+
}
|
|
30331
|
+
this.mdat = mdat(true);
|
|
30332
|
+
this.boxWriter.writeBox(this.mdat);
|
|
30333
|
+
for (const trackData of this.trackDatas) {
|
|
30334
|
+
for (const sample of trackData.sampleQueue) {
|
|
30335
|
+
await this.addSampleToTrack(trackData, sample);
|
|
30336
|
+
}
|
|
30337
|
+
trackData.sampleQueue.length = 0;
|
|
30338
|
+
}
|
|
30339
|
+
}
|
|
30048
30340
|
computeSampleTableSizeUpperBound() {
|
|
30049
30341
|
assert(this.fastStart === "reserve");
|
|
30050
30342
|
let upperBound = 0;
|
|
@@ -30095,11 +30387,31 @@ var Mediabunny = (() => {
|
|
|
30095
30387
|
}
|
|
30096
30388
|
}
|
|
30097
30389
|
}
|
|
30390
|
+
/** Internal function for external callers who want to full control fragment boundaries. */
|
|
30391
|
+
async forceFragmentFinalization() {
|
|
30392
|
+
assert(this.isFragmented);
|
|
30393
|
+
const release = await this.mutex.acquire();
|
|
30394
|
+
try {
|
|
30395
|
+
for (const trackData of this.trackDatas) {
|
|
30396
|
+
if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") {
|
|
30397
|
+
await this.processWebVTTCues(trackData, Infinity);
|
|
30398
|
+
}
|
|
30399
|
+
this.processTimestamps(trackData);
|
|
30400
|
+
}
|
|
30401
|
+
await this.interleaveSamples(true);
|
|
30402
|
+
await this.finalizeFragment();
|
|
30403
|
+
} finally {
|
|
30404
|
+
release();
|
|
30405
|
+
}
|
|
30406
|
+
}
|
|
30098
30407
|
/** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */
|
|
30099
30408
|
async finalize() {
|
|
30100
30409
|
const release = await this.mutex.acquire();
|
|
30101
30410
|
this.allTracksKnown.resolve();
|
|
30102
30411
|
this.ensureOneEnabledTrack();
|
|
30412
|
+
if (!this.mdat && this.fastStart === "reserve") {
|
|
30413
|
+
await this.createFastStartReserveMdat();
|
|
30414
|
+
}
|
|
30103
30415
|
for (const trackData of this.trackDatas) {
|
|
30104
30416
|
trackData.closed = true;
|
|
30105
30417
|
if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") {
|
|
@@ -30113,11 +30425,12 @@ var Mediabunny = (() => {
|
|
|
30113
30425
|
} else {
|
|
30114
30426
|
for (const trackData of this.trackDatas) {
|
|
30115
30427
|
await this.finalizeCurrentChunk(trackData);
|
|
30116
|
-
|
|
30117
|
-
|
|
30118
|
-
|
|
30119
|
-
|
|
30120
|
-
|
|
30428
|
+
if (trackData.startTimestampOffset !== null) {
|
|
30429
|
+
for (let i = 0; i < trackData.samples.length; i++) {
|
|
30430
|
+
const sample = trackData.samples[i];
|
|
30431
|
+
sample.timestamp -= trackData.startTimestampOffset;
|
|
30432
|
+
sample.decodeTimestamp -= trackData.startTimestampOffset;
|
|
30433
|
+
}
|
|
30121
30434
|
}
|
|
30122
30435
|
}
|
|
30123
30436
|
}
|
|
@@ -30142,16 +30455,16 @@ var Mediabunny = (() => {
|
|
|
30142
30455
|
if (currentChunkPos < 2 ** 32) break;
|
|
30143
30456
|
if (mdatSize >= 2 ** 32) this.mdat.largeSize = true;
|
|
30144
30457
|
}
|
|
30145
|
-
if (this.
|
|
30458
|
+
if (this.formatOptions.onMoov) {
|
|
30146
30459
|
this.writer.startTrackingWrites();
|
|
30147
30460
|
}
|
|
30148
30461
|
const movieBox = moov(this);
|
|
30149
30462
|
this.boxWriter.writeBox(movieBox);
|
|
30150
|
-
if (this.
|
|
30463
|
+
if (this.formatOptions.onMoov) {
|
|
30151
30464
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30152
|
-
this.
|
|
30465
|
+
this.formatOptions.onMoov(data, start);
|
|
30153
30466
|
}
|
|
30154
|
-
if (this.
|
|
30467
|
+
if (this.formatOptions.onMdat) {
|
|
30155
30468
|
this.writer.startTrackingWrites();
|
|
30156
30469
|
}
|
|
30157
30470
|
this.mdat.size = mdatSize;
|
|
@@ -30163,9 +30476,9 @@ var Mediabunny = (() => {
|
|
|
30163
30476
|
sample.data = null;
|
|
30164
30477
|
}
|
|
30165
30478
|
}
|
|
30166
|
-
if (this.
|
|
30479
|
+
if (this.formatOptions.onMdat) {
|
|
30167
30480
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30168
|
-
this.
|
|
30481
|
+
this.formatOptions.onMdat(data, start);
|
|
30169
30482
|
}
|
|
30170
30483
|
} else if (this.isFragmented) {
|
|
30171
30484
|
if (this.isCmaf) {
|
|
@@ -30189,29 +30502,29 @@ var Mediabunny = (() => {
|
|
|
30189
30502
|
this.mdat.size = mdatSize;
|
|
30190
30503
|
this.mdat.largeSize = mdatSize >= 2 ** 32;
|
|
30191
30504
|
this.boxWriter.patchBox(this.mdat);
|
|
30192
|
-
if (this.
|
|
30505
|
+
if (this.formatOptions.onMdat) {
|
|
30193
30506
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30194
|
-
this.
|
|
30507
|
+
this.formatOptions.onMdat(data, start);
|
|
30195
30508
|
}
|
|
30196
30509
|
const movieBox = moov(this);
|
|
30197
30510
|
if (this.fastStart === "reserve") {
|
|
30198
30511
|
assert(this.ftypSize !== null);
|
|
30199
30512
|
this.writer.seek(this.ftypSize);
|
|
30200
|
-
if (this.
|
|
30513
|
+
if (this.formatOptions.onMoov) {
|
|
30201
30514
|
this.writer.startTrackingWrites();
|
|
30202
30515
|
}
|
|
30203
30516
|
this.boxWriter.writeBox(movieBox);
|
|
30204
30517
|
const remainingSpace = this.boxWriter.offsets.get(this.mdat) - this.writer.getPos();
|
|
30205
30518
|
this.boxWriter.writeBox(free(remainingSpace));
|
|
30206
30519
|
} else {
|
|
30207
|
-
if (this.
|
|
30520
|
+
if (this.formatOptions.onMoov) {
|
|
30208
30521
|
this.writer.startTrackingWrites();
|
|
30209
30522
|
}
|
|
30210
30523
|
this.boxWriter.writeBox(movieBox);
|
|
30211
30524
|
}
|
|
30212
|
-
if (this.
|
|
30525
|
+
if (this.formatOptions.onMoov) {
|
|
30213
30526
|
const { data, start } = this.writer.stopTrackingWrites();
|
|
30214
|
-
this.
|
|
30527
|
+
this.formatOptions.onMoov(data, start);
|
|
30215
30528
|
}
|
|
30216
30529
|
}
|
|
30217
30530
|
release();
|
|
@@ -30258,6 +30571,21 @@ var Mediabunny = (() => {
|
|
|
30258
30571
|
this.createSegmentInfo();
|
|
30259
30572
|
this.createCues();
|
|
30260
30573
|
await this.writer.flush();
|
|
30574
|
+
for (const track of this.output.tracks) {
|
|
30575
|
+
if (track.isVideoTrack() && track.metadata.decoderConfig) {
|
|
30576
|
+
this.getVideoTrackData(
|
|
30577
|
+
track,
|
|
30578
|
+
track.metadata.primingPacket ?? null,
|
|
30579
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
30580
|
+
);
|
|
30581
|
+
} else if (track.isAudioTrack() && track.metadata.decoderConfig) {
|
|
30582
|
+
this.getAudioTrackData(
|
|
30583
|
+
track,
|
|
30584
|
+
track.metadata.primingPacket ?? null,
|
|
30585
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
30586
|
+
);
|
|
30587
|
+
}
|
|
30588
|
+
}
|
|
30261
30589
|
release();
|
|
30262
30590
|
}
|
|
30263
30591
|
writeEBMLHeader() {
|
|
@@ -30722,7 +31050,7 @@ var Mediabunny = (() => {
|
|
|
30722
31050
|
if (existingTrackData) {
|
|
30723
31051
|
return existingTrackData;
|
|
30724
31052
|
}
|
|
30725
|
-
validateVideoChunkMetadata(meta);
|
|
31053
|
+
validateVideoChunkMetadata(meta, track.source._codec);
|
|
30726
31054
|
assert(meta);
|
|
30727
31055
|
assert(meta.decoderConfig);
|
|
30728
31056
|
assert(meta.decoderConfig.codedWidth !== void 0);
|
|
@@ -30741,8 +31069,7 @@ var Mediabunny = (() => {
|
|
|
30741
31069
|
height: meta.decoderConfig.codedHeight,
|
|
30742
31070
|
aspectRatio,
|
|
30743
31071
|
decoderConfig: meta.decoderConfig,
|
|
30744
|
-
alphaMode: !!packet.sideData.alpha
|
|
30745
|
-
// The first packet determines if this track has alpha or not
|
|
31072
|
+
alphaMode: packet ? !!packet.sideData.alpha : null
|
|
30746
31073
|
},
|
|
30747
31074
|
chunkQueue: [],
|
|
30748
31075
|
lastWrittenMsTimestamp: null,
|
|
@@ -30772,12 +31099,15 @@ var Mediabunny = (() => {
|
|
|
30772
31099
|
if (existingTrackData) {
|
|
30773
31100
|
return existingTrackData;
|
|
30774
31101
|
}
|
|
30775
|
-
validateAudioChunkMetadata(meta);
|
|
31102
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
30776
31103
|
assert(meta);
|
|
30777
31104
|
assert(meta.decoderConfig);
|
|
30778
31105
|
const decoderConfig = { ...meta.decoderConfig };
|
|
30779
31106
|
let requiresAdtsStripping = false;
|
|
30780
31107
|
if (track.source._codec === "aac" && !decoderConfig.description) {
|
|
31108
|
+
if (!packet) {
|
|
31109
|
+
throw new Error("No AAC description provided; you must therefore provide a priming packet.");
|
|
31110
|
+
}
|
|
30781
31111
|
const adtsFrame = readAdtsFrameHeader(FileSlice4.tempFromBytes(packet.data));
|
|
30782
31112
|
if (!adtsFrame) {
|
|
30783
31113
|
throw new Error(
|
|
@@ -30847,6 +31177,7 @@ var Mediabunny = (() => {
|
|
|
30847
31177
|
const release = await this.mutex.acquire();
|
|
30848
31178
|
try {
|
|
30849
31179
|
const trackData = this.getVideoTrackData(track, packet, meta);
|
|
31180
|
+
trackData.info.alphaMode ??= !!packet.sideData.alpha;
|
|
30850
31181
|
let packetData = packet.data;
|
|
30851
31182
|
if (track.source._codec === "prores") {
|
|
30852
31183
|
if (packetData.byteLength < 8) {
|
|
@@ -31237,7 +31568,7 @@ ${cue.notes ?? ""}`;
|
|
|
31237
31568
|
data.sampleRate,
|
|
31238
31569
|
padding
|
|
31239
31570
|
);
|
|
31240
|
-
this.writer.
|
|
31571
|
+
this.writer.write(new Uint8Array(startPos + frameSize - this.writer.getPos()));
|
|
31241
31572
|
}
|
|
31242
31573
|
};
|
|
31243
31574
|
|
|
@@ -31322,22 +31653,95 @@ ${cue.notes ?? ""}`;
|
|
|
31322
31653
|
throw new Error("MP3 does not support subtitles.");
|
|
31323
31654
|
}
|
|
31324
31655
|
async finalize() {
|
|
31325
|
-
if (!this.xingFrameData || this.xingFramePos === null) {
|
|
31326
|
-
return;
|
|
31327
|
-
}
|
|
31328
31656
|
const release = await this.mutex.acquire();
|
|
31657
|
+
if (!this.xingFrameData && this.format._options.xingHeader === false) {
|
|
31658
|
+
throw new Error(
|
|
31659
|
+
"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."
|
|
31660
|
+
);
|
|
31661
|
+
}
|
|
31662
|
+
if (!this.xingFrameData) {
|
|
31663
|
+
const track = this.output.tracks[0];
|
|
31664
|
+
assert(track?.isAudioTrack());
|
|
31665
|
+
const primingPacket = track.metadata.primingPacket;
|
|
31666
|
+
if (primingPacket) {
|
|
31667
|
+
const view2 = toDataView(primingPacket.data);
|
|
31668
|
+
if (view2.byteLength < 4) {
|
|
31669
|
+
throw new Error("Invalid MP3 header in priming packet.");
|
|
31670
|
+
}
|
|
31671
|
+
const word = view2.getUint32(0, false);
|
|
31672
|
+
const header = readMp3FrameHeader(word, null).header;
|
|
31673
|
+
if (!header) {
|
|
31674
|
+
throw new Error("Invalid MP3 header in priming packet.");
|
|
31675
|
+
}
|
|
31676
|
+
this.xingFrameData = {
|
|
31677
|
+
mpegVersionId: header.mpegVersionId,
|
|
31678
|
+
layer: header.layer,
|
|
31679
|
+
frequencyIndex: header.frequencyIndex,
|
|
31680
|
+
sampleRate: header.sampleRate,
|
|
31681
|
+
channel: header.channel,
|
|
31682
|
+
modeExtension: header.modeExtension,
|
|
31683
|
+
copyright: header.copyright,
|
|
31684
|
+
original: header.original,
|
|
31685
|
+
emphasis: header.emphasis,
|
|
31686
|
+
frameCount: null,
|
|
31687
|
+
fileSize: null,
|
|
31688
|
+
toc: null
|
|
31689
|
+
};
|
|
31690
|
+
} else if (track.metadata.decoderConfig) {
|
|
31691
|
+
const { sampleRate, numberOfChannels } = track.metadata.decoderConfig;
|
|
31692
|
+
const mpegVersionIds = [3, 2, 0];
|
|
31693
|
+
let mpegVersionId = null;
|
|
31694
|
+
let frequencyIndex = -1;
|
|
31695
|
+
for (let i = 0; i < mpegVersionIds.length; i++) {
|
|
31696
|
+
frequencyIndex = SAMPLING_RATES.indexOf(sampleRate << i);
|
|
31697
|
+
if (frequencyIndex !== -1) {
|
|
31698
|
+
mpegVersionId = mpegVersionIds[i];
|
|
31699
|
+
break;
|
|
31700
|
+
}
|
|
31701
|
+
}
|
|
31702
|
+
if (mpegVersionId === null) {
|
|
31703
|
+
throw new Error(`${sampleRate} Hz is not a valid MP3 sample rate.`);
|
|
31704
|
+
}
|
|
31705
|
+
this.xingFrameData = {
|
|
31706
|
+
mpegVersionId,
|
|
31707
|
+
layer: 1,
|
|
31708
|
+
// Layer III
|
|
31709
|
+
frequencyIndex,
|
|
31710
|
+
sampleRate,
|
|
31711
|
+
channel: numberOfChannels === 1 ? 3 : 0,
|
|
31712
|
+
// 3 = single channel, 0 = stereo
|
|
31713
|
+
modeExtension: 0,
|
|
31714
|
+
copyright: 0,
|
|
31715
|
+
original: 0,
|
|
31716
|
+
emphasis: 0,
|
|
31717
|
+
frameCount: null,
|
|
31718
|
+
fileSize: null,
|
|
31719
|
+
toc: null
|
|
31720
|
+
};
|
|
31721
|
+
} else {
|
|
31722
|
+
throw new Error(
|
|
31723
|
+
"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."
|
|
31724
|
+
);
|
|
31725
|
+
}
|
|
31726
|
+
this.xingFramePos = this.writer.getPos();
|
|
31727
|
+
this.mp3Writer.writeXingFrame(this.xingFrameData);
|
|
31728
|
+
this.frameCount++;
|
|
31729
|
+
}
|
|
31730
|
+
assert(this.xingFramePos !== null);
|
|
31329
31731
|
const endPos = this.writer.getPos();
|
|
31330
31732
|
const audioDataEndPos = endPos - this.xingFramePos;
|
|
31331
31733
|
this.writer.seek(this.xingFramePos);
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31734
|
+
if (this.framePositions.length > 0) {
|
|
31735
|
+
const toc = new Uint8Array(100);
|
|
31736
|
+
for (let i = 0; i < 100; i++) {
|
|
31737
|
+
const index = Math.floor(this.framePositions.length * (i / 100));
|
|
31738
|
+
const byteOffset = this.framePositions[index] - this.xingFramePos;
|
|
31739
|
+
toc[i] = 256 * (byteOffset / audioDataEndPos);
|
|
31740
|
+
}
|
|
31741
|
+
this.xingFrameData.toc = toc;
|
|
31337
31742
|
}
|
|
31338
31743
|
this.xingFrameData.frameCount = this.frameCount;
|
|
31339
31744
|
this.xingFrameData.fileSize = audioDataEndPos;
|
|
31340
|
-
this.xingFrameData.toc = toc;
|
|
31341
31745
|
if (this.format._options.onXingFrame) {
|
|
31342
31746
|
this.writer.startTrackingWrites();
|
|
31343
31747
|
}
|
|
@@ -31365,6 +31769,12 @@ ${cue.notes ?? ""}`;
|
|
|
31365
31769
|
async start() {
|
|
31366
31770
|
const release = await this.mutex.acquire();
|
|
31367
31771
|
this.writer = await this.output._getRootWriter(true);
|
|
31772
|
+
for (const track of this.output.tracks) {
|
|
31773
|
+
assert(track.isAudioTrack());
|
|
31774
|
+
if (track.metadata.decoderConfig) {
|
|
31775
|
+
this.getTrackData(track, { decoderConfig: track.metadata.decoderConfig });
|
|
31776
|
+
}
|
|
31777
|
+
}
|
|
31368
31778
|
release();
|
|
31369
31779
|
}
|
|
31370
31780
|
async getMimeType() {
|
|
@@ -31386,7 +31796,7 @@ ${cue.notes ?? ""}`;
|
|
|
31386
31796
|
serialNumber = Math.floor(2 ** 32 * Math.random());
|
|
31387
31797
|
} while (this.trackDatas.some((td) => td.serialNumber === serialNumber));
|
|
31388
31798
|
assert(track.source._codec === "vorbis" || track.source._codec === "opus");
|
|
31389
|
-
validateAudioChunkMetadata(meta);
|
|
31799
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
31390
31800
|
assert(meta);
|
|
31391
31801
|
assert(meta.decoderConfig);
|
|
31392
31802
|
const newTrackData = {
|
|
@@ -31738,7 +32148,7 @@ ${cue.notes ?? ""}`;
|
|
|
31738
32148
|
if (existingTrackData) {
|
|
31739
32149
|
return existingTrackData;
|
|
31740
32150
|
}
|
|
31741
|
-
validateVideoChunkMetadata(meta);
|
|
32151
|
+
validateVideoChunkMetadata(meta, track.source._codec);
|
|
31742
32152
|
assert(meta?.decoderConfig);
|
|
31743
32153
|
const codec = track.source._codec;
|
|
31744
32154
|
assert(codec === "avc" || codec === "hevc");
|
|
@@ -31773,7 +32183,7 @@ ${cue.notes ?? ""}`;
|
|
|
31773
32183
|
if (existingTrackData) {
|
|
31774
32184
|
return existingTrackData;
|
|
31775
32185
|
}
|
|
31776
|
-
validateAudioChunkMetadata(meta);
|
|
32186
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
31777
32187
|
assert(meta?.decoderConfig);
|
|
31778
32188
|
const codec = track.source._codec;
|
|
31779
32189
|
assert(codec === "aac" || codec === "mp3" || codec === "ac3" || codec === "eac3");
|
|
@@ -32325,6 +32735,14 @@ ${cue.notes ?? ""}`;
|
|
|
32325
32735
|
const release = await this.mutex.acquire();
|
|
32326
32736
|
this.writer = await this.output._getRootWriter(false);
|
|
32327
32737
|
this.riffWriter = new RiffWriter(this.writer);
|
|
32738
|
+
const track = this.output.tracks[0];
|
|
32739
|
+
assert(track?.isAudioTrack());
|
|
32740
|
+
if (track.metadata.decoderConfig) {
|
|
32741
|
+
validateAudioChunkMetadata({ decoderConfig: track.metadata.decoderConfig }, track.source._codec);
|
|
32742
|
+
this.writeHeader(track, track.metadata.decoderConfig);
|
|
32743
|
+
this.sampleRate = track.metadata.decoderConfig.sampleRate;
|
|
32744
|
+
this.headerWritten = true;
|
|
32745
|
+
}
|
|
32328
32746
|
release();
|
|
32329
32747
|
}
|
|
32330
32748
|
async getMimeType() {
|
|
@@ -32337,7 +32755,7 @@ ${cue.notes ?? ""}`;
|
|
|
32337
32755
|
const release = await this.mutex.acquire();
|
|
32338
32756
|
try {
|
|
32339
32757
|
if (!this.headerWritten) {
|
|
32340
|
-
validateAudioChunkMetadata(meta);
|
|
32758
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
32341
32759
|
assert(meta);
|
|
32342
32760
|
assert(meta.decoderConfig);
|
|
32343
32761
|
this.writeHeader(track, meta.decoderConfig);
|
|
@@ -32561,6 +32979,11 @@ ${cue.notes ?? ""}`;
|
|
|
32561
32979
|
}
|
|
32562
32980
|
async finalize() {
|
|
32563
32981
|
const release = await this.mutex.acquire();
|
|
32982
|
+
if (!this.headerWritten) {
|
|
32983
|
+
throw new Error(
|
|
32984
|
+
"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."
|
|
32985
|
+
);
|
|
32986
|
+
}
|
|
32564
32987
|
const endPos = this.writer.getPos();
|
|
32565
32988
|
if (this.isRf64) {
|
|
32566
32989
|
assert(this.ds64RiffSizePos !== null);
|
|
@@ -33235,10 +33658,13 @@ ${cue.notes ?? ""}`;
|
|
|
33235
33658
|
`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
33659
|
);
|
|
33237
33660
|
}
|
|
33238
|
-
|
|
33239
|
-
|
|
33240
|
-
|
|
33241
|
-
|
|
33661
|
+
try {
|
|
33662
|
+
const support = await VideoEncoder.isConfigSupported(candidateConfig);
|
|
33663
|
+
if (support.supported) {
|
|
33664
|
+
selected = candidate;
|
|
33665
|
+
break;
|
|
33666
|
+
}
|
|
33667
|
+
} catch {
|
|
33242
33668
|
}
|
|
33243
33669
|
}
|
|
33244
33670
|
if (!selected) {
|
|
@@ -34294,8 +34720,14 @@ ${cue.notes ?? ""}`;
|
|
|
34294
34720
|
if (typeof AudioEncoder === "undefined") {
|
|
34295
34721
|
throw new Error("AudioEncoder is not supported by this browser.");
|
|
34296
34722
|
}
|
|
34297
|
-
|
|
34298
|
-
|
|
34723
|
+
let supported;
|
|
34724
|
+
try {
|
|
34725
|
+
const support = await AudioEncoder.isConfigSupported(encoderConfig);
|
|
34726
|
+
supported = support.supported ?? false;
|
|
34727
|
+
} catch {
|
|
34728
|
+
supported = false;
|
|
34729
|
+
}
|
|
34730
|
+
if (!supported) {
|
|
34299
34731
|
throw new Error(
|
|
34300
34732
|
`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
34733
|
);
|
|
@@ -35177,6 +35609,21 @@ ${cue.notes ?? ""}`;
|
|
|
35177
35609
|
references: variant.linkedGroup ? this.playlistDeclarations.filter((x) => x.groupId === variant.linkedGroup.name) : []
|
|
35178
35610
|
});
|
|
35179
35611
|
}
|
|
35612
|
+
for (const track of this.output.tracks) {
|
|
35613
|
+
if (track.isVideoTrack() && track.metadata.decoderConfig) {
|
|
35614
|
+
this.getVideoTrackData(
|
|
35615
|
+
track,
|
|
35616
|
+
track.metadata.primingPacket ?? null,
|
|
35617
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
35618
|
+
);
|
|
35619
|
+
} else if (track.isAudioTrack() && track.metadata.decoderConfig) {
|
|
35620
|
+
this.getAudioTrackData(
|
|
35621
|
+
track,
|
|
35622
|
+
track.metadata.primingPacket ?? null,
|
|
35623
|
+
{ decoderConfig: track.metadata.decoderConfig }
|
|
35624
|
+
);
|
|
35625
|
+
}
|
|
35626
|
+
}
|
|
35180
35627
|
release();
|
|
35181
35628
|
}
|
|
35182
35629
|
async getMimeType() {
|
|
@@ -35205,12 +35652,12 @@ ${cue.notes ?? ""}`;
|
|
|
35205
35652
|
release();
|
|
35206
35653
|
}
|
|
35207
35654
|
}
|
|
35208
|
-
getVideoTrackData(track, meta) {
|
|
35655
|
+
getVideoTrackData(track, packet, meta) {
|
|
35209
35656
|
let trackData = this.trackDatas.find((x) => x.track === track);
|
|
35210
35657
|
if (trackData) {
|
|
35211
35658
|
return trackData;
|
|
35212
35659
|
}
|
|
35213
|
-
validateVideoChunkMetadata(meta);
|
|
35660
|
+
validateVideoChunkMetadata(meta, track.source._codec);
|
|
35214
35661
|
assert(meta);
|
|
35215
35662
|
assert(meta?.decoderConfig);
|
|
35216
35663
|
const playlists = this.playlists.filter((x) => x.tracks.includes(track));
|
|
@@ -35222,18 +35669,19 @@ ${cue.notes ?? ""}`;
|
|
|
35222
35669
|
closed: false,
|
|
35223
35670
|
info: {
|
|
35224
35671
|
type: "video",
|
|
35225
|
-
decoderConfig: meta.decoderConfig
|
|
35672
|
+
decoderConfig: meta.decoderConfig,
|
|
35673
|
+
primingPacket: packet
|
|
35226
35674
|
}
|
|
35227
35675
|
};
|
|
35228
35676
|
this.trackDatas.push(trackData);
|
|
35229
35677
|
return trackData;
|
|
35230
35678
|
}
|
|
35231
|
-
getAudioTrackData(track, meta) {
|
|
35679
|
+
getAudioTrackData(track, packet, meta) {
|
|
35232
35680
|
let trackData = this.trackDatas.find((x) => x.track === track);
|
|
35233
35681
|
if (trackData) {
|
|
35234
35682
|
return trackData;
|
|
35235
35683
|
}
|
|
35236
|
-
validateAudioChunkMetadata(meta);
|
|
35684
|
+
validateAudioChunkMetadata(meta, track.source._codec);
|
|
35237
35685
|
assert(meta);
|
|
35238
35686
|
assert(meta?.decoderConfig);
|
|
35239
35687
|
const playlists = this.playlists.filter((x) => x.tracks.includes(track));
|
|
@@ -35245,14 +35693,15 @@ ${cue.notes ?? ""}`;
|
|
|
35245
35693
|
closed: false,
|
|
35246
35694
|
info: {
|
|
35247
35695
|
type: "audio",
|
|
35248
|
-
decoderConfig: meta.decoderConfig
|
|
35696
|
+
decoderConfig: meta.decoderConfig,
|
|
35697
|
+
primingPacket: packet
|
|
35249
35698
|
}
|
|
35250
35699
|
};
|
|
35251
35700
|
this.trackDatas.push(trackData);
|
|
35252
35701
|
return trackData;
|
|
35253
35702
|
}
|
|
35254
35703
|
async addEncodedVideoPacket(track, packet, meta) {
|
|
35255
|
-
const trackData = this.getVideoTrackData(track, meta);
|
|
35704
|
+
const trackData = this.getVideoTrackData(track, packet, meta);
|
|
35256
35705
|
const playlist = trackData.playlist;
|
|
35257
35706
|
const release = await playlist.mutex.acquire();
|
|
35258
35707
|
try {
|
|
@@ -35272,7 +35721,7 @@ ${cue.notes ?? ""}`;
|
|
|
35272
35721
|
}
|
|
35273
35722
|
}
|
|
35274
35723
|
async addEncodedAudioPacket(track, packet, meta) {
|
|
35275
|
-
const trackData = this.getAudioTrackData(track, meta);
|
|
35724
|
+
const trackData = this.getAudioTrackData(track, packet, meta);
|
|
35276
35725
|
const playlist = trackData.playlist;
|
|
35277
35726
|
const release = await playlist.mutex.acquire();
|
|
35278
35727
|
try {
|
|
@@ -35299,11 +35748,13 @@ ${cue.notes ?? ""}`;
|
|
|
35299
35748
|
if (!this.allTracksAreKnown(playlist)) {
|
|
35300
35749
|
return;
|
|
35301
35750
|
}
|
|
35751
|
+
const trackDatas = this.trackDatas.filter((x) => playlist.tracks.includes(x.track));
|
|
35302
35752
|
if (playlist.currentSegmentStartTimestamp === null) {
|
|
35303
|
-
|
|
35753
|
+
if (trackDatas.every((x) => x.closed)) {
|
|
35754
|
+
await this.onPlaylistDone(playlist);
|
|
35755
|
+
}
|
|
35304
35756
|
return;
|
|
35305
35757
|
}
|
|
35306
|
-
const trackDatas = this.trackDatas.filter((x) => playlist.tracks.includes(x.track));
|
|
35307
35758
|
const videoTrack = trackDatas.find((x) => x.info.type === "video");
|
|
35308
35759
|
const audioTrack = trackDatas.find((x) => x.info.type === "audio");
|
|
35309
35760
|
while (true) {
|
|
@@ -35422,12 +35873,68 @@ ${cue.notes ?? ""}`;
|
|
|
35422
35873
|
isRoot: false,
|
|
35423
35874
|
mimeType: playlist.segmentFormat.mimeType
|
|
35424
35875
|
});
|
|
35425
|
-
|
|
35876
|
+
let fragmentedIsobmffOutput = null;
|
|
35877
|
+
if (playlist.segmentFormat._isFragmentedIsobmff()) {
|
|
35878
|
+
fragmentedIsobmffOutput = {
|
|
35879
|
+
output: new Output({
|
|
35880
|
+
format: playlist.segmentFormat,
|
|
35881
|
+
target
|
|
35882
|
+
}),
|
|
35883
|
+
videoSource: null,
|
|
35884
|
+
audioSource: null,
|
|
35885
|
+
firstMoofPosition: null,
|
|
35886
|
+
currentFileSize: 0
|
|
35887
|
+
};
|
|
35888
|
+
target.on("write", ({ end }) => {
|
|
35889
|
+
fragmentedIsobmffOutput.currentFileSize = Math.max(
|
|
35890
|
+
fragmentedIsobmffOutput.currentFileSize,
|
|
35891
|
+
end
|
|
35892
|
+
);
|
|
35893
|
+
});
|
|
35894
|
+
const muxer = fragmentedIsobmffOutput.output._muxer;
|
|
35895
|
+
muxer.minimumFragmentDuration = Infinity;
|
|
35896
|
+
const originalOnMoof = muxer.formatOptions.onMoof;
|
|
35897
|
+
muxer.formatOptions.onMoof = (data, position, timestamp) => {
|
|
35898
|
+
fragmentedIsobmffOutput.firstMoofPosition = position;
|
|
35899
|
+
originalOnMoof?.(data, position, timestamp);
|
|
35900
|
+
muxer.formatOptions.onMoof = originalOnMoof;
|
|
35901
|
+
};
|
|
35902
|
+
if (videoTrack) {
|
|
35903
|
+
fragmentedIsobmffOutput.videoSource = new EncodedVideoPacketSource(
|
|
35904
|
+
videoTrack.track.source._codec
|
|
35905
|
+
);
|
|
35906
|
+
fragmentedIsobmffOutput.output.addVideoTrack(
|
|
35907
|
+
fragmentedIsobmffOutput.videoSource,
|
|
35908
|
+
{
|
|
35909
|
+
...videoTrack.track.metadata,
|
|
35910
|
+
decoderConfig: videoTrack.info.decoderConfig,
|
|
35911
|
+
primingPacket: videoTrack.info.primingPacket ?? void 0
|
|
35912
|
+
}
|
|
35913
|
+
);
|
|
35914
|
+
}
|
|
35915
|
+
if (audioTrack) {
|
|
35916
|
+
fragmentedIsobmffOutput.audioSource = new EncodedAudioPacketSource(
|
|
35917
|
+
audioTrack.track.source._codec
|
|
35918
|
+
);
|
|
35919
|
+
fragmentedIsobmffOutput.output.addAudioTrack(
|
|
35920
|
+
fragmentedIsobmffOutput.audioSource,
|
|
35921
|
+
{
|
|
35922
|
+
...audioTrack.track.metadata,
|
|
35923
|
+
decoderConfig: audioTrack.info.decoderConfig,
|
|
35924
|
+
primingPacket: audioTrack.info.primingPacket ?? void 0
|
|
35925
|
+
}
|
|
35926
|
+
);
|
|
35927
|
+
}
|
|
35928
|
+
await fragmentedIsobmffOutput.output.start();
|
|
35929
|
+
} else {
|
|
35930
|
+
target._start();
|
|
35931
|
+
}
|
|
35426
35932
|
playlist.singleFile = {
|
|
35427
35933
|
target,
|
|
35428
35934
|
path: relativeSegmentPath,
|
|
35429
35935
|
nextOffset: 0,
|
|
35430
|
-
info: segmentInfo2
|
|
35936
|
+
info: segmentInfo2,
|
|
35937
|
+
fragmentedIsobmffOutput
|
|
35431
35938
|
};
|
|
35432
35939
|
} else {
|
|
35433
35940
|
relativeSegmentPath = playlist.singleFile.path;
|
|
@@ -35450,95 +35957,114 @@ ${cue.notes ?? ""}`;
|
|
|
35450
35957
|
}
|
|
35451
35958
|
let segmentSize = 0;
|
|
35452
35959
|
let outputTarget = null;
|
|
35453
|
-
|
|
35454
|
-
|
|
35455
|
-
|
|
35456
|
-
|
|
35457
|
-
|
|
35458
|
-
|
|
35459
|
-
|
|
35460
|
-
|
|
35461
|
-
|
|
35462
|
-
|
|
35960
|
+
let maxEndTimestamp = -Infinity;
|
|
35961
|
+
let output = null;
|
|
35962
|
+
let videoSource = null;
|
|
35963
|
+
let audioSource = null;
|
|
35964
|
+
try {
|
|
35965
|
+
if (playlist.singleFile?.fragmentedIsobmffOutput) {
|
|
35966
|
+
output = playlist.singleFile.fragmentedIsobmffOutput.output;
|
|
35967
|
+
videoSource = playlist.singleFile.fragmentedIsobmffOutput.videoSource;
|
|
35968
|
+
audioSource = playlist.singleFile.fragmentedIsobmffOutput.audioSource;
|
|
35969
|
+
} else {
|
|
35970
|
+
output = new Output({
|
|
35971
|
+
format: playlist.segmentFormat,
|
|
35972
|
+
target: new PathedTarget(
|
|
35973
|
+
fullSegmentPath,
|
|
35974
|
+
async (request) => {
|
|
35975
|
+
const proxiedRequest = {
|
|
35976
|
+
...request,
|
|
35977
|
+
isRoot: false
|
|
35978
|
+
};
|
|
35979
|
+
if (request.isRoot) {
|
|
35980
|
+
if (playlist.singleFile) {
|
|
35981
|
+
const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset);
|
|
35982
|
+
slice.on("write", ({ end }) => segmentSize = Math.max(segmentSize, end));
|
|
35983
|
+
return slice;
|
|
35984
|
+
} else {
|
|
35985
|
+
const target = await this.output._getTarget(proxiedRequest);
|
|
35986
|
+
outputTarget = target;
|
|
35987
|
+
target.on("write", ({ end }) => segmentSize = Math.max(segmentSize, end));
|
|
35988
|
+
return target;
|
|
35989
|
+
}
|
|
35990
|
+
}
|
|
35991
|
+
return this.output._getTarget(proxiedRequest);
|
|
35992
|
+
}
|
|
35993
|
+
),
|
|
35994
|
+
initTarget: async () => {
|
|
35995
|
+
if (playlist.initSegment) {
|
|
35996
|
+
return new NullTarget();
|
|
35997
|
+
}
|
|
35463
35998
|
if (playlist.singleFile) {
|
|
35999
|
+
playlist.initSegment = {
|
|
36000
|
+
path: playlist.singleFile.path,
|
|
36001
|
+
duration: 0,
|
|
36002
|
+
timestamp: 0,
|
|
36003
|
+
byteSize: 0,
|
|
36004
|
+
byteOffset: 0,
|
|
36005
|
+
info: null
|
|
36006
|
+
};
|
|
35464
36007
|
const slice = playlist.singleFile.target.slice(playlist.singleFile.nextOffset);
|
|
35465
|
-
slice.on("write", ({ end }) =>
|
|
36008
|
+
slice.on("write", ({ end }) => {
|
|
36009
|
+
playlist.initSegment.byteSize = Math.max(playlist.initSegment.byteSize, end);
|
|
36010
|
+
});
|
|
36011
|
+
slice.on("finalized", () => {
|
|
36012
|
+
playlist.singleFile.nextOffset = playlist.initSegment.byteSize;
|
|
36013
|
+
});
|
|
35466
36014
|
return slice;
|
|
35467
36015
|
} else {
|
|
35468
|
-
const
|
|
35469
|
-
|
|
35470
|
-
|
|
36016
|
+
const playlistInfo = toPlaylistInfo(playlist);
|
|
36017
|
+
const initPath = await this.getInitPath(playlistInfo);
|
|
36018
|
+
validateInitPath(initPath);
|
|
36019
|
+
playlist.initSegment = {
|
|
36020
|
+
path: initPath,
|
|
36021
|
+
duration: 0,
|
|
36022
|
+
timestamp: 0,
|
|
36023
|
+
byteSize: 0,
|
|
36024
|
+
byteOffset: null,
|
|
36025
|
+
info: null
|
|
36026
|
+
};
|
|
36027
|
+
const fullInitPath = joinPaths(
|
|
36028
|
+
joinPaths(pathedTarget.rootPath, playlist.path),
|
|
36029
|
+
initPath
|
|
36030
|
+
);
|
|
36031
|
+
const target = await this.output._getTarget({
|
|
36032
|
+
path: fullInitPath,
|
|
36033
|
+
isRoot: false,
|
|
36034
|
+
mimeType: playlist.segmentFormat.mimeType
|
|
36035
|
+
});
|
|
36036
|
+
target.on("write", ({ end }) => {
|
|
36037
|
+
playlist.initSegment.byteSize = Math.max(playlist.initSegment.byteSize, end);
|
|
36038
|
+
});
|
|
36039
|
+
target.on("finalized", () => {
|
|
36040
|
+
this.format._options.onInit?.(target, playlistInfo);
|
|
36041
|
+
});
|
|
35471
36042
|
return target;
|
|
35472
36043
|
}
|
|
35473
36044
|
}
|
|
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
|
|
36045
|
+
});
|
|
36046
|
+
if (videoTrack) {
|
|
36047
|
+
videoSource = new EncodedVideoPacketSource(
|
|
36048
|
+
videoTrack.track.source._codec
|
|
35513
36049
|
);
|
|
35514
|
-
|
|
35515
|
-
|
|
35516
|
-
|
|
35517
|
-
|
|
35518
|
-
});
|
|
35519
|
-
target.on("write", ({ end }) => {
|
|
35520
|
-
playlist.initSegment.byteSize = Math.max(playlist.initSegment.byteSize, end);
|
|
36050
|
+
output.addVideoTrack(videoSource, {
|
|
36051
|
+
...videoTrack.track.metadata,
|
|
36052
|
+
decoderConfig: videoTrack.info.decoderConfig,
|
|
36053
|
+
primingPacket: videoTrack.info.primingPacket ?? void 0
|
|
35521
36054
|
});
|
|
35522
|
-
|
|
35523
|
-
|
|
36055
|
+
}
|
|
36056
|
+
if (audioTrack) {
|
|
36057
|
+
audioSource = new EncodedAudioPacketSource(
|
|
36058
|
+
audioTrack.track.source._codec
|
|
36059
|
+
);
|
|
36060
|
+
output.addAudioTrack(audioSource, {
|
|
36061
|
+
...audioTrack.track.metadata,
|
|
36062
|
+
decoderConfig: audioTrack.info.decoderConfig,
|
|
36063
|
+
primingPacket: audioTrack.info.primingPacket ?? void 0
|
|
35524
36064
|
});
|
|
35525
|
-
return target;
|
|
35526
36065
|
}
|
|
36066
|
+
await output.start();
|
|
35527
36067
|
}
|
|
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
36068
|
if (videoTrack) {
|
|
35543
36069
|
assert(videoSource);
|
|
35544
36070
|
const meta = { decoderConfig: videoTrack.info.decoderConfig };
|
|
@@ -35557,9 +36083,26 @@ ${cue.notes ?? ""}`;
|
|
|
35557
36083
|
maxEndTimestamp = Math.max(maxEndTimestamp, packet.timestamp + packet.duration);
|
|
35558
36084
|
}
|
|
35559
36085
|
}
|
|
35560
|
-
|
|
36086
|
+
if (playlist.singleFile?.fragmentedIsobmffOutput) {
|
|
36087
|
+
const muxer = playlist.singleFile.fragmentedIsobmffOutput.output._muxer;
|
|
36088
|
+
await muxer.forceFragmentFinalization();
|
|
36089
|
+
if (playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition !== null && !playlist.initSegment) {
|
|
36090
|
+
playlist.initSegment = {
|
|
36091
|
+
path: playlist.singleFile.path,
|
|
36092
|
+
duration: 0,
|
|
36093
|
+
timestamp: 0,
|
|
36094
|
+
byteSize: playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition,
|
|
36095
|
+
byteOffset: 0,
|
|
36096
|
+
info: null
|
|
36097
|
+
};
|
|
36098
|
+
playlist.singleFile.nextOffset = playlist.singleFile.fragmentedIsobmffOutput.firstMoofPosition;
|
|
36099
|
+
}
|
|
36100
|
+
segmentSize = playlist.singleFile.fragmentedIsobmffOutput.currentFileSize - playlist.singleFile.nextOffset;
|
|
36101
|
+
} else {
|
|
36102
|
+
await output.finalize();
|
|
36103
|
+
}
|
|
35561
36104
|
} catch (e) {
|
|
35562
|
-
await output
|
|
36105
|
+
await output?.cancel();
|
|
35563
36106
|
throw e;
|
|
35564
36107
|
}
|
|
35565
36108
|
if (segmentInfo) {
|
|
@@ -35617,8 +36160,12 @@ ${cue.notes ?? ""}`;
|
|
|
35617
36160
|
assert(!playlist.done);
|
|
35618
36161
|
playlist.done = true;
|
|
35619
36162
|
if (playlist.singleFile) {
|
|
35620
|
-
|
|
35621
|
-
|
|
36163
|
+
if (playlist.singleFile.fragmentedIsobmffOutput) {
|
|
36164
|
+
await playlist.singleFile.fragmentedIsobmffOutput.output.finalize();
|
|
36165
|
+
} else {
|
|
36166
|
+
await playlist.singleFile.target._flush();
|
|
36167
|
+
await playlist.singleFile.target._finalize();
|
|
36168
|
+
}
|
|
35622
36169
|
this.format._options.onSegment?.(playlist.singleFile.target, playlist.singleFile.info);
|
|
35623
36170
|
}
|
|
35624
36171
|
await this.writePlaylist(playlist);
|
|
@@ -35938,6 +36485,10 @@ ${cue.notes ?? ""}`;
|
|
|
35938
36485
|
_codecUnsupportedHint(codec) {
|
|
35939
36486
|
return "";
|
|
35940
36487
|
}
|
|
36488
|
+
/** @internal */
|
|
36489
|
+
_isFragmentedIsobmff() {
|
|
36490
|
+
return false;
|
|
36491
|
+
}
|
|
35941
36492
|
};
|
|
35942
36493
|
var IsobmffOutputFormat2 = class extends OutputFormat {
|
|
35943
36494
|
/** Internal constructor. */
|
|
@@ -35979,7 +36530,7 @@ ${cue.notes ?? ""}`;
|
|
|
35979
36530
|
video: { min: 0, max },
|
|
35980
36531
|
audio: { min: 0, max },
|
|
35981
36532
|
subtitle: { min: 0, max },
|
|
35982
|
-
total: { min:
|
|
36533
|
+
total: { min: 0, max }
|
|
35983
36534
|
};
|
|
35984
36535
|
}
|
|
35985
36536
|
get supportsVideoRotationMetadata() {
|
|
@@ -35992,6 +36543,10 @@ ${cue.notes ?? ""}`;
|
|
|
35992
36543
|
_createMuxer(output) {
|
|
35993
36544
|
return new IsobmffMuxer2(output, this);
|
|
35994
36545
|
}
|
|
36546
|
+
/** @internal */
|
|
36547
|
+
_isFragmentedIsobmff() {
|
|
36548
|
+
return this._options.fastStart === "fragmented";
|
|
36549
|
+
}
|
|
35995
36550
|
};
|
|
35996
36551
|
var Mp4OutputFormat = class extends IsobmffOutputFormat2 {
|
|
35997
36552
|
/** Creates a new {@link Mp4OutputFormat} configured with the specified `options`. */
|
|
@@ -36135,7 +36690,7 @@ ${cue.notes ?? ""}`;
|
|
|
36135
36690
|
video: { min: 0, max },
|
|
36136
36691
|
audio: { min: 0, max },
|
|
36137
36692
|
subtitle: { min: 0, max },
|
|
36138
|
-
total: { min:
|
|
36693
|
+
total: { min: 0, max }
|
|
36139
36694
|
};
|
|
36140
36695
|
}
|
|
36141
36696
|
get fileExtension() {
|
|
@@ -36319,7 +36874,7 @@ ${cue.notes ?? ""}`;
|
|
|
36319
36874
|
video: { min: 0, max: 0 },
|
|
36320
36875
|
audio: { min: 0, max },
|
|
36321
36876
|
subtitle: { min: 0, max: 0 },
|
|
36322
|
-
total: { min:
|
|
36877
|
+
total: { min: 0, max }
|
|
36323
36878
|
};
|
|
36324
36879
|
}
|
|
36325
36880
|
get fileExtension() {
|
|
@@ -36456,7 +37011,7 @@ ${cue.notes ?? ""}`;
|
|
|
36456
37011
|
video: { min: 0, max: maxVideo },
|
|
36457
37012
|
audio: { min: 0, max: maxAudio },
|
|
36458
37013
|
subtitle: { min: 0, max: 0 },
|
|
36459
|
-
total: { min:
|
|
37014
|
+
total: { min: 0, max: maxTotal }
|
|
36460
37015
|
};
|
|
36461
37016
|
}
|
|
36462
37017
|
get fileExtension() {
|
|
@@ -36561,7 +37116,7 @@ ${cue.notes ?? ""}`;
|
|
|
36561
37116
|
audio: { min: 0, max: supportsAudio ? Infinity : 0 },
|
|
36562
37117
|
subtitle: { min: 0, max: 0 },
|
|
36563
37118
|
// Currently disabled
|
|
36564
|
-
total: { min:
|
|
37119
|
+
total: { min: 0, max: Infinity }
|
|
36565
37120
|
};
|
|
36566
37121
|
}
|
|
36567
37122
|
get supportsVideoRotationMetadata() {
|
|
@@ -36892,6 +37447,17 @@ ${cue.notes ?? ""}`;
|
|
|
36892
37447
|
`Invalid video frame rate: ${metadata.frameRate}. Must be a positive number.`
|
|
36893
37448
|
);
|
|
36894
37449
|
}
|
|
37450
|
+
if (metadata.decoderConfig !== void 0) {
|
|
37451
|
+
validateVideoChunkMetadata({ decoderConfig: metadata.decoderConfig }, source._codec);
|
|
37452
|
+
}
|
|
37453
|
+
if (metadata.primingPacket !== void 0) {
|
|
37454
|
+
if (!(metadata.primingPacket instanceof EncodedPacket)) {
|
|
37455
|
+
throw new TypeError("metadata.primingPacket, when provided, must be an EncodedPacket.");
|
|
37456
|
+
}
|
|
37457
|
+
if (metadata.decoderConfig === void 0) {
|
|
37458
|
+
throw new TypeError("metadata.primingPacket can only be provided alongside metadata.decoderConfig.");
|
|
37459
|
+
}
|
|
37460
|
+
}
|
|
36895
37461
|
const metadataCopy = { ...metadata };
|
|
36896
37462
|
metadataCopy.group ??= this.defaultTrackGroup;
|
|
36897
37463
|
return this._addTrack(new OutputVideoTrack2(
|
|
@@ -36907,6 +37473,17 @@ ${cue.notes ?? ""}`;
|
|
|
36907
37473
|
throw new TypeError("source must be an AudioSource.");
|
|
36908
37474
|
}
|
|
36909
37475
|
validateBaseTrackMetadata(metadata);
|
|
37476
|
+
if (metadata.decoderConfig !== void 0) {
|
|
37477
|
+
validateAudioChunkMetadata({ decoderConfig: metadata.decoderConfig }, source._codec);
|
|
37478
|
+
}
|
|
37479
|
+
if (metadata.primingPacket !== void 0) {
|
|
37480
|
+
if (!(metadata.primingPacket instanceof EncodedPacket)) {
|
|
37481
|
+
throw new TypeError("metadata.primingPacket, when provided, must be an EncodedPacket.");
|
|
37482
|
+
}
|
|
37483
|
+
if (metadata.decoderConfig === void 0) {
|
|
37484
|
+
throw new TypeError("metadata.primingPacket can only be provided alongside metadata.decoderConfig.");
|
|
37485
|
+
}
|
|
37486
|
+
}
|
|
36910
37487
|
const metadataCopy = { ...metadata };
|
|
36911
37488
|
metadataCopy.group ??= this.defaultTrackGroup;
|
|
36912
37489
|
return this._addTrack(new OutputAudioTrack2(
|
|
@@ -37597,7 +38174,7 @@ ${cue.notes ?? ""}`;
|
|
|
37597
38174
|
this.output.setMetadataTags(outputTags);
|
|
37598
38175
|
}
|
|
37599
38176
|
if (!this._composable) {
|
|
37600
|
-
this.isValid = this.output.hasEnoughTracks();
|
|
38177
|
+
this.isValid = this.output.hasEnoughTracks() && this.output.tracks.length > 0;
|
|
37601
38178
|
} else {
|
|
37602
38179
|
this.isValid = true;
|
|
37603
38180
|
}
|