mediabunny 1.45.3 → 1.45.5

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 (59) hide show
  1. package/dist/bundles/mediabunny.cjs +216 -83
  2. package/dist/bundles/mediabunny.min.cjs +11 -11
  3. package/dist/bundles/mediabunny.min.mjs +11 -11
  4. package/dist/bundles/mediabunny.mjs +216 -83
  5. package/dist/bundles/mediabunny.node.cjs +216 -83
  6. package/dist/mediabunny.d.ts +64 -41
  7. package/dist/modules/shared/mp3-misc.d.ts +2 -1
  8. package/dist/modules/shared/mp3-misc.d.ts.map +1 -1
  9. package/dist/modules/shared/mp3-misc.js +5 -2
  10. package/dist/modules/src/id3.js +2 -2
  11. package/dist/modules/src/index.d.ts +1 -1
  12. package/dist/modules/src/index.d.ts.map +1 -1
  13. package/dist/modules/src/index.js +3 -1
  14. package/dist/modules/src/input-format.d.ts.map +1 -1
  15. package/dist/modules/src/input-format.js +2 -2
  16. package/dist/modules/src/input-track.js +1 -1
  17. package/dist/modules/src/input.d.ts.map +1 -1
  18. package/dist/modules/src/input.js +0 -12
  19. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  20. package/dist/modules/src/isobmff/isobmff-boxes.js +12 -8
  21. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  22. package/dist/modules/src/isobmff/isobmff-demuxer.js +6 -3
  23. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +1 -0
  24. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  25. package/dist/modules/src/isobmff/isobmff-muxer.js +22 -0
  26. package/dist/modules/src/media-sink.d.ts.map +1 -1
  27. package/dist/modules/src/media-sink.js +7 -1
  28. package/dist/modules/src/misc.d.ts +2 -0
  29. package/dist/modules/src/misc.d.ts.map +1 -1
  30. package/dist/modules/src/misc.js +1 -1
  31. package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +1 -1
  32. package/dist/modules/src/mp3/mp3-demuxer.js +4 -8
  33. package/dist/modules/src/mp3/mp3-reader.d.ts +1 -1
  34. package/dist/modules/src/mp3/mp3-reader.d.ts.map +1 -1
  35. package/dist/modules/src/mp3/mp3-reader.js +13 -6
  36. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.d.ts.map +1 -1
  37. package/dist/modules/src/mpeg-ts/mpeg-ts-demuxer.js +2 -2
  38. package/dist/modules/src/reader.d.ts.map +1 -1
  39. package/dist/modules/src/reader.js +4 -4
  40. package/dist/modules/src/source.d.ts +32 -11
  41. package/dist/modules/src/source.d.ts.map +1 -1
  42. package/dist/modules/src/source.js +151 -37
  43. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +1 -1
  45. package/src/id3.ts +2 -2
  46. package/src/index.ts +4 -0
  47. package/src/input-format.ts +6 -2
  48. package/src/input-track.ts +1 -1
  49. package/src/input.ts +0 -14
  50. package/src/isobmff/isobmff-boxes.ts +12 -8
  51. package/src/isobmff/isobmff-demuxer.ts +7 -3
  52. package/src/isobmff/isobmff-muxer.ts +28 -1
  53. package/src/media-sink.ts +9 -0
  54. package/src/misc.ts +1 -1
  55. package/src/mp3/mp3-demuxer.ts +9 -10
  56. package/src/mp3/mp3-reader.ts +21 -6
  57. package/src/mpeg-ts/mpeg-ts-demuxer.ts +6 -2
  58. package/src/reader.ts +5 -4
  59. package/src/source.ts +205 -44
