mediabunny 1.45.4 → 1.46.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.
Files changed (44) hide show
  1. package/dist/bundles/mediabunny.cjs +164 -42
  2. package/dist/bundles/mediabunny.min.cjs +11 -11
  3. package/dist/bundles/mediabunny.min.mjs +11 -11
  4. package/dist/bundles/mediabunny.mjs +164 -42
  5. package/dist/bundles/mediabunny.node.cjs +164 -42
  6. package/dist/mediabunny.d.ts +20 -5
  7. package/dist/modules/src/index.d.ts +1 -1
  8. package/dist/modules/src/index.d.ts.map +1 -1
  9. package/dist/modules/src/input-track.js +1 -1
  10. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  11. package/dist/modules/src/isobmff/isobmff-boxes.js +12 -8
  12. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  13. package/dist/modules/src/isobmff/isobmff-demuxer.js +6 -3
  14. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +1 -0
  15. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  16. package/dist/modules/src/isobmff/isobmff-muxer.js +22 -0
  17. package/dist/modules/src/media-sink.d.ts.map +1 -1
  18. package/dist/modules/src/media-sink.js +7 -1
  19. package/dist/modules/src/media-source.d.ts.map +1 -1
  20. package/dist/modules/src/media-source.js +9 -7
  21. package/dist/modules/src/misc.d.ts +10 -0
  22. package/dist/modules/src/misc.d.ts.map +1 -1
  23. package/dist/modules/src/misc.js +1 -1
  24. package/dist/modules/src/reader.d.ts.map +1 -1
  25. package/dist/modules/src/reader.js +4 -4
  26. package/dist/modules/src/sample.d.ts +7 -1
  27. package/dist/modules/src/sample.d.ts.map +1 -1
  28. package/dist/modules/src/sample.js +27 -4
  29. package/dist/modules/src/source.d.ts +5 -5
  30. package/dist/modules/src/source.d.ts.map +1 -1
  31. package/dist/modules/src/source.js +87 -18
  32. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  33. package/package.json +1 -1
  34. package/src/index.ts +1 -0
  35. package/src/input-track.ts +1 -1
  36. package/src/isobmff/isobmff-boxes.ts +12 -8
  37. package/src/isobmff/isobmff-demuxer.ts +7 -3
  38. package/src/isobmff/isobmff-muxer.ts +28 -1
  39. package/src/media-sink.ts +9 -0
  40. package/src/media-source.ts +10 -7
  41. package/src/misc.ts +10 -1
  42. package/src/reader.ts +5 -4
  43. package/src/sample.ts +43 -4
  44. package/src/source.ts +112 -20
