mediabunny 1.44.2 → 1.45.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/README.md +8 -8
- package/dist/bundles/mediabunny.cjs +1297 -447
- package/dist/bundles/mediabunny.min.cjs +17 -17
- package/dist/bundles/mediabunny.min.mjs +17 -17
- package/dist/bundles/mediabunny.mjs +1297 -447
- package/dist/bundles/mediabunny.node.cjs +1297 -447
- package/dist/mediabunny.d.ts +219 -15
- package/dist/modules/src/conversion.d.ts +5 -5
- package/dist/modules/src/conversion.d.ts.map +1 -1
- package/dist/modules/src/conversion.js +58 -162
- package/dist/modules/src/encode.d.ts +9 -4
- package/dist/modules/src/encode.d.ts.map +1 -1
- package/dist/modules/src/encode.js +5 -3
- package/dist/modules/src/flac/flac-demuxer.d.ts.map +1 -1
- package/dist/modules/src/flac/flac-demuxer.js +1 -1
- package/dist/modules/src/index.d.ts +1 -1
- package/dist/modules/src/index.d.ts.map +1 -1
- package/dist/modules/src/index.js +1 -1
- package/dist/modules/src/input-format.d.ts.map +1 -1
- package/dist/modules/src/input-format.js +2 -1
- package/dist/modules/src/isobmff/isobmff-muxer.js +1 -1
- package/dist/modules/src/media-sink.d.ts +31 -0
- package/dist/modules/src/media-sink.d.ts.map +1 -1
- package/dist/modules/src/media-sink.js +353 -79
- package/dist/modules/src/media-source.d.ts +30 -0
- package/dist/modules/src/media-source.d.ts.map +1 -1
- package/dist/modules/src/media-source.js +287 -148
- package/dist/modules/src/misc.d.ts +1 -4
- package/dist/modules/src/misc.d.ts.map +1 -1
- package/dist/modules/src/misc.js +4 -4
- package/dist/modules/src/sample.d.ts +200 -8
- package/dist/modules/src/sample.d.ts.map +1 -1
- package/dist/modules/src/sample.js +820 -99
- package/dist/modules/src/source.d.ts +1 -1
- package/dist/modules/src/source.d.ts.map +1 -1
- package/dist/modules/src/source.js +8 -5
- package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/src/conversion.ts +70 -192
- package/src/encode.ts +14 -6
- package/src/flac/flac-demuxer.ts +2 -1
- package/src/index.ts +6 -0
- package/src/input-format.ts +2 -1
- package/src/isobmff/isobmff-muxer.ts +1 -1
- package/src/media-sink.ts +450 -93
- package/src/media-source.ts +356 -168
- package/src/misc.ts +5 -5
- package/src/sample.ts +1129 -112
- package/src/source.ts +11 -7
|
@@ -103,6 +103,7 @@ var Mediabunny = (() => {
|
|
|
103
103
|
AudioBufferSink: () => AudioBufferSink,
|
|
104
104
|
AudioBufferSource: () => AudioBufferSource,
|
|
105
105
|
AudioSample: () => AudioSample,
|
|
106
|
+
AudioSampleResource: () => AudioSampleResource,
|
|
106
107
|
AudioSampleSink: () => AudioSampleSink,
|
|
107
108
|
AudioSampleSource: () => AudioSampleSource,
|
|
108
109
|
AudioSource: () => AudioSource,
|
|
@@ -200,6 +201,7 @@ var Mediabunny = (() => {
|
|
|
200
201
|
VIDEO_SAMPLE_PIXEL_FORMATS: () => VIDEO_SAMPLE_PIXEL_FORMATS,
|
|
201
202
|
VideoSample: () => VideoSample,
|
|
202
203
|
VideoSampleColorSpace: () => VideoSampleColorSpace,
|
|
204
|
+
VideoSampleResource: () => VideoSampleResource,
|
|
203
205
|
VideoSampleSink: () => VideoSampleSink,
|
|
204
206
|
VideoSampleSource: () => VideoSampleSource,
|
|
205
207
|
VideoSource: () => VideoSource,
|
|
@@ -230,7 +232,8 @@ var Mediabunny = (() => {
|
|
|
230
232
|
getFirstEncodableVideoCodec: () => getFirstEncodableVideoCodec,
|
|
231
233
|
prefer: () => prefer,
|
|
232
234
|
registerDecoder: () => registerDecoder,
|
|
233
|
-
registerEncoder: () => registerEncoder
|
|
235
|
+
registerEncoder: () => registerEncoder,
|
|
236
|
+
registerVideoSampleTransformer: () => registerVideoSampleTransformer
|
|
234
237
|
});
|
|
235
238
|
|
|
236
239
|
// src/misc.ts
|
|
@@ -661,8 +664,8 @@ var Mediabunny = (() => {
|
|
|
661
664
|
const nextDenominator = integer * currDenominator + prevDenominator;
|
|
662
665
|
if (nextDenominator > maxDenominator) {
|
|
663
666
|
return {
|
|
664
|
-
|
|
665
|
-
|
|
667
|
+
num: sign * currNumerator,
|
|
668
|
+
den: currDenominator
|
|
666
669
|
};
|
|
667
670
|
}
|
|
668
671
|
prevNumerator = currNumerator;
|
|
@@ -675,8 +678,8 @@ var Mediabunny = (() => {
|
|
|
675
678
|
}
|
|
676
679
|
}
|
|
677
680
|
return {
|
|
678
|
-
|
|
679
|
-
|
|
681
|
+
num: sign * currNumerator,
|
|
682
|
+
den: currDenominator
|
|
680
683
|
};
|
|
681
684
|
};
|
|
682
685
|
var CallSerializer = class {
|
|
@@ -13356,7 +13359,7 @@ var Mediabunny = (() => {
|
|
|
13356
13359
|
async getPacket(timestamp, options) {
|
|
13357
13360
|
assert(this.demuxer.audioInfo);
|
|
13358
13361
|
if (timestamp < 0) {
|
|
13359
|
-
|
|
13362
|
+
return null;
|
|
13360
13363
|
}
|
|
13361
13364
|
const release = await this.demuxer.readingMutex.acquire();
|
|
13362
13365
|
try {
|
|
@@ -15895,7 +15898,7 @@ var Mediabunny = (() => {
|
|
|
15895
15898
|
};
|
|
15896
15899
|
var URL_SOURCE_MIN_LOAD_AMOUNT = 0.5 * 2 ** 20;
|
|
15897
15900
|
var DEFAULT_RETRY_DELAY = (previousAttempts, error, src) => {
|
|
15898
|
-
const couldBeCorsError = error instanceof Error && (error.message.includes("Failed to fetch") || error.message.includes("Load failed") || error.message.includes("NetworkError when attempting to fetch resource"));
|
|
15901
|
+
const couldBeCorsError = error instanceof Error && (error.message.includes("Failed to fetch") || error.message.includes("Load failed") || error.message.includes("NetworkError when attempting to fetch resource")) && typeof window !== "undefined";
|
|
15899
15902
|
if (couldBeCorsError) {
|
|
15900
15903
|
let originOfSrc = null;
|
|
15901
15904
|
try {
|
|
@@ -16280,7 +16283,7 @@ var Mediabunny = (() => {
|
|
|
16280
16283
|
/** @internal */
|
|
16281
16284
|
this._pulling = false;
|
|
16282
16285
|
this._stream = stream;
|
|
16283
|
-
this._maxCacheSize = options.maxCacheSize ??
|
|
16286
|
+
this._maxCacheSize = options.maxCacheSize ?? 32 * 2 ** 20;
|
|
16284
16287
|
}
|
|
16285
16288
|
/** @internal */
|
|
16286
16289
|
_getFileSize() {
|
|
@@ -16377,6 +16380,7 @@ var Mediabunny = (() => {
|
|
|
16377
16380
|
}
|
|
16378
16381
|
const startIndex = this._currentIndex;
|
|
16379
16382
|
const endIndex = this._currentIndex + value.byteLength;
|
|
16383
|
+
this._dispatchRead(startIndex, endIndex);
|
|
16380
16384
|
for (let i = 0; i < this._pendingSlices.length; i++) {
|
|
16381
16385
|
const pendingSlice = this._pendingSlices[i];
|
|
16382
16386
|
const cappedStart = Math.max(startIndex, pendingSlice.start);
|
|
@@ -18293,7 +18297,8 @@ var Mediabunny = (() => {
|
|
|
18293
18297
|
let slice = input._reader.requestSlice(4, 4);
|
|
18294
18298
|
if (slice instanceof Promise) slice = await slice;
|
|
18295
18299
|
if (!slice) return false;
|
|
18296
|
-
|
|
18300
|
+
const fourCc = readAscii(slice, 4);
|
|
18301
|
+
return fourCc === "moof" || fourCc === "sidx";
|
|
18297
18302
|
}
|
|
18298
18303
|
get name() {
|
|
18299
18304
|
return "MP4";
|
|
@@ -18841,6 +18846,14 @@ var Mediabunny = (() => {
|
|
|
18841
18846
|
}
|
|
18842
18847
|
});
|
|
18843
18848
|
}
|
|
18849
|
+
var VideoSampleResource = class {
|
|
18850
|
+
constructor() {
|
|
18851
|
+
/** @internal */
|
|
18852
|
+
this._referenceCount = 0;
|
|
18853
|
+
/** @internal */
|
|
18854
|
+
this._lastAllocationBuffer = null;
|
|
18855
|
+
}
|
|
18856
|
+
};
|
|
18844
18857
|
var VIDEO_SAMPLE_PIXEL_FORMATS = [
|
|
18845
18858
|
// 4:2:0 Y, U, V
|
|
18846
18859
|
"I420",
|
|
@@ -18940,7 +18953,25 @@ var Mediabunny = (() => {
|
|
|
18940
18953
|
this.rotation = init.rotation ?? 0;
|
|
18941
18954
|
this.timestamp = init.timestamp;
|
|
18942
18955
|
this.duration = init.duration ?? 0;
|
|
18943
|
-
|
|
18956
|
+
let colorSpaceInit = init.colorSpace ?? null;
|
|
18957
|
+
if (colorSpaceInit === null) {
|
|
18958
|
+
if (this.format === "RGBA" || this.format === "RGBX" || this.format === "BGRA" || this.format === "BGRX") {
|
|
18959
|
+
colorSpaceInit = {
|
|
18960
|
+
primaries: "bt709",
|
|
18961
|
+
transfer: "iec61966-2-1",
|
|
18962
|
+
matrix: "rgb",
|
|
18963
|
+
fullRange: true
|
|
18964
|
+
};
|
|
18965
|
+
} else {
|
|
18966
|
+
colorSpaceInit = {
|
|
18967
|
+
primaries: "bt709",
|
|
18968
|
+
transfer: "bt709",
|
|
18969
|
+
matrix: "bt709",
|
|
18970
|
+
fullRange: false
|
|
18971
|
+
};
|
|
18972
|
+
}
|
|
18973
|
+
}
|
|
18974
|
+
this.colorSpace = new VideoSampleColorSpace(colorSpaceInit);
|
|
18944
18975
|
this.visibleRect = {
|
|
18945
18976
|
left: init.visibleRect?.left ?? 0,
|
|
18946
18977
|
top: init.visibleRect?.top ?? 0,
|
|
@@ -18951,8 +18982,8 @@ var Mediabunny = (() => {
|
|
|
18951
18982
|
this.squarePixelWidth = this.rotation % 180 === 0 ? init.displayWidth : init.displayHeight;
|
|
18952
18983
|
this.squarePixelHeight = this.rotation % 180 === 0 ? init.displayHeight : init.displayWidth;
|
|
18953
18984
|
} else {
|
|
18954
|
-
this.squarePixelWidth = this.
|
|
18955
|
-
this.squarePixelHeight = this.
|
|
18985
|
+
this.squarePixelWidth = this.visibleRect.width;
|
|
18986
|
+
this.squarePixelHeight = this.visibleRect.height;
|
|
18956
18987
|
}
|
|
18957
18988
|
} else if (typeof VideoFrame !== "undefined" && data instanceof VideoFrame) {
|
|
18958
18989
|
if (init?.rotation !== void 0 && ![0, 90, 180, 270].includes(init.rotation)) {
|
|
@@ -19043,8 +19074,53 @@ var Mediabunny = (() => {
|
|
|
19043
19074
|
transfer: "iec61966-2-1",
|
|
19044
19075
|
fullRange: true
|
|
19045
19076
|
});
|
|
19077
|
+
} else if (data instanceof VideoSampleResource) {
|
|
19078
|
+
if (!init || typeof init !== "object") {
|
|
19079
|
+
throw new TypeError("init must be an object.");
|
|
19080
|
+
}
|
|
19081
|
+
if (init.rotation !== void 0 && ![0, 90, 180, 270].includes(init.rotation)) {
|
|
19082
|
+
throw new TypeError("init.rotation, when provided, must be 0, 90, 180, or 270.");
|
|
19083
|
+
}
|
|
19084
|
+
if (!Number.isFinite(init.timestamp)) {
|
|
19085
|
+
throw new TypeError("init.timestamp must be a number.");
|
|
19086
|
+
}
|
|
19087
|
+
if (init.duration !== void 0 && (!Number.isFinite(init.duration) || init.duration < 0)) {
|
|
19088
|
+
throw new TypeError("init.duration, when provided, must be a non-negative number.");
|
|
19089
|
+
}
|
|
19090
|
+
this._data = data;
|
|
19091
|
+
data._referenceCount++;
|
|
19092
|
+
this.format = data.getFormat();
|
|
19093
|
+
if (this.format !== null && !VIDEO_SAMPLE_PIXEL_FORMATS.includes(this.format)) {
|
|
19094
|
+
throw new TypeError("getFormat() must return a VideoSamplePixelFormat or null.");
|
|
19095
|
+
}
|
|
19096
|
+
this.visibleRect = {
|
|
19097
|
+
left: 0,
|
|
19098
|
+
top: 0,
|
|
19099
|
+
width: data.getCodedWidth(),
|
|
19100
|
+
height: data.getCodedHeight()
|
|
19101
|
+
};
|
|
19102
|
+
if (!Number.isInteger(this.visibleRect.width) || this.visibleRect.width <= 0) {
|
|
19103
|
+
throw new TypeError("getCodedWidth() must return a positive integer.");
|
|
19104
|
+
}
|
|
19105
|
+
if (!Number.isInteger(this.visibleRect.height) || this.visibleRect.height <= 0) {
|
|
19106
|
+
throw new TypeError("getCodedHeight() must return a positive integer.");
|
|
19107
|
+
}
|
|
19108
|
+
this.squarePixelWidth = data.getSquarePixelWidth();
|
|
19109
|
+
if (!Number.isInteger(this.squarePixelWidth) || this.squarePixelWidth <= 0) {
|
|
19110
|
+
throw new TypeError("getSquarePixelWidth() must return a positive integer.");
|
|
19111
|
+
}
|
|
19112
|
+
this.squarePixelHeight = data.getSquarePixelHeight();
|
|
19113
|
+
if (!Number.isInteger(this.squarePixelHeight) || this.squarePixelHeight <= 0) {
|
|
19114
|
+
throw new TypeError("getSquarePixelHeight() must return a positive integer.");
|
|
19115
|
+
}
|
|
19116
|
+
this.rotation = init.rotation ?? 0;
|
|
19117
|
+
this.timestamp = init.timestamp;
|
|
19118
|
+
this.duration = init.duration ?? 0;
|
|
19119
|
+
this.colorSpace = data.getColorSpace();
|
|
19046
19120
|
} else {
|
|
19047
|
-
throw new TypeError(
|
|
19121
|
+
throw new TypeError(
|
|
19122
|
+
"Invalid data type: Must be a BufferSource, CanvasImageSource, or VideoSampleResource."
|
|
19123
|
+
);
|
|
19048
19124
|
}
|
|
19049
19125
|
this.pixelAspectRatio = simplifyRational({
|
|
19050
19126
|
num: this.squarePixelWidth * this.codedHeight,
|
|
@@ -19089,7 +19165,13 @@ var Mediabunny = (() => {
|
|
|
19089
19165
|
throw new Error("VideoSample is closed.");
|
|
19090
19166
|
}
|
|
19091
19167
|
assert(this._data !== null);
|
|
19092
|
-
if (
|
|
19168
|
+
if (this._data instanceof VideoSampleResource) {
|
|
19169
|
+
return new _VideoSample(this._data, {
|
|
19170
|
+
timestamp: this.timestamp,
|
|
19171
|
+
duration: this.duration,
|
|
19172
|
+
rotation: this.rotation
|
|
19173
|
+
});
|
|
19174
|
+
} else if (isVideoFrame(this._data)) {
|
|
19093
19175
|
return new _VideoSample(this._data.clone(), {
|
|
19094
19176
|
timestamp: this.timestamp,
|
|
19095
19177
|
duration: this.duration,
|
|
@@ -19134,7 +19216,12 @@ var Mediabunny = (() => {
|
|
|
19134
19216
|
return;
|
|
19135
19217
|
}
|
|
19136
19218
|
finalizationRegistry?.unregister(this);
|
|
19137
|
-
if (
|
|
19219
|
+
if (this._data instanceof VideoSampleResource) {
|
|
19220
|
+
this._data._referenceCount--;
|
|
19221
|
+
if (this._data._referenceCount === 0) {
|
|
19222
|
+
this._data.close();
|
|
19223
|
+
}
|
|
19224
|
+
} else if (isVideoFrame(this._data)) {
|
|
19138
19225
|
this._data.close();
|
|
19139
19226
|
} else {
|
|
19140
19227
|
this._data = null;
|
|
@@ -19142,35 +19229,24 @@ var Mediabunny = (() => {
|
|
|
19142
19229
|
this._closed = true;
|
|
19143
19230
|
}
|
|
19144
19231
|
/**
|
|
19145
|
-
* Returns the number of bytes required to hold this video sample's pixel data.
|
|
19232
|
+
* Returns the number of bytes required to hold this video sample's pixel data.
|
|
19146
19233
|
*/
|
|
19147
19234
|
allocationSize(options = {}) {
|
|
19148
19235
|
validateVideoFrameCopyToOptions(options);
|
|
19149
19236
|
if (this._closed) {
|
|
19150
19237
|
throw new Error("VideoSample is closed.");
|
|
19151
19238
|
}
|
|
19152
|
-
if (this.format
|
|
19153
|
-
throw new Error("Cannot get allocation size when format is null.
|
|
19154
|
-
}
|
|
19155
|
-
assert(this._data !== null);
|
|
19156
|
-
if (!isVideoFrame(this._data)) {
|
|
19157
|
-
if (options.colorSpace || options.format && options.format !== this.format || options.layout || options.rect) {
|
|
19158
|
-
const videoFrame = this.toVideoFrame();
|
|
19159
|
-
const size = videoFrame.allocationSize(options);
|
|
19160
|
-
videoFrame.close();
|
|
19161
|
-
return size;
|
|
19162
|
-
}
|
|
19239
|
+
if ((options.format ?? this.format) == null) {
|
|
19240
|
+
throw new Error("Cannot get allocation size when format is null.");
|
|
19163
19241
|
}
|
|
19164
19242
|
if (isVideoFrame(this._data)) {
|
|
19165
19243
|
return this._data.allocationSize(options);
|
|
19166
|
-
} else if (this._data instanceof Uint8Array) {
|
|
19167
|
-
return this._data.byteLength;
|
|
19168
|
-
} else {
|
|
19169
|
-
return this.codedWidth * this.codedHeight * 4;
|
|
19170
19244
|
}
|
|
19245
|
+
const combinedLayout = ParseVideoFrameCopyToOptions(this, options);
|
|
19246
|
+
return combinedLayout.allocationSize;
|
|
19171
19247
|
}
|
|
19172
19248
|
/**
|
|
19173
|
-
* Copies this video sample's pixel data to an ArrayBuffer or ArrayBufferView.
|
|
19249
|
+
* Copies this video sample's pixel data to an ArrayBuffer or ArrayBufferView.
|
|
19174
19250
|
* @returns The byte layout of the planes of the copied data.
|
|
19175
19251
|
*/
|
|
19176
19252
|
async copyTo(destination, options = {}) {
|
|
@@ -19181,37 +19257,139 @@ var Mediabunny = (() => {
|
|
|
19181
19257
|
if (this._closed) {
|
|
19182
19258
|
throw new Error("VideoSample is closed.");
|
|
19183
19259
|
}
|
|
19184
|
-
if (this.format
|
|
19185
|
-
throw new Error("Cannot copy video sample data when format is null.
|
|
19260
|
+
if ((options.format ?? this.format) == null) {
|
|
19261
|
+
throw new Error("Cannot copy video sample data when format is null.");
|
|
19186
19262
|
}
|
|
19187
19263
|
assert(this._data !== null);
|
|
19188
|
-
if (!isVideoFrame(this._data)) {
|
|
19189
|
-
if (options.colorSpace || options.format && options.format !== this.format || options.layout || options.rect) {
|
|
19190
|
-
const videoFrame = this.toVideoFrame();
|
|
19191
|
-
const layout = await videoFrame.copyTo(destination, options);
|
|
19192
|
-
videoFrame.close();
|
|
19193
|
-
return layout;
|
|
19194
|
-
}
|
|
19195
|
-
}
|
|
19196
19264
|
if (isVideoFrame(this._data)) {
|
|
19197
19265
|
return this._data.copyTo(destination, options);
|
|
19266
|
+
}
|
|
19267
|
+
if (options.format && !["RGBA", "RGBX", "BGRA", "BGRX"].includes(this.format) && ["RGBA", "RGBX", "BGRA", "BGRX"].includes(options.format)) {
|
|
19268
|
+
if (this._data instanceof VideoSampleResource) {
|
|
19269
|
+
var _stack = [];
|
|
19270
|
+
try {
|
|
19271
|
+
const rgbSample = __using(_stack, await this._data.toRgbSample(
|
|
19272
|
+
{
|
|
19273
|
+
timestamp: this.timestamp,
|
|
19274
|
+
duration: this.duration,
|
|
19275
|
+
rotation: this.rotation
|
|
19276
|
+
},
|
|
19277
|
+
options.colorSpace ?? "srgb"
|
|
19278
|
+
));
|
|
19279
|
+
if (!(rgbSample instanceof _VideoSample)) {
|
|
19280
|
+
throw new TypeError("toRgbSample() must return a VideoSample.");
|
|
19281
|
+
}
|
|
19282
|
+
if (!["RGBA", "RGBX", "BGRA", "BGRX"].includes(rgbSample.format)) {
|
|
19283
|
+
throw new Error(
|
|
19284
|
+
`Sample returned by toRgbSample was expected to have an RGB format, got '${rgbSample.format}' instead.`
|
|
19285
|
+
);
|
|
19286
|
+
}
|
|
19287
|
+
return await rgbSample.copyTo(destination, options);
|
|
19288
|
+
} catch (_) {
|
|
19289
|
+
var _error = _, _hasError = true;
|
|
19290
|
+
} finally {
|
|
19291
|
+
__callDispose(_stack, _error, _hasError);
|
|
19292
|
+
}
|
|
19293
|
+
} else {
|
|
19294
|
+
if (typeof VideoFrame === "undefined") {
|
|
19295
|
+
throw new Error(
|
|
19296
|
+
"For this sample, converting from a non-RGB to an RGB format requires VideoFrame to be defined."
|
|
19297
|
+
);
|
|
19298
|
+
}
|
|
19299
|
+
const tempFrame = this.toVideoFrame();
|
|
19300
|
+
const result = await tempFrame.copyTo(destination, options);
|
|
19301
|
+
tempFrame.close();
|
|
19302
|
+
return result;
|
|
19303
|
+
}
|
|
19304
|
+
}
|
|
19305
|
+
const combinedLayout = ParseVideoFrameCopyToOptions(this, options);
|
|
19306
|
+
assert(this.format);
|
|
19307
|
+
const destBytes = toUint8Array(destination);
|
|
19308
|
+
if (destBytes.byteLength < combinedLayout.allocationSize) {
|
|
19309
|
+
throw new TypeError(
|
|
19310
|
+
`Destination buffer too small. Required: ${combinedLayout.allocationSize}, Available: ${destBytes.byteLength}`
|
|
19311
|
+
);
|
|
19312
|
+
}
|
|
19313
|
+
const planeConfigs = getPlaneConfigs(this.format);
|
|
19314
|
+
let dataPlanes;
|
|
19315
|
+
if (this._data instanceof VideoSampleResource) {
|
|
19316
|
+
let result = this._data.getDataPlanes();
|
|
19317
|
+
if (result instanceof Promise) result = await result;
|
|
19318
|
+
if (!Array.isArray(result) || result.some((x) => !(x.data instanceof Uint8Array) || !Number.isInteger(x.stride) || x.stride < 0)) {
|
|
19319
|
+
throw new TypeError(
|
|
19320
|
+
'getDataPlanes() must return an array of objects with a Uint8Array "data" property and a non-negative integer "stride" property.'
|
|
19321
|
+
);
|
|
19322
|
+
}
|
|
19323
|
+
dataPlanes = result;
|
|
19198
19324
|
} else if (this._data instanceof Uint8Array) {
|
|
19199
19325
|
assert(this._layout);
|
|
19200
|
-
|
|
19201
|
-
|
|
19202
|
-
|
|
19326
|
+
assert(this._layout.length === planeConfigs.length);
|
|
19327
|
+
dataPlanes = this._layout.map((planeLayout, i) => {
|
|
19328
|
+
const height = Math.ceil(this.codedHeight / planeConfigs[i].heightDivisor);
|
|
19329
|
+
return {
|
|
19330
|
+
data: this._data.subarray(
|
|
19331
|
+
planeLayout.offset,
|
|
19332
|
+
planeLayout.offset + planeLayout.stride * height
|
|
19333
|
+
),
|
|
19334
|
+
stride: planeLayout.stride
|
|
19335
|
+
};
|
|
19336
|
+
});
|
|
19203
19337
|
} else {
|
|
19204
19338
|
const canvas = this._data;
|
|
19205
19339
|
const context = canvas.getContext("2d");
|
|
19206
19340
|
assert(context);
|
|
19207
19341
|
const imageData = context.getImageData(0, 0, this.codedWidth, this.codedHeight);
|
|
19208
|
-
|
|
19209
|
-
|
|
19210
|
-
return [{
|
|
19211
|
-
offset: 0,
|
|
19342
|
+
dataPlanes = [{
|
|
19343
|
+
data: toUint8Array(imageData.data),
|
|
19212
19344
|
stride: 4 * this.codedWidth
|
|
19213
19345
|
}];
|
|
19214
19346
|
}
|
|
19347
|
+
const planeLayouts = [];
|
|
19348
|
+
const numPlanes = planeConfigs.length;
|
|
19349
|
+
for (let planeIndex = 0; planeIndex < numPlanes; planeIndex++) {
|
|
19350
|
+
const computedLayout = combinedLayout.computedLayouts[planeIndex];
|
|
19351
|
+
const sourceStride = dataPlanes[planeIndex].stride;
|
|
19352
|
+
const sourceData = dataPlanes[planeIndex].data;
|
|
19353
|
+
let sourceOffset = computedLayout.sourceTop * sourceStride;
|
|
19354
|
+
sourceOffset += computedLayout.sourceLeftBytes;
|
|
19355
|
+
let destinationOffset = computedLayout.destinationOffset;
|
|
19356
|
+
const rowBytes = computedLayout.sourceWidthBytes;
|
|
19357
|
+
const layout = {
|
|
19358
|
+
offset: destinationOffset,
|
|
19359
|
+
stride: computedLayout.destinationStride
|
|
19360
|
+
};
|
|
19361
|
+
for (let row = 0; row < computedLayout.sourceHeight; row++) {
|
|
19362
|
+
if (sourceOffset + rowBytes > sourceData.byteLength) {
|
|
19363
|
+
throw new Error(`Source buffer OOB read.`);
|
|
19364
|
+
}
|
|
19365
|
+
if (destinationOffset + rowBytes > destBytes.byteLength) {
|
|
19366
|
+
throw new Error(`Destination buffer OOB write.`);
|
|
19367
|
+
}
|
|
19368
|
+
const srcSub = sourceData.subarray(sourceOffset, sourceOffset + rowBytes);
|
|
19369
|
+
destBytes.set(srcSub, destinationOffset);
|
|
19370
|
+
sourceOffset += sourceStride;
|
|
19371
|
+
destinationOffset += computedLayout.destinationStride;
|
|
19372
|
+
}
|
|
19373
|
+
planeLayouts.push(layout);
|
|
19374
|
+
}
|
|
19375
|
+
if (options.format !== void 0) {
|
|
19376
|
+
const needsRgbConversion = this.format.startsWith("RGB") !== options.format.startsWith("RGB");
|
|
19377
|
+
const needsAlphaConversion = this.format.includes("X") && options.format.includes("A");
|
|
19378
|
+
if (needsRgbConversion || needsAlphaConversion) {
|
|
19379
|
+
for (let i = 0; i < combinedLayout.allocationSize; i += 4) {
|
|
19380
|
+
if (needsRgbConversion) {
|
|
19381
|
+
const r = destBytes[i];
|
|
19382
|
+
const b = destBytes[i + 2];
|
|
19383
|
+
destBytes[i] = b;
|
|
19384
|
+
destBytes[i + 2] = r;
|
|
19385
|
+
}
|
|
19386
|
+
if (needsAlphaConversion) {
|
|
19387
|
+
destBytes[i + 3] = 255;
|
|
19388
|
+
}
|
|
19389
|
+
}
|
|
19390
|
+
}
|
|
19391
|
+
}
|
|
19392
|
+
return planeLayouts;
|
|
19215
19393
|
}
|
|
19216
19394
|
/**
|
|
19217
19395
|
* Converts this video sample to a VideoFrame for use with the WebCodecs API. The VideoFrame returned by this
|
|
@@ -19222,7 +19400,43 @@ var Mediabunny = (() => {
|
|
|
19222
19400
|
throw new Error("VideoSample is closed.");
|
|
19223
19401
|
}
|
|
19224
19402
|
assert(this._data !== null);
|
|
19225
|
-
if (
|
|
19403
|
+
if (this._data instanceof VideoSampleResource) {
|
|
19404
|
+
if (this.format === null) {
|
|
19405
|
+
throw new Error(
|
|
19406
|
+
"Cannot convert a VideoSampleResource-backed VideoSample to VideoFrame if format is null."
|
|
19407
|
+
);
|
|
19408
|
+
}
|
|
19409
|
+
const planes = this._data.getDataPlanes();
|
|
19410
|
+
if (planes instanceof Promise) {
|
|
19411
|
+
throw new Error(
|
|
19412
|
+
"Cannot convert a VideoSampleResource-backed VideoSample to VideoFrame if getDataPlanes() returns a promise."
|
|
19413
|
+
);
|
|
19414
|
+
}
|
|
19415
|
+
const size = planes.reduce((a, b) => a + b.data.byteLength, 0);
|
|
19416
|
+
const buffer = new Uint8Array(size);
|
|
19417
|
+
let offset = 0;
|
|
19418
|
+
const offsets = [];
|
|
19419
|
+
for (const plane of planes) {
|
|
19420
|
+
buffer.set(plane.data, offset);
|
|
19421
|
+
offsets.push(offset);
|
|
19422
|
+
offset += plane.data.byteLength;
|
|
19423
|
+
}
|
|
19424
|
+
return new VideoFrame(buffer, {
|
|
19425
|
+
format: this.format,
|
|
19426
|
+
layout: planes.map((x, i) => ({
|
|
19427
|
+
offset: offsets[i],
|
|
19428
|
+
stride: x.stride
|
|
19429
|
+
})),
|
|
19430
|
+
codedWidth: this.codedWidth,
|
|
19431
|
+
codedHeight: this.codedHeight,
|
|
19432
|
+
timestamp: this.microsecondTimestamp,
|
|
19433
|
+
duration: this.microsecondDuration,
|
|
19434
|
+
colorSpace: this.colorSpace,
|
|
19435
|
+
displayWidth: this.squarePixelWidth,
|
|
19436
|
+
// Not display* since we're not passing rotation
|
|
19437
|
+
displayHeight: this.squarePixelHeight
|
|
19438
|
+
});
|
|
19439
|
+
} else if (isVideoFrame(this._data)) {
|
|
19226
19440
|
return new VideoFrame(this._data, {
|
|
19227
19441
|
timestamp: this.microsecondTimestamp,
|
|
19228
19442
|
duration: this.microsecondDuration || void 0
|
|
@@ -19235,7 +19449,10 @@ var Mediabunny = (() => {
|
|
|
19235
19449
|
codedHeight: this.codedHeight,
|
|
19236
19450
|
timestamp: this.microsecondTimestamp,
|
|
19237
19451
|
duration: this.microsecondDuration || void 0,
|
|
19238
|
-
colorSpace: this.colorSpace
|
|
19452
|
+
colorSpace: this.colorSpace,
|
|
19453
|
+
displayWidth: this.squarePixelWidth,
|
|
19454
|
+
// Not display* since we're not passing rotation
|
|
19455
|
+
displayHeight: this.squarePixelHeight
|
|
19239
19456
|
});
|
|
19240
19457
|
} else {
|
|
19241
19458
|
return new VideoFrame(this._data, {
|
|
@@ -19350,8 +19567,9 @@ var Mediabunny = (() => {
|
|
|
19350
19567
|
const canvasHeight = context.canvas.height;
|
|
19351
19568
|
const rotation = options.rotation ?? this.rotation;
|
|
19352
19569
|
const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [this.squarePixelWidth, this.squarePixelHeight] : [this.squarePixelHeight, this.squarePixelWidth];
|
|
19353
|
-
|
|
19354
|
-
|
|
19570
|
+
let finalCrop = options.crop;
|
|
19571
|
+
if (finalCrop) {
|
|
19572
|
+
finalCrop = clampCropRectangle(finalCrop, rotatedWidth, rotatedHeight);
|
|
19355
19573
|
}
|
|
19356
19574
|
let dx;
|
|
19357
19575
|
let dy;
|
|
@@ -19414,7 +19632,7 @@ var Mediabunny = (() => {
|
|
|
19414
19632
|
* Converts this video sample to a
|
|
19415
19633
|
* [`CanvasImageSource`](https://udn.realityripple.com/docs/Web/API/CanvasImageSource) for drawing to a canvas.
|
|
19416
19634
|
*
|
|
19417
|
-
* You must use the value returned by this method immediately, as any VideoFrame created internally
|
|
19635
|
+
* You must use the value returned by this method immediately, as any VideoFrame created internally may
|
|
19418
19636
|
* automatically be closed in the next microtask.
|
|
19419
19637
|
*/
|
|
19420
19638
|
toCanvasImageSource() {
|
|
@@ -19422,7 +19640,7 @@ var Mediabunny = (() => {
|
|
|
19422
19640
|
throw new Error("VideoSample is closed.");
|
|
19423
19641
|
}
|
|
19424
19642
|
assert(this._data !== null);
|
|
19425
|
-
if (this._data instanceof Uint8Array) {
|
|
19643
|
+
if (this._data instanceof VideoSampleResource || this._data instanceof Uint8Array) {
|
|
19426
19644
|
const videoFrame = this.toVideoFrame();
|
|
19427
19645
|
queueMicrotask(() => videoFrame.close());
|
|
19428
19646
|
return videoFrame;
|
|
@@ -19430,6 +19648,142 @@ var Mediabunny = (() => {
|
|
|
19430
19648
|
return this._data;
|
|
19431
19649
|
}
|
|
19432
19650
|
}
|
|
19651
|
+
/**
|
|
19652
|
+
* Transform this video sample to a new video sample given the options. Can be used to resize, rotate, and crop
|
|
19653
|
+
* the sample.
|
|
19654
|
+
*
|
|
19655
|
+
* In non-browser environments, this method will not work by default. To make it work, register a custom
|
|
19656
|
+
* transformer function via {@link registerVideoSampleTransformer}.
|
|
19657
|
+
*/
|
|
19658
|
+
async transform(options) {
|
|
19659
|
+
if (!options || typeof options !== "object") {
|
|
19660
|
+
throw new TypeError("options must be an object.");
|
|
19661
|
+
}
|
|
19662
|
+
if (options.width !== void 0 && (!Number.isInteger(options.width) || options.width <= 0)) {
|
|
19663
|
+
throw new TypeError("options.width, when provided, must be a positive integer.");
|
|
19664
|
+
}
|
|
19665
|
+
if (options.height !== void 0 && (!Number.isInteger(options.height) || options.height <= 0)) {
|
|
19666
|
+
throw new TypeError("options.height, when provided, must be a positive integer.");
|
|
19667
|
+
}
|
|
19668
|
+
if (options.roundDimensionsTo !== void 0 && (!Number.isInteger(options.roundDimensionsTo) || options.roundDimensionsTo <= 0)) {
|
|
19669
|
+
throw new TypeError("options.roundDimensionsTo, when provided, must be a positive integer.");
|
|
19670
|
+
}
|
|
19671
|
+
if (options.fit !== void 0 && !["fill", "contain", "cover"].includes(options.fit)) {
|
|
19672
|
+
throw new TypeError('options.fit, when provided, must be one of "fill", "contain", or "cover".');
|
|
19673
|
+
}
|
|
19674
|
+
if (options.width !== void 0 && options.height !== void 0 && options.fit === void 0) {
|
|
19675
|
+
throw new TypeError(
|
|
19676
|
+
"When both options.width and options.height are provided, options.fit must also be provided."
|
|
19677
|
+
);
|
|
19678
|
+
}
|
|
19679
|
+
if (options.rotate !== void 0 && ![0, 90, 180, 270].includes(options.rotate)) {
|
|
19680
|
+
throw new TypeError("options.rotate, when provided, must be 0, 90, 180 or 270.");
|
|
19681
|
+
}
|
|
19682
|
+
if (options.crop !== void 0) {
|
|
19683
|
+
validateCropRectangle(options.crop, "options.");
|
|
19684
|
+
}
|
|
19685
|
+
if (options.alpha !== void 0 && !["keep", "discard"].includes(options.alpha)) {
|
|
19686
|
+
throw new TypeError("options.alpha, when provided, must be 'keep' or 'discard'.");
|
|
19687
|
+
}
|
|
19688
|
+
const rotation = normalizeRotation(this.rotation + (options.rotate ?? 0));
|
|
19689
|
+
const [rotatedWidth, rotatedHeight] = rotation % 180 === 0 ? [this.squarePixelWidth, this.squarePixelHeight] : [this.squarePixelHeight, this.squarePixelWidth];
|
|
19690
|
+
let finalCrop = options.crop;
|
|
19691
|
+
if (finalCrop) {
|
|
19692
|
+
finalCrop = clampCropRectangle(finalCrop, rotatedWidth, rotatedHeight);
|
|
19693
|
+
}
|
|
19694
|
+
const cropWidth = finalCrop ? finalCrop.width : rotatedWidth;
|
|
19695
|
+
const cropHeight = finalCrop ? finalCrop.height : rotatedHeight;
|
|
19696
|
+
const originalAspectRatio = cropWidth / cropHeight;
|
|
19697
|
+
let targetWidth;
|
|
19698
|
+
let targetHeight;
|
|
19699
|
+
if (options.width !== void 0 && options.height === void 0) {
|
|
19700
|
+
targetWidth = options.width;
|
|
19701
|
+
targetHeight = targetWidth / originalAspectRatio;
|
|
19702
|
+
} else if (options.width === void 0 && options.height !== void 0) {
|
|
19703
|
+
targetHeight = options.height;
|
|
19704
|
+
targetWidth = targetHeight * originalAspectRatio;
|
|
19705
|
+
} else if (options.width !== void 0 && options.height !== void 0) {
|
|
19706
|
+
targetWidth = options.width;
|
|
19707
|
+
targetHeight = options.height;
|
|
19708
|
+
} else {
|
|
19709
|
+
targetWidth = cropWidth;
|
|
19710
|
+
targetHeight = cropHeight;
|
|
19711
|
+
}
|
|
19712
|
+
targetWidth = roundToMultiple(targetWidth, options.roundDimensionsTo ?? 1);
|
|
19713
|
+
targetHeight = roundToMultiple(targetHeight, options.roundDimensionsTo ?? 1);
|
|
19714
|
+
const description = {
|
|
19715
|
+
width: targetWidth,
|
|
19716
|
+
height: targetHeight,
|
|
19717
|
+
fit: options.fit ?? "fill",
|
|
19718
|
+
rotation,
|
|
19719
|
+
crop: finalCrop ?? {
|
|
19720
|
+
left: 0,
|
|
19721
|
+
top: 0,
|
|
19722
|
+
width: rotatedWidth,
|
|
19723
|
+
height: rotatedHeight
|
|
19724
|
+
},
|
|
19725
|
+
alpha: options.alpha ?? "keep"
|
|
19726
|
+
};
|
|
19727
|
+
for (const transformer of registeredVideoSampleTransformers) {
|
|
19728
|
+
let result = transformer(this, description);
|
|
19729
|
+
if (result instanceof Promise) result = await result;
|
|
19730
|
+
if (result !== null) {
|
|
19731
|
+
return result;
|
|
19732
|
+
}
|
|
19733
|
+
}
|
|
19734
|
+
let canvas = null;
|
|
19735
|
+
let canvasIsNew = false;
|
|
19736
|
+
for (const entry of transformationCanvasCache) {
|
|
19737
|
+
if (entry.canvas.width === description.width && entry.canvas.height === description.height) {
|
|
19738
|
+
canvas = entry.canvas;
|
|
19739
|
+
entry.age = transformationCanvasCacheNextAge++;
|
|
19740
|
+
break;
|
|
19741
|
+
}
|
|
19742
|
+
}
|
|
19743
|
+
if (canvas === null) {
|
|
19744
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
19745
|
+
canvas = new OffscreenCanvas(description.width, description.height);
|
|
19746
|
+
} else {
|
|
19747
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
19748
|
+
throw new Error(
|
|
19749
|
+
"Cannot transform VideoSamples in this environment. Either run in an environment with OffscreenCanvas or HTMLCanvasElement, or supply a custom VideoSample transformer using registerVideoSampleTransformer()."
|
|
19750
|
+
);
|
|
19751
|
+
}
|
|
19752
|
+
canvas = document.createElement("canvas");
|
|
19753
|
+
canvas.width = description.width;
|
|
19754
|
+
canvas.height = description.height;
|
|
19755
|
+
}
|
|
19756
|
+
canvasIsNew = true;
|
|
19757
|
+
if (transformationCanvasCache.length >= TRANSFORMATION_CANVAS_CACHE_MAX_SIZE) {
|
|
19758
|
+
transformationCanvasCache.splice(arrayArgmin(transformationCanvasCache, (x) => x.age), 1);
|
|
19759
|
+
}
|
|
19760
|
+
transformationCanvasCache.push({
|
|
19761
|
+
canvas,
|
|
19762
|
+
age: transformationCanvasCacheNextAge++
|
|
19763
|
+
});
|
|
19764
|
+
}
|
|
19765
|
+
const context = canvas.getContext("2d", {
|
|
19766
|
+
alpha: true
|
|
19767
|
+
});
|
|
19768
|
+
assert(context);
|
|
19769
|
+
if (description.alpha === "discard") {
|
|
19770
|
+
context.fillStyle = "black";
|
|
19771
|
+
context.fillRect(0, 0, description.width, description.height);
|
|
19772
|
+
} else if (!canvasIsNew) {
|
|
19773
|
+
context.clearRect(0, 0, description.width, description.height);
|
|
19774
|
+
}
|
|
19775
|
+
this.drawWithFit(context, {
|
|
19776
|
+
fit: description.fit,
|
|
19777
|
+
rotation: description.rotation,
|
|
19778
|
+
crop: description.crop
|
|
19779
|
+
});
|
|
19780
|
+
return new _VideoSample(canvas, {
|
|
19781
|
+
timestamp: this.timestamp,
|
|
19782
|
+
duration: this.duration,
|
|
19783
|
+
rotation: 0
|
|
19784
|
+
// Any previous rotation is now baked in
|
|
19785
|
+
});
|
|
19786
|
+
}
|
|
19433
19787
|
/** Sets the rotation metadata of this video sample. */
|
|
19434
19788
|
setRotation(newRotation) {
|
|
19435
19789
|
if (![0, 90, 180, 270].includes(newRotation)) {
|
|
@@ -19456,6 +19810,16 @@ var Mediabunny = (() => {
|
|
|
19456
19810
|
this.close();
|
|
19457
19811
|
}
|
|
19458
19812
|
};
|
|
19813
|
+
var registeredVideoSampleTransformers = [];
|
|
19814
|
+
var registerVideoSampleTransformer = (transformer) => {
|
|
19815
|
+
if (registeredVideoSampleTransformers.includes(transformer)) {
|
|
19816
|
+
return;
|
|
19817
|
+
}
|
|
19818
|
+
registeredVideoSampleTransformers.push(transformer);
|
|
19819
|
+
};
|
|
19820
|
+
var TRANSFORMATION_CANVAS_CACHE_MAX_SIZE = 3;
|
|
19821
|
+
var transformationCanvasCache = [];
|
|
19822
|
+
var transformationCanvasCacheNextAge = 0;
|
|
19459
19823
|
var VideoSampleColorSpace = class {
|
|
19460
19824
|
/** Creates a new VideoSampleColorSpace. */
|
|
19461
19825
|
constructor(init) {
|
|
@@ -19651,15 +20015,135 @@ var Mediabunny = (() => {
|
|
|
19651
20015
|
assert(false);
|
|
19652
20016
|
}
|
|
19653
20017
|
};
|
|
20018
|
+
var ParseVideoFrameCopyToOptions = (sample, options) => {
|
|
20019
|
+
const defaultRect = {
|
|
20020
|
+
left: 0,
|
|
20021
|
+
top: 0,
|
|
20022
|
+
width: sample.codedWidth,
|
|
20023
|
+
height: sample.codedHeight
|
|
20024
|
+
};
|
|
20025
|
+
const overrideRect = options.rect;
|
|
20026
|
+
const parsedRect = ParseVisibleRect(
|
|
20027
|
+
defaultRect,
|
|
20028
|
+
overrideRect,
|
|
20029
|
+
sample.codedWidth,
|
|
20030
|
+
sample.codedHeight,
|
|
20031
|
+
sample.format
|
|
20032
|
+
);
|
|
20033
|
+
const optLayout = options.layout;
|
|
20034
|
+
let format;
|
|
20035
|
+
if (!options.format || options.format === sample.format) {
|
|
20036
|
+
format = sample.format;
|
|
20037
|
+
} else if (["RGBA", "RGBX", "BGRA", "BGRX"].includes(options.format)) {
|
|
20038
|
+
format = options.format;
|
|
20039
|
+
} else {
|
|
20040
|
+
throw new Error("NotSupportedError: Invalid destination format.");
|
|
20041
|
+
}
|
|
20042
|
+
return ComputeLayoutAndAllocationSize(parsedRect, format, optLayout);
|
|
20043
|
+
};
|
|
20044
|
+
var ParseVisibleRect = (defaultRect, overrideRect, codedWidth, codedHeight, format) => {
|
|
20045
|
+
const sourceRect = { ...defaultRect };
|
|
20046
|
+
if (overrideRect !== void 0) {
|
|
20047
|
+
if (overrideRect.width === 0 || overrideRect.height === 0) {
|
|
20048
|
+
throw new TypeError("visibleRect dimensions cannot be zero.");
|
|
20049
|
+
}
|
|
20050
|
+
if ((overrideRect.x || 0) + (overrideRect.width || 0) > codedWidth) {
|
|
20051
|
+
throw new TypeError("visibleRect exceeds codedWidth.");
|
|
20052
|
+
}
|
|
20053
|
+
if ((overrideRect.y || 0) + (overrideRect.height || 0) > codedHeight) {
|
|
20054
|
+
throw new TypeError("visibleRect exceeds codedHeight.");
|
|
20055
|
+
}
|
|
20056
|
+
sourceRect.x = overrideRect.x || 0;
|
|
20057
|
+
sourceRect.y = overrideRect.y || 0;
|
|
20058
|
+
sourceRect.width = overrideRect.width || 0;
|
|
20059
|
+
sourceRect.height = overrideRect.height || 0;
|
|
20060
|
+
}
|
|
20061
|
+
const validAlignment = VerifyRectOffsetAlignment(format, sourceRect);
|
|
20062
|
+
if (!validAlignment) {
|
|
20063
|
+
throw new TypeError("visibleRect alignment is invalid for the format.");
|
|
20064
|
+
}
|
|
20065
|
+
return sourceRect;
|
|
20066
|
+
};
|
|
20067
|
+
var VerifyRectOffsetAlignment = (format, rect) => {
|
|
20068
|
+
if (format === null) return true;
|
|
20069
|
+
const planes = getPlaneConfigs(format);
|
|
20070
|
+
for (let planeIndex = 0; planeIndex < planes.length; planeIndex++) {
|
|
20071
|
+
const plane = planes[planeIndex];
|
|
20072
|
+
const sampleWidth = plane.widthDivisor;
|
|
20073
|
+
const sampleHeight = plane.heightDivisor;
|
|
20074
|
+
if ((rect.x || 0) % sampleWidth !== 0) return false;
|
|
20075
|
+
if ((rect.y || 0) % sampleHeight !== 0) return false;
|
|
20076
|
+
}
|
|
20077
|
+
return true;
|
|
20078
|
+
};
|
|
20079
|
+
var ComputeLayoutAndAllocationSize = (parsedRect, format, layout) => {
|
|
20080
|
+
const planes = getPlaneConfigs(format);
|
|
20081
|
+
const numPlanes = planes.length;
|
|
20082
|
+
if (layout !== void 0 && layout.length !== numPlanes) {
|
|
20083
|
+
throw new TypeError(`Layout must have ${numPlanes} planes.`);
|
|
20084
|
+
}
|
|
20085
|
+
let minAllocationSize = 0;
|
|
20086
|
+
const computedLayouts = [];
|
|
20087
|
+
const endOffsets = [];
|
|
20088
|
+
for (let planeIndex = 0; planeIndex < numPlanes; planeIndex++) {
|
|
20089
|
+
const plane = planes[planeIndex];
|
|
20090
|
+
const sampleBytes = plane.sampleBytes;
|
|
20091
|
+
const sampleWidth = plane.widthDivisor;
|
|
20092
|
+
const sampleHeight = plane.heightDivisor;
|
|
20093
|
+
const computedLayout = {
|
|
20094
|
+
destinationOffset: 0,
|
|
20095
|
+
destinationStride: 0,
|
|
20096
|
+
sourceTop: 0,
|
|
20097
|
+
sourceHeight: 0,
|
|
20098
|
+
sourceLeftBytes: 0,
|
|
20099
|
+
sourceWidthBytes: 0
|
|
20100
|
+
};
|
|
20101
|
+
computedLayout.sourceTop = Math.ceil(Math.trunc(parsedRect.y || 0) / sampleHeight);
|
|
20102
|
+
computedLayout.sourceHeight = Math.ceil(Math.trunc(parsedRect.height || 0) / sampleHeight);
|
|
20103
|
+
computedLayout.sourceLeftBytes = Math.floor(Math.trunc(parsedRect.x || 0) / sampleWidth) * sampleBytes;
|
|
20104
|
+
computedLayout.sourceWidthBytes = Math.floor(Math.trunc(parsedRect.width || 0) / sampleWidth) * sampleBytes;
|
|
20105
|
+
if (layout !== void 0) {
|
|
20106
|
+
const planeLayout = layout[planeIndex];
|
|
20107
|
+
if (planeLayout.stride < computedLayout.sourceWidthBytes) {
|
|
20108
|
+
throw new TypeError(`Stride for plane ${planeIndex} is too small.`);
|
|
20109
|
+
}
|
|
20110
|
+
computedLayout.destinationOffset = planeLayout.offset;
|
|
20111
|
+
computedLayout.destinationStride = planeLayout.stride;
|
|
20112
|
+
} else {
|
|
20113
|
+
computedLayout.destinationOffset = minAllocationSize;
|
|
20114
|
+
computedLayout.destinationStride = computedLayout.sourceWidthBytes;
|
|
20115
|
+
}
|
|
20116
|
+
const planeSize = computedLayout.destinationStride * computedLayout.sourceHeight;
|
|
20117
|
+
const planeEnd = planeSize + computedLayout.destinationOffset;
|
|
20118
|
+
if (planeEnd > 4294967295) {
|
|
20119
|
+
throw new TypeError("Allocation size exceeds limit.");
|
|
20120
|
+
}
|
|
20121
|
+
endOffsets.push(planeEnd);
|
|
20122
|
+
minAllocationSize = Math.max(minAllocationSize, planeEnd);
|
|
20123
|
+
for (let earlierPlaneIndex = 0; earlierPlaneIndex < planeIndex; earlierPlaneIndex++) {
|
|
20124
|
+
const earlierLayout = computedLayouts[earlierPlaneIndex];
|
|
20125
|
+
if (endOffsets[planeIndex] <= earlierLayout.destinationOffset || endOffsets[earlierPlaneIndex] <= computedLayout.destinationOffset) {
|
|
20126
|
+
continue;
|
|
20127
|
+
}
|
|
20128
|
+
throw new TypeError("Planes overlap.");
|
|
20129
|
+
}
|
|
20130
|
+
computedLayouts.push(computedLayout);
|
|
20131
|
+
}
|
|
20132
|
+
return {
|
|
20133
|
+
allocationSize: minAllocationSize,
|
|
20134
|
+
computedLayouts
|
|
20135
|
+
};
|
|
20136
|
+
};
|
|
19654
20137
|
var AUDIO_SAMPLE_FORMATS = /* @__PURE__ */ new Set(
|
|
19655
20138
|
["f32", "f32-planar", "s16", "s16-planar", "s32", "s32-planar", "u8", "u8-planar"]
|
|
19656
20139
|
);
|
|
20140
|
+
var AudioSampleResource = class {
|
|
20141
|
+
constructor() {
|
|
20142
|
+
/** @internal */
|
|
20143
|
+
this._referenceCount = 0;
|
|
20144
|
+
}
|
|
20145
|
+
};
|
|
19657
20146
|
var AudioSample = class _AudioSample {
|
|
19658
|
-
/**
|
|
19659
|
-
* Creates a new {@link AudioSample}, either from an existing
|
|
19660
|
-
* [`AudioData`](https://developer.mozilla.org/en-US/docs/Web/API/AudioData) or from raw bytes specified in
|
|
19661
|
-
* {@link AudioSampleInit}.
|
|
19662
|
-
*/
|
|
19663
20147
|
constructor(init) {
|
|
19664
20148
|
/** @internal */
|
|
19665
20149
|
this._closed = false;
|
|
@@ -19674,6 +20158,30 @@ var Mediabunny = (() => {
|
|
|
19674
20158
|
this.numberOfChannels = init.numberOfChannels;
|
|
19675
20159
|
this.timestamp = init.timestamp / 1e6;
|
|
19676
20160
|
this.duration = init.numberOfFrames / init.sampleRate;
|
|
20161
|
+
} else if (init instanceof AudioSampleResource) {
|
|
20162
|
+
this._data = init;
|
|
20163
|
+
init._referenceCount++;
|
|
20164
|
+
this.format = init.getFormat();
|
|
20165
|
+
if (!AUDIO_SAMPLE_FORMATS.has(this.format)) {
|
|
20166
|
+
throw new TypeError("getFormat() must return an AudioSampleFormat.");
|
|
20167
|
+
}
|
|
20168
|
+
this.sampleRate = init.getSampleRate();
|
|
20169
|
+
if (!Number.isInteger(this.sampleRate) || this.sampleRate <= 0) {
|
|
20170
|
+
throw new TypeError("getSampleRate() must return a positive integer.");
|
|
20171
|
+
}
|
|
20172
|
+
this.numberOfFrames = init.getNumberOfFrames();
|
|
20173
|
+
if (!Number.isInteger(this.numberOfFrames) || this.numberOfFrames < 0) {
|
|
20174
|
+
throw new TypeError("getNumberOfFrames() must return a non-negative integer.");
|
|
20175
|
+
}
|
|
20176
|
+
this.numberOfChannels = init.getNumberOfChannels();
|
|
20177
|
+
if (!Number.isInteger(this.numberOfChannels) || this.numberOfChannels <= 0) {
|
|
20178
|
+
throw new TypeError("getNumberOfChannels() must return a positive integer.");
|
|
20179
|
+
}
|
|
20180
|
+
this.timestamp = init.getTimestamp();
|
|
20181
|
+
if (!Number.isFinite(this.timestamp)) {
|
|
20182
|
+
throw new TypeError("getTimestamp() must return a finite number.");
|
|
20183
|
+
}
|
|
20184
|
+
this.duration = this.numberOfFrames / this.sampleRate;
|
|
19677
20185
|
} else {
|
|
19678
20186
|
if (!init || typeof init !== "object") {
|
|
19679
20187
|
throw new TypeError("Invalid AudioDataInit: must be an object.");
|
|
@@ -19787,7 +20295,8 @@ var Mediabunny = (() => {
|
|
|
19787
20295
|
if (this._closed) {
|
|
19788
20296
|
throw new Error("AudioSample is closed.");
|
|
19789
20297
|
}
|
|
19790
|
-
const {
|
|
20298
|
+
const { format, frameCount: optFrameCount, frameOffset: optFrameOffset } = options;
|
|
20299
|
+
let { planeIndex } = options;
|
|
19791
20300
|
const srcFormat = this.format;
|
|
19792
20301
|
const destFormat = format ?? this.format;
|
|
19793
20302
|
if (!destFormat) throw new Error("Destination format not determined");
|
|
@@ -19837,11 +20346,42 @@ var Mediabunny = (() => {
|
|
|
19837
20346
|
});
|
|
19838
20347
|
}
|
|
19839
20348
|
} else {
|
|
19840
|
-
const uint8Data = this._data;
|
|
19841
|
-
const srcView = toDataView(uint8Data);
|
|
19842
20349
|
const readFn = getReadFunction(srcFormat);
|
|
19843
20350
|
const srcBytesPerSample = getBytesPerSample(srcFormat);
|
|
19844
20351
|
const srcIsPlanar = formatIsPlanar(srcFormat);
|
|
20352
|
+
let uint8Data;
|
|
20353
|
+
if (this._data instanceof AudioSampleResource) {
|
|
20354
|
+
const getDataPlaneValidated = (index) => {
|
|
20355
|
+
const result = this._data.getDataPlane(index);
|
|
20356
|
+
if (!(result instanceof Uint8Array)) {
|
|
20357
|
+
throw new TypeError("getDataPlane() must return a Uint8Array.");
|
|
20358
|
+
}
|
|
20359
|
+
const expectedSize = numFrames * srcBytesPerSample * (srcIsPlanar ? 1 : numChannels);
|
|
20360
|
+
if (result.byteLength !== expectedSize) {
|
|
20361
|
+
throw new TypeError(
|
|
20362
|
+
`Data plane ${index} has invalid size. Expected exactly ${expectedSize} bytes, got ${result.byteLength} bytes.`
|
|
20363
|
+
);
|
|
20364
|
+
}
|
|
20365
|
+
return result;
|
|
20366
|
+
};
|
|
20367
|
+
if (srcIsPlanar) {
|
|
20368
|
+
if (destIsPlanar) {
|
|
20369
|
+
uint8Data = getDataPlaneValidated(planeIndex);
|
|
20370
|
+
planeIndex = 0;
|
|
20371
|
+
} else {
|
|
20372
|
+
uint8Data = new Uint8Array(numFrames * srcBytesPerSample * numChannels);
|
|
20373
|
+
for (let ch = 0; ch < numChannels; ch++) {
|
|
20374
|
+
const planeData = getDataPlaneValidated(ch);
|
|
20375
|
+
uint8Data.set(planeData, ch * numFrames * srcBytesPerSample);
|
|
20376
|
+
}
|
|
20377
|
+
}
|
|
20378
|
+
} else {
|
|
20379
|
+
uint8Data = getDataPlaneValidated(0);
|
|
20380
|
+
}
|
|
20381
|
+
} else {
|
|
20382
|
+
uint8Data = this._data;
|
|
20383
|
+
}
|
|
20384
|
+
const srcView = toDataView(uint8Data);
|
|
19845
20385
|
for (let i = 0; i < copyFrameCount; i++) {
|
|
19846
20386
|
if (destIsPlanar) {
|
|
19847
20387
|
const destOffset = i * destBytesPerSample;
|
|
@@ -19875,7 +20415,11 @@ var Mediabunny = (() => {
|
|
|
19875
20415
|
if (this._closed) {
|
|
19876
20416
|
throw new Error("AudioSample is closed.");
|
|
19877
20417
|
}
|
|
19878
|
-
if (
|
|
20418
|
+
if (this._data instanceof AudioSampleResource) {
|
|
20419
|
+
const sample = new _AudioSample(this._data);
|
|
20420
|
+
sample.setTimestamp(this.timestamp);
|
|
20421
|
+
return sample;
|
|
20422
|
+
} else if (isAudioData(this._data)) {
|
|
19879
20423
|
const sample = new _AudioSample(this._data.clone());
|
|
19880
20424
|
sample.setTimestamp(this.timestamp);
|
|
19881
20425
|
return sample;
|
|
@@ -19899,7 +20443,12 @@ var Mediabunny = (() => {
|
|
|
19899
20443
|
return;
|
|
19900
20444
|
}
|
|
19901
20445
|
finalizationRegistry?.unregister(this);
|
|
19902
|
-
if (
|
|
20446
|
+
if (this._data instanceof AudioSampleResource) {
|
|
20447
|
+
this._data._referenceCount--;
|
|
20448
|
+
if (this._data._referenceCount === 0) {
|
|
20449
|
+
this._data.close();
|
|
20450
|
+
}
|
|
20451
|
+
} else if (isAudioData(this._data)) {
|
|
19903
20452
|
this._data.close();
|
|
19904
20453
|
} else {
|
|
19905
20454
|
this._data = new Uint8Array(0);
|
|
@@ -19914,36 +20463,13 @@ var Mediabunny = (() => {
|
|
|
19914
20463
|
if (this._closed) {
|
|
19915
20464
|
throw new Error("AudioSample is closed.");
|
|
19916
20465
|
}
|
|
19917
|
-
if (
|
|
20466
|
+
if (this._data instanceof AudioSampleResource) {
|
|
20467
|
+
return this._createAudioDataFromData();
|
|
20468
|
+
} else if (isAudioData(this._data)) {
|
|
19918
20469
|
if (this._data.timestamp === this.microsecondTimestamp) {
|
|
19919
20470
|
return this._data.clone();
|
|
19920
20471
|
} else {
|
|
19921
|
-
|
|
19922
|
-
const size = this.allocationSize({ planeIndex: 0, format: this.format });
|
|
19923
|
-
const data = new ArrayBuffer(size * this.numberOfChannels);
|
|
19924
|
-
for (let i = 0; i < this.numberOfChannels; i++) {
|
|
19925
|
-
this.copyTo(new Uint8Array(data, i * size, size), { planeIndex: i, format: this.format });
|
|
19926
|
-
}
|
|
19927
|
-
return new AudioData({
|
|
19928
|
-
format: this.format,
|
|
19929
|
-
sampleRate: this.sampleRate,
|
|
19930
|
-
numberOfFrames: this.numberOfFrames,
|
|
19931
|
-
numberOfChannels: this.numberOfChannels,
|
|
19932
|
-
timestamp: this.microsecondTimestamp,
|
|
19933
|
-
data
|
|
19934
|
-
});
|
|
19935
|
-
} else {
|
|
19936
|
-
const data = new ArrayBuffer(this.allocationSize({ planeIndex: 0, format: this.format }));
|
|
19937
|
-
this.copyTo(data, { planeIndex: 0, format: this.format });
|
|
19938
|
-
return new AudioData({
|
|
19939
|
-
format: this.format,
|
|
19940
|
-
sampleRate: this.sampleRate,
|
|
19941
|
-
numberOfFrames: this.numberOfFrames,
|
|
19942
|
-
numberOfChannels: this.numberOfChannels,
|
|
19943
|
-
timestamp: this.microsecondTimestamp,
|
|
19944
|
-
data
|
|
19945
|
-
});
|
|
19946
|
-
}
|
|
20472
|
+
return this._createAudioDataFromData();
|
|
19947
20473
|
}
|
|
19948
20474
|
} else {
|
|
19949
20475
|
return new AudioData({
|
|
@@ -19957,6 +20483,35 @@ var Mediabunny = (() => {
|
|
|
19957
20483
|
});
|
|
19958
20484
|
}
|
|
19959
20485
|
}
|
|
20486
|
+
/** @internal */
|
|
20487
|
+
_createAudioDataFromData() {
|
|
20488
|
+
if (formatIsPlanar(this.format)) {
|
|
20489
|
+
const size = this.allocationSize({ planeIndex: 0, format: this.format });
|
|
20490
|
+
const data = new ArrayBuffer(size * this.numberOfChannels);
|
|
20491
|
+
for (let i = 0; i < this.numberOfChannels; i++) {
|
|
20492
|
+
this.copyTo(new Uint8Array(data, i * size, size), { planeIndex: i, format: this.format });
|
|
20493
|
+
}
|
|
20494
|
+
return new AudioData({
|
|
20495
|
+
format: this.format,
|
|
20496
|
+
sampleRate: this.sampleRate,
|
|
20497
|
+
numberOfFrames: this.numberOfFrames,
|
|
20498
|
+
numberOfChannels: this.numberOfChannels,
|
|
20499
|
+
timestamp: this.microsecondTimestamp,
|
|
20500
|
+
data
|
|
20501
|
+
});
|
|
20502
|
+
} else {
|
|
20503
|
+
const data = new ArrayBuffer(this.allocationSize({ planeIndex: 0, format: this.format }));
|
|
20504
|
+
this.copyTo(data, { planeIndex: 0, format: this.format });
|
|
20505
|
+
return new AudioData({
|
|
20506
|
+
format: this.format,
|
|
20507
|
+
sampleRate: this.sampleRate,
|
|
20508
|
+
numberOfFrames: this.numberOfFrames,
|
|
20509
|
+
numberOfChannels: this.numberOfChannels,
|
|
20510
|
+
timestamp: this.microsecondTimestamp,
|
|
20511
|
+
data
|
|
20512
|
+
});
|
|
20513
|
+
}
|
|
20514
|
+
}
|
|
19960
20515
|
/** Convert this audio sample to an AudioBuffer for use with the Web Audio API. */
|
|
19961
20516
|
toAudioBuffer() {
|
|
19962
20517
|
if (this._closed) {
|
|
@@ -20270,9 +20825,9 @@ var Mediabunny = (() => {
|
|
|
20270
20825
|
"When both config.transform.width and config.transform.height are provided, config.transform.fit must also be provided."
|
|
20271
20826
|
);
|
|
20272
20827
|
}
|
|
20273
|
-
if (config.transform.fit !== void 0 && ["fill", "contain", "cover"].includes(config.sizeChangeBehavior)) {
|
|
20828
|
+
if (config.transform.fit !== void 0 && ["fill", "contain", "cover"].includes(config.sizeChangeBehavior) && config.transform.fit !== config.sizeChangeBehavior) {
|
|
20274
20829
|
throw new TypeError(
|
|
20275
|
-
"config.transform.fit cannot
|
|
20830
|
+
"config.transform.fit, when provided, cannot differ from config.sizeChangeBehavior when config.sizeChangeBehavior is 'fill', 'contain' or 'cover', as sizeChangeBehavior already determines the fitting algorithm."
|
|
20276
20831
|
);
|
|
20277
20832
|
}
|
|
20278
20833
|
if (config.transform.rotate !== void 0 && ![0, 90, 180, 270].includes(config.transform.rotate)) {
|
|
@@ -21117,7 +21672,7 @@ var Mediabunny = (() => {
|
|
|
21117
21672
|
};
|
|
21118
21673
|
var BaseMediaSampleSink = class {
|
|
21119
21674
|
/** @internal */
|
|
21120
|
-
mediaSamplesInRange(startTimestamp =
|
|
21675
|
+
mediaSamplesInRange(startTimestamp = -Infinity, endTimestamp = Infinity, options) {
|
|
21121
21676
|
validateTimestamp(startTimestamp);
|
|
21122
21677
|
validateTimestamp(endTimestamp);
|
|
21123
21678
|
const sampleQueue = [];
|
|
@@ -21414,7 +21969,6 @@ var Mediabunny = (() => {
|
|
|
21414
21969
|
return decodedSampleQueueSize === 0 ? 40 : 8;
|
|
21415
21970
|
};
|
|
21416
21971
|
var VideoDecoderWrapper = class extends DecoderWrapper {
|
|
21417
|
-
// For HEVC stuff
|
|
21418
21972
|
constructor(onSample, onError, codec, decoderConfig, rotation, timeResolution) {
|
|
21419
21973
|
super(onSample, onError);
|
|
21420
21974
|
this.codec = codec;
|
|
@@ -21438,13 +21992,14 @@ var Mediabunny = (() => {
|
|
|
21438
21992
|
this.colorQueue = [];
|
|
21439
21993
|
this.alphaQueue = [];
|
|
21440
21994
|
this.merger = null;
|
|
21441
|
-
this.mergerCreationFailed = false;
|
|
21442
21995
|
this.decodedAlphaChunkCount = 0;
|
|
21443
21996
|
this.alphaDecoderQueueSize = 0;
|
|
21444
21997
|
/** Each value is the number of decoded alpha chunks at which a null alpha frame should be added. */
|
|
21445
21998
|
this.nullAlphaFrameQueue = [];
|
|
21446
21999
|
this.currentAlphaPacketIndex = 0;
|
|
21447
22000
|
this.alphaRaslSkipped = false;
|
|
22001
|
+
// For HEVC stuff
|
|
22002
|
+
this.frameHandlerSerializer = new CallSerializer();
|
|
21448
22003
|
const MatchingCustomDecoder = customVideoDecoders.find((x) => x.supports(codec, decoderConfig));
|
|
21449
22004
|
if (MatchingCustomDecoder) {
|
|
21450
22005
|
this.customDecoder = new MatchingCustomDecoder();
|
|
@@ -21459,13 +22014,15 @@ var Mediabunny = (() => {
|
|
|
21459
22014
|
void this.customDecoderCallSerializer.call(() => this.customDecoder.init());
|
|
21460
22015
|
} else {
|
|
21461
22016
|
const colorHandler = (frame) => {
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
|
|
21465
|
-
|
|
21466
|
-
|
|
21467
|
-
|
|
21468
|
-
|
|
22017
|
+
this.frameHandlerSerializer.call(async () => {
|
|
22018
|
+
if (this.alphaQueue.length > 0) {
|
|
22019
|
+
const alphaFrame = this.alphaQueue.shift();
|
|
22020
|
+
assert(alphaFrame !== void 0);
|
|
22021
|
+
await this.mergeAlpha(frame, alphaFrame);
|
|
22022
|
+
} else {
|
|
22023
|
+
this.colorQueue.push(frame);
|
|
22024
|
+
}
|
|
22025
|
+
}).catch((error) => this.onError(error));
|
|
21469
22026
|
};
|
|
21470
22027
|
if (codec === "avc" && this.decoderConfig.description && isChromium()) {
|
|
21471
22028
|
const record = deserializeAvcDecoderConfigurationRecord(toUint8Array(this.decoderConfig.description));
|
|
@@ -21546,41 +22103,36 @@ var Mediabunny = (() => {
|
|
|
21546
22103
|
this.currentPacketIndex++;
|
|
21547
22104
|
}
|
|
21548
22105
|
decodeAlphaData(packet) {
|
|
21549
|
-
if (!packet.sideData.alpha
|
|
22106
|
+
if (!packet.sideData.alpha) {
|
|
21550
22107
|
this.pushNullAlphaFrame();
|
|
21551
22108
|
return;
|
|
21552
22109
|
}
|
|
21553
22110
|
if (!this.merger) {
|
|
21554
|
-
|
|
21555
|
-
this.merger = new ColorAlphaMerger();
|
|
21556
|
-
} catch (error) {
|
|
21557
|
-
console.error("Due to an error, only color data will be decoded.", error);
|
|
21558
|
-
this.mergerCreationFailed = true;
|
|
21559
|
-
this.decodeAlphaData(packet);
|
|
21560
|
-
return;
|
|
21561
|
-
}
|
|
22111
|
+
this.merger = new ColorAlphaMerger();
|
|
21562
22112
|
}
|
|
21563
22113
|
if (!this.alphaDecoder) {
|
|
21564
22114
|
const alphaHandler = (frame) => {
|
|
21565
|
-
this.
|
|
21566
|
-
if (this.colorQueue.length > 0) {
|
|
21567
|
-
const colorFrame = this.colorQueue.shift();
|
|
21568
|
-
assert(colorFrame !== void 0);
|
|
21569
|
-
this.mergeAlpha(colorFrame, frame);
|
|
21570
|
-
} else {
|
|
21571
|
-
this.alphaQueue.push(frame);
|
|
21572
|
-
}
|
|
21573
|
-
this.decodedAlphaChunkCount++;
|
|
21574
|
-
while (this.nullAlphaFrameQueue.length > 0 && this.nullAlphaFrameQueue[0] === this.decodedAlphaChunkCount) {
|
|
21575
|
-
this.nullAlphaFrameQueue.shift();
|
|
22115
|
+
this.frameHandlerSerializer.call(async () => {
|
|
21576
22116
|
if (this.colorQueue.length > 0) {
|
|
21577
22117
|
const colorFrame = this.colorQueue.shift();
|
|
21578
22118
|
assert(colorFrame !== void 0);
|
|
21579
|
-
this.mergeAlpha(colorFrame,
|
|
22119
|
+
await this.mergeAlpha(colorFrame, frame);
|
|
21580
22120
|
} else {
|
|
21581
|
-
this.alphaQueue.push(
|
|
22121
|
+
this.alphaQueue.push(frame);
|
|
22122
|
+
}
|
|
22123
|
+
this.decodedAlphaChunkCount++;
|
|
22124
|
+
while (this.nullAlphaFrameQueue.length > 0 && this.nullAlphaFrameQueue[0] === this.decodedAlphaChunkCount) {
|
|
22125
|
+
this.nullAlphaFrameQueue.shift();
|
|
22126
|
+
if (this.colorQueue.length > 0) {
|
|
22127
|
+
const colorFrame = this.colorQueue.shift();
|
|
22128
|
+
assert(colorFrame !== void 0);
|
|
22129
|
+
await this.mergeAlpha(colorFrame, null);
|
|
22130
|
+
} else {
|
|
22131
|
+
this.alphaQueue.push(null);
|
|
22132
|
+
}
|
|
21582
22133
|
}
|
|
21583
|
-
|
|
22134
|
+
this.alphaDecoderQueueSize--;
|
|
22135
|
+
}).catch((error) => this.onError(error));
|
|
21584
22136
|
};
|
|
21585
22137
|
const stack = new Error("Decoding error").stack;
|
|
21586
22138
|
this.alphaDecoder = new VideoDecoder({
|
|
@@ -21662,20 +22214,14 @@ var Mediabunny = (() => {
|
|
|
21662
22214
|
sample.setRotation(this.rotation);
|
|
21663
22215
|
this.onSample(sample);
|
|
21664
22216
|
}
|
|
21665
|
-
mergeAlpha(color, alpha) {
|
|
22217
|
+
async mergeAlpha(color, alpha) {
|
|
21666
22218
|
if (!alpha) {
|
|
21667
22219
|
const finalSample2 = new VideoSample(color);
|
|
21668
22220
|
this.sampleHandler(finalSample2);
|
|
21669
22221
|
return;
|
|
21670
22222
|
}
|
|
21671
22223
|
assert(this.merger);
|
|
21672
|
-
this.merger.update(color, alpha);
|
|
21673
|
-
color.close();
|
|
21674
|
-
alpha.close();
|
|
21675
|
-
const finalFrame = new VideoFrame(this.merger.canvas, {
|
|
21676
|
-
timestamp: color.timestamp,
|
|
21677
|
-
duration: color.duration ?? void 0
|
|
21678
|
-
});
|
|
22224
|
+
const finalFrame = await this.merger.update(color, alpha);
|
|
21679
22225
|
const finalSample = new VideoSample(finalFrame);
|
|
21680
22226
|
this.sampleHandler(finalSample);
|
|
21681
22227
|
}
|
|
@@ -21688,6 +22234,7 @@ var Mediabunny = (() => {
|
|
|
21688
22234
|
this.decoder.flush(),
|
|
21689
22235
|
this.alphaDecoder?.flush()
|
|
21690
22236
|
]);
|
|
22237
|
+
await this.frameHandlerSerializer.currentPromise;
|
|
21691
22238
|
this.colorQueue.forEach((x) => x.close());
|
|
21692
22239
|
this.colorQueue.length = 0;
|
|
21693
22240
|
this.alphaQueue.forEach((x) => x?.close());
|
|
@@ -21727,29 +22274,57 @@ var Mediabunny = (() => {
|
|
|
21727
22274
|
this.sampleQueue.length = 0;
|
|
21728
22275
|
}
|
|
21729
22276
|
};
|
|
21730
|
-
var
|
|
22277
|
+
var mergerGpuUnavailable = false;
|
|
22278
|
+
var _ColorAlphaMerger = class _ColorAlphaMerger {
|
|
21731
22279
|
constructor() {
|
|
21732
|
-
|
|
21733
|
-
|
|
22280
|
+
this.canvas = null;
|
|
22281
|
+
this.gl = null;
|
|
22282
|
+
this.program = null;
|
|
22283
|
+
this.vao = null;
|
|
22284
|
+
this.colorTexture = null;
|
|
22285
|
+
this.alphaTexture = null;
|
|
22286
|
+
this.worker = null;
|
|
22287
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
22288
|
+
this.nextRequestId = 0;
|
|
22289
|
+
const canMakeCanvas = typeof OffscreenCanvas !== "undefined" || typeof document !== "undefined" && typeof document.createElement === "function";
|
|
22290
|
+
if (!_ColorAlphaMerger.forceCpu && canMakeCanvas && !mergerGpuUnavailable) {
|
|
22291
|
+
try {
|
|
22292
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
22293
|
+
this.canvas = new OffscreenCanvas(300, 150);
|
|
22294
|
+
} else {
|
|
22295
|
+
this.canvas = document.createElement("canvas");
|
|
22296
|
+
}
|
|
22297
|
+
const gl = this.canvas.getContext("webgl2", {
|
|
22298
|
+
premultipliedAlpha: false
|
|
22299
|
+
});
|
|
22300
|
+
if (!gl) {
|
|
22301
|
+
throw new Error("Couldn't acquire WebGL 2 context.");
|
|
22302
|
+
}
|
|
22303
|
+
this.gl = gl;
|
|
22304
|
+
this.program = this.createProgram();
|
|
22305
|
+
this.vao = this.createVAO();
|
|
22306
|
+
this.colorTexture = this.createTexture();
|
|
22307
|
+
this.alphaTexture = this.createTexture();
|
|
22308
|
+
this.gl.useProgram(this.program);
|
|
22309
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.program, "u_colorTexture"), 0);
|
|
22310
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.program, "u_alphaTexture"), 1);
|
|
22311
|
+
} catch (error) {
|
|
22312
|
+
this.gl = null;
|
|
22313
|
+
this.canvas = null;
|
|
22314
|
+
mergerGpuUnavailable = true;
|
|
22315
|
+
console.warn("Falling back to CPU for color/alpha merging.", error);
|
|
22316
|
+
}
|
|
22317
|
+
}
|
|
22318
|
+
}
|
|
22319
|
+
async update(color, alpha) {
|
|
22320
|
+
if (this.gl) {
|
|
22321
|
+
return this.updateGpu(color, alpha);
|
|
21734
22322
|
} else {
|
|
21735
|
-
this.
|
|
22323
|
+
return this.updateCpu(color, alpha);
|
|
21736
22324
|
}
|
|
21737
|
-
const gl = this.canvas.getContext("webgl2", {
|
|
21738
|
-
premultipliedAlpha: false
|
|
21739
|
-
});
|
|
21740
|
-
if (!gl) {
|
|
21741
|
-
throw new Error("Couldn't acquire WebGL 2 context.");
|
|
21742
|
-
}
|
|
21743
|
-
this.gl = gl;
|
|
21744
|
-
this.program = this.createProgram();
|
|
21745
|
-
this.vao = this.createVAO();
|
|
21746
|
-
this.colorTexture = this.createTexture();
|
|
21747
|
-
this.alphaTexture = this.createTexture();
|
|
21748
|
-
this.gl.useProgram(this.program);
|
|
21749
|
-
this.gl.uniform1i(this.gl.getUniformLocation(this.program, "u_colorTexture"), 0);
|
|
21750
|
-
this.gl.uniform1i(this.gl.getUniformLocation(this.program, "u_alphaTexture"), 1);
|
|
21751
22325
|
}
|
|
21752
22326
|
createProgram() {
|
|
22327
|
+
assert(this.gl);
|
|
21753
22328
|
const vertexShader = this.createShader(this.gl.VERTEX_SHADER, `#version 300 es
|
|
21754
22329
|
in vec2 a_position;
|
|
21755
22330
|
in vec2 a_texCoord;
|
|
@@ -21781,12 +22356,15 @@ var Mediabunny = (() => {
|
|
|
21781
22356
|
return program;
|
|
21782
22357
|
}
|
|
21783
22358
|
createShader(type, source) {
|
|
22359
|
+
assert(this.gl);
|
|
21784
22360
|
const shader = this.gl.createShader(type);
|
|
21785
22361
|
this.gl.shaderSource(shader, source);
|
|
21786
22362
|
this.gl.compileShader(shader);
|
|
21787
22363
|
return shader;
|
|
21788
22364
|
}
|
|
21789
22365
|
createVAO() {
|
|
22366
|
+
assert(this.gl);
|
|
22367
|
+
assert(this.program);
|
|
21790
22368
|
const vao = this.gl.createVertexArray();
|
|
21791
22369
|
this.gl.bindVertexArray(vao);
|
|
21792
22370
|
const vertices = new Float32Array([
|
|
@@ -21819,6 +22397,7 @@ var Mediabunny = (() => {
|
|
|
21819
22397
|
return vao;
|
|
21820
22398
|
}
|
|
21821
22399
|
createTexture() {
|
|
22400
|
+
assert(this.gl);
|
|
21822
22401
|
const texture = this.gl.createTexture();
|
|
21823
22402
|
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
|
|
21824
22403
|
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
|
|
@@ -21827,7 +22406,9 @@ var Mediabunny = (() => {
|
|
|
21827
22406
|
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
|
|
21828
22407
|
return texture;
|
|
21829
22408
|
}
|
|
21830
|
-
|
|
22409
|
+
updateGpu(color, alpha) {
|
|
22410
|
+
assert(this.gl);
|
|
22411
|
+
assert(this.canvas);
|
|
21831
22412
|
if (color.displayWidth !== this.canvas.width || color.displayHeight !== this.canvas.height) {
|
|
21832
22413
|
this.canvas.width = color.displayWidth;
|
|
21833
22414
|
this.canvas.height = color.displayHeight;
|
|
@@ -21842,11 +22423,215 @@ var Mediabunny = (() => {
|
|
|
21842
22423
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
21843
22424
|
this.gl.bindVertexArray(this.vao);
|
|
21844
22425
|
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
|
|
22426
|
+
const finalFrame = new VideoFrame(this.canvas, {
|
|
22427
|
+
timestamp: color.timestamp,
|
|
22428
|
+
duration: color.duration ?? void 0
|
|
22429
|
+
});
|
|
22430
|
+
color.close();
|
|
22431
|
+
alpha.close();
|
|
22432
|
+
return finalFrame;
|
|
22433
|
+
}
|
|
22434
|
+
updateCpu(color, alpha) {
|
|
22435
|
+
if (!this.worker) {
|
|
22436
|
+
const blob = new Blob(
|
|
22437
|
+
[`(${colorAlphaMergerWorkerCode.toString()})()`],
|
|
22438
|
+
{ type: "application/javascript" }
|
|
22439
|
+
);
|
|
22440
|
+
const url2 = URL.createObjectURL(blob);
|
|
22441
|
+
this.worker = new Worker(url2);
|
|
22442
|
+
URL.revokeObjectURL(url2);
|
|
22443
|
+
this.worker.addEventListener("message", (event) => {
|
|
22444
|
+
const data = event.data;
|
|
22445
|
+
const pending2 = this.pendingRequests.get(data.id);
|
|
22446
|
+
if (!pending2) {
|
|
22447
|
+
return;
|
|
22448
|
+
}
|
|
22449
|
+
this.pendingRequests.delete(data.id);
|
|
22450
|
+
if ("error" in data) {
|
|
22451
|
+
pending2.reject(new Error(data.error));
|
|
22452
|
+
} else {
|
|
22453
|
+
pending2.resolve(data.frame);
|
|
22454
|
+
}
|
|
22455
|
+
});
|
|
22456
|
+
this.worker.addEventListener("error", (event) => {
|
|
22457
|
+
const error = new Error(event.message || "Color/alpha merge worker error.");
|
|
22458
|
+
for (const pending2 of this.pendingRequests.values()) {
|
|
22459
|
+
pending2.reject(error);
|
|
22460
|
+
}
|
|
22461
|
+
this.pendingRequests.clear();
|
|
22462
|
+
});
|
|
22463
|
+
}
|
|
22464
|
+
const id = this.nextRequestId++;
|
|
22465
|
+
const pending = promiseWithResolvers();
|
|
22466
|
+
this.pendingRequests.set(id, pending);
|
|
22467
|
+
this.worker.postMessage({ id, color, alpha }, { transfer: [color, alpha] });
|
|
22468
|
+
return pending.promise;
|
|
21845
22469
|
}
|
|
21846
22470
|
close() {
|
|
21847
|
-
this.gl
|
|
22471
|
+
this.gl?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
21848
22472
|
this.gl = null;
|
|
21849
|
-
|
|
22473
|
+
this.canvas = null;
|
|
22474
|
+
this.worker?.terminate();
|
|
22475
|
+
this.worker = null;
|
|
22476
|
+
const error = new Error("Color/alpha merger closed.");
|
|
22477
|
+
for (const pending of this.pendingRequests.values()) {
|
|
22478
|
+
pending.reject(error);
|
|
22479
|
+
}
|
|
22480
|
+
this.pendingRequests.clear();
|
|
22481
|
+
}
|
|
22482
|
+
};
|
|
22483
|
+
_ColorAlphaMerger.forceCpu = true;
|
|
22484
|
+
var ColorAlphaMerger = _ColorAlphaMerger;
|
|
22485
|
+
var colorAlphaMergerWorkerCode = () => {
|
|
22486
|
+
let cpuAlphaBuffer = null;
|
|
22487
|
+
let cpuColorBuffer = null;
|
|
22488
|
+
let chain = Promise.resolve();
|
|
22489
|
+
self.addEventListener("message", (event) => {
|
|
22490
|
+
const { id, color, alpha } = event.data;
|
|
22491
|
+
chain = chain.then(async () => {
|
|
22492
|
+
try {
|
|
22493
|
+
const frame = await merge(color, alpha);
|
|
22494
|
+
self.postMessage({ id, frame }, { transfer: [frame] });
|
|
22495
|
+
} catch (error) {
|
|
22496
|
+
self.postMessage({ id, error: error.message });
|
|
22497
|
+
} finally {
|
|
22498
|
+
color.close();
|
|
22499
|
+
alpha.close();
|
|
22500
|
+
}
|
|
22501
|
+
});
|
|
22502
|
+
});
|
|
22503
|
+
const merge = async (color, alpha) => {
|
|
22504
|
+
const format = color.format;
|
|
22505
|
+
const alphaFormat = alpha.format;
|
|
22506
|
+
if (!format || !alphaFormat) {
|
|
22507
|
+
throw new Error("CPU color/alpha merging requires a known VideoFrame format.");
|
|
22508
|
+
}
|
|
22509
|
+
const colorIs10 = format.includes("P10");
|
|
22510
|
+
const colorIs12 = format.includes("P12");
|
|
22511
|
+
const alphaIs10 = alphaFormat.includes("P10");
|
|
22512
|
+
const alphaIs12 = alphaFormat.includes("P12");
|
|
22513
|
+
if (alphaIs10 !== colorIs10 || alphaIs12 !== colorIs12) {
|
|
22514
|
+
throw new Error(
|
|
22515
|
+
`CPU color/alpha merging requires the alpha frame to have the same bit depth as the color frame (color: '${format}', alpha: '${alphaFormat}').`
|
|
22516
|
+
);
|
|
22517
|
+
}
|
|
22518
|
+
const width = color.codedWidth;
|
|
22519
|
+
const height = color.codedHeight;
|
|
22520
|
+
if (format === "RGBX" || format === "RGBA" || format === "BGRX" || format === "BGRA") {
|
|
22521
|
+
return await mergeInterleavedRgba(color, alpha, width, height, format);
|
|
22522
|
+
} else if (format === "I420" || format === "I420P10" || format === "I420P12" || format === "I422" || format === "I422P10" || format === "I422P12" || format === "I444" || format === "I444P10" || format === "I444P12") {
|
|
22523
|
+
return await mergePlanarYuv(color, alpha, width, height, format);
|
|
22524
|
+
} else if (format === "NV12") {
|
|
22525
|
+
return await mergeNv12(color, alpha, width, height);
|
|
22526
|
+
}
|
|
22527
|
+
throw new Error(`CPU color/alpha merging does not support format '${format}'.`);
|
|
22528
|
+
};
|
|
22529
|
+
const mergeInterleavedRgba = async (color, alpha, width, height, format) => {
|
|
22530
|
+
const pixelCount = width * height;
|
|
22531
|
+
const output = new Uint8Array(pixelCount * 4);
|
|
22532
|
+
await color.copyTo(output);
|
|
22533
|
+
const alphaY = await readAlpha(alpha, width, height, 1);
|
|
22534
|
+
for (let i = 0, j = 3; i < pixelCount; i++, j += 4) {
|
|
22535
|
+
output[j] = alphaY[i];
|
|
22536
|
+
}
|
|
22537
|
+
const outputFormat = format === "RGBX" || format === "RGBA" ? "RGBA" : "BGRA";
|
|
22538
|
+
const init = {
|
|
22539
|
+
format: outputFormat,
|
|
22540
|
+
codedWidth: width,
|
|
22541
|
+
codedHeight: height,
|
|
22542
|
+
timestamp: color.timestamp,
|
|
22543
|
+
duration: color.duration ?? void 0,
|
|
22544
|
+
transfer: [output.buffer]
|
|
22545
|
+
};
|
|
22546
|
+
return new VideoFrame(output, init);
|
|
22547
|
+
};
|
|
22548
|
+
const mergePlanarYuv = async (color, alpha, width, height, format) => {
|
|
22549
|
+
const is10 = format.includes("P10");
|
|
22550
|
+
const is12 = format.includes("P12");
|
|
22551
|
+
const bytesPerSample = is10 || is12 ? 2 : 1;
|
|
22552
|
+
let chromaW;
|
|
22553
|
+
let chromaH;
|
|
22554
|
+
if (format.startsWith("I420")) {
|
|
22555
|
+
chromaW = Math.ceil(width / 2);
|
|
22556
|
+
chromaH = Math.ceil(height / 2);
|
|
22557
|
+
} else if (format.startsWith("I422")) {
|
|
22558
|
+
chromaW = Math.ceil(width / 2);
|
|
22559
|
+
chromaH = height;
|
|
22560
|
+
} else {
|
|
22561
|
+
chromaW = width;
|
|
22562
|
+
chromaH = height;
|
|
22563
|
+
}
|
|
22564
|
+
const ySamples = width * height;
|
|
22565
|
+
const uvSamples = chromaW * chromaH;
|
|
22566
|
+
const yBytes = ySamples * bytesPerSample;
|
|
22567
|
+
const uvBytes = uvSamples * bytesPerSample;
|
|
22568
|
+
const aBytes = ySamples * bytesPerSample;
|
|
22569
|
+
const outputBytes = yBytes + 2 * uvBytes + aBytes;
|
|
22570
|
+
const output = new Uint8Array(outputBytes);
|
|
22571
|
+
await color.copyTo(output);
|
|
22572
|
+
const alphaY = await readAlpha(alpha, width, height, bytesPerSample);
|
|
22573
|
+
const aOffset = yBytes + 2 * uvBytes;
|
|
22574
|
+
output.set(alphaY, aOffset);
|
|
22575
|
+
const outputFormat = format.slice(0, 4) + "A" + format.slice(4);
|
|
22576
|
+
const init = {
|
|
22577
|
+
format: outputFormat,
|
|
22578
|
+
codedWidth: width,
|
|
22579
|
+
codedHeight: height,
|
|
22580
|
+
timestamp: color.timestamp,
|
|
22581
|
+
duration: color.duration ?? void 0,
|
|
22582
|
+
transfer: [output.buffer]
|
|
22583
|
+
};
|
|
22584
|
+
return new VideoFrame(output, init);
|
|
22585
|
+
};
|
|
22586
|
+
const mergeNv12 = async (color, alpha, width, height) => {
|
|
22587
|
+
const ySize = width * height;
|
|
22588
|
+
const chromaW = Math.ceil(width / 2);
|
|
22589
|
+
const chromaH = Math.ceil(height / 2);
|
|
22590
|
+
const uvSize = chromaW * chromaH;
|
|
22591
|
+
const sourceSize = color.allocationSize();
|
|
22592
|
+
if (!cpuColorBuffer || cpuColorBuffer.byteLength !== sourceSize) {
|
|
22593
|
+
cpuColorBuffer = new Uint8Array(sourceSize);
|
|
22594
|
+
}
|
|
22595
|
+
await color.copyTo(cpuColorBuffer);
|
|
22596
|
+
const output = new Uint8Array(ySize + 2 * uvSize + ySize);
|
|
22597
|
+
output.set(cpuColorBuffer.subarray(0, ySize), 0);
|
|
22598
|
+
const uOffset = ySize;
|
|
22599
|
+
const vOffset = ySize + uvSize;
|
|
22600
|
+
const uvStart = ySize;
|
|
22601
|
+
for (let i = 0; i < uvSize; i++) {
|
|
22602
|
+
output[uOffset + i] = cpuColorBuffer[uvStart + i * 2];
|
|
22603
|
+
output[vOffset + i] = cpuColorBuffer[uvStart + i * 2 + 1];
|
|
22604
|
+
}
|
|
22605
|
+
const alphaY = await readAlpha(alpha, width, height, 1);
|
|
22606
|
+
output.set(alphaY, ySize + 2 * uvSize);
|
|
22607
|
+
const init = {
|
|
22608
|
+
format: "I420A",
|
|
22609
|
+
codedWidth: width,
|
|
22610
|
+
codedHeight: height,
|
|
22611
|
+
timestamp: color.timestamp,
|
|
22612
|
+
duration: color.duration ?? void 0,
|
|
22613
|
+
transfer: [output.buffer]
|
|
22614
|
+
};
|
|
22615
|
+
return new VideoFrame(output, init);
|
|
22616
|
+
};
|
|
22617
|
+
const readAlpha = async (alpha, width, height, bytesPerSample) => {
|
|
22618
|
+
const size = alpha.allocationSize();
|
|
22619
|
+
if (!cpuAlphaBuffer || cpuAlphaBuffer.byteLength !== size) {
|
|
22620
|
+
cpuAlphaBuffer = new Uint8Array(size);
|
|
22621
|
+
}
|
|
22622
|
+
await alpha.copyTo(cpuAlphaBuffer);
|
|
22623
|
+
const format = alpha.format;
|
|
22624
|
+
if (format === "RGBA" || format === "BGRA" || format === "RGBX" || format === "BGRX") {
|
|
22625
|
+
const rOffset = format === "RGBA" || format === "RGBX" ? 0 : 2;
|
|
22626
|
+
const pixelCount = width * height;
|
|
22627
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
22628
|
+
cpuAlphaBuffer[i] = cpuAlphaBuffer[i * 4 + rOffset];
|
|
22629
|
+
}
|
|
22630
|
+
return cpuAlphaBuffer.subarray(0, pixelCount);
|
|
22631
|
+
} else {
|
|
22632
|
+
return cpuAlphaBuffer.subarray(0, width * height * bytesPerSample);
|
|
22633
|
+
}
|
|
22634
|
+
};
|
|
21850
22635
|
};
|
|
21851
22636
|
var VideoSampleSink = class extends BaseMediaSampleSink {
|
|
21852
22637
|
/** Creates a new {@link VideoSampleSink} for the given {@link InputVideoTrack}. */
|
|
@@ -21898,7 +22683,7 @@ var Mediabunny = (() => {
|
|
|
21898
22683
|
* @param endTimestamp - The timestamp in seconds at which to stop yielding samples (exclusive).
|
|
21899
22684
|
* @param options - Options used for the underlying packet retrieval.
|
|
21900
22685
|
*/
|
|
21901
|
-
samples(startTimestamp
|
|
22686
|
+
samples(startTimestamp, endTimestamp, options = {}) {
|
|
21902
22687
|
return this.mediaSamplesInRange(startTimestamp, endTimestamp, options);
|
|
21903
22688
|
}
|
|
21904
22689
|
/**
|
|
@@ -21907,6 +22692,9 @@ var Mediabunny = (() => {
|
|
|
21907
22692
|
* once, and is therefore more efficient than manually getting the sample for every timestamp. The iterator may
|
|
21908
22693
|
* yield null if no frame is available for a given timestamp.
|
|
21909
22694
|
*
|
|
22695
|
+
* This method is good for sparse access of media data. If you want primarily sequential media access, prefer
|
|
22696
|
+
* {@link VideoSampleSink.samples} instead.
|
|
22697
|
+
*
|
|
21910
22698
|
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
|
21911
22699
|
* @param options - Options used for the underlying packet retrieval.
|
|
21912
22700
|
*/
|
|
@@ -22064,7 +22852,7 @@ var Mediabunny = (() => {
|
|
|
22064
22852
|
* @param endTimestamp - The timestamp in seconds at which to stop yielding canvases (exclusive).
|
|
22065
22853
|
* @param options - Options used for the underlying packet retrieval.
|
|
22066
22854
|
*/
|
|
22067
|
-
async *canvases(startTimestamp
|
|
22855
|
+
async *canvases(startTimestamp, endTimestamp, options) {
|
|
22068
22856
|
await this._ensureInit();
|
|
22069
22857
|
yield* mapAsyncGenerator(
|
|
22070
22858
|
this._videoSampleSink.samples(startTimestamp, endTimestamp, options),
|
|
@@ -22077,6 +22865,9 @@ var Mediabunny = (() => {
|
|
|
22077
22865
|
* therefore more efficient than manually getting the canvas for every timestamp. The iterator may yield null if
|
|
22078
22866
|
* no frame is available for a given timestamp.
|
|
22079
22867
|
*
|
|
22868
|
+
* This method is good for sparse access of media data. If you want primarily sequential media access, prefer
|
|
22869
|
+
* {@link CanvasSink.canvases} instead.
|
|
22870
|
+
*
|
|
22080
22871
|
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
|
22081
22872
|
* @param options - Options used for the underlying packet retrieval.
|
|
22082
22873
|
*/
|
|
@@ -22098,9 +22889,18 @@ var Mediabunny = (() => {
|
|
|
22098
22889
|
// Internal state to accumulate a precise current timestamp based on audio durations, not the (potentially
|
|
22099
22890
|
// inaccurate) packet timestamps.
|
|
22100
22891
|
this.currentTimestamp = null;
|
|
22892
|
+
// Chromium does not respect negative packet timestamps, so we must do the fixin' ourselves
|
|
22893
|
+
this.expectedFirstTimestamp = null;
|
|
22894
|
+
this.timestampOffset = 0;
|
|
22101
22895
|
const sampleHandler = (sample) => {
|
|
22102
|
-
|
|
22103
|
-
|
|
22896
|
+
let sampleTimestamp = sample.timestamp;
|
|
22897
|
+
if (this.expectedFirstTimestamp && this.currentTimestamp === null) {
|
|
22898
|
+
this.timestampOffset = this.expectedFirstTimestamp - sampleTimestamp;
|
|
22899
|
+
;
|
|
22900
|
+
}
|
|
22901
|
+
sampleTimestamp += this.timestampOffset;
|
|
22902
|
+
if (this.currentTimestamp === null || Math.abs(sampleTimestamp - this.currentTimestamp) >= sample.duration) {
|
|
22903
|
+
this.currentTimestamp = sampleTimestamp;
|
|
22104
22904
|
}
|
|
22105
22905
|
const preciseTimestamp = this.currentTimestamp;
|
|
22106
22906
|
this.currentTimestamp += sample.duration;
|
|
@@ -22156,16 +22956,20 @@ var Mediabunny = (() => {
|
|
|
22156
22956
|
void this.customDecoderCallSerializer.call(() => this.customDecoder.decode(packet)).then(() => this.customDecoderQueueSize--);
|
|
22157
22957
|
} else {
|
|
22158
22958
|
assert(this.decoder);
|
|
22959
|
+
this.expectedFirstTimestamp ??= packet.timestamp;
|
|
22159
22960
|
this.decoder.decode(packet.toEncodedAudioChunk());
|
|
22160
22961
|
}
|
|
22161
22962
|
}
|
|
22162
|
-
flush() {
|
|
22963
|
+
async flush() {
|
|
22163
22964
|
if (this.customDecoder) {
|
|
22164
|
-
|
|
22965
|
+
await this.customDecoderCallSerializer.call(() => this.customDecoder.flush());
|
|
22165
22966
|
} else {
|
|
22166
22967
|
assert(this.decoder);
|
|
22167
|
-
|
|
22968
|
+
await this.decoder.flush();
|
|
22168
22969
|
}
|
|
22970
|
+
this.currentTimestamp = null;
|
|
22971
|
+
this.expectedFirstTimestamp = null;
|
|
22972
|
+
this.timestampOffset = 0;
|
|
22169
22973
|
}
|
|
22170
22974
|
close() {
|
|
22171
22975
|
if (this.customDecoder) {
|
|
@@ -22408,7 +23212,7 @@ var Mediabunny = (() => {
|
|
|
22408
23212
|
* @param endTimestamp - The timestamp in seconds at which to stop yielding samples (exclusive).
|
|
22409
23213
|
* @param options - Options used for the underlying packet retrieval.
|
|
22410
23214
|
*/
|
|
22411
|
-
samples(startTimestamp
|
|
23215
|
+
samples(startTimestamp, endTimestamp, options = {}) {
|
|
22412
23216
|
return this.mediaSamplesInRange(startTimestamp, endTimestamp, options);
|
|
22413
23217
|
}
|
|
22414
23218
|
/**
|
|
@@ -22417,6 +23221,9 @@ var Mediabunny = (() => {
|
|
|
22417
23221
|
* once, and is therefore more efficient than manually getting the sample for every timestamp. The iterator may
|
|
22418
23222
|
* yield null if no sample is available for a given timestamp.
|
|
22419
23223
|
*
|
|
23224
|
+
* This method is good for sparse access of media data. If you want primarily sequential media access, prefer
|
|
23225
|
+
* {@link AudioSampleSink.samples} instead.
|
|
23226
|
+
*
|
|
22420
23227
|
* @param timestamps - An iterable or async iterable of timestamps in seconds.
|
|
22421
23228
|
* @param options - Options used for the underlying packet retrieval.
|
|
22422
23229
|
*/
|
|
@@ -22463,7 +23270,7 @@ var Mediabunny = (() => {
|
|
|
22463
23270
|
* @param endTimestamp - The timestamp in seconds at which to stop yielding buffers (exclusive).
|
|
22464
23271
|
* @param options - Options used for the underlying packet retrieval.
|
|
22465
23272
|
*/
|
|
22466
|
-
buffers(startTimestamp
|
|
23273
|
+
buffers(startTimestamp, endTimestamp, options) {
|
|
22467
23274
|
return mapAsyncGenerator(
|
|
22468
23275
|
this._audioSampleSink.samples(startTimestamp, endTimestamp, options),
|
|
22469
23276
|
(data) => this._audioSampleToWrappedArrayBuffer(data)
|
|
@@ -27814,7 +28621,7 @@ var Mediabunny = (() => {
|
|
|
27814
28621
|
const timescale = computeRationalApproximation(
|
|
27815
28622
|
1 / (track.metadata.frameRate ?? GLOBAL_TIMESCALE),
|
|
27816
28623
|
1e6
|
|
27817
|
-
).
|
|
28624
|
+
).den;
|
|
27818
28625
|
const displayAspectWidth = decoderConfig.displayAspectWidth;
|
|
27819
28626
|
const displayAspectHeight = decoderConfig.displayAspectHeight;
|
|
27820
28627
|
const pixelAspectRatio = displayAspectWidth === void 0 || displayAspectHeight === void 0 ? { num: 1, den: 1 } : simplifyRational({
|
|
@@ -31324,7 +32131,6 @@ ${cue.notes ?? ""}`;
|
|
|
31324
32131
|
this.encoder = null;
|
|
31325
32132
|
this.muxer = null;
|
|
31326
32133
|
this.lastMultipleOfKeyFrameInterval = -1;
|
|
31327
|
-
this.resizeCanvas = null;
|
|
31328
32134
|
// Tracks the input dimensions of the first frame
|
|
31329
32135
|
this.codedWidth = null;
|
|
31330
32136
|
this.codedHeight = null;
|
|
@@ -31353,12 +32159,6 @@ ${cue.notes ?? ""}`;
|
|
|
31353
32159
|
*/
|
|
31354
32160
|
this.error = null;
|
|
31355
32161
|
this.lastMuxerPromise = Promise.resolve();
|
|
31356
|
-
const sizeChangeBehavior = encodingConfig.sizeChangeBehavior ?? "deny";
|
|
31357
|
-
if (["fill", "contain", "cover"].includes(sizeChangeBehavior) && encodingConfig.transform?.fit !== void 0) {
|
|
31358
|
-
throw new TypeError(
|
|
31359
|
-
`Cannot set 'fit' when 'sizeChangeBehavior' is '${sizeChangeBehavior}'. The size change behavior determines the fit in this case.`
|
|
31360
|
-
);
|
|
31361
|
-
}
|
|
31362
32162
|
}
|
|
31363
32163
|
async add(videoSample, shouldClose, encodeOptions) {
|
|
31364
32164
|
const originalSample = videoSample;
|
|
@@ -31384,17 +32184,8 @@ ${cue.notes ?? ""}`;
|
|
|
31384
32184
|
const hasTransformConfig = config.transform?.width !== void 0 || config.transform?.height !== void 0 || config.transform?.rotate !== void 0 || config.transform?.crop !== void 0 || config.transform?.force === true;
|
|
31385
32185
|
const needsTransform = hasTransformConfig || isSizeChange && sizeChangeBehavior !== "passThrough";
|
|
31386
32186
|
if (needsTransform) {
|
|
31387
|
-
|
|
31388
|
-
|
|
31389
|
-
let finalCrop = config.transform?.crop;
|
|
31390
|
-
if (finalCrop) {
|
|
31391
|
-
finalCrop = clampCropRectangle(finalCrop, rotatedWidth, rotatedHeight);
|
|
31392
|
-
}
|
|
31393
|
-
const cropWidth = finalCrop ? finalCrop.width : rotatedWidth;
|
|
31394
|
-
const cropHeight = finalCrop ? finalCrop.height : rotatedHeight;
|
|
31395
|
-
const originalAspectRatio = cropWidth / cropHeight;
|
|
31396
|
-
let targetWidth;
|
|
31397
|
-
let targetHeight;
|
|
32187
|
+
let targetWidth = config.transform?.width;
|
|
32188
|
+
let targetHeight = config.transform?.height;
|
|
31398
32189
|
let appliedFit = config.transform?.fit ?? "fill";
|
|
31399
32190
|
if (isSizeChange && sizeChangeBehavior !== "passThrough") {
|
|
31400
32191
|
assert(this.outputWidth);
|
|
@@ -31403,69 +32194,24 @@ ${cue.notes ?? ""}`;
|
|
|
31403
32194
|
targetWidth = this.outputWidth;
|
|
31404
32195
|
targetHeight = this.outputHeight;
|
|
31405
32196
|
appliedFit = sizeChangeBehavior;
|
|
31406
|
-
} else {
|
|
31407
|
-
if (config.transform?.width !== void 0 && config.transform?.height === void 0) {
|
|
31408
|
-
targetWidth = config.transform.width;
|
|
31409
|
-
targetHeight = ceilToMultipleOfTwo(Math.round(targetWidth / originalAspectRatio));
|
|
31410
|
-
} else if (config.transform?.width === void 0 && config.transform?.height !== void 0) {
|
|
31411
|
-
targetHeight = config.transform.height;
|
|
31412
|
-
targetWidth = ceilToMultipleOfTwo(Math.round(targetHeight * originalAspectRatio));
|
|
31413
|
-
} else if (config.transform?.width !== void 0 && config.transform?.height !== void 0) {
|
|
31414
|
-
targetWidth = config.transform?.width;
|
|
31415
|
-
targetHeight = config.transform?.height;
|
|
31416
|
-
} else {
|
|
31417
|
-
targetWidth = cropWidth;
|
|
31418
|
-
targetHeight = cropHeight;
|
|
31419
|
-
}
|
|
31420
32197
|
}
|
|
31421
|
-
|
|
31422
|
-
|
|
31423
|
-
|
|
31424
|
-
|
|
31425
|
-
|
|
31426
|
-
|
|
31427
|
-
if (typeof document !== "undefined") {
|
|
31428
|
-
this.resizeCanvas = document.createElement("canvas");
|
|
31429
|
-
this.resizeCanvas.width = targetWidth;
|
|
31430
|
-
this.resizeCanvas.height = targetHeight;
|
|
31431
|
-
} else {
|
|
31432
|
-
this.resizeCanvas = new OffscreenCanvas(targetWidth, targetHeight);
|
|
31433
|
-
}
|
|
31434
|
-
canvasIsNew = true;
|
|
31435
|
-
} else if (this.resizeCanvas.width !== targetWidth || this.resizeCanvas.height !== targetHeight) {
|
|
31436
|
-
this.resizeCanvas.width = targetWidth;
|
|
31437
|
-
this.resizeCanvas.height = targetHeight;
|
|
31438
|
-
}
|
|
31439
|
-
const context = this.resizeCanvas.getContext("2d", {
|
|
31440
|
-
// Firefox has VideoFrame glitches with opaque canvases
|
|
31441
|
-
alpha: this.encodingConfig.alpha === "keep" || isFirefox()
|
|
31442
|
-
});
|
|
31443
|
-
assert(context);
|
|
31444
|
-
if (typeof context.resetTransform === "function") {
|
|
31445
|
-
context.resetTransform();
|
|
31446
|
-
}
|
|
31447
|
-
if (!canvasIsNew) {
|
|
31448
|
-
if (isFirefox()) {
|
|
31449
|
-
context.fillStyle = "black";
|
|
31450
|
-
context.fillRect(0, 0, targetWidth, targetHeight);
|
|
31451
|
-
} else {
|
|
31452
|
-
context.clearRect(0, 0, targetWidth, targetHeight);
|
|
31453
|
-
}
|
|
31454
|
-
}
|
|
31455
|
-
videoSample.drawWithFit(context, {
|
|
32198
|
+
const transformed = await videoSample.transform({
|
|
32199
|
+
width: targetWidth,
|
|
32200
|
+
height: targetHeight,
|
|
32201
|
+
roundDimensionsTo: 2,
|
|
32202
|
+
crop: config.transform?.crop,
|
|
32203
|
+
rotate: config.transform?.rotate,
|
|
31456
32204
|
fit: appliedFit,
|
|
31457
|
-
|
|
31458
|
-
crop: finalCrop
|
|
32205
|
+
alpha: config.alpha
|
|
31459
32206
|
});
|
|
32207
|
+
if (this.outputWidth === null || this.outputHeight === null) {
|
|
32208
|
+
this.outputWidth = transformed.displayWidth;
|
|
32209
|
+
this.outputHeight = transformed.displayHeight;
|
|
32210
|
+
}
|
|
31460
32211
|
if (shouldClose) {
|
|
31461
32212
|
videoSample.close();
|
|
31462
32213
|
}
|
|
31463
|
-
videoSample =
|
|
31464
|
-
timestamp: videoSample.timestamp,
|
|
31465
|
-
duration: videoSample.duration,
|
|
31466
|
-
rotation: 0
|
|
31467
|
-
// Rotation is now baked into the canvas
|
|
31468
|
-
});
|
|
32214
|
+
videoSample = transformed;
|
|
31469
32215
|
shouldClose = true;
|
|
31470
32216
|
} else {
|
|
31471
32217
|
if (this.outputWidth === null || this.outputHeight === null) {
|
|
@@ -31605,24 +32351,12 @@ ${cue.notes ?? ""}`;
|
|
|
31605
32351
|
const width = videoFrame.displayWidth;
|
|
31606
32352
|
const height = videoFrame.displayHeight;
|
|
31607
32353
|
if (!this.splitter) {
|
|
31608
|
-
|
|
31609
|
-
this.splitter = new ColorAlphaSplitter(width, height);
|
|
31610
|
-
} catch (error) {
|
|
31611
|
-
console.error("Due to an error, only color data will be encoded.", error);
|
|
31612
|
-
this.splitterCreationFailed = true;
|
|
31613
|
-
this.alphaFrameQueue.push(null);
|
|
31614
|
-
this.encoder.encode(videoFrame, finalEncodeOptions);
|
|
31615
|
-
videoFrame.close();
|
|
31616
|
-
}
|
|
31617
|
-
}
|
|
31618
|
-
if (this.splitter) {
|
|
31619
|
-
const colorFrame = this.splitter.extractColor(videoFrame);
|
|
31620
|
-
const alphaFrame = this.splitter.extractAlpha(videoFrame);
|
|
31621
|
-
this.alphaFrameQueue.push(alphaFrame);
|
|
31622
|
-
this.encoder.encode(colorFrame, finalEncodeOptions);
|
|
31623
|
-
colorFrame.close();
|
|
31624
|
-
videoFrame.close();
|
|
32354
|
+
this.splitter = new ColorAlphaSplitter(width, height);
|
|
31625
32355
|
}
|
|
32356
|
+
const { colorFrame, alphaFrame } = await this.splitter.update(videoFrame);
|
|
32357
|
+
this.alphaFrameQueue.push(alphaFrame);
|
|
32358
|
+
this.encoder.encode(colorFrame, finalEncodeOptions);
|
|
32359
|
+
colorFrame.close();
|
|
31626
32360
|
}
|
|
31627
32361
|
}
|
|
31628
32362
|
if (this.encoder.encodeQueueSize >= 4) {
|
|
@@ -31849,35 +32583,78 @@ ${cue.notes ?? ""}`;
|
|
|
31849
32583
|
}
|
|
31850
32584
|
}
|
|
31851
32585
|
};
|
|
31852
|
-
var
|
|
32586
|
+
var splitterGpuUnavailable = false;
|
|
32587
|
+
var _ColorAlphaSplitter = class _ColorAlphaSplitter {
|
|
31853
32588
|
constructor(initialWidth, initialHeight) {
|
|
31854
|
-
this.
|
|
31855
|
-
|
|
31856
|
-
|
|
32589
|
+
this.canvas = null;
|
|
32590
|
+
this.gl = null;
|
|
32591
|
+
this.colorProgram = null;
|
|
32592
|
+
this.alphaProgram = null;
|
|
32593
|
+
this.vao = null;
|
|
32594
|
+
this.sourceTexture = null;
|
|
32595
|
+
this.alphaResolutionLocation = null;
|
|
32596
|
+
this.worker = null;
|
|
32597
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
32598
|
+
this.nextRequestId = 0;
|
|
32599
|
+
const canMakeCanvas = typeof OffscreenCanvas !== "undefined" || typeof document !== "undefined" && typeof document.createElement === "function";
|
|
32600
|
+
if (!_ColorAlphaSplitter.forceCpu && canMakeCanvas && !splitterGpuUnavailable) {
|
|
32601
|
+
try {
|
|
32602
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
32603
|
+
this.canvas = new OffscreenCanvas(initialWidth, initialHeight);
|
|
32604
|
+
} else {
|
|
32605
|
+
this.canvas = document.createElement("canvas");
|
|
32606
|
+
this.canvas.width = initialWidth;
|
|
32607
|
+
this.canvas.height = initialHeight;
|
|
32608
|
+
}
|
|
32609
|
+
const gl = this.canvas.getContext("webgl2", {
|
|
32610
|
+
alpha: true
|
|
32611
|
+
// Needed due to the YUV thing we do for alpha
|
|
32612
|
+
});
|
|
32613
|
+
if (!gl) {
|
|
32614
|
+
throw new Error("Couldn't acquire WebGL 2 context.");
|
|
32615
|
+
}
|
|
32616
|
+
this.gl = gl;
|
|
32617
|
+
this.colorProgram = this.createColorProgram();
|
|
32618
|
+
this.alphaProgram = this.createAlphaProgram();
|
|
32619
|
+
this.vao = this.createVAO();
|
|
32620
|
+
this.sourceTexture = this.createTexture();
|
|
32621
|
+
this.alphaResolutionLocation = this.gl.getUniformLocation(this.alphaProgram, "u_resolution");
|
|
32622
|
+
this.gl.useProgram(this.colorProgram);
|
|
32623
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.colorProgram, "u_sourceTexture"), 0);
|
|
32624
|
+
this.gl.useProgram(this.alphaProgram);
|
|
32625
|
+
this.gl.uniform1i(this.gl.getUniformLocation(this.alphaProgram, "u_sourceTexture"), 0);
|
|
32626
|
+
} catch (error) {
|
|
32627
|
+
this.gl = null;
|
|
32628
|
+
this.canvas = null;
|
|
32629
|
+
splitterGpuUnavailable = true;
|
|
32630
|
+
console.warn("Falling back to CPU for color/alpha splitting.", error);
|
|
32631
|
+
}
|
|
32632
|
+
}
|
|
32633
|
+
}
|
|
32634
|
+
async update(sourceFrame) {
|
|
32635
|
+
if (this.gl) {
|
|
32636
|
+
return this.updateGpu(sourceFrame);
|
|
31857
32637
|
} else {
|
|
31858
|
-
this.
|
|
31859
|
-
this.canvas.width = initialWidth;
|
|
31860
|
-
this.canvas.height = initialHeight;
|
|
32638
|
+
return this.updateCpu(sourceFrame);
|
|
31861
32639
|
}
|
|
31862
|
-
|
|
31863
|
-
|
|
31864
|
-
|
|
31865
|
-
|
|
31866
|
-
if (
|
|
31867
|
-
|
|
31868
|
-
|
|
31869
|
-
|
|
31870
|
-
this.
|
|
31871
|
-
this.
|
|
31872
|
-
this.
|
|
31873
|
-
|
|
31874
|
-
|
|
31875
|
-
|
|
31876
|
-
|
|
31877
|
-
this.gl.useProgram(this.alphaProgram);
|
|
31878
|
-
this.gl.uniform1i(this.gl.getUniformLocation(this.alphaProgram, "u_sourceTexture"), 0);
|
|
32640
|
+
}
|
|
32641
|
+
updateGpu(sourceFrame) {
|
|
32642
|
+
assert(this.gl);
|
|
32643
|
+
assert(this.canvas);
|
|
32644
|
+
if (sourceFrame.displayWidth !== this.canvas.width || sourceFrame.displayHeight !== this.canvas.height) {
|
|
32645
|
+
this.canvas.width = sourceFrame.displayWidth;
|
|
32646
|
+
this.canvas.height = sourceFrame.displayHeight;
|
|
32647
|
+
}
|
|
32648
|
+
this.gl.activeTexture(this.gl.TEXTURE0);
|
|
32649
|
+
this.gl.bindTexture(this.gl.TEXTURE_2D, this.sourceTexture);
|
|
32650
|
+
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, sourceFrame);
|
|
32651
|
+
const colorFrame = this.runColorProgram(sourceFrame);
|
|
32652
|
+
const alphaFrame = this.runAlphaProgram(sourceFrame);
|
|
32653
|
+
sourceFrame.close();
|
|
32654
|
+
return { colorFrame, alphaFrame };
|
|
31879
32655
|
}
|
|
31880
32656
|
createVertexShader() {
|
|
32657
|
+
assert(this.gl);
|
|
31881
32658
|
return this.createShader(this.gl.VERTEX_SHADER, `#version 300 es
|
|
31882
32659
|
in vec2 a_position;
|
|
31883
32660
|
in vec2 a_texCoord;
|
|
@@ -31890,6 +32667,7 @@ ${cue.notes ?? ""}`;
|
|
|
31890
32667
|
`);
|
|
31891
32668
|
}
|
|
31892
32669
|
createColorProgram() {
|
|
32670
|
+
assert(this.gl);
|
|
31893
32671
|
const vertexShader = this.createVertexShader();
|
|
31894
32672
|
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `#version 300 es
|
|
31895
32673
|
precision highp float;
|
|
@@ -31910,6 +32688,7 @@ ${cue.notes ?? ""}`;
|
|
|
31910
32688
|
return program;
|
|
31911
32689
|
}
|
|
31912
32690
|
createAlphaProgram() {
|
|
32691
|
+
assert(this.gl);
|
|
31913
32692
|
const vertexShader = this.createVertexShader();
|
|
31914
32693
|
const fragmentShader = this.createShader(this.gl.FRAGMENT_SHADER, `#version 300 es
|
|
31915
32694
|
precision highp float;
|
|
@@ -31963,6 +32742,7 @@ ${cue.notes ?? ""}`;
|
|
|
31963
32742
|
return program;
|
|
31964
32743
|
}
|
|
31965
32744
|
createShader(type, source) {
|
|
32745
|
+
assert(this.gl);
|
|
31966
32746
|
const shader = this.gl.createShader(type);
|
|
31967
32747
|
this.gl.shaderSource(shader, source);
|
|
31968
32748
|
this.gl.compileShader(shader);
|
|
@@ -31972,6 +32752,8 @@ ${cue.notes ?? ""}`;
|
|
|
31972
32752
|
return shader;
|
|
31973
32753
|
}
|
|
31974
32754
|
createVAO() {
|
|
32755
|
+
assert(this.gl);
|
|
32756
|
+
assert(this.colorProgram);
|
|
31975
32757
|
const vao = this.gl.createVertexArray();
|
|
31976
32758
|
this.gl.bindVertexArray(vao);
|
|
31977
32759
|
const vertices = new Float32Array([
|
|
@@ -32004,6 +32786,7 @@ ${cue.notes ?? ""}`;
|
|
|
32004
32786
|
return vao;
|
|
32005
32787
|
}
|
|
32006
32788
|
createTexture() {
|
|
32789
|
+
assert(this.gl);
|
|
32007
32790
|
const texture = this.gl.createTexture();
|
|
32008
32791
|
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
|
|
32009
32792
|
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
|
|
@@ -32012,21 +32795,9 @@ ${cue.notes ?? ""}`;
|
|
|
32012
32795
|
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
|
|
32013
32796
|
return texture;
|
|
32014
32797
|
}
|
|
32015
|
-
|
|
32016
|
-
|
|
32017
|
-
|
|
32018
|
-
}
|
|
32019
|
-
if (sourceFrame.displayWidth !== this.canvas.width || sourceFrame.displayHeight !== this.canvas.height) {
|
|
32020
|
-
this.canvas.width = sourceFrame.displayWidth;
|
|
32021
|
-
this.canvas.height = sourceFrame.displayHeight;
|
|
32022
|
-
}
|
|
32023
|
-
this.gl.activeTexture(this.gl.TEXTURE0);
|
|
32024
|
-
this.gl.bindTexture(this.gl.TEXTURE_2D, this.sourceTexture);
|
|
32025
|
-
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, sourceFrame);
|
|
32026
|
-
this.lastFrame = sourceFrame;
|
|
32027
|
-
}
|
|
32028
|
-
extractColor(sourceFrame) {
|
|
32029
|
-
this.updateTexture(sourceFrame);
|
|
32798
|
+
runColorProgram(sourceFrame) {
|
|
32799
|
+
assert(this.gl);
|
|
32800
|
+
assert(this.canvas);
|
|
32030
32801
|
this.gl.useProgram(this.colorProgram);
|
|
32031
32802
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
32032
32803
|
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
@@ -32038,8 +32809,9 @@ ${cue.notes ?? ""}`;
|
|
|
32038
32809
|
alpha: "discard"
|
|
32039
32810
|
});
|
|
32040
32811
|
}
|
|
32041
|
-
|
|
32042
|
-
this.
|
|
32812
|
+
runAlphaProgram(sourceFrame) {
|
|
32813
|
+
assert(this.gl);
|
|
32814
|
+
assert(this.canvas);
|
|
32043
32815
|
this.gl.useProgram(this.alphaProgram);
|
|
32044
32816
|
this.gl.uniform2f(this.alphaResolutionLocation, this.canvas.width, this.canvas.height);
|
|
32045
32817
|
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
@@ -32065,11 +32837,180 @@ ${cue.notes ?? ""}`;
|
|
|
32065
32837
|
};
|
|
32066
32838
|
return new VideoFrame(yuv, init);
|
|
32067
32839
|
}
|
|
32840
|
+
updateCpu(sourceFrame) {
|
|
32841
|
+
if (!this.worker) {
|
|
32842
|
+
const blob = new Blob(
|
|
32843
|
+
[`(${colorAlphaSplitterWorkerCode.toString()})()`],
|
|
32844
|
+
{ type: "application/javascript" }
|
|
32845
|
+
);
|
|
32846
|
+
const url2 = URL.createObjectURL(blob);
|
|
32847
|
+
this.worker = new Worker(url2);
|
|
32848
|
+
URL.revokeObjectURL(url2);
|
|
32849
|
+
this.worker.addEventListener("message", (event) => {
|
|
32850
|
+
const data = event.data;
|
|
32851
|
+
const pending2 = this.pendingRequests.get(data.id);
|
|
32852
|
+
if (!pending2) {
|
|
32853
|
+
return;
|
|
32854
|
+
}
|
|
32855
|
+
this.pendingRequests.delete(data.id);
|
|
32856
|
+
if ("error" in data) {
|
|
32857
|
+
pending2.reject(new Error(data.error));
|
|
32858
|
+
} else {
|
|
32859
|
+
pending2.resolve({ colorFrame: data.colorFrame, alphaFrame: data.alphaFrame });
|
|
32860
|
+
}
|
|
32861
|
+
});
|
|
32862
|
+
this.worker.addEventListener("error", (event) => {
|
|
32863
|
+
const error = new Error(event.message || "Color/alpha splitter worker error.");
|
|
32864
|
+
for (const pending2 of this.pendingRequests.values()) {
|
|
32865
|
+
pending2.reject(error);
|
|
32866
|
+
}
|
|
32867
|
+
this.pendingRequests.clear();
|
|
32868
|
+
});
|
|
32869
|
+
}
|
|
32870
|
+
const id = this.nextRequestId++;
|
|
32871
|
+
const pending = promiseWithResolvers();
|
|
32872
|
+
this.pendingRequests.set(id, pending);
|
|
32873
|
+
this.worker.postMessage({ id, sourceFrame }, { transfer: [sourceFrame] });
|
|
32874
|
+
return pending.promise;
|
|
32875
|
+
}
|
|
32068
32876
|
close() {
|
|
32069
|
-
this.gl
|
|
32877
|
+
this.gl?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
32070
32878
|
this.gl = null;
|
|
32879
|
+
this.canvas = null;
|
|
32880
|
+
this.worker?.terminate();
|
|
32881
|
+
this.worker = null;
|
|
32882
|
+
const error = new Error("Color/alpha splitter closed.");
|
|
32883
|
+
for (const pending of this.pendingRequests.values()) {
|
|
32884
|
+
pending.reject(error);
|
|
32885
|
+
}
|
|
32886
|
+
this.pendingRequests.clear();
|
|
32071
32887
|
}
|
|
32072
32888
|
};
|
|
32889
|
+
_ColorAlphaSplitter.forceCpu = true;
|
|
32890
|
+
var ColorAlphaSplitter = _ColorAlphaSplitter;
|
|
32891
|
+
var colorAlphaSplitterWorkerCode = () => {
|
|
32892
|
+
let cpuSourceBuffer = null;
|
|
32893
|
+
let chain = Promise.resolve();
|
|
32894
|
+
self.addEventListener("message", (event) => {
|
|
32895
|
+
const { id, sourceFrame } = event.data;
|
|
32896
|
+
chain = chain.then(async () => {
|
|
32897
|
+
try {
|
|
32898
|
+
const { colorFrame, alphaFrame } = await split(sourceFrame);
|
|
32899
|
+
self.postMessage({ id, colorFrame, alphaFrame }, { transfer: [colorFrame, alphaFrame] });
|
|
32900
|
+
} catch (error) {
|
|
32901
|
+
self.postMessage({ id, error: error.message });
|
|
32902
|
+
} finally {
|
|
32903
|
+
sourceFrame.close();
|
|
32904
|
+
}
|
|
32905
|
+
});
|
|
32906
|
+
});
|
|
32907
|
+
const split = async (sourceFrame) => {
|
|
32908
|
+
const format = sourceFrame.format;
|
|
32909
|
+
if (!format) {
|
|
32910
|
+
throw new Error("CPU color/alpha splitting requires a known VideoFrame format.");
|
|
32911
|
+
}
|
|
32912
|
+
const width = sourceFrame.codedWidth;
|
|
32913
|
+
const height = sourceFrame.codedHeight;
|
|
32914
|
+
const sourceSize = sourceFrame.allocationSize();
|
|
32915
|
+
if (!cpuSourceBuffer || cpuSourceBuffer.byteLength !== sourceSize) {
|
|
32916
|
+
cpuSourceBuffer = new Uint8Array(sourceSize);
|
|
32917
|
+
}
|
|
32918
|
+
await sourceFrame.copyTo(cpuSourceBuffer);
|
|
32919
|
+
if (format === "RGBA" || format === "BGRA") {
|
|
32920
|
+
return splitInterleavedRgba(cpuSourceBuffer, width, height, format, sourceFrame);
|
|
32921
|
+
} else if (format === "I420A" || format === "I420AP10" || format === "I420AP12" || format === "I422A" || format === "I422AP10" || format === "I422AP12" || format === "I444A" || format === "I444AP10" || format === "I444AP12") {
|
|
32922
|
+
return splitPlanarYuvA(cpuSourceBuffer, width, height, format, sourceFrame);
|
|
32923
|
+
}
|
|
32924
|
+
throw new Error(`CPU color/alpha splitting does not support format '${format}'.`);
|
|
32925
|
+
};
|
|
32926
|
+
const splitInterleavedRgba = (source, width, height, format, sourceFrame) => {
|
|
32927
|
+
const pixelCount = width * height;
|
|
32928
|
+
const chromaW = Math.ceil(width / 2);
|
|
32929
|
+
const chromaH = Math.ceil(height / 2);
|
|
32930
|
+
const alphaSize = pixelCount + chromaW * chromaH * 2;
|
|
32931
|
+
const alphaBuffer = new Uint8Array(alphaSize);
|
|
32932
|
+
for (let i = 0, j = 3; i < pixelCount; i++, j += 4) {
|
|
32933
|
+
alphaBuffer[i] = source[j];
|
|
32934
|
+
}
|
|
32935
|
+
alphaBuffer.fill(128, pixelCount);
|
|
32936
|
+
const colorFrame = new VideoFrame(source, {
|
|
32937
|
+
format: format === "RGBA" ? "RGBX" : "BGRX",
|
|
32938
|
+
codedWidth: width,
|
|
32939
|
+
codedHeight: height,
|
|
32940
|
+
timestamp: sourceFrame.timestamp,
|
|
32941
|
+
duration: sourceFrame.duration ?? void 0
|
|
32942
|
+
// No transfer!
|
|
32943
|
+
});
|
|
32944
|
+
const alphaInit = {
|
|
32945
|
+
format: "I420",
|
|
32946
|
+
codedWidth: width,
|
|
32947
|
+
codedHeight: height,
|
|
32948
|
+
timestamp: sourceFrame.timestamp,
|
|
32949
|
+
duration: sourceFrame.duration ?? void 0,
|
|
32950
|
+
transfer: [alphaBuffer.buffer]
|
|
32951
|
+
};
|
|
32952
|
+
const alphaFrame = new VideoFrame(alphaBuffer, alphaInit);
|
|
32953
|
+
return { colorFrame, alphaFrame };
|
|
32954
|
+
};
|
|
32955
|
+
const splitPlanarYuvA = (source, width, height, format, sourceFrame) => {
|
|
32956
|
+
const is10 = format.includes("P10");
|
|
32957
|
+
const is12 = format.includes("P12");
|
|
32958
|
+
const bytesPerSample = is10 || is12 ? 2 : 1;
|
|
32959
|
+
let chromaW;
|
|
32960
|
+
let chromaH;
|
|
32961
|
+
if (format.startsWith("I420")) {
|
|
32962
|
+
chromaW = Math.ceil(width / 2);
|
|
32963
|
+
chromaH = Math.ceil(height / 2);
|
|
32964
|
+
} else if (format.startsWith("I422")) {
|
|
32965
|
+
chromaW = Math.ceil(width / 2);
|
|
32966
|
+
chromaH = height;
|
|
32967
|
+
} else {
|
|
32968
|
+
chromaW = width;
|
|
32969
|
+
chromaH = height;
|
|
32970
|
+
}
|
|
32971
|
+
const ySamples = width * height;
|
|
32972
|
+
const uvSamples = chromaW * chromaH;
|
|
32973
|
+
const yBytes = ySamples * bytesPerSample;
|
|
32974
|
+
const uvBytes = uvSamples * bytesPerSample;
|
|
32975
|
+
const aBytes = ySamples * bytesPerSample;
|
|
32976
|
+
const colorBytes = yBytes + uvBytes * 2;
|
|
32977
|
+
const colorFormat = format.replace("A", "");
|
|
32978
|
+
const alphaChromaW = Math.ceil(width / 2);
|
|
32979
|
+
const alphaChromaH = Math.ceil(height / 2);
|
|
32980
|
+
const alphaUvSamples = alphaChromaW * alphaChromaH;
|
|
32981
|
+
const alphaUvBytes = alphaUvSamples * bytesPerSample;
|
|
32982
|
+
const alphaSize = aBytes + 2 * alphaUvBytes;
|
|
32983
|
+
const alphaBuffer = new Uint8Array(alphaSize);
|
|
32984
|
+
const aPlaneStart = colorBytes;
|
|
32985
|
+
alphaBuffer.set(source.subarray(aPlaneStart, aPlaneStart + aBytes), 0);
|
|
32986
|
+
const uvOffset = aBytes;
|
|
32987
|
+
const neutralChroma = is10 ? 512 : is12 ? 2048 : 128;
|
|
32988
|
+
if (bytesPerSample === 1) {
|
|
32989
|
+
alphaBuffer.fill(neutralChroma, uvOffset);
|
|
32990
|
+
} else {
|
|
32991
|
+
const uvView = new Uint16Array(alphaBuffer.buffer, uvOffset, 2 * alphaUvSamples);
|
|
32992
|
+
uvView.fill(neutralChroma);
|
|
32993
|
+
}
|
|
32994
|
+
const alphaFormat = is10 ? "I420P10" : is12 ? "I420P12" : "I420";
|
|
32995
|
+
const colorFrame = new VideoFrame(source.subarray(0, colorBytes), {
|
|
32996
|
+
format: colorFormat,
|
|
32997
|
+
codedWidth: width,
|
|
32998
|
+
codedHeight: height,
|
|
32999
|
+
timestamp: sourceFrame.timestamp,
|
|
33000
|
+
duration: sourceFrame.duration ?? void 0
|
|
33001
|
+
});
|
|
33002
|
+
const alphaInit = {
|
|
33003
|
+
format: alphaFormat,
|
|
33004
|
+
codedWidth: width,
|
|
33005
|
+
codedHeight: height,
|
|
33006
|
+
timestamp: sourceFrame.timestamp,
|
|
33007
|
+
duration: sourceFrame.duration ?? void 0,
|
|
33008
|
+
transfer: [alphaBuffer.buffer]
|
|
33009
|
+
};
|
|
33010
|
+
const alphaFrame = new VideoFrame(alphaBuffer, alphaInit);
|
|
33011
|
+
return { colorFrame, alphaFrame };
|
|
33012
|
+
};
|
|
33013
|
+
};
|
|
32073
33014
|
var VideoSampleSource = class extends VideoSource {
|
|
32074
33015
|
/**
|
|
32075
33016
|
* Creates a new {@link VideoSampleSource} whose samples are encoded according to the specified
|
|
@@ -36036,6 +36977,9 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
36036
36977
|
throw new Error("Conversion cannot be executed twice.");
|
|
36037
36978
|
}
|
|
36038
36979
|
this._executed = true;
|
|
36980
|
+
for (const id of this._outputTrackIds) {
|
|
36981
|
+
this._synchronizer.declareTrack(id);
|
|
36982
|
+
}
|
|
36039
36983
|
if (this.onProgress) {
|
|
36040
36984
|
const uniqueUtilizedTracks = new Set(this.utilizedTracks);
|
|
36041
36985
|
const durationPromises = [...uniqueUtilizedTracks].map(async (track) => {
|
|
@@ -36101,7 +37045,8 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
36101
37045
|
return;
|
|
36102
37046
|
}
|
|
36103
37047
|
let videoSource;
|
|
36104
|
-
const
|
|
37048
|
+
const innateRotation = await track.getRotation();
|
|
37049
|
+
const totalRotation = normalizeRotation(innateRotation + (trackOptions.rotate ?? 0));
|
|
36105
37050
|
let outputTrackRotation = totalRotation;
|
|
36106
37051
|
const canUseRotationMetadata = this.output.format.supportsVideoRotationMetadata && (trackOptions.allowRotationMetadata ?? true);
|
|
36107
37052
|
const squarePixelWidth = await track.getSquarePixelWidth();
|
|
@@ -36191,10 +37136,10 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
36191
37136
|
keyFrameInterval: trackOptions.keyFrameInterval,
|
|
36192
37137
|
sizeChangeBehavior: trackOptions.fit ?? "passThrough",
|
|
36193
37138
|
alpha,
|
|
36194
|
-
hardwareAcceleration: trackOptions.hardwareAcceleration
|
|
37139
|
+
hardwareAcceleration: trackOptions.hardwareAcceleration,
|
|
37140
|
+
transform: {}
|
|
36195
37141
|
};
|
|
36196
|
-
|
|
36197
|
-
videoSource = source;
|
|
37142
|
+
assert(encodingConfig.transform);
|
|
36198
37143
|
let needsRerender = width !== originalWidth || height !== originalHeight || totalRotation !== 0 && (!canUseRotationMetadata || trackOptions.process !== void 0) || !!crop || squarePixelWidth !== await track.getCodedWidth() || squarePixelHeight !== await track.getCodedHeight();
|
|
36199
37144
|
if (!needsRerender) {
|
|
36200
37145
|
const tempOutput = new Output({
|
|
@@ -36221,136 +37166,36 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
36221
37166
|
await tempOutput.cancel();
|
|
36222
37167
|
}
|
|
36223
37168
|
}
|
|
37169
|
+
if (trackOptions.frameRate) {
|
|
37170
|
+
encodingConfig.transform.frameRate = trackOptions.frameRate;
|
|
37171
|
+
}
|
|
36224
37172
|
if (needsRerender) {
|
|
36225
37173
|
outputTrackRotation = 0;
|
|
36226
|
-
|
|
36227
|
-
|
|
36228
|
-
|
|
36229
|
-
|
|
36230
|
-
|
|
36231
|
-
|
|
36232
|
-
|
|
36233
|
-
|
|
36234
|
-
|
|
36235
|
-
|
|
36236
|
-
|
|
36237
|
-
|
|
36238
|
-
|
|
36239
|
-
|
|
36240
|
-
let lastCanvas = null;
|
|
36241
|
-
let lastCanvasTimestamp = null;
|
|
36242
|
-
let lastCanvasEndTimestamp = null;
|
|
36243
|
-
const padFrames = async (until) => {
|
|
36244
|
-
assert(lastCanvas);
|
|
36245
|
-
assert(frameRate !== void 0);
|
|
36246
|
-
const frameDifference = Math.round((until - lastCanvasTimestamp) * frameRate);
|
|
36247
|
-
for (let i = 1; i < frameDifference; i++) {
|
|
36248
|
-
const sample = new VideoSample(lastCanvas, {
|
|
36249
|
-
timestamp: lastCanvasTimestamp + i / frameRate,
|
|
36250
|
-
duration: 1 / frameRate
|
|
36251
|
-
});
|
|
36252
|
-
await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
|
|
36253
|
-
sample.close();
|
|
36254
|
-
}
|
|
36255
|
-
};
|
|
36256
|
-
for await (const { canvas, timestamp, duration } of iterator) {
|
|
36257
|
-
if (this._canceled) {
|
|
36258
|
-
return;
|
|
36259
|
-
}
|
|
36260
|
-
let adjustedSampleTimestamp = Math.max(timestamp - this._startTimestamp, 0);
|
|
36261
|
-
lastCanvasEndTimestamp = adjustedSampleTimestamp + duration;
|
|
36262
|
-
if (frameRate !== void 0) {
|
|
36263
|
-
const alignedTimestamp = floorToDivisor(adjustedSampleTimestamp, frameRate);
|
|
36264
|
-
if (lastCanvas !== null) {
|
|
36265
|
-
if (alignedTimestamp <= lastCanvasTimestamp) {
|
|
36266
|
-
lastCanvas = canvas;
|
|
36267
|
-
lastCanvasTimestamp = alignedTimestamp;
|
|
36268
|
-
continue;
|
|
36269
|
-
} else {
|
|
36270
|
-
await padFrames(alignedTimestamp);
|
|
36271
|
-
}
|
|
36272
|
-
}
|
|
36273
|
-
adjustedSampleTimestamp = alignedTimestamp;
|
|
36274
|
-
}
|
|
36275
|
-
const sample = new VideoSample(canvas, {
|
|
36276
|
-
timestamp: adjustedSampleTimestamp,
|
|
36277
|
-
duration: frameRate !== void 0 ? 1 / frameRate : duration
|
|
36278
|
-
});
|
|
36279
|
-
await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
|
|
37174
|
+
encodingConfig.transform.width = width;
|
|
37175
|
+
encodingConfig.transform.height = height;
|
|
37176
|
+
encodingConfig.transform.fit = trackOptions.fit ?? "fill";
|
|
37177
|
+
encodingConfig.transform.rotate = normalizeRotation(totalRotation - innateRotation);
|
|
37178
|
+
encodingConfig.transform.crop = crop;
|
|
37179
|
+
encodingConfig.transform.alpha = alpha;
|
|
37180
|
+
}
|
|
37181
|
+
const source = new VideoSampleSource(encodingConfig);
|
|
37182
|
+
videoSource = source;
|
|
37183
|
+
this._trackPromises.push((async () => {
|
|
37184
|
+
await this._started;
|
|
37185
|
+
const sink = new VideoSampleSink(track);
|
|
37186
|
+
for await (const sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
|
|
37187
|
+
if (this._canceled) {
|
|
36280
37188
|
sample.close();
|
|
36281
|
-
|
|
36282
|
-
lastCanvas = canvas;
|
|
36283
|
-
lastCanvasTimestamp = adjustedSampleTimestamp;
|
|
36284
|
-
}
|
|
36285
|
-
}
|
|
36286
|
-
if (lastCanvas) {
|
|
36287
|
-
assert(lastCanvasEndTimestamp !== null);
|
|
36288
|
-
assert(frameRate !== void 0);
|
|
36289
|
-
await padFrames(floorToDivisor(lastCanvasEndTimestamp, frameRate));
|
|
36290
|
-
}
|
|
36291
|
-
source.close();
|
|
36292
|
-
this._synchronizer.closeTrack(outputTrackId);
|
|
36293
|
-
})());
|
|
36294
|
-
} else {
|
|
36295
|
-
this._trackPromises.push((async () => {
|
|
36296
|
-
await this._started;
|
|
36297
|
-
const sink = new VideoSampleSink(track);
|
|
36298
|
-
const frameRate = trackOptions.frameRate;
|
|
36299
|
-
let lastSample = null;
|
|
36300
|
-
let lastSampleTimestamp = null;
|
|
36301
|
-
let lastSampleEndTimestamp = null;
|
|
36302
|
-
const padFrames = async (until) => {
|
|
36303
|
-
assert(lastSample);
|
|
36304
|
-
assert(frameRate !== void 0);
|
|
36305
|
-
const frameDifference = Math.round((until - lastSampleTimestamp) * frameRate);
|
|
36306
|
-
for (let i = 1; i < frameDifference; i++) {
|
|
36307
|
-
lastSample.setTimestamp(lastSampleTimestamp + i / frameRate);
|
|
36308
|
-
lastSample.setDuration(1 / frameRate);
|
|
36309
|
-
await this._registerVideoSample(trackOptions, outputTrackId, source, lastSample);
|
|
36310
|
-
}
|
|
36311
|
-
lastSample.close();
|
|
36312
|
-
};
|
|
36313
|
-
for await (const sample of sink.samples(this._startTimestamp, this._endTimestamp)) {
|
|
36314
|
-
if (this._canceled) {
|
|
36315
|
-
sample.close();
|
|
36316
|
-
lastSample?.close();
|
|
36317
|
-
return;
|
|
36318
|
-
}
|
|
36319
|
-
let adjustedSampleTimestamp = Math.max(sample.timestamp - this._startTimestamp, 0);
|
|
36320
|
-
lastSampleEndTimestamp = adjustedSampleTimestamp + sample.duration;
|
|
36321
|
-
if (frameRate !== void 0) {
|
|
36322
|
-
const alignedTimestamp = floorToDivisor(adjustedSampleTimestamp, frameRate);
|
|
36323
|
-
if (lastSample !== null) {
|
|
36324
|
-
if (alignedTimestamp <= lastSampleTimestamp) {
|
|
36325
|
-
lastSample.close();
|
|
36326
|
-
lastSample = sample;
|
|
36327
|
-
lastSampleTimestamp = alignedTimestamp;
|
|
36328
|
-
continue;
|
|
36329
|
-
} else {
|
|
36330
|
-
await padFrames(alignedTimestamp);
|
|
36331
|
-
}
|
|
36332
|
-
}
|
|
36333
|
-
adjustedSampleTimestamp = alignedTimestamp;
|
|
36334
|
-
sample.setDuration(1 / frameRate);
|
|
36335
|
-
}
|
|
36336
|
-
sample.setTimestamp(adjustedSampleTimestamp);
|
|
36337
|
-
await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
|
|
36338
|
-
if (frameRate !== void 0) {
|
|
36339
|
-
lastSample = sample;
|
|
36340
|
-
lastSampleTimestamp = adjustedSampleTimestamp;
|
|
36341
|
-
} else {
|
|
36342
|
-
sample.close();
|
|
36343
|
-
}
|
|
36344
|
-
}
|
|
36345
|
-
if (lastSample) {
|
|
36346
|
-
assert(lastSampleEndTimestamp !== null);
|
|
36347
|
-
assert(frameRate !== void 0);
|
|
36348
|
-
await padFrames(floorToDivisor(lastSampleEndTimestamp, frameRate));
|
|
37189
|
+
return;
|
|
36349
37190
|
}
|
|
36350
|
-
|
|
36351
|
-
|
|
36352
|
-
|
|
36353
|
-
|
|
37191
|
+
const adjustedSampleTimestamp = Math.max(sample.timestamp - this._startTimestamp, 0);
|
|
37192
|
+
sample.setTimestamp(adjustedSampleTimestamp);
|
|
37193
|
+
await this._registerVideoSample(trackOptions, outputTrackId, source, sample);
|
|
37194
|
+
sample.close();
|
|
37195
|
+
}
|
|
37196
|
+
source.close();
|
|
37197
|
+
this._synchronizer.closeTrack(outputTrackId);
|
|
37198
|
+
})());
|
|
36354
37199
|
}
|
|
36355
37200
|
let ownGroup = null;
|
|
36356
37201
|
if (!trackOptions.group) {
|
|
@@ -36668,32 +37513,22 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
36668
37513
|
this.name = "ConversionCanceledError";
|
|
36669
37514
|
}
|
|
36670
37515
|
};
|
|
36671
|
-
var MAX_TIMESTAMP_GAP =
|
|
37516
|
+
var MAX_TIMESTAMP_GAP = 1;
|
|
36672
37517
|
var TrackSynchronizer = class {
|
|
36673
37518
|
constructor() {
|
|
36674
37519
|
this.maxTimestamps = /* @__PURE__ */ new Map();
|
|
36675
37520
|
// Track ID -> timestamp
|
|
36676
37521
|
this.resolvers = [];
|
|
36677
37522
|
}
|
|
36678
|
-
|
|
36679
|
-
|
|
36680
|
-
for (const [, timestamp] of this.maxTimestamps) {
|
|
36681
|
-
newMin = Math.min(newMin, timestamp);
|
|
36682
|
-
}
|
|
36683
|
-
for (let i = 0; i < this.resolvers.length; i++) {
|
|
36684
|
-
const entry = this.resolvers[i];
|
|
36685
|
-
if (entry.timestamp - newMin < MAX_TIMESTAMP_GAP) {
|
|
36686
|
-
entry.resolve();
|
|
36687
|
-
this.resolvers.splice(i, 1);
|
|
36688
|
-
i--;
|
|
36689
|
-
}
|
|
36690
|
-
}
|
|
36691
|
-
return newMin;
|
|
37523
|
+
declareTrack(trackId) {
|
|
37524
|
+
this.maxTimestamps.set(trackId, 0);
|
|
36692
37525
|
}
|
|
36693
37526
|
shouldWait(trackId, timestamp) {
|
|
36694
|
-
|
|
37527
|
+
const currentValue = this.maxTimestamps.get(trackId);
|
|
37528
|
+
assert(currentValue !== void 0);
|
|
37529
|
+
this.maxTimestamps.set(trackId, Math.max(timestamp, currentValue));
|
|
36695
37530
|
const newMin = this.computeMinAndMaybeResolve();
|
|
36696
|
-
return timestamp - newMin
|
|
37531
|
+
return timestamp - newMin > MAX_TIMESTAMP_GAP;
|
|
36697
37532
|
}
|
|
36698
37533
|
wait(timestamp) {
|
|
36699
37534
|
const { promise, resolve } = promiseWithResolvers();
|
|
@@ -36707,6 +37542,21 @@ The @mediabunny/mp3-encoder extension package provides support for encoding MP3.
|
|
|
36707
37542
|
this.maxTimestamps.delete(trackId);
|
|
36708
37543
|
this.computeMinAndMaybeResolve();
|
|
36709
37544
|
}
|
|
37545
|
+
computeMinAndMaybeResolve() {
|
|
37546
|
+
let newMin = Infinity;
|
|
37547
|
+
for (const [, timestamp] of this.maxTimestamps) {
|
|
37548
|
+
newMin = Math.min(newMin, timestamp);
|
|
37549
|
+
}
|
|
37550
|
+
for (let i = 0; i < this.resolvers.length; i++) {
|
|
37551
|
+
const entry = this.resolvers[i];
|
|
37552
|
+
if (entry.timestamp - newMin < MAX_TIMESTAMP_GAP) {
|
|
37553
|
+
entry.resolve();
|
|
37554
|
+
this.resolvers.splice(i, 1);
|
|
37555
|
+
i--;
|
|
37556
|
+
}
|
|
37557
|
+
}
|
|
37558
|
+
return newMin;
|
|
37559
|
+
}
|
|
36710
37560
|
};
|
|
36711
37561
|
|
|
36712
37562
|
// src/index.ts
|