mediabunny 1.19.0 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundles/mediabunny.cjs +904 -104
- package/dist/bundles/mediabunny.min.cjs +90 -4
- package/dist/bundles/mediabunny.min.mjs +90 -4
- package/dist/bundles/mediabunny.mjs +904 -104
- package/dist/mediabunny.d.ts +76 -12
- package/dist/modules/src/codec-data.d.ts +3 -3
- package/dist/modules/src/codec-data.d.ts.map +1 -1
- package/dist/modules/src/codec-data.js +8 -13
- package/dist/modules/src/encode.d.ts +8 -0
- package/dist/modules/src/encode.d.ts.map +1 -1
- package/dist/modules/src/encode.js +6 -1
- package/dist/modules/src/index.d.ts +1 -1
- package/dist/modules/src/index.d.ts.map +1 -1
- package/dist/modules/src/input-track.d.ts +3 -0
- package/dist/modules/src/input-track.d.ts.map +1 -1
- package/dist/modules/src/input-track.js +7 -1
- package/dist/modules/src/input.d.ts.map +1 -1
- package/dist/modules/src/input.js +2 -4
- package/dist/modules/src/isobmff/isobmff-demuxer.js +3 -0
- package/dist/modules/src/matroska/ebml.d.ts +1 -0
- package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
- package/dist/modules/src/matroska/ebml.js +1 -0
- package/dist/modules/src/matroska/matroska-demuxer.d.ts +6 -0
- package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-demuxer.js +60 -1
- package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-muxer.js +9 -4
- package/dist/modules/src/media-sink.d.ts +5 -0
- package/dist/modules/src/media-sink.d.ts.map +1 -1
- package/dist/modules/src/media-sink.js +340 -50
- package/dist/modules/src/media-source.d.ts.map +1 -1
- package/dist/modules/src/media-source.js +413 -27
- package/dist/modules/src/misc.d.ts +1 -0
- package/dist/modules/src/misc.d.ts.map +1 -1
- package/dist/modules/src/misc.js +7 -2
- package/dist/modules/src/output-format.d.ts +8 -0
- package/dist/modules/src/output-format.d.ts.map +1 -1
- package/dist/modules/src/output-format.js +8 -0
- package/dist/modules/src/packet.d.ts +37 -6
- package/dist/modules/src/packet.d.ts.map +1 -1
- package/dist/modules/src/packet.js +49 -7
- package/dist/modules/src/reader.d.ts.map +1 -1
- package/dist/modules/src/reader.js +23 -4
- package/dist/modules/src/sample.d.ts +11 -2
- package/dist/modules/src/sample.d.ts.map +1 -1
- package/dist/modules/src/sample.js +17 -1
- package/dist/modules/src/source.d.ts.map +1 -1
- package/dist/modules/src/source.js +7 -3
- package/dist/modules/src/tags.d.ts +4 -4
- package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
- package/dist/modules/src/wave/wave-demuxer.d.ts.map +1 -1
- package/dist/modules/src/wave/wave-demuxer.js +7 -2
- package/package.json +5 -5
- package/src/codec-data.ts +14 -22
- package/src/encode.ts +14 -1
- package/src/index.ts +1 -0
- package/src/input-track.ts +10 -1
- package/src/input.ts +3 -5
- package/src/isobmff/isobmff-demuxer.ts +4 -0
- package/src/matroska/ebml.ts +1 -0
- package/src/matroska/matroska-demuxer.ts +62 -1
- package/src/matroska/matroska-muxer.ts +11 -4
- package/src/media-sink.ts +423 -63
- package/src/media-source.ts +498 -31
- package/src/misc.ts +8 -2
- package/src/output-format.ts +8 -0
- package/src/packet.ts +80 -5
- package/src/reader.ts +38 -4
- package/src/sample.ts +23 -2
- package/src/source.ts +9 -3
- package/src/tags.ts +4 -4
- package/src/wave/wave-demuxer.ts +6 -2
|
@@ -259,7 +259,7 @@ var Mediabunny = (() => {
|
|
|
259
259
|
}
|
|
260
260
|
};
|
|
261
261
|
var toUint8Array = (source) => {
|
|
262
|
-
if (source
|
|
262
|
+
if (source.constructor === Uint8Array) {
|
|
263
263
|
return source;
|
|
264
264
|
} else if (source instanceof ArrayBuffer) {
|
|
265
265
|
return new Uint8Array(source);
|
|
@@ -268,7 +268,7 @@ var Mediabunny = (() => {
|
|
|
268
268
|
}
|
|
269
269
|
};
|
|
270
270
|
var toDataView = (source) => {
|
|
271
|
-
if (source
|
|
271
|
+
if (source.constructor === DataView) {
|
|
272
272
|
return source;
|
|
273
273
|
} else if (source instanceof ArrayBuffer) {
|
|
274
274
|
return new DataView(source);
|
|
@@ -706,6 +706,9 @@ var Mediabunny = (() => {
|
|
|
706
706
|
}
|
|
707
707
|
return true;
|
|
708
708
|
};
|
|
709
|
+
var polyfillSymbolDispose = () => {
|
|
710
|
+
Symbol.dispose ??= Symbol("Symbol.dispose");
|
|
711
|
+
};
|
|
709
712
|
|
|
710
713
|
// src/tags.ts
|
|
711
714
|
var RichImageData = class {
|
|
@@ -2755,23 +2758,18 @@ var Mediabunny = (() => {
|
|
|
2755
2758
|
}
|
|
2756
2759
|
return { modeBlockflags };
|
|
2757
2760
|
};
|
|
2758
|
-
var determineVideoPacketType =
|
|
2759
|
-
|
|
2760
|
-
switch (videoTrack.codec) {
|
|
2761
|
+
var determineVideoPacketType = (codec, decoderConfig, packetData) => {
|
|
2762
|
+
switch (codec) {
|
|
2761
2763
|
case "avc":
|
|
2762
2764
|
{
|
|
2763
|
-
const
|
|
2764
|
-
assert(decoderConfig);
|
|
2765
|
-
const nalUnits = extractAvcNalUnits(packet.data, decoderConfig);
|
|
2765
|
+
const nalUnits = extractAvcNalUnits(packetData, decoderConfig);
|
|
2766
2766
|
const isKeyframe = nalUnits.some((x) => extractNalUnitTypeForAvc(x) === 5 /* IDR */);
|
|
2767
2767
|
return isKeyframe ? "key" : "delta";
|
|
2768
2768
|
}
|
|
2769
2769
|
;
|
|
2770
2770
|
case "hevc":
|
|
2771
2771
|
{
|
|
2772
|
-
const
|
|
2773
|
-
assert(decoderConfig);
|
|
2774
|
-
const nalUnits = extractHevcNalUnits(packet.data, decoderConfig);
|
|
2772
|
+
const nalUnits = extractHevcNalUnits(packetData, decoderConfig);
|
|
2775
2773
|
const isKeyframe = nalUnits.some((x) => {
|
|
2776
2774
|
const type = extractNalUnitTypeForHevc(x);
|
|
2777
2775
|
return 16 /* BLA_W_LP */ <= type && type <= 23 /* RSV_IRAP_VCL23 */;
|
|
@@ -2781,13 +2779,13 @@ var Mediabunny = (() => {
|
|
|
2781
2779
|
;
|
|
2782
2780
|
case "vp8":
|
|
2783
2781
|
{
|
|
2784
|
-
const frameType =
|
|
2782
|
+
const frameType = packetData[0] & 1;
|
|
2785
2783
|
return frameType === 0 ? "key" : "delta";
|
|
2786
2784
|
}
|
|
2787
2785
|
;
|
|
2788
2786
|
case "vp9":
|
|
2789
2787
|
{
|
|
2790
|
-
const bitstream = new Bitstream(
|
|
2788
|
+
const bitstream = new Bitstream(packetData);
|
|
2791
2789
|
if (bitstream.readBits(2) !== 2) {
|
|
2792
2790
|
return null;
|
|
2793
2791
|
}
|
|
@@ -2809,7 +2807,7 @@ var Mediabunny = (() => {
|
|
|
2809
2807
|
case "av1":
|
|
2810
2808
|
{
|
|
2811
2809
|
let reducedStillPictureHeader = false;
|
|
2812
|
-
for (const { type, data } of iterateAv1PacketObus(
|
|
2810
|
+
for (const { type, data } of iterateAv1PacketObus(packetData)) {
|
|
2813
2811
|
if (type === 1) {
|
|
2814
2812
|
const bitstream = new Bitstream(data);
|
|
2815
2813
|
bitstream.skipBits(4);
|
|
@@ -2832,7 +2830,7 @@ var Mediabunny = (() => {
|
|
|
2832
2830
|
;
|
|
2833
2831
|
default:
|
|
2834
2832
|
{
|
|
2835
|
-
assertNever(
|
|
2833
|
+
assertNever(codec);
|
|
2836
2834
|
assert(false);
|
|
2837
2835
|
}
|
|
2838
2836
|
;
|
|
@@ -3263,7 +3261,7 @@ var Mediabunny = (() => {
|
|
|
3263
3261
|
var PLACEHOLDER_DATA = new Uint8Array(0);
|
|
3264
3262
|
var EncodedPacket = class _EncodedPacket {
|
|
3265
3263
|
/** Creates a new {@link EncodedPacket} from raw bytes and timing information. */
|
|
3266
|
-
constructor(data, type, timestamp, duration, sequenceNumber = -1, byteLength) {
|
|
3264
|
+
constructor(data, type, timestamp, duration, sequenceNumber = -1, byteLength, sideData) {
|
|
3267
3265
|
this.data = data;
|
|
3268
3266
|
this.type = type;
|
|
3269
3267
|
this.timestamp = timestamp;
|
|
@@ -3295,7 +3293,20 @@ var Mediabunny = (() => {
|
|
|
3295
3293
|
if (!Number.isInteger(byteLength) || byteLength < 0) {
|
|
3296
3294
|
throw new TypeError("byteLength must be a non-negative integer.");
|
|
3297
3295
|
}
|
|
3296
|
+
if (sideData !== void 0 && (typeof sideData !== "object" || !sideData)) {
|
|
3297
|
+
throw new TypeError("sideData, when provided, must be an object.");
|
|
3298
|
+
}
|
|
3299
|
+
if (sideData?.alpha !== void 0 && !(sideData.alpha instanceof Uint8Array)) {
|
|
3300
|
+
throw new TypeError("sideData.alpha, when provided, must be a Uint8Array.");
|
|
3301
|
+
}
|
|
3302
|
+
if (sideData?.alphaByteLength !== void 0 && (!Number.isInteger(sideData.alphaByteLength) || sideData.alphaByteLength < 0)) {
|
|
3303
|
+
throw new TypeError("sideData.alphaByteLength, when provided, must be a non-negative integer.");
|
|
3304
|
+
}
|
|
3298
3305
|
this.byteLength = byteLength;
|
|
3306
|
+
this.sideData = sideData ?? {};
|
|
3307
|
+
if (this.sideData.alpha && this.sideData.alphaByteLength === void 0) {
|
|
3308
|
+
this.sideData.alphaByteLength = this.sideData.alpha.byteLength;
|
|
3309
|
+
}
|
|
3299
3310
|
}
|
|
3300
3311
|
/** If this packet is a metadata-only packet. Metadata-only packets don't contain their packet data. */
|
|
3301
3312
|
get isMetadataOnly() {
|
|
@@ -3309,7 +3320,9 @@ var Mediabunny = (() => {
|
|
|
3309
3320
|
get microsecondDuration() {
|
|
3310
3321
|
return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
|
|
3311
3322
|
}
|
|
3312
|
-
/** Converts this packet to an
|
|
3323
|
+
/** Converts this packet to an
|
|
3324
|
+
* [`EncodedVideoChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedVideoChunk) for use with the
|
|
3325
|
+
* WebCodecs API. */
|
|
3313
3326
|
toEncodedVideoChunk() {
|
|
3314
3327
|
if (this.isMetadataOnly) {
|
|
3315
3328
|
throw new TypeError("Metadata-only packets cannot be converted to a video chunk.");
|
|
@@ -3324,7 +3337,31 @@ var Mediabunny = (() => {
|
|
|
3324
3337
|
duration: this.microsecondDuration
|
|
3325
3338
|
});
|
|
3326
3339
|
}
|
|
3327
|
-
/**
|
|
3340
|
+
/**
|
|
3341
|
+
* Converts this packet to an
|
|
3342
|
+
* [`EncodedVideoChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedVideoChunk) for use with the
|
|
3343
|
+
* WebCodecs API, using the alpha side data instead of the color data. Throws if no alpha side data is defined.
|
|
3344
|
+
*/
|
|
3345
|
+
alphaToEncodedVideoChunk(type = this.type) {
|
|
3346
|
+
if (!this.sideData.alpha) {
|
|
3347
|
+
throw new TypeError("This packet does not contain alpha side data.");
|
|
3348
|
+
}
|
|
3349
|
+
if (this.isMetadataOnly) {
|
|
3350
|
+
throw new TypeError("Metadata-only packets cannot be converted to a video chunk.");
|
|
3351
|
+
}
|
|
3352
|
+
if (typeof EncodedVideoChunk === "undefined") {
|
|
3353
|
+
throw new Error("Your browser does not support EncodedVideoChunk.");
|
|
3354
|
+
}
|
|
3355
|
+
return new EncodedVideoChunk({
|
|
3356
|
+
data: this.sideData.alpha,
|
|
3357
|
+
type,
|
|
3358
|
+
timestamp: this.microsecondTimestamp,
|
|
3359
|
+
duration: this.microsecondDuration
|
|
3360
|
+
});
|
|
3361
|
+
}
|
|
3362
|
+
/** Converts this packet to an
|
|
3363
|
+
* [`EncodedAudioChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedAudioChunk) for use with the
|
|
3364
|
+
* WebCodecs API. */
|
|
3328
3365
|
toEncodedAudioChunk() {
|
|
3329
3366
|
if (this.isMetadataOnly) {
|
|
3330
3367
|
throw new TypeError("Metadata-only packets cannot be converted to an audio chunk.");
|
|
@@ -3340,10 +3377,12 @@ var Mediabunny = (() => {
|
|
|
3340
3377
|
});
|
|
3341
3378
|
}
|
|
3342
3379
|
/**
|
|
3343
|
-
* Creates an EncodedPacket from an
|
|
3344
|
-
*
|
|
3380
|
+
* Creates an {@link EncodedPacket} from an
|
|
3381
|
+
* [`EncodedVideoChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedVideoChunk) or
|
|
3382
|
+
* [`EncodedAudioChunk`](https://developer.mozilla.org/en-US/docs/Web/API/EncodedAudioChunk). This method is useful
|
|
3383
|
+
* for converting chunks from the WebCodecs API to `EncodedPacket` instances.
|
|
3345
3384
|
*/
|
|
3346
|
-
static fromEncodedChunk(chunk) {
|
|
3385
|
+
static fromEncodedChunk(chunk, sideData) {
|
|
3347
3386
|
if (!(chunk instanceof EncodedVideoChunk || chunk instanceof EncodedAudioChunk)) {
|
|
3348
3387
|
throw new TypeError("chunk must be an EncodedVideoChunk or EncodedAudioChunk.");
|
|
3349
3388
|
}
|
|
@@ -3353,7 +3392,10 @@ var Mediabunny = (() => {
|
|
|
3353
3392
|
data,
|
|
3354
3393
|
chunk.type,
|
|
3355
3394
|
chunk.timestamp / 1e6,
|
|
3356
|
-
(chunk.duration ?? 0) / 1e6
|
|
3395
|
+
(chunk.duration ?? 0) / 1e6,
|
|
3396
|
+
void 0,
|
|
3397
|
+
void 0,
|
|
3398
|
+
sideData
|
|
3357
3399
|
);
|
|
3358
3400
|
}
|
|
3359
3401
|
/** Clones this packet while optionally updating timing information. */
|
|
@@ -3455,6 +3497,7 @@ var Mediabunny = (() => {
|
|
|
3455
3497
|
};
|
|
3456
3498
|
|
|
3457
3499
|
// src/sample.ts
|
|
3500
|
+
polyfillSymbolDispose();
|
|
3458
3501
|
var VideoSample = class _VideoSample {
|
|
3459
3502
|
constructor(data, init) {
|
|
3460
3503
|
/** @internal */
|
|
@@ -3586,6 +3629,13 @@ var Mediabunny = (() => {
|
|
|
3586
3629
|
get microsecondDuration() {
|
|
3587
3630
|
return Math.trunc(SECOND_TO_MICROSECOND_FACTOR * this.duration);
|
|
3588
3631
|
}
|
|
3632
|
+
/**
|
|
3633
|
+
* Whether this sample uses a pixel format that can hold transparency data. Note that this doesn't necessarily mean
|
|
3634
|
+
* that the sample is transparent.
|
|
3635
|
+
*/
|
|
3636
|
+
get hasAlpha() {
|
|
3637
|
+
return this.format && this.format.includes("A");
|
|
3638
|
+
}
|
|
3589
3639
|
/** Clones this video sample. */
|
|
3590
3640
|
clone() {
|
|
3591
3641
|
if (this._closed) {
|
|
@@ -3908,6 +3958,10 @@ var Mediabunny = (() => {
|
|
|
3908
3958
|
}
|
|
3909
3959
|
this.duration = newDuration;
|
|
3910
3960
|
}
|
|
3961
|
+
/** Calls `.close()`. */
|
|
3962
|
+
[Symbol.dispose]() {
|
|
3963
|
+
this.close();
|
|
3964
|
+
}
|
|
3911
3965
|
};
|
|
3912
3966
|
var isVideoFrame = (x) => {
|
|
3913
3967
|
return typeof VideoFrame !== "undefined" && x instanceof VideoFrame;
|
|
@@ -4286,6 +4340,10 @@ var Mediabunny = (() => {
|
|
|
4286
4340
|
}
|
|
4287
4341
|
this.timestamp = newTimestamp;
|
|
4288
4342
|
}
|
|
4343
|
+
/** Calls `.close()`. */
|
|
4344
|
+
[Symbol.dispose]() {
|
|
4345
|
+
this.close();
|
|
4346
|
+
}
|
|
4289
4347
|
/** @internal */
|
|
4290
4348
|
static *_fromAudioBuffer(audioBuffer, timestamp) {
|
|
4291
4349
|
if (!(audioBuffer instanceof AudioBuffer)) {
|
|
@@ -4988,6 +5046,20 @@ var Mediabunny = (() => {
|
|
|
4988
5046
|
// Safari-specific thing, check usage.
|
|
4989
5047
|
this.currentPacketIndex = 0;
|
|
4990
5048
|
this.raslSkipped = false;
|
|
5049
|
+
// For HEVC stuff
|
|
5050
|
+
// Alpha stuff
|
|
5051
|
+
this.alphaDecoder = null;
|
|
5052
|
+
this.alphaHadKeyframe = false;
|
|
5053
|
+
this.colorQueue = [];
|
|
5054
|
+
this.alphaQueue = [];
|
|
5055
|
+
this.merger = null;
|
|
5056
|
+
this.mergerCreationFailed = false;
|
|
5057
|
+
this.decodedAlphaChunkCount = 0;
|
|
5058
|
+
this.alphaDecoderQueueSize = 0;
|
|
5059
|
+
/** Each value is the number of decoded alpha chunks at which a null alpha frame should be added. */
|
|
5060
|
+
this.nullAlphaFrameQueue = [];
|
|
5061
|
+
this.currentAlphaPacketIndex = 0;
|
|
5062
|
+
this.alphaRaslSkipped = false;
|
|
4991
5063
|
const MatchingCustomDecoder = customVideoDecoders.find((x) => x.supports(codec, decoderConfig));
|
|
4992
5064
|
if (MatchingCustomDecoder) {
|
|
4993
5065
|
this.customDecoder = new MatchingCustomDecoder();
|
|
@@ -5001,51 +5073,42 @@ var Mediabunny = (() => {
|
|
|
5001
5073
|
};
|
|
5002
5074
|
void this.customDecoderCallSerializer.call(() => this.customDecoder.init());
|
|
5003
5075
|
} else {
|
|
5004
|
-
const
|
|
5005
|
-
if (
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
}
|
|
5010
|
-
this.sampleQueue.length = 0;
|
|
5011
|
-
}
|
|
5012
|
-
insertSorted(this.sampleQueue, sample, (x) => x.timestamp);
|
|
5076
|
+
const colorHandler = (frame) => {
|
|
5077
|
+
if (this.alphaQueue.length > 0) {
|
|
5078
|
+
const alphaFrame = this.alphaQueue.shift();
|
|
5079
|
+
assert(alphaFrame !== void 0);
|
|
5080
|
+
this.mergeAlpha(frame, alphaFrame);
|
|
5013
5081
|
} else {
|
|
5014
|
-
|
|
5015
|
-
assert(timestamp !== void 0);
|
|
5016
|
-
sample.setTimestamp(timestamp);
|
|
5017
|
-
this.finalizeAndEmitSample(sample);
|
|
5082
|
+
this.colorQueue.push(frame);
|
|
5018
5083
|
}
|
|
5019
5084
|
};
|
|
5020
5085
|
this.decoder = new VideoDecoder({
|
|
5021
|
-
output: (frame) =>
|
|
5086
|
+
output: (frame) => {
|
|
5087
|
+
try {
|
|
5088
|
+
colorHandler(frame);
|
|
5089
|
+
} catch (error) {
|
|
5090
|
+
this.onError(error);
|
|
5091
|
+
}
|
|
5092
|
+
},
|
|
5022
5093
|
error: onError
|
|
5023
5094
|
});
|
|
5024
5095
|
this.decoder.configure(decoderConfig);
|
|
5025
5096
|
}
|
|
5026
5097
|
}
|
|
5027
|
-
finalizeAndEmitSample(sample) {
|
|
5028
|
-
sample.setTimestamp(Math.round(sample.timestamp * this.timeResolution) / this.timeResolution);
|
|
5029
|
-
sample.setDuration(Math.round(sample.duration * this.timeResolution) / this.timeResolution);
|
|
5030
|
-
sample.setRotation(this.rotation);
|
|
5031
|
-
this.onSample(sample);
|
|
5032
|
-
}
|
|
5033
5098
|
getDecodeQueueSize() {
|
|
5034
5099
|
if (this.customDecoder) {
|
|
5035
5100
|
return this.customDecoderQueueSize;
|
|
5036
5101
|
} else {
|
|
5037
5102
|
assert(this.decoder);
|
|
5038
|
-
return
|
|
5103
|
+
return Math.max(
|
|
5104
|
+
this.decoder.decodeQueueSize,
|
|
5105
|
+
this.alphaDecoder?.decodeQueueSize ?? 0
|
|
5106
|
+
);
|
|
5039
5107
|
}
|
|
5040
5108
|
}
|
|
5041
5109
|
decode(packet) {
|
|
5042
5110
|
if (this.codec === "hevc" && this.currentPacketIndex > 0 && !this.raslSkipped) {
|
|
5043
|
-
|
|
5044
|
-
const hasRaslPicture = nalUnits.some((x) => {
|
|
5045
|
-
const type = extractNalUnitTypeForHevc(x);
|
|
5046
|
-
return type === 8 /* RASL_N */ || type === 9 /* RASL_R */;
|
|
5047
|
-
});
|
|
5048
|
-
if (hasRaslPicture) {
|
|
5111
|
+
if (this.hasHevcRaslPicture(packet.data)) {
|
|
5049
5112
|
return;
|
|
5050
5113
|
}
|
|
5051
5114
|
this.raslSkipped = true;
|
|
@@ -5060,14 +5123,156 @@ var Mediabunny = (() => {
|
|
|
5060
5123
|
insertSorted(this.inputTimestamps, packet.timestamp, (x) => x);
|
|
5061
5124
|
}
|
|
5062
5125
|
this.decoder.decode(packet.toEncodedVideoChunk());
|
|
5126
|
+
this.decodeAlphaData(packet);
|
|
5063
5127
|
}
|
|
5064
5128
|
}
|
|
5129
|
+
decodeAlphaData(packet) {
|
|
5130
|
+
if (!packet.sideData.alpha || this.mergerCreationFailed) {
|
|
5131
|
+
this.pushNullAlphaFrame();
|
|
5132
|
+
return;
|
|
5133
|
+
}
|
|
5134
|
+
if (!this.merger) {
|
|
5135
|
+
try {
|
|
5136
|
+
this.merger = new ColorAlphaMerger();
|
|
5137
|
+
} catch (error) {
|
|
5138
|
+
console.error("Due to an error, only color data will be decoded.", error);
|
|
5139
|
+
this.mergerCreationFailed = true;
|
|
5140
|
+
this.decodeAlphaData(packet);
|
|
5141
|
+
return;
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
if (!this.alphaDecoder) {
|
|
5145
|
+
const alphaHandler = (frame) => {
|
|
5146
|
+
this.alphaDecoderQueueSize--;
|
|
5147
|
+
if (this.colorQueue.length > 0) {
|
|
5148
|
+
const colorFrame = this.colorQueue.shift();
|
|
5149
|
+
assert(colorFrame !== void 0);
|
|
5150
|
+
this.mergeAlpha(colorFrame, frame);
|
|
5151
|
+
} else {
|
|
5152
|
+
this.alphaQueue.push(frame);
|
|
5153
|
+
}
|
|
5154
|
+
this.decodedAlphaChunkCount++;
|
|
5155
|
+
while (this.nullAlphaFrameQueue.length > 0 && this.nullAlphaFrameQueue[0] === this.decodedAlphaChunkCount) {
|
|
5156
|
+
this.nullAlphaFrameQueue.shift();
|
|
5157
|
+
if (this.colorQueue.length > 0) {
|
|
5158
|
+
const colorFrame = this.colorQueue.shift();
|
|
5159
|
+
assert(colorFrame !== void 0);
|
|
5160
|
+
this.mergeAlpha(colorFrame, null);
|
|
5161
|
+
} else {
|
|
5162
|
+
this.alphaQueue.push(null);
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
};
|
|
5166
|
+
this.alphaDecoder = new VideoDecoder({
|
|
5167
|
+
output: (frame) => {
|
|
5168
|
+
try {
|
|
5169
|
+
alphaHandler(frame);
|
|
5170
|
+
} catch (error) {
|
|
5171
|
+
this.onError(error);
|
|
5172
|
+
}
|
|
5173
|
+
},
|
|
5174
|
+
error: this.onError
|
|
5175
|
+
});
|
|
5176
|
+
this.alphaDecoder.configure(this.decoderConfig);
|
|
5177
|
+
}
|
|
5178
|
+
const type = determineVideoPacketType(this.codec, this.decoderConfig, packet.sideData.alpha);
|
|
5179
|
+
if (!this.alphaHadKeyframe) {
|
|
5180
|
+
this.alphaHadKeyframe = type === "key";
|
|
5181
|
+
}
|
|
5182
|
+
if (this.alphaHadKeyframe) {
|
|
5183
|
+
if (this.codec === "hevc" && this.currentAlphaPacketIndex > 0 && !this.alphaRaslSkipped) {
|
|
5184
|
+
if (this.hasHevcRaslPicture(packet.sideData.alpha)) {
|
|
5185
|
+
this.pushNullAlphaFrame();
|
|
5186
|
+
return;
|
|
5187
|
+
}
|
|
5188
|
+
this.alphaRaslSkipped = true;
|
|
5189
|
+
}
|
|
5190
|
+
this.currentAlphaPacketIndex++;
|
|
5191
|
+
this.alphaDecoder.decode(packet.alphaToEncodedVideoChunk(type ?? packet.type));
|
|
5192
|
+
this.alphaDecoderQueueSize++;
|
|
5193
|
+
} else {
|
|
5194
|
+
this.pushNullAlphaFrame();
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
5197
|
+
pushNullAlphaFrame() {
|
|
5198
|
+
if (this.alphaDecoderQueueSize === 0) {
|
|
5199
|
+
this.alphaQueue.push(null);
|
|
5200
|
+
} else {
|
|
5201
|
+
this.nullAlphaFrameQueue.push(this.decodedAlphaChunkCount + this.alphaDecoderQueueSize);
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
/**
|
|
5205
|
+
* If we're using HEVC, we need to make sure to skip any RASL slices that follow a non-IDR key frame such as
|
|
5206
|
+
* CRA_NUT. This is because RASL slices cannot be decoded without data before the CRA_NUT. Browsers behave
|
|
5207
|
+
* differently here: Chromium drops the packets, Safari throws a decoder error. Either way, it's not good
|
|
5208
|
+
* and causes bugs upstream. So, let's take the dropping into our own hands.
|
|
5209
|
+
*/
|
|
5210
|
+
hasHevcRaslPicture(packetData) {
|
|
5211
|
+
const nalUnits = extractHevcNalUnits(packetData, this.decoderConfig);
|
|
5212
|
+
return nalUnits.some((x) => {
|
|
5213
|
+
const type = extractNalUnitTypeForHevc(x);
|
|
5214
|
+
return type === 8 /* RASL_N */ || type === 9 /* RASL_R */;
|
|
5215
|
+
});
|
|
5216
|
+
}
|
|
5217
|
+
/** Handler for the WebCodecs VideoDecoder for ironing out browser differences. */
|
|
5218
|
+
sampleHandler(sample) {
|
|
5219
|
+
if (isSafari()) {
|
|
5220
|
+
if (this.sampleQueue.length > 0 && sample.timestamp >= last(this.sampleQueue).timestamp) {
|
|
5221
|
+
for (const sample2 of this.sampleQueue) {
|
|
5222
|
+
this.finalizeAndEmitSample(sample2);
|
|
5223
|
+
}
|
|
5224
|
+
this.sampleQueue.length = 0;
|
|
5225
|
+
}
|
|
5226
|
+
insertSorted(this.sampleQueue, sample, (x) => x.timestamp);
|
|
5227
|
+
} else {
|
|
5228
|
+
const timestamp = this.inputTimestamps.shift();
|
|
5229
|
+
assert(timestamp !== void 0);
|
|
5230
|
+
sample.setTimestamp(timestamp);
|
|
5231
|
+
this.finalizeAndEmitSample(sample);
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
finalizeAndEmitSample(sample) {
|
|
5235
|
+
sample.setTimestamp(Math.round(sample.timestamp * this.timeResolution) / this.timeResolution);
|
|
5236
|
+
sample.setDuration(Math.round(sample.duration * this.timeResolution) / this.timeResolution);
|
|
5237
|
+
sample.setRotation(this.rotation);
|
|
5238
|
+
this.onSample(sample);
|
|
5239
|
+
}
|
|
5240
|
+
mergeAlpha(color, alpha) {
|
|
5241
|
+
if (!alpha) {
|
|
5242
|
+
const finalSample2 = new VideoSample(color);
|
|
5243
|
+
this.sampleHandler(finalSample2);
|
|
5244
|
+
return;
|
|
5245
|
+
}
|
|
5246
|
+
assert(this.merger);
|
|
5247
|
+
this.merger.update(color, alpha);
|
|
5248
|
+
color.close();
|
|
5249
|
+
alpha.close();
|
|
5250
|
+
const finalFrame = new VideoFrame(this.merger.canvas, {
|
|
5251
|
+
timestamp: color.timestamp,
|
|
5252
|
+
duration: color.duration ?? void 0
|
|
5253
|
+
});
|
|
5254
|
+
const finalSample = new VideoSample(finalFrame);
|
|
5255
|
+
this.sampleHandler(finalSample);
|
|
5256
|
+
}
|
|
5065
5257
|
async flush() {
|
|
5066
5258
|
if (this.customDecoder) {
|
|
5067
5259
|
await this.customDecoderCallSerializer.call(() => this.customDecoder.flush());
|
|
5068
5260
|
} else {
|
|
5069
5261
|
assert(this.decoder);
|
|
5070
|
-
await
|
|
5262
|
+
await Promise.all([
|
|
5263
|
+
this.decoder.flush(),
|
|
5264
|
+
this.alphaDecoder?.flush()
|
|
5265
|
+
]);
|
|
5266
|
+
this.colorQueue.forEach((x) => x.close());
|
|
5267
|
+
this.colorQueue.length = 0;
|
|
5268
|
+
this.alphaQueue.forEach((x) => x?.close());
|
|
5269
|
+
this.alphaQueue.length = 0;
|
|
5270
|
+
this.alphaHadKeyframe = false;
|
|
5271
|
+
this.decodedAlphaChunkCount = 0;
|
|
5272
|
+
this.alphaDecoderQueueSize = 0;
|
|
5273
|
+
this.nullAlphaFrameQueue.length = 0;
|
|
5274
|
+
this.currentAlphaPacketIndex = 0;
|
|
5275
|
+
this.alphaRaslSkipped = false;
|
|
5071
5276
|
}
|
|
5072
5277
|
if (isSafari()) {
|
|
5073
5278
|
for (const sample of this.sampleQueue) {
|
|
@@ -5084,6 +5289,12 @@ var Mediabunny = (() => {
|
|
|
5084
5289
|
} else {
|
|
5085
5290
|
assert(this.decoder);
|
|
5086
5291
|
this.decoder.close();
|
|
5292
|
+
this.alphaDecoder?.close();
|
|
5293
|
+
this.colorQueue.forEach((x) => x.close());
|
|
5294
|
+
this.colorQueue.length = 0;
|
|
5295
|
+
this.alphaQueue.forEach((x) => x?.close());
|
|
5296
|
+
this.alphaQueue.length = 0;
|
|
5297
|
+
this.merger?.close();
|
|
5087
5298
|
}
|
|
5088
5299
|
for (const sample of this.sampleQueue) {
|
|
5089
5300
|
sample.close();
|
|
@@ -5091,6 +5302,127 @@ var Mediabunny = (() => {
|
|
|
5091
5302
|
this.sampleQueue.length = 0;
|
|
5092
5303
|
}
|
|
5093
5304
|
};
|
|
5305
|
+
var ColorAlphaMerger = class {
|
|
5306
|
+
constructor() {
|
|
5307
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
5308
|
+
this.canvas = new OffscreenCanvas(300, 150);
|
|
5309
|
+
} else {
|
|
5310
|
+
this.canvas = document.createElement("canvas");
|
|
5311
|
+
}
|
|
5312
|
+
const gl = this.canvas.getContext("webgl2", {
|
|
5313
|
+
premultipliedAlpha: false
|
|
5314
|
+
});
|
|
5315
|
+
if (!gl) {
|
|
5316
|
+
throw new Error("Couldn't acquire WebGL 2 context.");
|
|
5317
|
+
}
|
|
5318
|
+
this.gl = gl;
|
|
5319
|
+
this.program = this.createProgram();
|
|
5320
|
+
this.vao = this.createVAO();
|
|
5321
|
+
this.colorTexture = this.createTexture();
|
|
5322
|
+
this.alphaTexture = this.createTexture();
|
|
5323
|
+
this.gl.useProgram(this.program);
|
|
5324
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.program, "u_colorTexture"), 0);
|
|
5325
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.program, "u_alphaTexture"), 1);
|
|
5326
|
+
}
|
|
5327
|
+
createProgram() {
|
|
5328
|
+
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, `#version 300 es
|
|
5329
|
+
in vec2 a_position;
|
|
5330
|
+
in vec2 a_texCoord;
|
|
5331
|
+
out vec2 v_texCoord;
|
|
5332
|
+
|
|
5333
|
+
void main() {
|
|
5334
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
5335
|
+
v_texCoord = a_texCoord;
|
|
5336
|
+
}
|
|
5337
|
+
`);
|
|
5338
|
+
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `#version 300 es
|
|
5339
|
+
precision highp float;
|
|
5340
|
+
|
|
5341
|
+
uniform sampler2D u_colorTexture;
|
|
5342
|
+
uniform sampler2D u_alphaTexture;
|
|
5343
|
+
in vec2 v_texCoord;
|
|
5344
|
+
out vec4 fragColor;
|
|
5345
|
+
|
|
5346
|
+
void main() {
|
|
5347
|
+
vec3 color = texture(u_colorTexture, v_texCoord).rgb;
|
|
5348
|
+
float alpha = texture(u_alphaTexture, v_texCoord).r;
|
|
5349
|
+
fragColor = vec4(color, alpha);
|
|
5350
|
+
}
|
|
5351
|
+
`);
|
|
5352
|
+
const program = this.gl.createProgram();
|
|
5353
|
+
this.gl.attachShader(program, vertexShader);
|
|
5354
|
+
this.gl.attachShader(program, fragmentShader);
|
|
5355
|
+
this.gl.linkProgram(program);
|
|
5356
|
+
return program;
|
|
5357
|
+
}
|
|
5358
|
+
createShader(type, source) {
|
|
5359
|
+
const shader = this.gl.createShader(type);
|
|
5360
|
+
this.gl.shaderSource(shader, source);
|
|
5361
|
+
this.gl.compileShader(shader);
|
|
5362
|
+
return shader;
|
|
5363
|
+
}
|
|
5364
|
+
createVAO() {
|
|
5365
|
+
const vao = this.gl.createVertexArray();
|
|
5366
|
+
this.gl.bindVertexArray(vao);
|
|
5367
|
+
const vertices = new Float32Array([
|
|
5368
|
+
-1,
|
|
5369
|
+
-1,
|
|
5370
|
+
0,
|
|
5371
|
+
1,
|
|
5372
|
+
1,
|
|
5373
|
+
-1,
|
|
5374
|
+
1,
|
|
5375
|
+
1,
|
|
5376
|
+
-1,
|
|
5377
|
+
1,
|
|
5378
|
+
0,
|
|
5379
|
+
0,
|
|
5380
|
+
1,
|
|
5381
|
+
1,
|
|
5382
|
+
1,
|
|
5383
|
+
0
|
|
5384
|
+
]);
|
|
5385
|
+
const buffer = this.gl.createBuffer();
|
|
5386
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
|
|
5387
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
|
|
5388
|
+
const positionLocation = this.gl.getAttribLocation(this.program, "a_position");
|
|
5389
|
+
const texCoordLocation = this.gl.getAttribLocation(this.program, "a_texCoord");
|
|
5390
|
+
this.gl.enableVertexAttribArray(positionLocation);
|
|
5391
|
+
this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 16, 0);
|
|
5392
|
+
this.gl.enableVertexAttribArray(texCoordLocation);
|
|
5393
|
+
this.gl.vertexAttribPointer(texCoordLocation, 2, this.gl.FLOAT, false, 16, 8);
|
|
5394
|
+
return vao;
|
|
5395
|
+
}
|
|
5396
|
+
createTexture() {
|
|
5397
|
+
const texture = this.gl.createTexture();
|
|
5398
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
|
|
5399
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
|
|
5400
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
|
|
5401
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
|
|
5402
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
|
|
5403
|
+
return texture;
|
|
5404
|
+
}
|
|
5405
|
+
update(color, alpha) {
|
|
5406
|
+
if (color.displayWidth !== this.canvas.width || color.displayHeight !== this.canvas.height) {
|
|
5407
|
+
this.canvas.width = color.displayWidth;
|
|
5408
|
+
this.canvas.height = color.displayHeight;
|
|
5409
|
+
}
|
|
5410
|
+
this.gl.activeTexture(this.gl.TEXTURE0);
|
|
5411
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.colorTexture);
|
|
5412
|
+
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, color);
|
|
5413
|
+
this.gl.activeTexture(this.gl.TEXTURE1);
|
|
5414
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.alphaTexture);
|
|
5415
|
+
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, alpha);
|
|
5416
|
+
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
5417
|
+
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
5418
|
+
this.gl.bindVertexArray(this.vao);
|
|
5419
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
5420
|
+
}
|
|
5421
|
+
close() {
|
|
5422
|
+
this.gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
5423
|
+
this.gl = null;
|
|
5424
|
+
}
|
|
5425
|
+
};
|
|
5094
5426
|
var VideoSampleSink = class extends BaseMediaSampleSink {
|
|
5095
5427
|
/** Creates a new {@link VideoSampleSink} for the given {@link InputVideoTrack}. */
|
|
5096
5428
|
constructor(videoTrack) {
|
|
@@ -5165,6 +5497,9 @@ var Mediabunny = (() => {
|
|
|
5165
5497
|
if (options && typeof options !== "object") {
|
|
5166
5498
|
throw new TypeError("options must be an object.");
|
|
5167
5499
|
}
|
|
5500
|
+
if (options.alpha !== void 0 && typeof options.alpha !== "boolean") {
|
|
5501
|
+
throw new TypeError("options.alpha, when provided, must be a boolean.");
|
|
5502
|
+
}
|
|
5168
5503
|
if (options.width !== void 0 && (!Number.isInteger(options.width) || options.width <= 0)) {
|
|
5169
5504
|
throw new TypeError("options.width, when defined, must be a positive integer.");
|
|
5170
5505
|
}
|
|
@@ -5207,6 +5542,7 @@ var Mediabunny = (() => {
|
|
|
5207
5542
|
height = options.height;
|
|
5208
5543
|
}
|
|
5209
5544
|
this._videoTrack = videoTrack;
|
|
5545
|
+
this._alpha = options.alpha ?? false;
|
|
5210
5546
|
this._width = width;
|
|
5211
5547
|
this._height = height;
|
|
5212
5548
|
this._rotation = rotation;
|
|
@@ -5236,13 +5572,13 @@ var Mediabunny = (() => {
|
|
|
5236
5572
|
this._nextCanvasIndex = (this._nextCanvasIndex + 1) % this._canvasPool.length;
|
|
5237
5573
|
}
|
|
5238
5574
|
const context = canvas.getContext("2d", {
|
|
5239
|
-
alpha: isFirefox()
|
|
5575
|
+
alpha: this._alpha || isFirefox()
|
|
5240
5576
|
// Firefox has VideoFrame glitches with opaque canvases
|
|
5241
5577
|
});
|
|
5242
5578
|
assert(context);
|
|
5243
5579
|
context.resetTransform();
|
|
5244
5580
|
if (!canvasIsNew) {
|
|
5245
|
-
if (isFirefox()) {
|
|
5581
|
+
if (!this._alpha && isFirefox()) {
|
|
5246
5582
|
context.fillStyle = "black";
|
|
5247
5583
|
context.fillRect(0, 0, this._width, this._height);
|
|
5248
5584
|
} else {
|
|
@@ -5340,7 +5676,13 @@ var Mediabunny = (() => {
|
|
|
5340
5676
|
void this.customDecoderCallSerializer.call(() => this.customDecoder.init());
|
|
5341
5677
|
} else {
|
|
5342
5678
|
this.decoder = new AudioDecoder({
|
|
5343
|
-
output: (data) =>
|
|
5679
|
+
output: (data) => {
|
|
5680
|
+
try {
|
|
5681
|
+
sampleHandler(new AudioSample(data));
|
|
5682
|
+
} catch (error) {
|
|
5683
|
+
this.onError(error);
|
|
5684
|
+
}
|
|
5685
|
+
},
|
|
5344
5686
|
error: onError
|
|
5345
5687
|
});
|
|
5346
5688
|
this.decoder.configure(decoderConfig);
|
|
@@ -5818,6 +6160,10 @@ var Mediabunny = (() => {
|
|
|
5818
6160
|
const colorSpace = await this._backing.getColorSpace();
|
|
5819
6161
|
return colorSpace.primaries === "bt2020" || colorSpace.primaries === "smpte432" || colorSpace.transfer === "pg" || colorSpace.transfer === "hlg" || colorSpace.matrix === "bt2020-ncl";
|
|
5820
6162
|
}
|
|
6163
|
+
/** Checks if this track may contain transparent samples with alpha data. */
|
|
6164
|
+
canBeTransparent() {
|
|
6165
|
+
return this._backing.canBeTransparent();
|
|
6166
|
+
}
|
|
5821
6167
|
/**
|
|
5822
6168
|
* Returns the [decoder configuration](https://www.w3.org/TR/webcodecs/#video-decoder-config) for decoding the
|
|
5823
6169
|
* track's packets using a [`VideoDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/VideoDecoder). Returns
|
|
@@ -5861,7 +6207,9 @@ var Mediabunny = (() => {
|
|
|
5861
6207
|
if (this.codec === null) {
|
|
5862
6208
|
return null;
|
|
5863
6209
|
}
|
|
5864
|
-
|
|
6210
|
+
const decoderConfig = await this.getDecoderConfig();
|
|
6211
|
+
assert(decoderConfig);
|
|
6212
|
+
return determineVideoPacketType(this.codec, decoderConfig, packet.data);
|
|
5865
6213
|
}
|
|
5866
6214
|
};
|
|
5867
6215
|
var InputAudioTrack = class extends InputTrack {
|
|
@@ -8441,6 +8789,9 @@ var Mediabunny = (() => {
|
|
|
8441
8789
|
fullRange: this.internalTrack.info.colorSpace?.fullRange
|
|
8442
8790
|
};
|
|
8443
8791
|
}
|
|
8792
|
+
async canBeTransparent() {
|
|
8793
|
+
return false;
|
|
8794
|
+
}
|
|
8444
8795
|
async getDecoderConfig() {
|
|
8445
8796
|
if (!this.internalTrack.info.codec) {
|
|
8446
8797
|
return null;
|
|
@@ -9100,6 +9451,7 @@ var Mediabunny = (() => {
|
|
|
9100
9451
|
this.currentTrack = null;
|
|
9101
9452
|
this.currentCluster = null;
|
|
9102
9453
|
this.currentBlock = null;
|
|
9454
|
+
this.currentBlockAdditional = null;
|
|
9103
9455
|
this.currentCueTime = null;
|
|
9104
9456
|
this.currentDecodingInstruction = null;
|
|
9105
9457
|
this.currentTagTargetIsMovie = true;
|
|
@@ -9531,7 +9883,8 @@ var Mediabunny = (() => {
|
|
|
9531
9883
|
referencedTimestamps: originalBlock.referencedTimestamps,
|
|
9532
9884
|
data: frameData,
|
|
9533
9885
|
lacing: 0 /* None */,
|
|
9534
|
-
decoded: true
|
|
9886
|
+
decoded: true,
|
|
9887
|
+
mainAdditional: originalBlock.mainAdditional
|
|
9535
9888
|
});
|
|
9536
9889
|
}
|
|
9537
9890
|
blockIndex += frameCount;
|
|
@@ -9761,7 +10114,8 @@ var Mediabunny = (() => {
|
|
|
9761
10114
|
rotation: 0,
|
|
9762
10115
|
codec: null,
|
|
9763
10116
|
codecDescription: null,
|
|
9764
|
-
colorSpace: null
|
|
10117
|
+
colorSpace: null,
|
|
10118
|
+
alphaMode: false
|
|
9765
10119
|
};
|
|
9766
10120
|
} else if (type === 2) {
|
|
9767
10121
|
this.currentTrack.info = {
|
|
@@ -9870,6 +10224,13 @@ var Mediabunny = (() => {
|
|
|
9870
10224
|
}
|
|
9871
10225
|
;
|
|
9872
10226
|
break;
|
|
10227
|
+
case 21440 /* AlphaMode */:
|
|
10228
|
+
{
|
|
10229
|
+
if (this.currentTrack?.info?.type !== "video") break;
|
|
10230
|
+
this.currentTrack.info.alphaMode = readUnsignedInt(slice, size) === 1;
|
|
10231
|
+
}
|
|
10232
|
+
;
|
|
10233
|
+
break;
|
|
9873
10234
|
case 21936 /* Colour */:
|
|
9874
10235
|
{
|
|
9875
10236
|
if (this.currentTrack?.info?.type !== "video") break;
|
|
@@ -10032,7 +10393,8 @@ var Mediabunny = (() => {
|
|
|
10032
10393
|
referencedTimestamps: [],
|
|
10033
10394
|
data: blockData,
|
|
10034
10395
|
lacing,
|
|
10035
|
-
decoded: !hasDecodingInstructions
|
|
10396
|
+
decoded: !hasDecodingInstructions,
|
|
10397
|
+
mainAdditional: null
|
|
10036
10398
|
});
|
|
10037
10399
|
}
|
|
10038
10400
|
;
|
|
@@ -10071,12 +10433,48 @@ var Mediabunny = (() => {
|
|
|
10071
10433
|
referencedTimestamps: [],
|
|
10072
10434
|
data: blockData,
|
|
10073
10435
|
lacing,
|
|
10074
|
-
decoded: !hasDecodingInstructions
|
|
10436
|
+
decoded: !hasDecodingInstructions,
|
|
10437
|
+
mainAdditional: null
|
|
10075
10438
|
};
|
|
10076
10439
|
trackData.blocks.push(this.currentBlock);
|
|
10077
10440
|
}
|
|
10078
10441
|
;
|
|
10079
10442
|
break;
|
|
10443
|
+
case 30113 /* BlockAdditions */:
|
|
10444
|
+
{
|
|
10445
|
+
this.readContiguousElements(slice.slice(dataStartPos, size));
|
|
10446
|
+
}
|
|
10447
|
+
;
|
|
10448
|
+
break;
|
|
10449
|
+
case 166 /* BlockMore */:
|
|
10450
|
+
{
|
|
10451
|
+
if (!this.currentBlock) break;
|
|
10452
|
+
this.currentBlockAdditional = {
|
|
10453
|
+
addId: 1,
|
|
10454
|
+
data: null
|
|
10455
|
+
};
|
|
10456
|
+
this.readContiguousElements(slice.slice(dataStartPos, size));
|
|
10457
|
+
if (this.currentBlockAdditional.data && this.currentBlockAdditional.addId === 1) {
|
|
10458
|
+
this.currentBlock.mainAdditional = this.currentBlockAdditional.data;
|
|
10459
|
+
}
|
|
10460
|
+
this.currentBlockAdditional = null;
|
|
10461
|
+
}
|
|
10462
|
+
;
|
|
10463
|
+
break;
|
|
10464
|
+
case 165 /* BlockAdditional */:
|
|
10465
|
+
{
|
|
10466
|
+
if (!this.currentBlockAdditional) break;
|
|
10467
|
+
this.currentBlockAdditional.data = readBytes(slice, size);
|
|
10468
|
+
}
|
|
10469
|
+
;
|
|
10470
|
+
break;
|
|
10471
|
+
case 238 /* BlockAddID */:
|
|
10472
|
+
{
|
|
10473
|
+
if (!this.currentBlockAdditional) break;
|
|
10474
|
+
this.currentBlockAdditional.addId = readUnsignedInt(slice, size);
|
|
10475
|
+
}
|
|
10476
|
+
;
|
|
10477
|
+
break;
|
|
10080
10478
|
case 155 /* BlockDuration */:
|
|
10081
10479
|
{
|
|
10082
10480
|
if (!this.currentBlock) break;
|
|
@@ -10654,13 +11052,19 @@ var Mediabunny = (() => {
|
|
|
10654
11052
|
const data = options.metadataOnly ? PLACEHOLDER_DATA : block.data;
|
|
10655
11053
|
const timestamp = block.timestamp / this.internalTrack.segment.timestampFactor;
|
|
10656
11054
|
const duration = block.duration / this.internalTrack.segment.timestampFactor;
|
|
11055
|
+
const sideData = {};
|
|
11056
|
+
if (block.mainAdditional && this.internalTrack.info?.type === "video" && this.internalTrack.info.alphaMode) {
|
|
11057
|
+
sideData.alpha = options.metadataOnly ? PLACEHOLDER_DATA : block.mainAdditional;
|
|
11058
|
+
sideData.alphaByteLength = block.mainAdditional.byteLength;
|
|
11059
|
+
}
|
|
10657
11060
|
const packet = new EncodedPacket(
|
|
10658
11061
|
data,
|
|
10659
11062
|
block.isKeyFrame ? "key" : "delta",
|
|
10660
11063
|
timestamp,
|
|
10661
11064
|
duration,
|
|
10662
11065
|
cluster.dataStartPos + blockIndex,
|
|
10663
|
-
block.data.byteLength
|
|
11066
|
+
block.data.byteLength,
|
|
11067
|
+
sideData
|
|
10664
11068
|
);
|
|
10665
11069
|
this.packetToClusterLocation.set(packet, { cluster, blockIndex });
|
|
10666
11070
|
return packet;
|
|
@@ -10879,6 +11283,9 @@ var Mediabunny = (() => {
|
|
|
10879
11283
|
fullRange: this.internalTrack.info.colorSpace?.fullRange
|
|
10880
11284
|
};
|
|
10881
11285
|
}
|
|
11286
|
+
async canBeTransparent() {
|
|
11287
|
+
return this.internalTrack.info.alphaMode;
|
|
11288
|
+
}
|
|
10882
11289
|
async getDecoderConfig() {
|
|
10883
11290
|
if (!this.internalTrack.info.codec) {
|
|
10884
11291
|
return null;
|
|
@@ -13345,8 +13752,11 @@ var Mediabunny = (() => {
|
|
|
13345
13752
|
break;
|
|
13346
13753
|
}
|
|
13347
13754
|
} else if (chunkId === "ds64") {
|
|
13348
|
-
|
|
13349
|
-
|
|
13755
|
+
let ds64Slice = this.reader.requestSlice(startPos, chunkSize);
|
|
13756
|
+
if (ds64Slice instanceof Promise) ds64Slice = await ds64Slice;
|
|
13757
|
+
if (!ds64Slice) break;
|
|
13758
|
+
const riffChunkSize = readU64(ds64Slice, littleEndian);
|
|
13759
|
+
dataChunkSize = readU64(ds64Slice, littleEndian);
|
|
13350
13760
|
totalFileSize = Math.min(riffChunkSize + 8, this.reader.fileSize ?? Infinity);
|
|
13351
13761
|
} else if (chunkId === "LIST") {
|
|
13352
13762
|
await this.parseListChunk(startPos, chunkSize, littleEndian);
|
|
@@ -14840,7 +15250,7 @@ var Mediabunny = (() => {
|
|
|
14840
15250
|
|
|
14841
15251
|
// src/source.ts
|
|
14842
15252
|
var nodeAlias = __toESM(require_node(), 1);
|
|
14843
|
-
var node = nodeAlias;
|
|
15253
|
+
var node = typeof nodeAlias !== "undefined" ? nodeAlias : void 0;
|
|
14844
15254
|
var Source = class {
|
|
14845
15255
|
constructor() {
|
|
14846
15256
|
/** @internal */
|
|
@@ -15283,6 +15693,7 @@ var Mediabunny = (() => {
|
|
|
15283
15693
|
let data = this._options.read(worker.currentPos, originalTargetPos);
|
|
15284
15694
|
if (data instanceof Promise) data = await data;
|
|
15285
15695
|
if (data instanceof Uint8Array) {
|
|
15696
|
+
data = toUint8Array(data);
|
|
15286
15697
|
if (data.length !== originalTargetPos - worker.currentPos) {
|
|
15287
15698
|
throw new Error(
|
|
15288
15699
|
`options.read returned a Uint8Array with unexpected length: Requested ${originalTargetPos - worker.currentPos} bytes, but got ${data.length}.`
|
|
@@ -15305,8 +15716,9 @@ var Mediabunny = (() => {
|
|
|
15305
15716
|
if (!(value instanceof Uint8Array)) {
|
|
15306
15717
|
throw new TypeError("ReadableStream returned by options.read must yield Uint8Array chunks.");
|
|
15307
15718
|
}
|
|
15308
|
-
|
|
15309
|
-
this.
|
|
15719
|
+
const data2 = toUint8Array(value);
|
|
15720
|
+
this.onread?.(worker.currentPos, worker.currentPos + data2.length);
|
|
15721
|
+
this._orchestrator.supplyWorkerData(worker, data2);
|
|
15310
15722
|
}
|
|
15311
15723
|
} else {
|
|
15312
15724
|
throw new TypeError("options.read must return or resolve to a Uint8Array or a ReadableStream.");
|
|
@@ -15850,7 +16262,7 @@ var Mediabunny = (() => {
|
|
|
15850
16262
|
};
|
|
15851
16263
|
|
|
15852
16264
|
// src/input.ts
|
|
15853
|
-
|
|
16265
|
+
polyfillSymbolDispose();
|
|
15854
16266
|
var Input = class {
|
|
15855
16267
|
/**
|
|
15856
16268
|
* Creates a new input file from the specified options. No reading operations will be performed until methods are
|
|
@@ -16101,53 +16513,73 @@ var Mediabunny = (() => {
|
|
|
16101
16513
|
);
|
|
16102
16514
|
}
|
|
16103
16515
|
};
|
|
16516
|
+
var checkIsInRange = (slice, bytesToRead) => {
|
|
16517
|
+
if (slice.filePos < slice.start || slice.filePos + bytesToRead > slice.end) {
|
|
16518
|
+
throw new RangeError(
|
|
16519
|
+
`Tried reading [${slice.filePos}, ${slice.filePos + bytesToRead}), but slice is [${slice.start}, ${slice.end}).`
|
|
16520
|
+
);
|
|
16521
|
+
}
|
|
16522
|
+
};
|
|
16104
16523
|
var readBytes = (slice, length) => {
|
|
16524
|
+
checkIsInRange(slice, length);
|
|
16105
16525
|
const bytes2 = slice.bytes.subarray(slice.bufferPos, slice.bufferPos + length);
|
|
16106
16526
|
slice.bufferPos += length;
|
|
16107
16527
|
return bytes2;
|
|
16108
16528
|
};
|
|
16109
|
-
var readU8 = (slice) =>
|
|
16529
|
+
var readU8 = (slice) => {
|
|
16530
|
+
checkIsInRange(slice, 1);
|
|
16531
|
+
return slice.view.getUint8(slice.bufferPos++);
|
|
16532
|
+
};
|
|
16110
16533
|
var readU16 = (slice, littleEndian) => {
|
|
16534
|
+
checkIsInRange(slice, 2);
|
|
16111
16535
|
const value = slice.view.getUint16(slice.bufferPos, littleEndian);
|
|
16112
16536
|
slice.bufferPos += 2;
|
|
16113
16537
|
return value;
|
|
16114
16538
|
};
|
|
16115
16539
|
var readU16Be = (slice) => {
|
|
16540
|
+
checkIsInRange(slice, 2);
|
|
16116
16541
|
const value = slice.view.getUint16(slice.bufferPos, false);
|
|
16117
16542
|
slice.bufferPos += 2;
|
|
16118
16543
|
return value;
|
|
16119
16544
|
};
|
|
16120
16545
|
var readU24Be = (slice) => {
|
|
16546
|
+
checkIsInRange(slice, 3);
|
|
16121
16547
|
const value = getUint24(slice.view, slice.bufferPos, false);
|
|
16122
16548
|
slice.bufferPos += 3;
|
|
16123
16549
|
return value;
|
|
16124
16550
|
};
|
|
16125
16551
|
var readI16Be = (slice) => {
|
|
16552
|
+
checkIsInRange(slice, 2);
|
|
16126
16553
|
const value = slice.view.getInt16(slice.bufferPos, false);
|
|
16127
16554
|
slice.bufferPos += 2;
|
|
16128
16555
|
return value;
|
|
16129
16556
|
};
|
|
16130
16557
|
var readU32 = (slice, littleEndian) => {
|
|
16558
|
+
checkIsInRange(slice, 4);
|
|
16131
16559
|
const value = slice.view.getUint32(slice.bufferPos, littleEndian);
|
|
16132
16560
|
slice.bufferPos += 4;
|
|
16133
16561
|
return value;
|
|
16134
16562
|
};
|
|
16135
16563
|
var readU32Be = (slice) => {
|
|
16564
|
+
checkIsInRange(slice, 4);
|
|
16136
16565
|
const value = slice.view.getUint32(slice.bufferPos, false);
|
|
16137
16566
|
slice.bufferPos += 4;
|
|
16138
16567
|
return value;
|
|
16139
16568
|
};
|
|
16140
16569
|
var readU32Le = (slice) => {
|
|
16570
|
+
checkIsInRange(slice, 4);
|
|
16141
16571
|
const value = slice.view.getUint32(slice.bufferPos, true);
|
|
16142
16572
|
slice.bufferPos += 4;
|
|
16143
16573
|
return value;
|
|
16144
16574
|
};
|
|
16145
16575
|
var readI32Be = (slice) => {
|
|
16576
|
+
checkIsInRange(slice, 4);
|
|
16146
16577
|
const value = slice.view.getInt32(slice.bufferPos, false);
|
|
16147
16578
|
slice.bufferPos += 4;
|
|
16148
16579
|
return value;
|
|
16149
16580
|
};
|
|
16150
16581
|
var readI32Le = (slice) => {
|
|
16582
|
+
checkIsInRange(slice, 4);
|
|
16151
16583
|
const value = slice.view.getInt32(slice.bufferPos, true);
|
|
16152
16584
|
slice.bufferPos += 4;
|
|
16153
16585
|
return value;
|
|
@@ -16180,19 +16612,19 @@ var Mediabunny = (() => {
|
|
|
16180
16612
|
return high * 4294967296 + low;
|
|
16181
16613
|
};
|
|
16182
16614
|
var readF32Be = (slice) => {
|
|
16615
|
+
checkIsInRange(slice, 4);
|
|
16183
16616
|
const value = slice.view.getFloat32(slice.bufferPos, false);
|
|
16184
16617
|
slice.bufferPos += 4;
|
|
16185
16618
|
return value;
|
|
16186
16619
|
};
|
|
16187
16620
|
var readF64Be = (slice) => {
|
|
16621
|
+
checkIsInRange(slice, 8);
|
|
16188
16622
|
const value = slice.view.getFloat64(slice.bufferPos, false);
|
|
16189
16623
|
slice.bufferPos += 8;
|
|
16190
16624
|
return value;
|
|
16191
16625
|
};
|
|
16192
16626
|
var readAscii = (slice, length) => {
|
|
16193
|
-
|
|
16194
|
-
throw new RangeError("Reading past end of slice.");
|
|
16195
|
-
}
|
|
16627
|
+
checkIsInRange(slice, length);
|
|
16196
16628
|
let str = "";
|
|
16197
16629
|
for (let i = 0; i < length; i++) {
|
|
16198
16630
|
str += String.fromCharCode(slice.bytes[slice.bufferPos++]);
|
|
@@ -19468,6 +19900,7 @@ var Mediabunny = (() => {
|
|
|
19468
19900
|
const videoElement = { id: 224 /* Video */, data: [
|
|
19469
19901
|
{ id: 176 /* PixelWidth */, data: trackData.info.width },
|
|
19470
19902
|
{ id: 186 /* PixelHeight */, data: trackData.info.height },
|
|
19903
|
+
trackData.info.alphaMode ? { id: 21440 /* AlphaMode */, data: 1 } : null,
|
|
19471
19904
|
colorSpaceIsComplete(colorSpace) ? {
|
|
19472
19905
|
id: 21936 /* Colour */,
|
|
19473
19906
|
data: [
|
|
@@ -19785,7 +20218,7 @@ var Mediabunny = (() => {
|
|
|
19785
20218
|
codecStrings
|
|
19786
20219
|
});
|
|
19787
20220
|
}
|
|
19788
|
-
getVideoTrackData(track, meta) {
|
|
20221
|
+
getVideoTrackData(track, packet, meta) {
|
|
19789
20222
|
const existingTrackData = this.trackDatas.find((x) => x.track === track);
|
|
19790
20223
|
if (existingTrackData) {
|
|
19791
20224
|
return existingTrackData;
|
|
@@ -19801,7 +20234,9 @@ var Mediabunny = (() => {
|
|
|
19801
20234
|
info: {
|
|
19802
20235
|
width: meta.decoderConfig.codedWidth,
|
|
19803
20236
|
height: meta.decoderConfig.codedHeight,
|
|
19804
|
-
decoderConfig: meta.decoderConfig
|
|
20237
|
+
decoderConfig: meta.decoderConfig,
|
|
20238
|
+
alphaMode: !!packet.sideData.alpha
|
|
20239
|
+
// The first packet determines if this track has alpha or not
|
|
19805
20240
|
},
|
|
19806
20241
|
chunkQueue: [],
|
|
19807
20242
|
lastWrittenMsTimestamp: null
|
|
@@ -19881,7 +20316,7 @@ var Mediabunny = (() => {
|
|
|
19881
20316
|
async addEncodedVideoPacket(track, packet, meta) {
|
|
19882
20317
|
const release = await this.mutex.acquire();
|
|
19883
20318
|
try {
|
|
19884
|
-
const trackData = this.getVideoTrackData(track, meta);
|
|
20319
|
+
const trackData = this.getVideoTrackData(track, packet, meta);
|
|
19885
20320
|
const isKeyFrame = packet.type === "key";
|
|
19886
20321
|
let timestamp = this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, isKeyFrame);
|
|
19887
20322
|
let duration = packet.duration;
|
|
@@ -19889,7 +20324,8 @@ var Mediabunny = (() => {
|
|
|
19889
20324
|
timestamp = roundToMultiple(timestamp, 1 / track.metadata.frameRate);
|
|
19890
20325
|
duration = roundToMultiple(duration, 1 / track.metadata.frameRate);
|
|
19891
20326
|
}
|
|
19892
|
-
const
|
|
20327
|
+
const additions = trackData.info.alphaMode ? packet.sideData.alpha ?? null : null;
|
|
20328
|
+
const videoChunk = this.createInternalChunk(packet.data, timestamp, duration, packet.type, additions);
|
|
19893
20329
|
if (track.source._codec === "vp9") this.fixVP9ColorSpace(trackData, videoChunk);
|
|
19894
20330
|
trackData.chunkQueue.push(videoChunk);
|
|
19895
20331
|
await this.interleaveChunks();
|
|
@@ -20063,8 +20499,9 @@ ${cue.notes ?? ""}`;
|
|
|
20063
20499
|
} : null,
|
|
20064
20500
|
chunk.additions ? { id: 30113 /* BlockAdditions */, data: [
|
|
20065
20501
|
{ id: 166 /* BlockMore */, data: [
|
|
20066
|
-
{ id:
|
|
20067
|
-
|
|
20502
|
+
{ id: 238 /* BlockAddID */, data: 1 },
|
|
20503
|
+
// Some players expect BlockAddID to come first
|
|
20504
|
+
{ id: 165 /* BlockAdditional */, data: chunk.additions }
|
|
20068
20505
|
] }
|
|
20069
20506
|
] } : null,
|
|
20070
20507
|
msDuration > 0 ? { id: 155 /* BlockDuration */, data: msDuration } : null
|
|
@@ -21471,6 +21908,9 @@ ${cue.notes ?? ""}`;
|
|
|
21471
21908
|
if (!options || typeof options !== "object") {
|
|
21472
21909
|
throw new TypeError("Encoding options must be an object.");
|
|
21473
21910
|
}
|
|
21911
|
+
if (options.alpha !== void 0 && !["discard", "keep"].includes(options.alpha)) {
|
|
21912
|
+
throw new TypeError("options.alpha, when provided, must be 'discard' or 'keep'.");
|
|
21913
|
+
}
|
|
21474
21914
|
if (options.bitrateMode !== void 0 && !["constant", "variable"].includes(options.bitrateMode)) {
|
|
21475
21915
|
throw new TypeError("bitrateMode, when provided, must be 'constant' or 'variable'.");
|
|
21476
21916
|
}
|
|
@@ -21510,8 +21950,8 @@ ${cue.notes ?? ""}`;
|
|
|
21510
21950
|
height: options.height,
|
|
21511
21951
|
bitrate: resolvedBitrate,
|
|
21512
21952
|
bitrateMode: options.bitrateMode,
|
|
21953
|
+
alpha: options.alpha ?? "discard",
|
|
21513
21954
|
framerate: options.framerate,
|
|
21514
|
-
// this.source._connectedTrack?.metadata.frameRate,
|
|
21515
21955
|
latencyMode: options.latencyMode,
|
|
21516
21956
|
hardwareAcceleration: options.hardwareAcceleration,
|
|
21517
21957
|
scalabilityMode: options.scalabilityMode,
|
|
@@ -21714,7 +22154,9 @@ ${cue.notes ?? ""}`;
|
|
|
21714
22154
|
height,
|
|
21715
22155
|
bitrate,
|
|
21716
22156
|
framerate: void 0,
|
|
21717
|
-
...restOptions
|
|
22157
|
+
...restOptions,
|
|
22158
|
+
alpha: "discard"
|
|
22159
|
+
// Since we handle alpha ourselves
|
|
21718
22160
|
});
|
|
21719
22161
|
const support = await VideoEncoder.isConfigSupported(encoderConfig);
|
|
21720
22162
|
return support.supported === true;
|
|
@@ -21949,12 +22391,18 @@ ${cue.notes ?? ""}`;
|
|
|
21949
22391
|
this.customEncoder = null;
|
|
21950
22392
|
this.customEncoderCallSerializer = new CallSerializer();
|
|
21951
22393
|
this.customEncoderQueueSize = 0;
|
|
22394
|
+
// Alpha stuff
|
|
22395
|
+
this.alphaEncoder = null;
|
|
22396
|
+
this.splitter = null;
|
|
22397
|
+
this.splitterCreationFailed = false;
|
|
22398
|
+
this.alphaFrameQueue = [];
|
|
21952
22399
|
/**
|
|
21953
22400
|
* Encoders typically throw their errors "out of band", meaning asynchronously in some other execution context.
|
|
21954
22401
|
* However, we want to surface these errors to the user within the normal control flow, so they don't go uncaught.
|
|
21955
22402
|
* So, we keep track of the encoder error and throw it as soon as we get the chance.
|
|
21956
22403
|
*/
|
|
21957
|
-
this.
|
|
22404
|
+
this.error = null;
|
|
22405
|
+
this.errorNeedsNewStack = true;
|
|
21958
22406
|
}
|
|
21959
22407
|
async add(videoSample, shouldClose, encodeOptions) {
|
|
21960
22408
|
try {
|
|
@@ -22028,7 +22476,7 @@ ${cue.notes ?? ""}`;
|
|
|
22028
22476
|
if (this.customEncoder) {
|
|
22029
22477
|
this.customEncoderQueueSize++;
|
|
22030
22478
|
const clonedSample = videoSample.clone();
|
|
22031
|
-
const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample, finalEncodeOptions)).then(() => this.customEncoderQueueSize--).catch((error) => this.
|
|
22479
|
+
const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample, finalEncodeOptions)).then(() => this.customEncoderQueueSize--).catch((error) => this.error ??= error).finally(() => {
|
|
22032
22480
|
clonedSample.close();
|
|
22033
22481
|
});
|
|
22034
22482
|
if (this.customEncoderQueueSize >= 4) {
|
|
@@ -22037,8 +22485,39 @@ ${cue.notes ?? ""}`;
|
|
|
22037
22485
|
} else {
|
|
22038
22486
|
assert(this.encoder);
|
|
22039
22487
|
const videoFrame = videoSample.toVideoFrame();
|
|
22040
|
-
this.
|
|
22041
|
-
|
|
22488
|
+
if (!this.alphaEncoder) {
|
|
22489
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
22490
|
+
videoFrame.close();
|
|
22491
|
+
} else {
|
|
22492
|
+
const frameDefinitelyHasNoAlpha = !!videoFrame.format && !videoFrame.format.includes("A");
|
|
22493
|
+
if (frameDefinitelyHasNoAlpha || this.splitterCreationFailed) {
|
|
22494
|
+
this.alphaFrameQueue.push(null);
|
|
22495
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
22496
|
+
videoFrame.close();
|
|
22497
|
+
} else {
|
|
22498
|
+
const width = videoFrame.displayWidth;
|
|
22499
|
+
const height = videoFrame.displayHeight;
|
|
22500
|
+
if (!this.splitter) {
|
|
22501
|
+
try {
|
|
22502
|
+
this.splitter = new ColorAlphaSplitter(width, height);
|
|
22503
|
+
} catch (error) {
|
|
22504
|
+
console.error("Due to an error, only color data will be encoded.", error);
|
|
22505
|
+
this.splitterCreationFailed = true;
|
|
22506
|
+
this.alphaFrameQueue.push(null);
|
|
22507
|
+
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
22508
|
+
videoFrame.close();
|
|
22509
|
+
}
|
|
22510
|
+
}
|
|
22511
|
+
if (this.splitter) {
|
|
22512
|
+
const colorFrame = this.splitter.extractColor(videoFrame);
|
|
22513
|
+
const alphaFrame = this.splitter.extractAlpha(videoFrame);
|
|
22514
|
+
this.alphaFrameQueue.push(alphaFrame);
|
|
22515
|
+
this.encoder.encode(colorFrame, finalEncodeOptions);
|
|
22516
|
+
colorFrame.close();
|
|
22517
|
+
videoFrame.close();
|
|
22518
|
+
}
|
|
22519
|
+
}
|
|
22520
|
+
}
|
|
22042
22521
|
if (shouldClose) {
|
|
22043
22522
|
videoSample.close();
|
|
22044
22523
|
}
|
|
@@ -22054,9 +22533,6 @@ ${cue.notes ?? ""}`;
|
|
|
22054
22533
|
}
|
|
22055
22534
|
}
|
|
22056
22535
|
ensureEncoder(videoSample) {
|
|
22057
|
-
if (this.encoder) {
|
|
22058
|
-
return;
|
|
22059
|
-
}
|
|
22060
22536
|
const encoderError = new Error();
|
|
22061
22537
|
this.ensureEncoderPromise = (async () => {
|
|
22062
22538
|
const encoderConfig = buildVideoEncoderConfig({
|
|
@@ -22082,13 +22558,20 @@ ${cue.notes ?? ""}`;
|
|
|
22082
22558
|
throw new TypeError("The second argument passed to onPacket must be an object or undefined.");
|
|
22083
22559
|
}
|
|
22084
22560
|
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
|
22085
|
-
void this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta)
|
|
22561
|
+
void this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta).catch((error) => {
|
|
22562
|
+
this.error ??= error;
|
|
22563
|
+
this.errorNeedsNewStack = false;
|
|
22564
|
+
});
|
|
22086
22565
|
};
|
|
22087
22566
|
await this.customEncoder.init();
|
|
22088
22567
|
} else {
|
|
22089
22568
|
if (typeof VideoEncoder === "undefined") {
|
|
22090
22569
|
throw new Error("VideoEncoder is not supported by this browser.");
|
|
22091
22570
|
}
|
|
22571
|
+
encoderConfig.alpha = "discard";
|
|
22572
|
+
if (this.encodingConfig.alpha === "keep") {
|
|
22573
|
+
encoderConfig.latencyMode = "quality";
|
|
22574
|
+
}
|
|
22092
22575
|
const hasOddDimension = encoderConfig.width % 2 === 1 || encoderConfig.height % 2 === 1;
|
|
22093
22576
|
if (hasOddDimension && (this.encodingConfig.codec === "avc" || this.encodingConfig.codec === "hevc")) {
|
|
22094
22577
|
throw new Error(
|
|
@@ -22101,18 +22584,81 @@ ${cue.notes ?? ""}`;
|
|
|
22101
22584
|
`This specific encoder configuration (${encoderConfig.codec}, ${encoderConfig.bitrate} bps, ${encoderConfig.width}x${encoderConfig.height}, hardware acceleration: ${encoderConfig.hardwareAcceleration ?? "no-preference"}) is not supported by this browser. Consider using another codec or changing your video parameters.`
|
|
22102
22585
|
);
|
|
22103
22586
|
}
|
|
22587
|
+
const colorChunkQueue = [];
|
|
22588
|
+
const nullAlphaChunkQueue = [];
|
|
22589
|
+
let encodedAlphaChunkCount = 0;
|
|
22590
|
+
let alphaEncoderQueue = 0;
|
|
22591
|
+
const addPacket = (colorChunk, alphaChunk, meta) => {
|
|
22592
|
+
const sideData = {};
|
|
22593
|
+
if (alphaChunk) {
|
|
22594
|
+
const alphaData = new Uint8Array(alphaChunk.byteLength);
|
|
22595
|
+
alphaChunk.copyTo(alphaData);
|
|
22596
|
+
sideData.alpha = alphaData;
|
|
22597
|
+
}
|
|
22598
|
+
const packet = EncodedPacket.fromEncodedChunk(colorChunk, sideData);
|
|
22599
|
+
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
|
22600
|
+
void this.muxer.addEncodedVideoPacket(this.source._connectedTrack, packet, meta).catch((error) => {
|
|
22601
|
+
this.error ??= error;
|
|
22602
|
+
this.errorNeedsNewStack = false;
|
|
22603
|
+
});
|
|
22604
|
+
};
|
|
22104
22605
|
this.encoder = new VideoEncoder({
|
|
22105
22606
|
output: (chunk, meta) => {
|
|
22106
|
-
|
|
22107
|
-
|
|
22108
|
-
|
|
22607
|
+
if (!this.alphaEncoder) {
|
|
22608
|
+
addPacket(chunk, null, meta);
|
|
22609
|
+
return;
|
|
22610
|
+
}
|
|
22611
|
+
const alphaFrame = this.alphaFrameQueue.shift();
|
|
22612
|
+
assert(alphaFrame !== void 0);
|
|
22613
|
+
if (alphaFrame) {
|
|
22614
|
+
this.alphaEncoder.encode(alphaFrame, {
|
|
22615
|
+
// Crucial: The alpha frame is forced to be a key frame whenever the color frame
|
|
22616
|
+
// also is. Without this, playback can glitch and even crash in some browsers.
|
|
22617
|
+
// This is the reason why the two encoders are wired in series and not in parallel.
|
|
22618
|
+
keyFrame: chunk.type === "key"
|
|
22619
|
+
});
|
|
22620
|
+
alphaEncoderQueue++;
|
|
22621
|
+
alphaFrame.close();
|
|
22622
|
+
colorChunkQueue.push({ chunk, meta });
|
|
22623
|
+
} else {
|
|
22624
|
+
if (alphaEncoderQueue === 0) {
|
|
22625
|
+
addPacket(chunk, null, meta);
|
|
22626
|
+
} else {
|
|
22627
|
+
nullAlphaChunkQueue.push(encodedAlphaChunkCount + alphaEncoderQueue);
|
|
22628
|
+
colorChunkQueue.push({ chunk, meta });
|
|
22629
|
+
}
|
|
22630
|
+
}
|
|
22109
22631
|
},
|
|
22110
22632
|
error: (error) => {
|
|
22111
22633
|
error.stack = encoderError.stack;
|
|
22112
|
-
this.
|
|
22634
|
+
this.error ??= error;
|
|
22113
22635
|
}
|
|
22114
22636
|
});
|
|
22115
22637
|
this.encoder.configure(encoderConfig);
|
|
22638
|
+
if (this.encodingConfig.alpha === "keep") {
|
|
22639
|
+
this.alphaEncoder = new VideoEncoder({
|
|
22640
|
+
// We ignore the alpha chunk's metadata
|
|
22641
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
22642
|
+
output: (chunk, meta) => {
|
|
22643
|
+
alphaEncoderQueue--;
|
|
22644
|
+
const colorChunk = colorChunkQueue.shift();
|
|
22645
|
+
assert(colorChunk !== void 0);
|
|
22646
|
+
addPacket(colorChunk.chunk, chunk, colorChunk.meta);
|
|
22647
|
+
encodedAlphaChunkCount++;
|
|
22648
|
+
while (nullAlphaChunkQueue.length > 0 && nullAlphaChunkQueue[0] === encodedAlphaChunkCount) {
|
|
22649
|
+
nullAlphaChunkQueue.shift();
|
|
22650
|
+
const colorChunk2 = colorChunkQueue.shift();
|
|
22651
|
+
assert(colorChunk2 !== void 0);
|
|
22652
|
+
addPacket(colorChunk2.chunk, null, colorChunk2.meta);
|
|
22653
|
+
}
|
|
22654
|
+
},
|
|
22655
|
+
error: (error) => {
|
|
22656
|
+
error.stack = encoderError.stack;
|
|
22657
|
+
this.error ??= error;
|
|
22658
|
+
}
|
|
22659
|
+
});
|
|
22660
|
+
this.alphaEncoder.configure(encoderConfig);
|
|
22661
|
+
}
|
|
22116
22662
|
}
|
|
22117
22663
|
assert(this.source._connectedTrack);
|
|
22118
22664
|
this.muxer = this.source._connectedTrack.output._muxer;
|
|
@@ -22129,10 +22675,16 @@ ${cue.notes ?? ""}`;
|
|
|
22129
22675
|
} else if (this.encoder) {
|
|
22130
22676
|
if (!forceClose) {
|
|
22131
22677
|
await this.encoder.flush();
|
|
22678
|
+
await this.alphaEncoder?.flush();
|
|
22132
22679
|
}
|
|
22133
22680
|
if (this.encoder.state !== "closed") {
|
|
22134
22681
|
this.encoder.close();
|
|
22135
22682
|
}
|
|
22683
|
+
if (this.alphaEncoder && this.alphaEncoder.state !== "closed") {
|
|
22684
|
+
this.alphaEncoder.close();
|
|
22685
|
+
}
|
|
22686
|
+
this.alphaFrameQueue.forEach((x) => x?.close());
|
|
22687
|
+
this.splitter?.close();
|
|
22136
22688
|
}
|
|
22137
22689
|
if (!forceClose) this.checkForEncoderError();
|
|
22138
22690
|
}
|
|
@@ -22144,12 +22696,235 @@ ${cue.notes ?? ""}`;
|
|
|
22144
22696
|
}
|
|
22145
22697
|
}
|
|
22146
22698
|
checkForEncoderError() {
|
|
22147
|
-
if (this.
|
|
22148
|
-
this.
|
|
22149
|
-
|
|
22699
|
+
if (this.error) {
|
|
22700
|
+
if (this.errorNeedsNewStack) {
|
|
22701
|
+
this.error.stack = new Error().stack;
|
|
22702
|
+
}
|
|
22703
|
+
throw this.error;
|
|
22150
22704
|
}
|
|
22151
22705
|
}
|
|
22152
22706
|
};
|
|
22707
|
+
var ColorAlphaSplitter = class {
|
|
22708
|
+
constructor(initialWidth, initialHeight) {
|
|
22709
|
+
this.lastFrame = null;
|
|
22710
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
22711
|
+
this.canvas = new OffscreenCanvas(initialWidth, initialHeight);
|
|
22712
|
+
} else {
|
|
22713
|
+
this.canvas = document.createElement("canvas");
|
|
22714
|
+
this.canvas.width = initialWidth;
|
|
22715
|
+
this.canvas.height = initialHeight;
|
|
22716
|
+
}
|
|
22717
|
+
const gl = this.canvas.getContext("webgl2", {
|
|
22718
|
+
alpha: true
|
|
22719
|
+
// Needed due to the YUV thing we do for alpha
|
|
22720
|
+
});
|
|
22721
|
+
if (!gl) {
|
|
22722
|
+
throw new Error("Couldn't acquire WebGL 2 context.");
|
|
22723
|
+
}
|
|
22724
|
+
this.gl = gl;
|
|
22725
|
+
this.colorProgram = this.createColorProgram();
|
|
22726
|
+
this.alphaProgram = this.createAlphaProgram();
|
|
22727
|
+
this.vao = this.createVAO();
|
|
22728
|
+
this.sourceTexture = this.createTexture();
|
|
22729
|
+
this.alphaResolutionLocation = this.gl.getUniformLocation(this.alphaProgram, "u_resolution");
|
|
22730
|
+
this.gl.useProgram(this.colorProgram);
|
|
22731
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.colorProgram, "u_sourceTexture"), 0);
|
|
22732
|
+
this.gl.useProgram(this.alphaProgram);
|
|
22733
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.alphaProgram, "u_sourceTexture"), 0);
|
|
22734
|
+
}
|
|
22735
|
+
createVertexShader() {
|
|
22736
|
+
return this.createShader(this.gl.VERTEX_SHADER, `#version 300 es
|
|
22737
|
+
in vec2 a_position;
|
|
22738
|
+
in vec2 a_texCoord;
|
|
22739
|
+
out vec2 v_texCoord;
|
|
22740
|
+
|
|
22741
|
+
void main() {
|
|
22742
|
+
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
22743
|
+
v_texCoord = a_texCoord;
|
|
22744
|
+
}
|
|
22745
|
+
`);
|
|
22746
|
+
}
|
|
22747
|
+
createColorProgram() {
|
|
22748
|
+
const vertexShader = this.createVertexShader();
|
|
22749
|
+
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `#version 300 es
|
|
22750
|
+
precision highp float;
|
|
22751
|
+
|
|
22752
|
+
uniform sampler2D u_sourceTexture;
|
|
22753
|
+
in vec2 v_texCoord;
|
|
22754
|
+
out vec4 fragColor;
|
|
22755
|
+
|
|
22756
|
+
void main() {
|
|
22757
|
+
vec4 source = texture(u_sourceTexture, v_texCoord);
|
|
22758
|
+
fragColor = vec4(source.rgb, 1.0);
|
|
22759
|
+
}
|
|
22760
|
+
`);
|
|
22761
|
+
const program = this.gl.createProgram();
|
|
22762
|
+
this.gl.attachShader(program, vertexShader);
|
|
22763
|
+
this.gl.attachShader(program, fragmentShader);
|
|
22764
|
+
this.gl.linkProgram(program);
|
|
22765
|
+
return program;
|
|
22766
|
+
}
|
|
22767
|
+
createAlphaProgram() {
|
|
22768
|
+
const vertexShader = this.createVertexShader();
|
|
22769
|
+
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `#version 300 es
|
|
22770
|
+
precision highp float;
|
|
22771
|
+
|
|
22772
|
+
uniform sampler2D u_sourceTexture;
|
|
22773
|
+
uniform vec2 u_resolution; // The width and height of the canvas
|
|
22774
|
+
in vec2 v_texCoord;
|
|
22775
|
+
out vec4 fragColor;
|
|
22776
|
+
|
|
22777
|
+
// This function determines the value for a single byte in the YUV stream
|
|
22778
|
+
float getByteValue(float byteOffset) {
|
|
22779
|
+
float width = u_resolution.x;
|
|
22780
|
+
float height = u_resolution.y;
|
|
22781
|
+
|
|
22782
|
+
float yPlaneSize = width * height;
|
|
22783
|
+
|
|
22784
|
+
if (byteOffset < yPlaneSize) {
|
|
22785
|
+
// This byte is in the luma plane. Find the corresponding pixel coordinates to sample from
|
|
22786
|
+
float y = floor(byteOffset / width);
|
|
22787
|
+
float x = mod(byteOffset, width);
|
|
22788
|
+
|
|
22789
|
+
// Add 0.5 to sample the center of the texel
|
|
22790
|
+
vec2 sampleCoord = (vec2(x, y) + 0.5) / u_resolution;
|
|
22791
|
+
|
|
22792
|
+
// The luma value is the alpha from the source texture
|
|
22793
|
+
return texture(u_sourceTexture, sampleCoord).a;
|
|
22794
|
+
} else {
|
|
22795
|
+
// Write a fixed value for chroma and beyond
|
|
22796
|
+
return 128.0 / 255.0;
|
|
22797
|
+
}
|
|
22798
|
+
}
|
|
22799
|
+
|
|
22800
|
+
void main() {
|
|
22801
|
+
// Each fragment writes 4 bytes (R, G, B, A)
|
|
22802
|
+
float pixelIndex = floor(gl_FragCoord.y) * u_resolution.x + floor(gl_FragCoord.x);
|
|
22803
|
+
float baseByteOffset = pixelIndex * 4.0;
|
|
22804
|
+
|
|
22805
|
+
vec4 result;
|
|
22806
|
+
for (int i = 0; i < 4; i++) {
|
|
22807
|
+
float currentByteOffset = baseByteOffset + float(i);
|
|
22808
|
+
result[i] = getByteValue(currentByteOffset);
|
|
22809
|
+
}
|
|
22810
|
+
|
|
22811
|
+
fragColor = result;
|
|
22812
|
+
}
|
|
22813
|
+
`);
|
|
22814
|
+
const program = this.gl.createProgram();
|
|
22815
|
+
this.gl.attachShader(program, vertexShader);
|
|
22816
|
+
this.gl.attachShader(program, fragmentShader);
|
|
22817
|
+
this.gl.linkProgram(program);
|
|
22818
|
+
return program;
|
|
22819
|
+
}
|
|
22820
|
+
createShader(type, source) {
|
|
22821
|
+
const shader = this.gl.createShader(type);
|
|
22822
|
+
this.gl.shaderSource(shader, source);
|
|
22823
|
+
this.gl.compileShader(shader);
|
|
22824
|
+
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
|
|
22825
|
+
console.error("Shader compile error:", this.gl.getShaderInfoLog(shader));
|
|
22826
|
+
}
|
|
22827
|
+
return shader;
|
|
22828
|
+
}
|
|
22829
|
+
createVAO() {
|
|
22830
|
+
const vao = this.gl.createVertexArray();
|
|
22831
|
+
this.gl.bindVertexArray(vao);
|
|
22832
|
+
const vertices = new Float32Array([
|
|
22833
|
+
-1,
|
|
22834
|
+
-1,
|
|
22835
|
+
0,
|
|
22836
|
+
1,
|
|
22837
|
+
1,
|
|
22838
|
+
-1,
|
|
22839
|
+
1,
|
|
22840
|
+
1,
|
|
22841
|
+
-1,
|
|
22842
|
+
1,
|
|
22843
|
+
0,
|
|
22844
|
+
0,
|
|
22845
|
+
1,
|
|
22846
|
+
1,
|
|
22847
|
+
1,
|
|
22848
|
+
0
|
|
22849
|
+
]);
|
|
22850
|
+
const buffer = this.gl.createBuffer();
|
|
22851
|
+
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
|
|
22852
|
+
this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
|
|
22853
|
+
const positionLocation = this.gl.getAttribLocation(this.colorProgram, "a_position");
|
|
22854
|
+
const texCoordLocation = this.gl.getAttribLocation(this.colorProgram, "a_texCoord");
|
|
22855
|
+
this.gl.enableVertexAttribArray(positionLocation);
|
|
22856
|
+
this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 16, 0);
|
|
22857
|
+
this.gl.enableVertexAttribArray(texCoordLocation);
|
|
22858
|
+
this.gl.vertexAttribPointer(texCoordLocation, 2, this.gl.FLOAT, false, 16, 8);
|
|
22859
|
+
return vao;
|
|
22860
|
+
}
|
|
22861
|
+
createTexture() {
|
|
22862
|
+
const texture = this.gl.createTexture();
|
|
22863
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
|
|
22864
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
|
|
22865
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
|
|
22866
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
|
|
22867
|
+
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
|
|
22868
|
+
return texture;
|
|
22869
|
+
}
|
|
22870
|
+
updateTexture(sourceFrame) {
|
|
22871
|
+
if (this.lastFrame === sourceFrame) {
|
|
22872
|
+
return;
|
|
22873
|
+
}
|
|
22874
|
+
if (sourceFrame.displayWidth !== this.canvas.width || sourceFrame.displayHeight !== this.canvas.height) {
|
|
22875
|
+
this.canvas.width = sourceFrame.displayWidth;
|
|
22876
|
+
this.canvas.height = sourceFrame.displayHeight;
|
|
22877
|
+
}
|
|
22878
|
+
this.gl.activeTexture(this.gl.TEXTURE0);
|
|
22879
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.sourceTexture);
|
|
22880
|
+
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, sourceFrame);
|
|
22881
|
+
this.lastFrame = sourceFrame;
|
|
22882
|
+
}
|
|
22883
|
+
extractColor(sourceFrame) {
|
|
22884
|
+
this.updateTexture(sourceFrame);
|
|
22885
|
+
this.gl.useProgram(this.colorProgram);
|
|
22886
|
+
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
22887
|
+
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
22888
|
+
this.gl.bindVertexArray(this.vao);
|
|
22889
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
22890
|
+
return new VideoFrame(this.canvas, {
|
|
22891
|
+
timestamp: sourceFrame.timestamp,
|
|
22892
|
+
duration: sourceFrame.duration ?? void 0,
|
|
22893
|
+
alpha: "discard"
|
|
22894
|
+
});
|
|
22895
|
+
}
|
|
22896
|
+
extractAlpha(sourceFrame) {
|
|
22897
|
+
this.updateTexture(sourceFrame);
|
|
22898
|
+
this.gl.useProgram(this.alphaProgram);
|
|
22899
|
+
this.gl.uniform2f(this.alphaResolutionLocation, this.canvas.width, this.canvas.height);
|
|
22900
|
+
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
22901
|
+
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
22902
|
+
this.gl.bindVertexArray(this.vao);
|
|
22903
|
+
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
22904
|
+
const { width, height } = this.canvas;
|
|
22905
|
+
const chromaSamples = Math.ceil(width / 2) * Math.ceil(height / 2);
|
|
22906
|
+
const yuvSize = width * height + chromaSamples * 2;
|
|
22907
|
+
const requiredHeight = Math.ceil(yuvSize / (width * 4));
|
|
22908
|
+
let yuv = new Uint8Array(4 * width * requiredHeight);
|
|
22909
|
+
this.gl.readPixels(0, 0, width, requiredHeight, this.gl.RGBA, this.gl.UNSIGNED_BYTE, yuv);
|
|
22910
|
+
yuv = yuv.subarray(0, yuvSize);
|
|
22911
|
+
assert(yuv[width * height] === 128);
|
|
22912
|
+
assert(yuv[yuv.length - 1] === 128);
|
|
22913
|
+
const init = {
|
|
22914
|
+
format: "I420",
|
|
22915
|
+
codedWidth: width,
|
|
22916
|
+
codedHeight: height,
|
|
22917
|
+
timestamp: sourceFrame.timestamp,
|
|
22918
|
+
duration: sourceFrame.duration ?? void 0,
|
|
22919
|
+
transfer: [yuv.buffer]
|
|
22920
|
+
};
|
|
22921
|
+
return new VideoFrame(yuv, init);
|
|
22922
|
+
}
|
|
22923
|
+
close() {
|
|
22924
|
+
this.gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
22925
|
+
this.gl = null;
|
|
22926
|
+
}
|
|
22927
|
+
};
|
|
22153
22928
|
var VideoSampleSource = class extends VideoSource {
|
|
22154
22929
|
/**
|
|
22155
22930
|
* Creates a new {@link VideoSampleSource} whose samples are encoded according to the specified
|
|
@@ -22411,7 +23186,8 @@ ${cue.notes ?? ""}`;
|
|
|
22411
23186
|
* However, we want to surface these errors to the user within the normal control flow, so they don't go uncaught.
|
|
22412
23187
|
* So, we keep track of the encoder error and throw it as soon as we get the chance.
|
|
22413
23188
|
*/
|
|
22414
|
-
this.
|
|
23189
|
+
this.error = null;
|
|
23190
|
+
this.errorNeedsNewStack = true;
|
|
22415
23191
|
}
|
|
22416
23192
|
async add(audioSample, shouldClose) {
|
|
22417
23193
|
try {
|
|
@@ -22439,7 +23215,7 @@ ${cue.notes ?? ""}`;
|
|
|
22439
23215
|
if (this.customEncoder) {
|
|
22440
23216
|
this.customEncoderQueueSize++;
|
|
22441
23217
|
const clonedSample = audioSample.clone();
|
|
22442
|
-
const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample)).then(() => this.customEncoderQueueSize--).catch((error) => this.
|
|
23218
|
+
const promise = this.customEncoderCallSerializer.call(() => this.customEncoder.encode(clonedSample)).then(() => this.customEncoderQueueSize--).catch((error) => this.error ??= error).finally(() => {
|
|
22443
23219
|
clonedSample.close();
|
|
22444
23220
|
});
|
|
22445
23221
|
if (this.customEncoderQueueSize >= 4) {
|
|
@@ -22520,9 +23296,6 @@ ${cue.notes ?? ""}`;
|
|
|
22520
23296
|
}
|
|
22521
23297
|
}
|
|
22522
23298
|
ensureEncoder(audioSample) {
|
|
22523
|
-
if (this.encoderInitialized) {
|
|
22524
|
-
return;
|
|
22525
|
-
}
|
|
22526
23299
|
const encoderError = new Error();
|
|
22527
23300
|
this.ensureEncoderPromise = (async () => {
|
|
22528
23301
|
const { numberOfChannels, sampleRate } = audioSample;
|
|
@@ -22548,7 +23321,10 @@ ${cue.notes ?? ""}`;
|
|
|
22548
23321
|
throw new TypeError("The second argument passed to onPacket must be an object or undefined.");
|
|
22549
23322
|
}
|
|
22550
23323
|
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
|
22551
|
-
void this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta)
|
|
23324
|
+
void this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => {
|
|
23325
|
+
this.error ??= error;
|
|
23326
|
+
this.errorNeedsNewStack = false;
|
|
23327
|
+
});
|
|
22552
23328
|
};
|
|
22553
23329
|
await this.customEncoder.init();
|
|
22554
23330
|
} else if (PCM_AUDIO_CODECS.includes(this.encodingConfig.codec)) {
|
|
@@ -22567,11 +23343,14 @@ ${cue.notes ?? ""}`;
|
|
|
22567
23343
|
output: (chunk, meta) => {
|
|
22568
23344
|
const packet = EncodedPacket.fromEncodedChunk(chunk);
|
|
22569
23345
|
this.encodingConfig.onEncodedPacket?.(packet, meta);
|
|
22570
|
-
void this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta)
|
|
23346
|
+
void this.muxer.addEncodedAudioPacket(this.source._connectedTrack, packet, meta).catch((error) => {
|
|
23347
|
+
this.error ??= error;
|
|
23348
|
+
this.errorNeedsNewStack = false;
|
|
23349
|
+
});
|
|
22571
23350
|
},
|
|
22572
23351
|
error: (error) => {
|
|
22573
23352
|
error.stack = encoderError.stack;
|
|
22574
|
-
this.
|
|
23353
|
+
this.error ??= error;
|
|
22575
23354
|
}
|
|
22576
23355
|
});
|
|
22577
23356
|
this.encoder.configure(encoderConfig);
|
|
@@ -22703,9 +23482,11 @@ ${cue.notes ?? ""}`;
|
|
|
22703
23482
|
}
|
|
22704
23483
|
}
|
|
22705
23484
|
checkForEncoderError() {
|
|
22706
|
-
if (this.
|
|
22707
|
-
this.
|
|
22708
|
-
|
|
23485
|
+
if (this.error) {
|
|
23486
|
+
if (this.errorNeedsNewStack) {
|
|
23487
|
+
this.error.stack = new Error().stack;
|
|
23488
|
+
}
|
|
23489
|
+
throw this.error;
|
|
22709
23490
|
}
|
|
22710
23491
|
}
|
|
22711
23492
|
};
|
|
@@ -23014,9 +23795,15 @@ ${cue.notes ?? ""}`;
|
|
|
23014
23795
|
/** Creates a new {@link TextSubtitleSource} where added text chunks are in the specified `codec`. */
|
|
23015
23796
|
constructor(codec) {
|
|
23016
23797
|
super(codec);
|
|
23798
|
+
/** @internal */
|
|
23799
|
+
this._error = null;
|
|
23017
23800
|
this._parser = new SubtitleParser({
|
|
23018
23801
|
codec,
|
|
23019
|
-
output: (cue, metadata) =>
|
|
23802
|
+
output: (cue, metadata) => {
|
|
23803
|
+
void this._connectedTrack?.output._muxer.addSubtitleCue(this._connectedTrack, cue, metadata).catch((error) => {
|
|
23804
|
+
this._error ??= error;
|
|
23805
|
+
});
|
|
23806
|
+
}
|
|
23020
23807
|
});
|
|
23021
23808
|
}
|
|
23022
23809
|
/**
|
|
@@ -23030,10 +23817,23 @@ ${cue.notes ?? ""}`;
|
|
|
23030
23817
|
if (typeof text !== "string") {
|
|
23031
23818
|
throw new TypeError("text must be a string.");
|
|
23032
23819
|
}
|
|
23820
|
+
this._checkForError();
|
|
23033
23821
|
this._ensureValidAdd();
|
|
23034
23822
|
this._parser.parse(text);
|
|
23035
23823
|
return this._connectedTrack.output._muxer.mutex.currentPromise;
|
|
23036
23824
|
}
|
|
23825
|
+
/** @internal */
|
|
23826
|
+
_checkForError() {
|
|
23827
|
+
if (this._error) {
|
|
23828
|
+
throw this._error;
|
|
23829
|
+
}
|
|
23830
|
+
}
|
|
23831
|
+
/** @internal */
|
|
23832
|
+
async _flushAndClose(forceClose) {
|
|
23833
|
+
if (!forceClose) {
|
|
23834
|
+
this._checkForError();
|
|
23835
|
+
}
|
|
23836
|
+
}
|
|
23037
23837
|
};
|
|
23038
23838
|
|
|
23039
23839
|
// src/output.ts
|