@@ -6353,18 +6353,21 @@ var Mediabunny = (() => {
6353
6353
  }
6354
6354
  assert(track.info?.type === "video");
6355
6355
  const colourType = readAscii(slice, 4);
6356
- if (colourType !== "nclx") {
6356
+ if (colourType !== "nclx" && colourType !== "nclc") {
6357
6357
  break;
6358
6358
  }
6359
6359
  const colourPrimaries = readU16Be(slice);
6360
6360
  const transferCharacteristics = readU16Be(slice);
6361
6361
  const matrixCoefficients = readU16Be(slice);
6362
- const fullRangeFlag = Boolean(readU8(slice) & 128);
6362
+ let fullRange = void 0;
6363
+ if (colourType === "nclx") {
6364
+ fullRange = Boolean(readU8(slice) & 128);
6365
+ }
6363
6366
  track.info.colorSpace = {
6364
6367
  primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
6365
6368
  transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
6366
6369
  matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
6367
- fullRange: fullRangeFlag
6370
+ fullRange
6368
6371
  };
6369
6372
  }
6370
6373
  ;
@@ -15956,8 +15959,8 @@ var Mediabunny = (() => {
15956
15959
  /**
15957
15960
  * Creates a new {@link UrlSource} backed by the resource at the specified URL.
15958
15961
  *
15959
- * When passing a `Request` instance, note that the `signal` and `headers.Range` options will be overridden by
15960
- * Mediabunny. If you want to cancel ongoing requests, use {@link Input.dispose}.
15962
+ * When passing a `Request` instance, note that its `signal` will be overridden by Mediabunny; if you want to cancel
15963
+ * ongoing requests, use {@link Input.dispose}.
15961
15964
  */
15962
15965
  constructor(url2, options = {}) {
15963
15966
  if (typeof url2 !== "string" && !(url2 instanceof URL) && !(typeof Request !== "undefined" && url2 instanceof Request)) {
@@ -15983,6 +15986,10 @@ var Mediabunny = (() => {
15983
15986
  }
15984
15987
  const urlString = url2 instanceof Request ? url2.url : url2 instanceof URL ? url2.href : url2;
15985
15988
  super(urlString, (request) => new _UrlSource(request.path, this._options));
15989
+ /** @internal */
15990
+ this._offset = 0;
15991
+ /** @internal */
15992
+ this._length = null;
15986
15993
  /**
15987
15994
  * Note that this value being true does NOT mean the file size can't change anymore; it just signals that we have at
15988
15995
  * least checked if we know the file size or not.
@@ -15992,6 +15999,33 @@ var Mediabunny = (() => {
15992
15999
  this._url = url2;
15993
16000
  this._options = options;
15994
16001
  this._getRetryDelay = options.getRetryDelay ?? DEFAULT_RETRY_DELAY;
16002
+ this._requestInit = { ...options.requestInit };
16003
+ let rangeHeaderValue = null;
16004
+ if (options.requestInit?.headers) {
16005
+ const headers = { ...normalizeHeaders(options.requestInit.headers) };
16006
+ const rangeKey = Object.keys(headers).find((key) => key.toLowerCase() === "range");
16007
+ if (rangeKey !== void 0) {
16008
+ rangeHeaderValue = headers[rangeKey];
16009
+ delete headers[rangeKey];
16010
+ this._requestInit.headers = headers;
16011
+ }
16012
+ }
16013
+ if (url2 instanceof Request) {
16014
+ const requestRange = url2.headers.get("Range");
16015
+ if (requestRange !== null) {
16016
+ rangeHeaderValue ??= requestRange;
16017
+ const strippedRequest = new Request(url2);
16018
+ strippedRequest.headers.delete("Range");
16019
+ this._url = strippedRequest;
16020
+ }
16021
+ }
16022
+ if (rangeHeaderValue !== null) {
16023
+ const parsed = parseByteRangeHeader(rangeHeaderValue);
16024
+ if (parsed) {
16025
+ this._offset = parsed.offset;
16026
+ this._length = parsed.length;
16027
+ }
16028
+ }
15995
16029
  const DEFAULT_PARALLELISM = 2;
15996
16030
  this._orchestrator = new ReadOrchestrator({
15997
16031
  maxCacheSize: options.maxCacheSize ?? 64 * 2 ** 20,
@@ -16002,11 +16036,39 @@ var Mediabunny = (() => {
16002
16036
  }
16003
16037
  /** @internal */
16004
16038
  _getFileSize() {
16005
- return this._fileSizeDetermined ? this._orchestrator.fileSize : void 0;
16039
+ if (!this._fileSizeDetermined) {
16040
+ return this._length !== null ? this._length : void 0;
16041
+ }
16042
+ const baseSize = this._orchestrator.fileSize;
16043
+ if (baseSize === null) {
16044
+ return this._length !== null ? this._length : null;
16045
+ }
16046
+ return clamp(baseSize - this._offset, 0, this._length ?? Infinity);
16006
16047
  }
16007
16048
  /** @internal */
16008
16049
  _read(start, end, minReadPosition, maxReadPosition) {
16009
- return this._orchestrator.read(start, end, minReadPosition, maxReadPosition);
16050
+ if (this._length !== null && end > this._length) {
16051
+ return null;
16052
+ }
16053
+ const offset = this._offset;
16054
+ const result = this._orchestrator.read(
16055
+ offset + start,
16056
+ offset + end,
16057
+ Math.max(offset + minReadPosition, offset),
16058
+ offset + Math.min(maxReadPosition, this._length ?? Infinity)
16059
+ );
16060
+ const processResult = (result2) => {
16061
+ if (!result2) {
16062
+ return null;
16063
+ }
16064
+ result2.offset -= this._offset;
16065
+ return result2;
16066
+ };
16067
+ if (result instanceof Promise) {
16068
+ return result.then(processResult);
16069
+ } else {
16070
+ return processResult(result);
16071
+ }
16010
16072
  }
16011
16073
  /** @internal */
16012
16074
  async _runWorker(worker) {
@@ -16015,7 +16077,7 @@ var Mediabunny = (() => {
16015
16077
  const response = await retriedFetch(
16016
16078
  this._options.fetchFn ?? fetch,
16017
16079
  this._url,
16018
- mergeRequestInit(this._options.requestInit ?? {}, {
16080
+ mergeRequestInit(this._requestInit, {
16019
16081
  headers: {
16020
16082
  // Always sending a range request is a good way to probe if the server supports them
16021
16083
  Range: `bytes=${worker.currentPos}-`
@@ -16124,6 +16186,22 @@ var Mediabunny = (() => {
16124
16186
  this._orchestrator.dispose();
16125
16187
  }
16126
16188
  };
16189
+ var BYTE_RANGE_REGEX = /^bytes=(\d+)-(\d*)$/;
16190
+ var parseByteRangeHeader = (value) => {
16191
+ const match = BYTE_RANGE_REGEX.exec(value.trim());
16192
+ if (!match) {
16193
+ return null;
16194
+ }
16195
+ const offset = Number(match[1]);
16196
+ const end = match[2] === "" ? null : Number(match[2]);
16197
+ if (end !== null && end < offset) {
16198
+ return null;
16199
+ }
16200
+ return {
16201
+ offset,
16202
+ length: end !== null ? end - offset + 1 : null
16203
+ };
16204
+ };
16127
16205
  var FilePathSource = class _FilePathSource extends PathedSource {
16128
16206
  /** Creates a new {@link FilePathSource} backed by the file at the specified file path. */
16129
16207
  constructor(filePath, options = {}) {
@@ -17037,20 +17115,17 @@ var Mediabunny = (() => {
17037
17115
  this._offset + minReadPosition,
17038
17116
  this._offset + maxReadPosition
17039
17117
  );
17040
- if (result instanceof Promise) {
17041
- return result.then((result2) => {
17042
- if (!result2) {
17043
- return null;
17044
- }
17045
- result2.offset -= this._offset;
17046
- return result2;
17047
- });
17048
- } else {
17049
- if (!result) {
17118
+ const processResult = (result2) => {
17119
+ if (!result2) {
17050
17120
  return null;
17051
17121
  }
17052
- result.offset -= this._offset;
17053
- return result;
17122
+ result2.offset -= this._offset;
17123
+ return result2;
17124
+ };
17125
+ if (result instanceof Promise) {
17126
+ return result.then(processResult);
17127
+ } else {
17128
+ return processResult(result);
17054
17129
  }
17055
17130
  }
17056
17131
  /** @internal */
@@ -18993,7 +19068,7 @@ var Mediabunny = (() => {
18993
19068
  "init.displayWidth and init.displayHeight must be either both provided or both omitted."
18994
19069
  );
18995
19070
  }
18996
- this._data = toUint8Array(data).slice();
19071
+ this._data = init._doNotCopy ? toUint8Array(data) : toUint8Array(data).slice();
18997
19072
  this._layout = init.layout ?? createDefaultPlaneLayout(init.format, init.codedWidth, init.codedHeight);
18998
19073
  this.format = init.format;
18999
19074
  this.rotation = init.rotation ?? 0;
@@ -19103,7 +19178,11 @@ var Mediabunny = (() => {
19103
19178
  // Firefox has VideoFrame glitches with opaque canvases
19104
19179
  willReadFrequently: true
19105
19180
  });
19106
- assert(context);
19181
+ if (!context) {
19182
+ throw new Error(
19183
+ "OffscreenCanvas must have support for the '2d' context in order to create a VideoSample from this data."
19184
+ );
19185
+ }
19107
19186
  context.drawImage(data, 0, 0);
19108
19187
  this._data = canvas;
19109
19188
  this._layout = null;
@@ -19168,6 +19247,7 @@ var Mediabunny = (() => {
19168
19247
  "Invalid data type: Must be a BufferSource, CanvasImageSource, or VideoSampleResource."
19169
19248
  );
19170
19249
  }
19250
+ this.encodeOptions = init?.encodeOptions ?? {};
19171
19251
  this.pixelAspectRatio = simplifyRational({
19172
19252
  num: this.squarePixelWidth * this.codedHeight,
19173
19253
  den: this.squarePixelHeight * this.codedWidth
@@ -19215,13 +19295,15 @@ var Mediabunny = (() => {
19215
19295
  return new _VideoSample(this._data, {
19216
19296
  timestamp: this.timestamp,
19217
19297
  duration: this.duration,
19218
- rotation: this.rotation
19298
+ rotation: this.rotation,
19299
+ encodeOptions: this.encodeOptions
19219
19300
  });
19220
19301
  } else if (isVideoFrame(this._data)) {
19221
19302
  return new _VideoSample(this._data.clone(), {
19222
19303
  timestamp: this.timestamp,
19223
19304
  duration: this.duration,
19224
- rotation: this.rotation
19305
+ rotation: this.rotation,
19306
+ encodeOptions: this.encodeOptions
19225
19307
  });
19226
19308
  } else if (this._data instanceof Uint8Array) {
19227
19309
  assert(this._layout);
@@ -19236,7 +19318,10 @@ var Mediabunny = (() => {
19236
19318
  rotation: this.rotation,
19237
19319
  visibleRect: this.visibleRect,
19238
19320
  displayWidth: this.displayWidth,
19239
- displayHeight: this.displayHeight
19321
+ displayHeight: this.displayHeight,
19322
+ encodeOptions: this.encodeOptions,
19323
+ // It's already been copied, if we copy it again we make the clone unnecessarily expensive
19324
+ _doNotCopy: true
19240
19325
  });
19241
19326
  } else {
19242
19327
  return new _VideoSample(this._data, {
@@ -19249,7 +19334,8 @@ var Mediabunny = (() => {
19249
19334
  rotation: this.rotation,
19250
19335
  visibleRect: this.visibleRect,
19251
19336
  displayWidth: this.displayWidth,
19252
- displayHeight: this.displayHeight
19337
+ displayHeight: this.displayHeight,
19338
+ encodeOptions: this.encodeOptions
19253
19339
  });
19254
19340
  }
19255
19341
  }
@@ -19811,7 +19897,11 @@ var Mediabunny = (() => {
19811
19897
  const context = canvas.getContext("2d", {
19812
19898
  alpha: true
19813
19899
  });
19814
- assert(context);
19900
+ if (!context) {
19901
+ throw new Error(
19902
+ "The '2d' canvas context is required to transform VideoSamples. Register a custom transformer using registerVideoSampleTransformer to work around this limitation."
19903
+ );
19904
+ }
19815
19905
  if (description.alpha === "discard") {
19816
19906
  context.fillStyle = "black";
19817
19907
  context.fillRect(0, 0, description.width, description.height);
@@ -19851,6 +19941,13 @@ var Mediabunny = (() => {
19851
19941
  }
19852
19942
  this.duration = newDuration;
19853
19943
  }
19944
+ /** Sets the encode options used when this sample is passed to an encoder. */
19945
+ setEncodeOptions(newEncodeOptions) {
19946
+ if (!newEncodeOptions || typeof newEncodeOptions !== "object") {
19947
+ throw new TypeError("newEncodeOptions must be an object.");
19948
+ }
19949
+ this.encodeOptions = newEncodeOptions;
19950
+ }
19854
19951
  /** Calls `.close()`. */
19855
19952
  [Symbol.dispose]() {
19856
19953
  this.close();
@@ -22130,6 +22227,9 @@ var Mediabunny = (() => {
22130
22227
  const filteredNalUnits = [];
22131
22228
  for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) {
22132
22229
  const type = extractNalUnitTypeForAvc(packet.data[loc.offset]);
22230
+ if (type === 9 /* AUD */) {
22231
+ filteredNalUnits.length = 0;
22232
+ }
22133
22233
  if (!(type >= 20 && type <= 31)) {
22134
22234
  filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length));
22135
22235
  }
@@ -23816,7 +23916,7 @@ var Mediabunny = (() => {
23816
23916
  /** If this method returns true, the track's samples use a high dynamic range (HDR). */
23817
23917
  async hasHighDynamicRange() {
23818
23918
  const colorSpace = await this._backing.getColorSpace();
23819
- return colorSpace.primaries === "bt2020" || colorSpace.primaries === "smpte432" || colorSpace.transfer === "pg" || colorSpace.transfer === "hlg" || colorSpace.matrix === "bt2020-ncl";
23919
+ return colorSpace.primaries === "bt2020" || colorSpace.primaries === "smpte432" || colorSpace.transfer === "pq" || colorSpace.transfer === "hlg" || colorSpace.matrix === "bt2020-ncl";
23820
23920
  }
23821
23921
  /** Checks if this track may contain transparent samples with alpha data. */
23822
23922
  async canBeTransparent() {
@@ -24550,13 +24650,13 @@ var Mediabunny = (() => {
24550
24650
  if (chunks.length === 1 && this.fileSizeNonStrict !== null) {
24551
24651
  return this.requestSlice(0, this.fileSizeNonStrict);
24552
24652
  }
24553
- const startOffset = chunks.length * CHUNK_SIZE;
24554
- let slice = this.requestSliceRange(startOffset, 0, CHUNK_SIZE);
24653
+ let slice = this.requestSliceRange(currentSize, 0, CHUNK_SIZE);
24555
24654
  if (slice instanceof Promise) slice = await slice;
24556
- if (!slice) {
24655
+ if (!slice || slice.length === 0) {
24557
24656
  break;
24558
24657
  }
24559
- chunks.push(readBytes(slice, slice.length));
24658
+ const chunk = readBytes(slice, slice.length);
24659
+ chunks.push(chunk);
24560
24660
  currentSize += slice.length;
24561
24661
  }
24562
24662
  const joined = new Uint8Array(currentSize);
@@ -26413,12 +26513,14 @@ var Mediabunny = (() => {
26413
26513
  };
26414
26514
  var mdat = (reserveLargeSize) => ({ type: "mdat", largeSize: reserveLargeSize });
26415
26515
  var free = (size) => ({ type: "free", size });
26416
- var moov = (muxer) => box("moov", void 0, [
26417
- mvhd(muxer.creationTime, muxer.trackDatas),
26418
- ...muxer.trackDatas.map((x) => trak(x, muxer.creationTime)),
26419
- muxer.isFragmented ? mvex(muxer.trackDatas) : null,
26420
- udta(muxer)
26421
- ]);
26516
+ var moov = (muxer) => {
26517
+ return box("moov", void 0, [
26518
+ mvhd(muxer.creationTime, muxer.trackDatas),
26519
+ ...muxer.trackDatas.map((x) => trak(x, muxer.creationTime)),
26520
+ muxer.isFragmented ? mvex(muxer.trackDatas) : null,
26521
+ udta(muxer)
26522
+ ]);
26523
+ };
26422
26524
  var mvhd = (creationTime, trackDatas) => {
26423
26525
  const duration = Math.max(
26424
26526
  0,
@@ -26729,7 +26831,7 @@ var Mediabunny = (() => {
26729
26831
  ]);
26730
26832
  };
26731
26833
  var colr = (trackData) => box("colr", [
26732
- ascii("nclx"),
26834
+ ascii(trackData.muxer.isQuickTime ? "nclc" : "nclx"),
26733
26835
  // Colour type
26734
26836
  u16(COLOR_PRIMARIES_MAP[trackData.info.decoderConfig.colorSpace.primaries]),
26735
26837
  // Colour primaries
@@ -26737,7 +26839,7 @@ var Mediabunny = (() => {
26737
26839
  // Transfer characteristics
26738
26840
  u16(MATRIX_COEFFICIENTS_MAP[trackData.info.decoderConfig.colorSpace.matrix]),
26739
26841
  // Matrix coefficients
26740
- u8((trackData.info.decoderConfig.colorSpace.fullRange ? 1 : 0) << 7)
26842
+ trackData.muxer.isQuickTime ? [] : u8((trackData.info.decoderConfig.colorSpace.fullRange ? 1 : 0) << 7)
26741
26843
  // Full range flag
26742
26844
  ]);
26743
26845
  var avcC = (trackData) => trackData.info.decoderConfig && box("avcC", [
@@ -29237,6 +29339,7 @@ var Mediabunny = (() => {
29237
29339
  if (this.format._options.onMoov) {
29238
29340
  boxWriter.writer.startTrackingWrites();
29239
29341
  }
29342
+ this.ensureOneEnabledTrack();
29240
29343
  const movieBox = moov(this);
29241
29344
  boxWriter.writeBox(movieBox);
29242
29345
  if (this.format._options.onMoov) {
@@ -29319,6 +29422,7 @@ var Mediabunny = (() => {
29319
29422
  assert(this.boxWriter);
29320
29423
  if (this.allTracksAreKnown()) {
29321
29424
  if (!this.mdat) {
29425
+ this.ensureOneEnabledTrack();
29322
29426
  const moovBox = moov(this);
29323
29427
  const moovSize = this.boxWriter.measureBox(moovBox);
29324
29428
  const reservedSize = moovSize + this.computeSampleTableSizeUpperBound() + 4096;
@@ -29375,10 +29479,27 @@ var Mediabunny = (() => {
29375
29479
  }
29376
29480
  release();
29377
29481
  }
29482
+ ensureOneEnabledTrack() {
29483
+ for (const type of ["video", "audio", "subtitle"]) {
29484
+ const tracks = this.trackDatas.filter((t) => t.type === type);
29485
+ if (tracks.length === 0) {
29486
+ continue;
29487
+ }
29488
+ const hasEnabled = tracks.some((t) => t.track.metadata.disposition?.default !== false);
29489
+ if (!hasEnabled) {
29490
+ const firstTrack = tracks[0];
29491
+ firstTrack.track.metadata.disposition = {
29492
+ ...firstTrack.track.metadata.disposition,
29493
+ default: true
29494
+ };
29495
+ }
29496
+ }
29497
+ }
29378
29498
  /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */
29379
29499
  async finalize() {
29380
29500
  const release = await this.mutex.acquire();
29381
29501
  this.allTracksKnown.resolve();
29502
+ this.ensureOneEnabledTrack();
29382
29503
  for (const trackData of this.trackDatas) {
29383
29504
  trackData.closed = true;
29384
29505
  if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") {
@@ -32331,9 +32452,10 @@ ${cue.notes ?? ""}`;
32331
32452
  assert(this.encoderInitialized);
32332
32453
  const keyFrameInterval = this.encodingConfig.keyFrameInterval ?? 2;
32333
32454
  const multipleOfKeyFrameInterval = Math.floor(sampleToEncode.timestamp / keyFrameInterval);
32455
+ const mergedEncodeOptions = { ...sampleToEncode.encodeOptions, ...encodeOptions };
32334
32456
  const finalEncodeOptions = {
32335
- ...encodeOptions,
32336
- keyFrame: encodeOptions?.keyFrame || keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval
32457
+ ...mergedEncodeOptions,
32458
+ keyFrame: mergedEncodeOptions.keyFrame !== void 0 ? mergedEncodeOptions.keyFrame : keyFrameInterval === 0 || multipleOfKeyFrameInterval !== this.lastMultipleOfKeyFrameInterval
32337
32459
  };
32338
32460
  this.lastMultipleOfKeyFrameInterval = multipleOfKeyFrameInterval;
32339
32461
  if (this.customEncoder) {