@@ -120,6 +120,7 @@ var Mediabunny = (() => {
120
120
  CustomAudioDecoder: () => CustomAudioDecoder,
121
121
  CustomAudioEncoder: () => CustomAudioEncoder,
122
122
  CustomPathedSource: () => CustomPathedSource,
123
+ CustomSource: () => CustomSource,
123
124
  CustomVideoDecoder: () => CustomVideoDecoder,
124
125
  CustomVideoEncoder: () => CustomVideoEncoder,
125
126
  EncodedAudioPacketSource: () => EncodedAudioPacketSource,
@@ -2283,7 +2284,7 @@ var Mediabunny = (() => {
2283
2284
  };
2284
2285
 
2285
2286
  // shared/mp3-misc.ts
2286
- var FRAME_HEADER_SIZE = 4;
2287
+ var MP3_FRAME_HEADER_SIZE = 4;
2287
2288
  var SAMPLING_RATES = [44100, 48e3, 32e3];
2288
2289
  var KILOBIT_RATES = [
2289
2290
  // lowSamplingFrequency === 0
@@ -2551,6 +2552,9 @@ var Mediabunny = (() => {
2551
2552
  }
2552
2553
  return unsynchsafed;
2553
2554
  };
2555
+ var getMp3ChannelCount = (channel) => {
2556
+ return channel === 3 ? 1 : 2;
2557
+ };
2554
2558
 
2555
2559
  // shared/ac3-misc.ts
2556
2560
  var AC3_SAMPLE_RATES = [48e3, 44100, 32e3];
@@ -6349,18 +6353,21 @@ var Mediabunny = (() => {
6349
6353
  }
6350
6354
  assert(track.info?.type === "video");
6351
6355
  const colourType = readAscii(slice, 4);
6352
- if (colourType !== "nclx") {
6356
+ if (colourType !== "nclx" && colourType !== "nclc") {
6353
6357
  break;
6354
6358
  }
6355
6359
  const colourPrimaries = readU16Be(slice);
6356
6360
  const transferCharacteristics = readU16Be(slice);
6357
6361
  const matrixCoefficients = readU16Be(slice);
6358
- const fullRangeFlag = Boolean(readU8(slice) & 128);
6362
+ let fullRange = void 0;
6363
+ if (colourType === "nclx") {
6364
+ fullRange = Boolean(readU8(slice) & 128);
6365
+ }
6359
6366
  track.info.colorSpace = {
6360
6367
  primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
6361
6368
  transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
6362
6369
  matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
6363
- fullRange: fullRangeFlag
6370
+ fullRange
6364
6371
  };
6365
6372
  }
6366
6373
  ;
@@ -10880,20 +10887,22 @@ var Mediabunny = (() => {
10880
10887
  };
10881
10888
 
10882
10889
  // src/mp3/mp3-reader.ts
10883
- var readNextMp3FrameHeader = async (reader, startPos, until) => {
10890
+ var readNextMp3FrameHeader = async (reader, startPos, until, ref = null) => {
10884
10891
  const CHUNK_SIZE = 2 ** 16;
10885
10892
  let currentPos = startPos;
10886
10893
  while (until === null || currentPos < until) {
10887
10894
  const maxLength = until !== null ? Math.min(CHUNK_SIZE, until - currentPos) : CHUNK_SIZE;
10888
- let slice = reader.requestSliceRange(currentPos, FRAME_HEADER_SIZE, maxLength);
10895
+ let slice = reader.requestSliceRange(currentPos, MP3_FRAME_HEADER_SIZE, maxLength);
10889
10896
  if (slice instanceof Promise) slice = await slice;
10890
- if (!slice || slice.length < FRAME_HEADER_SIZE) break;
10891
- while (slice.remainingLength >= FRAME_HEADER_SIZE) {
10897
+ if (!slice || slice.length < MP3_FRAME_HEADER_SIZE) break;
10898
+ while (slice.remainingLength >= MP3_FRAME_HEADER_SIZE) {
10892
10899
  const posBeforeRead = slice.filePos;
10893
10900
  const word = readU32Be(slice);
10894
10901
  const remainingBytes = reader.fileSize !== null ? reader.fileSize - currentPos : null;
10895
10902
  const result = readMp3FrameHeader(word, remainingBytes);
10896
- if (result.header) {
10903
+ if (result.header && (!ref || // This condition helps us recover malformed streams
10904
+ // https://stackoverflow.com/a/20884944
10905
+ result.header.sampleRate === ref.sampleRate && result.header.mpegVersionId === ref.mpegVersionId && result.header.layer === ref.layer && getMp3ChannelCount(result.header.channel) === getMp3ChannelCount(ref.channel))) {
10897
10906
  return { header: result.header, startPos: currentPos };
10898
10907
  }
10899
10908
  slice.filePos = posBeforeRead + result.bytesAdvanced;
@@ -10948,7 +10957,12 @@ var Mediabunny = (() => {
10948
10957
  this.lastLoadedPos = slice2.filePos + id3V2Header.size;
10949
10958
  }
10950
10959
  }
10951
- const result = await readNextMp3FrameHeader(this.reader, this.lastLoadedPos, this.reader.fileSize);
10960
+ const result = await readNextMp3FrameHeader(
10961
+ this.reader,
10962
+ this.lastLoadedPos,
10963
+ this.reader.fileSize,
10964
+ this.firstFrameHeader
10965
+ );
10952
10966
  if (!result) {
10953
10967
  this.lastSampleLoaded = true;
10954
10968
  return;
@@ -10982,11 +10996,6 @@ var Mediabunny = (() => {
10982
10996
  this.firstFrameHeader = header;
10983
10997
  this.firstFrameHeaderPos = result.startPos;
10984
10998
  }
10985
- if (header.sampleRate !== this.firstFrameHeader.sampleRate) {
10986
- console.warn(
10987
- `MP3 changed sample rate mid-file: ${this.firstFrameHeader.sampleRate} Hz to ${header.sampleRate} Hz. Might be a bug, so please report this file.`
10988
- );
10989
- }
10990
10999
  const sampleDuration = header.audioSamplesInFrame / this.firstFrameHeader.sampleRate;
10991
11000
  const sample = {
10992
11001
  timestamp: this.nextTimestampInSamples / this.firstFrameHeader.sampleRate,
@@ -11113,7 +11122,7 @@ var Mediabunny = (() => {
11113
11122
  }
11114
11123
  getNumberOfChannels() {
11115
11124
  assert(this.demuxer.firstFrameHeader);
11116
- return this.demuxer.firstFrameHeader.channel === 3 ? 1 : 2;
11125
+ return getMp3ChannelCount(this.demuxer.firstFrameHeader.channel);
11117
11126
  }
11118
11127
  getSampleRate() {
11119
11128
  assert(this.demuxer.firstFrameHeader);
@@ -11128,7 +11137,7 @@ var Mediabunny = (() => {
11128
11137
  assert(this.demuxer.firstFrameHeader);
11129
11138
  return {
11130
11139
  codec: "mp3",
11131
- numberOfChannels: this.demuxer.firstFrameHeader.channel === 3 ? 1 : 2,
11140
+ numberOfChannels: getMp3ChannelCount(this.demuxer.firstFrameHeader.channel),
11132
11141
  sampleRate: this.demuxer.firstFrameHeader.sampleRate
11133
11142
  };
11134
11143
  }
@@ -13850,7 +13859,7 @@ var Mediabunny = (() => {
13850
13859
  "Invalid MP3 audio stream; could not read frame header from first packet."
13851
13860
  );
13852
13861
  }
13853
- elementaryStream.info.numberOfChannels = result.header.channel === 3 ? 1 : 2;
13862
+ elementaryStream.info.numberOfChannels = getMp3ChannelCount(result.header.channel);
13854
13863
  elementaryStream.info.sampleRate = result.header.sampleRate;
13855
13864
  } else if (elementaryStream.info.codec === "ac3") {
13856
13865
  const frameInfo = parseAc3SyncFrame(context.suppliedPacket.data);
@@ -14914,12 +14923,12 @@ var Mediabunny = (() => {
14914
14923
  }
14915
14924
  this.skip(-1);
14916
14925
  const possibleHeaderStartPos = this.currentPos;
14917
- let remaining2 = this.ensureBuffered(FRAME_HEADER_SIZE);
14926
+ let remaining2 = this.ensureBuffered(MP3_FRAME_HEADER_SIZE);
14918
14927
  if (remaining2 instanceof Promise) remaining2 = await remaining2;
14919
- if (remaining2 < FRAME_HEADER_SIZE) {
14928
+ if (remaining2 < MP3_FRAME_HEADER_SIZE) {
14920
14929
  return;
14921
14930
  }
14922
- const headerBytes = this.readBytes(FRAME_HEADER_SIZE);
14931
+ const headerBytes = this.readBytes(MP3_FRAME_HEADER_SIZE);
14923
14932
  const word = toDataView(headerBytes).getUint32(0);
14924
14933
  const result = readMp3FrameHeader(word, null);
14925
14934
  if (result.header) {
@@ -15562,9 +15571,15 @@ var Mediabunny = (() => {
15562
15571
  var node = typeof nodeAlias !== "undefined" ? nodeAlias : void 0;
15563
15572
  var DEFAULT_MIN_READ_POSITION = 0;
15564
15573
  var DEFAULT_MAX_READ_POSITION = Infinity;
15574
+ var sourceFinalizationRegistry = null;
15575
+ if (typeof FinalizationRegistry !== "undefined") {
15576
+ sourceFinalizationRegistry = new FinalizationRegistry((cleanup) => {
15577
+ cleanup();
15578
+ });
15579
+ }
15565
15580
  var Source = class extends EventEmitter {
15566
15581
  constructor() {
15567
- super(...arguments);
15582
+ super();
15568
15583
  /** @internal */
15569
15584
  this._disposed = false;
15570
15585
  /** @internal */
@@ -15574,6 +15589,13 @@ var Mediabunny = (() => {
15574
15589
  * @internal
15575
15590
  */
15576
15591
  this._usedForHls = false;
15592
+ /**
15593
+ * FinalizationRegistry for rogue refs to this source that didn't get freed. It lives on the Source itself so that
15594
+ * in case the Source transitively points back to itself and forms a cycle (for example through a custom
15595
+ * CustomSource callback) that we're not leaking memory.
15596
+ * @internal
15597
+ */
15598
+ this._refFinalizationRegistry = null;
15577
15599
  /** @internal */
15578
15600
  this._sizePromise = null;
15579
15601
  /**
@@ -15582,6 +15604,11 @@ var Mediabunny = (() => {
15582
15604
  * @deprecated Use `source.on('read', ({ start, end }) => ...)` instead.
15583
15605
  */
15584
15606
  this.onread = null;
15607
+ if (typeof FinalizationRegistry !== "undefined") {
15608
+ this._refFinalizationRegistry = new FinalizationRegistry((source) => {
15609
+ source._decrementRefCount();
15610
+ });
15611
+ }
15585
15612
  }
15586
15613
  /**
15587
15614
  * Resolves with the total size of the file in bytes. This function is memoized, meaning only the first call
@@ -15647,6 +15674,18 @@ var Mediabunny = (() => {
15647
15674
  ref() {
15648
15675
  return new SourceRef(this);
15649
15676
  }
15677
+ /** @internal */
15678
+ _incrementRefCount() {
15679
+ this._refCount++;
15680
+ }
15681
+ /** @internal */
15682
+ _decrementRefCount() {
15683
+ this._refCount--;
15684
+ if (this._refCount === 0) {
15685
+ this._dispose();
15686
+ this._disposed = true;
15687
+ }
15688
+ }
15650
15689
  };
15651
15690
  var SourceRef = class {
15652
15691
  /** @internal */
@@ -15656,7 +15695,8 @@ var Mediabunny = (() => {
15656
15695
  if (source._disposed) {
15657
15696
  throw new Error("Cannot ref a disposed source.");
15658
15697
  }
15659
- source._refCount++;
15698
+ source._incrementRefCount();
15699
+ source._refFinalizationRegistry?.register(this, source, this);
15660
15700
  this._source = source;
15661
15701
  }
15662
15702
  /** The {@link Source} this ref references. Accessing this field throws an error after having freed the ref. */
@@ -15680,11 +15720,8 @@ var Mediabunny = (() => {
15680
15720
  }
15681
15721
  const source = this.source;
15682
15722
  assert(source._refCount > 0);
15683
- source._refCount--;
15684
- if (source._refCount === 0) {
15685
- source._dispose();
15686
- source._disposed = true;
15687
- }
15723
+ source._decrementRefCount();
15724
+ source._refFinalizationRegistry?.unregister(this);
15688
15725
  this._freed = true;
15689
15726
  this._source = null;
15690
15727
  }
@@ -15922,8 +15959,8 @@ var Mediabunny = (() => {
15922
15959
  /**
15923
15960
  * Creates a new {@link UrlSource} backed by the resource at the specified URL.
15924
15961
  *
15925
- * When passing a `Request` instance, note that the `signal` and `headers.Range` options will be overridden by
15926
- * 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}.
15927
15964
  */
15928
15965
  constructor(url2, options = {}) {
15929
15966
  if (typeof url2 !== "string" && !(url2 instanceof URL) && !(typeof Request !== "undefined" && url2 instanceof Request)) {
@@ -15949,6 +15986,10 @@ var Mediabunny = (() => {
15949
15986
  }
15950
15987
  const urlString = url2 instanceof Request ? url2.url : url2 instanceof URL ? url2.href : url2;
15951
15988
  super(urlString, (request) => new _UrlSource(request.path, this._options));
15989
+ /** @internal */
15990
+ this._offset = 0;
15991
+ /** @internal */
15992
+ this._length = null;
15952
15993
  /**
15953
15994
  * Note that this value being true does NOT mean the file size can't change anymore; it just signals that we have at
15954
15995
  * least checked if we know the file size or not.
@@ -15958,6 +15999,33 @@ var Mediabunny = (() => {
15958
15999
  this._url = url2;
15959
16000
  this._options = options;
15960
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
+ }
15961
16029
  const DEFAULT_PARALLELISM = 2;
15962
16030
  this._orchestrator = new ReadOrchestrator({
15963
16031
  maxCacheSize: options.maxCacheSize ?? 64 * 2 ** 20,
@@ -15968,11 +16036,39 @@ var Mediabunny = (() => {
15968
16036
  }
15969
16037
  /** @internal */
15970
16038
  _getFileSize() {
15971
- 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);
15972
16047
  }
15973
16048
  /** @internal */
15974
16049
  _read(start, end, minReadPosition, maxReadPosition) {
15975
- 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
+ }
15976
16072
  }
15977
16073
  /** @internal */
15978
16074
  async _runWorker(worker) {
@@ -15981,7 +16077,7 @@ var Mediabunny = (() => {
15981
16077
  const response = await retriedFetch(
15982
16078
  this._options.fetchFn ?? fetch,
15983
16079
  this._url,
15984
- mergeRequestInit(this._options.requestInit ?? {}, {
16080
+ mergeRequestInit(this._requestInit, {
15985
16081
  headers: {
15986
16082
  // Always sending a range request is a good way to probe if the server supports them
15987
16083
  Range: `bytes=${worker.currentPos}-`
@@ -16090,6 +16186,22 @@ var Mediabunny = (() => {
16090
16186
  this._orchestrator.dispose();
16091
16187
  }
16092
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
+ };
16093
16205
  var FilePathSource = class _FilePathSource extends PathedSource {
16094
16206
  /** Creates a new {@link FilePathSource} backed by the file at the specified file path. */
16095
16207
  constructor(filePath, options = {}) {
@@ -16110,10 +16222,14 @@ var Mediabunny = (() => {
16110
16222
  super(filePath, (request) => new _FilePathSource(request.path, options));
16111
16223
  /** @internal */
16112
16224
  this._fileHandle = null;
16113
- this._streamSource = new StreamSource({
16225
+ this._customSource = new CustomSource({
16114
16226
  getSize: async () => {
16115
- this._fileHandle = await node.fs.open(filePath, "r");
16116
- const stats = await this._fileHandle.stat();
16227
+ const fileHandle = await node.fs.open(filePath, "r");
16228
+ this._fileHandle = fileHandle;
16229
+ sourceFinalizationRegistry?.register(this, () => {
16230
+ void fileHandle.close();
16231
+ }, this);
16232
+ const stats = await fileHandle.stat();
16117
16233
  return stats.size;
16118
16234
  },
16119
16235
  read: async (start, end) => {
@@ -16128,21 +16244,24 @@ var Mediabunny = (() => {
16128
16244
  }
16129
16245
  /** @internal */
16130
16246
  _read(start, end, minReadPosition, maxReadPosition) {
16131
- return this._streamSource._read(start, end, minReadPosition, maxReadPosition);
16247
+ return this._customSource._read(start, end, minReadPosition, maxReadPosition);
16132
16248
  }
16133
16249
  /** @internal */
16134
16250
  _getFileSize() {
16135
- return this._streamSource._getFileSize();
16251
+ return this._customSource._getFileSize();
16136
16252
  }
16137
16253
  /** @internal */
16138
16254
  _dispose() {
16139
- this._streamSource._dispose();
16140
- void this._fileHandle?.close();
16141
- this._fileHandle = null;
16255
+ this._customSource._dispose();
16256
+ if (this._fileHandle) {
16257
+ void this._fileHandle.close();
16258
+ this._fileHandle = null;
16259
+ sourceFinalizationRegistry?.unregister(this);
16260
+ }
16142
16261
  }
16143
16262
  };
16144
- var StreamSource = class extends Source {
16145
- /** Creates a new {@link StreamSource} whose behavior is specified by `options`. */
16263
+ var CustomSource = class extends Source {
16264
+ /** Creates a new {@link CustomSource} whose behavior is specified by `options`. */
16146
16265
  constructor(options) {
16147
16266
  if (!options || typeof options !== "object") {
16148
16267
  throw new TypeError("options must be an object.");
@@ -16253,6 +16372,7 @@ var Mediabunny = (() => {
16253
16372
  this._options.dispose?.();
16254
16373
  }
16255
16374
  };
16375
+ var StreamSource = CustomSource;
16256
16376
  var ReadableStreamSource = class extends Source {
16257
16377
  /** Creates a new {@link ReadableStreamSource} backed by the specified `ReadableStream<Uint8Array>`. */
16258
16378
  constructor(stream, options = {}) {
@@ -16995,20 +17115,17 @@ var Mediabunny = (() => {
16995
17115
  this._offset + minReadPosition,
16996
17116
  this._offset + maxReadPosition
16997
17117
  );
16998
- if (result instanceof Promise) {
16999
- return result.then((result2) => {
17000
- if (!result2) {
17001
- return null;
17002
- }
17003
- result2.offset -= this._offset;
17004
- return result2;
17005
- });
17006
- } else {
17007
- if (!result) {
17118
+ const processResult = (result2) => {
17119
+ if (!result2) {
17008
17120
  return null;
17009
17121
  }
17010
- result.offset -= this._offset;
17011
- 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);
17012
17129
  }
17013
17130
  }
17014
17131
  /** @internal */
@@ -18449,7 +18566,11 @@ var Mediabunny = (() => {
18449
18566
  return true;
18450
18567
  }
18451
18568
  currentPos = firstResult.startPos + firstResult.header.totalSize;
18452
- const secondResult = await readNextMp3FrameHeader(input._reader, currentPos, currentPos + FRAME_HEADER_SIZE);
18569
+ const secondResult = await readNextMp3FrameHeader(
18570
+ input._reader,
18571
+ currentPos,
18572
+ currentPos + MP3_FRAME_HEADER_SIZE
18573
+ );
18453
18574
  if (!secondResult) {
18454
18575
  return false;
18455
18576
  }
@@ -22084,6 +22205,9 @@ var Mediabunny = (() => {
22084
22205
  const filteredNalUnits = [];
22085
22206
  for (const loc of iterateAvcNalUnits(packet.data, this.decoderConfig)) {
22086
22207
  const type = extractNalUnitTypeForAvc(packet.data[loc.offset]);
22208
+ if (type === 9 /* AUD */) {
22209
+ filteredNalUnits.length = 0;
22210
+ }
22087
22211
  if (!(type >= 20 && type <= 31)) {
22088
22212
  filteredNalUnits.push(packet.data.subarray(loc.offset, loc.offset + loc.length));
22089
22213
  }
@@ -23770,7 +23894,7 @@ var Mediabunny = (() => {
23770
23894
  /** If this method returns true, the track's samples use a high dynamic range (HDR). */
23771
23895
  async hasHighDynamicRange() {
23772
23896
  const colorSpace = await this._backing.getColorSpace();
23773
- return colorSpace.primaries === "bt2020" || colorSpace.primaries === "smpte432" || colorSpace.transfer === "pg" || colorSpace.transfer === "hlg" || colorSpace.matrix === "bt2020-ncl";
23897
+ return colorSpace.primaries === "bt2020" || colorSpace.primaries === "smpte432" || colorSpace.transfer === "pq" || colorSpace.transfer === "hlg" || colorSpace.matrix === "bt2020-ncl";
23774
23898
  }
23775
23899
  /** Checks if this track may contain transparent samples with alpha data. */
23776
23900
  async canBeTransparent() {
@@ -24050,16 +24174,6 @@ var Mediabunny = (() => {
24050
24174
  polyfillSymbolDispose();
24051
24175
  var DEFAULT_SOURCE_CACHE_GROUP = 1;
24052
24176
  var ENCRYPTION_KEY_CACHE_GROUP = 2;
24053
- var inputFinalizationRegistry = null;
24054
- if (typeof FinalizationRegistry !== "undefined") {
24055
- inputFinalizationRegistry = new FinalizationRegistry((refs) => {
24056
- for (const ref of refs) {
24057
- if (!ref.freed) {
24058
- ref.free();
24059
- }
24060
- }
24061
- });
24062
- }
24063
24177
  var Input = class _Input extends EventEmitter {
24064
24178
  /**
24065
24179
  * Creates a new input file from the specified options. No reading operations will be performed until methods are
@@ -24114,7 +24228,6 @@ var Mediabunny = (() => {
24114
24228
  this._rootRef = options.source;
24115
24229
  }
24116
24230
  this._sourceRefs.push(this._rootRef);
24117
- inputFinalizationRegistry?.register(this, this._sourceRefs, this);
24118
24231
  }
24119
24232
  /** True if the input has been disposed. */
24120
24233
  get disposed() {
@@ -24402,7 +24515,6 @@ var Mediabunny = (() => {
24402
24515
  ref.free();
24403
24516
  }
24404
24517
  this._sourceRefs.length = 0;
24405
- inputFinalizationRegistry?.unregister(this);
24406
24518
  void this._demuxerPromise?.then((demuxer) => demuxer.dispose());
24407
24519
  }
24408
24520
  /**
@@ -24516,13 +24628,13 @@ var Mediabunny = (() => {
24516
24628
  if (chunks.length === 1 && this.fileSizeNonStrict !== null) {
24517
24629
  return this.requestSlice(0, this.fileSizeNonStrict);
24518
24630
  }
24519
- const startOffset = chunks.length * CHUNK_SIZE;
24520
- let slice = this.requestSliceRange(startOffset, 0, CHUNK_SIZE);
24631
+ let slice = this.requestSliceRange(currentSize, 0, CHUNK_SIZE);
24521
24632
  if (slice instanceof Promise) slice = await slice;
24522
- if (!slice) {
24633
+ if (!slice || slice.length === 0) {
24523
24634
  break;
24524
24635
  }
24525
- chunks.push(readBytes(slice, slice.length));
24636
+ const chunk = readBytes(slice, slice.length);
24637
+ chunks.push(chunk);
24526
24638
  currentSize += slice.length;
24527
24639
  }
24528
24640
  const joined = new Uint8Array(currentSize);
@@ -24918,7 +25030,7 @@ var Mediabunny = (() => {
24918
25030
  const yearText = readId3V1String(slice, 4);
24919
25031
  const year = Number.parseInt(yearText, 10);
24920
25032
  if (Number.isInteger(year) && year > 0) {
24921
- tags.date ??= new Date(year, 0, 1);
25033
+ tags.date ??= new Date(String(year));
24922
25034
  }
24923
25035
  const commentBytes = readBytes(slice, 30);
24924
25036
  let comment;
@@ -25138,7 +25250,7 @@ var Mediabunny = (() => {
25138
25250
  const yearText = reader.readId3V2EncodingAndText(frameEndPos);
25139
25251
  const year = Number.parseInt(yearText, 10);
25140
25252
  if (Number.isInteger(year)) {
25141
- tags.date ??= new Date(year, 0, 1);
25253
+ tags.date ??= new Date(String(year));
25142
25254
  }
25143
25255
  }
25144
25256
  ;
@@ -26379,12 +26491,14 @@ var Mediabunny = (() => {
26379
26491
  };
26380
26492
  var mdat = (reserveLargeSize) => ({ type: "mdat", largeSize: reserveLargeSize });
26381
26493
  var free = (size) => ({ type: "free", size });
26382
- var moov = (muxer) => box("moov", void 0, [
26383
- mvhd(muxer.creationTime, muxer.trackDatas),
26384
- ...muxer.trackDatas.map((x) => trak(x, muxer.creationTime)),
26385
- muxer.isFragmented ? mvex(muxer.trackDatas) : null,
26386
- udta(muxer)
26387
- ]);
26494
+ var moov = (muxer) => {
26495
+ return box("moov", void 0, [
26496
+ mvhd(muxer.creationTime, muxer.trackDatas),
26497
+ ...muxer.trackDatas.map((x) => trak(x, muxer.creationTime)),
26498
+ muxer.isFragmented ? mvex(muxer.trackDatas) : null,
26499
+ udta(muxer)
26500
+ ]);
26501
+ };
26388
26502
  var mvhd = (creationTime, trackDatas) => {
26389
26503
  const duration = Math.max(
26390
26504
  0,
@@ -26695,7 +26809,7 @@ var Mediabunny = (() => {
26695
26809
  ]);
26696
26810
  };
26697
26811
  var colr = (trackData) => box("colr", [
26698
- ascii("nclx"),
26812
+ ascii(trackData.muxer.isQuickTime ? "nclc" : "nclx"),
26699
26813
  // Colour type
26700
26814
  u16(COLOR_PRIMARIES_MAP[trackData.info.decoderConfig.colorSpace.primaries]),
26701
26815
  // Colour primaries
@@ -26703,7 +26817,7 @@ var Mediabunny = (() => {
26703
26817
  // Transfer characteristics
26704
26818
  u16(MATRIX_COEFFICIENTS_MAP[trackData.info.decoderConfig.colorSpace.matrix]),
26705
26819
  // Matrix coefficients
26706
- u8((trackData.info.decoderConfig.colorSpace.fullRange ? 1 : 0) << 7)
26820
+ trackData.muxer.isQuickTime ? [] : u8((trackData.info.decoderConfig.colorSpace.fullRange ? 1 : 0) << 7)
26707
26821
  // Full range flag
26708
26822
  ]);
26709
26823
  var avcC = (trackData) => trackData.info.decoderConfig && box("avcC", [
@@ -29203,6 +29317,7 @@ var Mediabunny = (() => {
29203
29317
  if (this.format._options.onMoov) {
29204
29318
  boxWriter.writer.startTrackingWrites();
29205
29319
  }
29320
+ this.ensureOneEnabledTrack();
29206
29321
  const movieBox = moov(this);
29207
29322
  boxWriter.writeBox(movieBox);
29208
29323
  if (this.format._options.onMoov) {
@@ -29285,6 +29400,7 @@ var Mediabunny = (() => {
29285
29400
  assert(this.boxWriter);
29286
29401
  if (this.allTracksAreKnown()) {
29287
29402
  if (!this.mdat) {
29403
+ this.ensureOneEnabledTrack();
29288
29404
  const moovBox = moov(this);
29289
29405
  const moovSize = this.boxWriter.measureBox(moovBox);
29290
29406
  const reservedSize = moovSize + this.computeSampleTableSizeUpperBound() + 4096;
@@ -29341,10 +29457,27 @@ var Mediabunny = (() => {
29341
29457
  }
29342
29458
  release();
29343
29459
  }
29460
+ ensureOneEnabledTrack() {
29461
+ for (const type of ["video", "audio", "subtitle"]) {
29462
+ const tracks = this.trackDatas.filter((t) => t.type === type);
29463
+ if (tracks.length === 0) {
29464
+ continue;
29465
+ }
29466
+ const hasEnabled = tracks.some((t) => t.track.metadata.disposition?.default !== false);
29467
+ if (!hasEnabled) {
29468
+ const firstTrack = tracks[0];
29469
+ firstTrack.track.metadata.disposition = {
29470
+ ...firstTrack.track.metadata.disposition,
29471
+ default: true
29472
+ };
29473
+ }
29474
+ }
29475
+ }
29344
29476
  /** Finalizes the file, making it ready for use. Must be called after all video and audio chunks have been added. */
29345
29477
  async finalize() {
29346
29478
  const release = await this.mutex.acquire();
29347
29479
  this.allTracksKnown.resolve();
29480
+ this.ensureOneEnabledTrack();
29348
29481
  for (const trackData of this.trackDatas) {
29349
29482
  trackData.closed = true;
29350
29483
  if (trackData.type === "subtitle" && trackData.track.source._codec === "webvtt") {