mediabunny 1.0.5 → 1.1.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.
@@ -6224,6 +6224,9 @@ ${cue.notes ?? ""}`;
6224
6224
  return null;
6225
6225
  }
6226
6226
  reader.pos += 1;
6227
+ if (firstByte !== 255) {
6228
+ return null;
6229
+ }
6227
6230
  if ((secondByte & 224) !== 224) {
6228
6231
  return null;
6229
6232
  }
@@ -7366,18 +7369,52 @@ ${cue.notes ?? ""}`;
7366
7369
  });
7367
7370
  }
7368
7371
  }
7369
- /**
7370
- * Draws the video sample to a 2D canvas context. Rotation metadata will be taken into account.
7371
- *
7372
- * @param dx - The x-coordinate in the destination canvas at which to place the top-left corner of the source image.
7373
- * @param dy - The y-coordinate in the destination canvas at which to place the top-left corner of the source image.
7374
- * @param dWidth - The width in pixels with which to draw the image in the destination canvas.
7375
- * @param dHeight - The height in pixels with which to draw the image in the destination canvas.
7376
- */
7377
- draw(context, dx, dy, dWidth = this.displayWidth, dHeight = this.displayHeight) {
7372
+ draw(context, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) {
7373
+ let sx = 0;
7374
+ let sy = 0;
7375
+ let sWidth = this.displayWidth;
7376
+ let sHeight = this.displayHeight;
7377
+ let dx = 0;
7378
+ let dy = 0;
7379
+ let dWidth = this.displayWidth;
7380
+ let dHeight = this.displayHeight;
7381
+ if (arg5 !== void 0) {
7382
+ sx = arg1;
7383
+ sy = arg2;
7384
+ sWidth = arg3;
7385
+ sHeight = arg4;
7386
+ dx = arg5;
7387
+ dy = arg6;
7388
+ if (arg7 !== void 0) {
7389
+ dWidth = arg7;
7390
+ dHeight = arg8;
7391
+ } else {
7392
+ dWidth = sWidth;
7393
+ dHeight = sHeight;
7394
+ }
7395
+ } else {
7396
+ dx = arg1;
7397
+ dy = arg2;
7398
+ if (arg3 !== void 0) {
7399
+ dWidth = arg3;
7400
+ dHeight = arg4;
7401
+ }
7402
+ }
7378
7403
  if (!(typeof CanvasRenderingContext2D !== "undefined" && context instanceof CanvasRenderingContext2D || typeof OffscreenCanvasRenderingContext2D !== "undefined" && context instanceof OffscreenCanvasRenderingContext2D)) {
7379
7404
  throw new TypeError("context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D.");
7380
7405
  }
7406
+ if (!Number.isFinite(sx)) {
7407
+ throw new TypeError("sx must be a number.");
7408
+ }
7409
+ if (!Number.isFinite(sy)) {
7410
+ throw new TypeError("sy must be a number.");
7411
+ }
7412
+ if (!Number.isFinite(sWidth) || sWidth < 0) {
7413
+ throw new TypeError("sWidth must be a non-negative number.");
7414
+ }
7415
+ if (!Number.isFinite(sHeight) || sHeight < 0) {
7416
+ throw new TypeError("sHeight must be a non-negative number.");
7417
+ }
7381
7418
  if (!Number.isFinite(dx)) {
7382
7419
  throw new TypeError("dx must be a number.");
7383
7420
  }
@@ -7393,6 +7430,26 @@ ${cue.notes ?? ""}`;
7393
7430
  if (this._closed) {
7394
7431
  throw new Error("VideoSample is closed.");
7395
7432
  }
7433
+ if (this.rotation === 90) {
7434
+ [sx, sy, sWidth, sHeight] = [
7435
+ sy,
7436
+ this.codedHeight - sx - sWidth,
7437
+ sHeight,
7438
+ sWidth
7439
+ ];
7440
+ } else if (this.rotation === 180) {
7441
+ [sx, sy] = [
7442
+ this.codedWidth - sx - sWidth,
7443
+ this.codedHeight - sy - sHeight
7444
+ ];
7445
+ } else if (this.rotation === 270) {
7446
+ [sx, sy, sWidth, sHeight] = [
7447
+ this.codedWidth - sy - sHeight,
7448
+ sx,
7449
+ sHeight,
7450
+ sWidth
7451
+ ];
7452
+ }
7396
7453
  const source = this.toCanvasImageSource();
7397
7454
  context.save();
7398
7455
  const centerX = dx + dWidth / 2;
@@ -7403,6 +7460,10 @@ ${cue.notes ?? ""}`;
7403
7460
  context.scale(1 / aspectRatioChange, aspectRatioChange);
7404
7461
  context.drawImage(
7405
7462
  source,
7463
+ sx,
7464
+ sy,
7465
+ sWidth,
7466
+ sHeight,
7406
7467
  -dWidth / 2,
7407
7468
  -dHeight / 2,
7408
7469
  dWidth,
@@ -8367,6 +8428,7 @@ ${cue.notes ?? ""}`;
8367
8428
  finalizeAndEmitSample(sample) {
8368
8429
  sample.setTimestamp(Math.round(sample.timestamp * this.timeResolution) / this.timeResolution);
8369
8430
  sample.setDuration(Math.round(sample.duration * this.timeResolution) / this.timeResolution);
8431
+ sample.setRotation(this.rotation);
8370
8432
  this.onSample(sample);
8371
8433
  }
8372
8434
  getDecodeQueueSize() {
@@ -11823,6 +11885,7 @@ ${cue.notes ?? ""}`;
11823
11885
  fragmentLookupTable: null,
11824
11886
  currentFragmentState: null,
11825
11887
  fragments: [],
11888
+ fragmentsWithKeyFrame: [],
11826
11889
  editListPreviousSegmentDurations: 0,
11827
11890
  editListOffset: 0
11828
11891
  };
@@ -11911,7 +11974,8 @@ ${cue.notes ?? ""}`;
11911
11974
  continue;
11912
11975
  }
11913
11976
  if (mediaRate !== 1) {
11914
- throw new Error("Unsupported edit list: media rate must be 1.");
11977
+ console.warn("Unsupported edit list entry: media rate must be 1.");
11978
+ break;
11915
11979
  }
11916
11980
  track.editListPreviousSegmentDurations = previousSegmentDurations;
11917
11981
  track.editListOffset = mediaTime;
@@ -12099,7 +12163,8 @@ ${cue.notes ?? ""}`;
12099
12163
  } else if (sampleSize === 16) {
12100
12164
  track.info.codec = "pcm-s16be";
12101
12165
  } else {
12102
- throw new Error(`Unsupported sample size ${sampleSize} for codec 'twos'.`);
12166
+ console.warn(`Unsupported sample size ${sampleSize} for codec 'twos'.`);
12167
+ track.info.codec = null;
12103
12168
  }
12104
12169
  } else if (lowercaseBoxName === "sowt") {
12105
12170
  if (sampleSize === 8) {
@@ -12107,7 +12172,8 @@ ${cue.notes ?? ""}`;
12107
12172
  } else if (sampleSize === 16) {
12108
12173
  track.info.codec = "pcm-s16";
12109
12174
  } else {
12110
- throw new Error(`Unsupported sample size ${sampleSize} for codec 'sowt'.`);
12175
+ console.warn(`Unsupported sample size ${sampleSize} for codec 'sowt'.`);
12176
+ track.info.codec = null;
12111
12177
  }
12112
12178
  } else if (lowercaseBoxName === "raw ") {
12113
12179
  track.info.codec = "pcm-u8";
@@ -12327,7 +12393,8 @@ ${cue.notes ?? ""}`;
12327
12393
  } else if (pcmSampleSize === 32) {
12328
12394
  track.info.codec = "pcm-s32";
12329
12395
  } else {
12330
- throw new Error(`Invalid ipcm sample size ${pcmSampleSize}.`);
12396
+ console.warn(`Invalid ipcm sample size ${pcmSampleSize}.`);
12397
+ track.info.codec = null;
12331
12398
  }
12332
12399
  } else {
12333
12400
  if (pcmSampleSize === 16) {
@@ -12337,7 +12404,8 @@ ${cue.notes ?? ""}`;
12337
12404
  } else if (pcmSampleSize === 32) {
12338
12405
  track.info.codec = "pcm-s32be";
12339
12406
  } else {
12340
- throw new Error(`Invalid ipcm sample size ${pcmSampleSize}.`);
12407
+ console.warn(`Invalid ipcm sample size ${pcmSampleSize}.`);
12408
+ track.info.codec = null;
12341
12409
  }
12342
12410
  }
12343
12411
  } else if (track.info.codec === "pcm-f32be") {
@@ -12347,7 +12415,8 @@ ${cue.notes ?? ""}`;
12347
12415
  } else if (pcmSampleSize === 64) {
12348
12416
  track.info.codec = "pcm-f64";
12349
12417
  } else {
12350
- throw new Error(`Invalid fpcm sample size ${pcmSampleSize}.`);
12418
+ console.warn(`Invalid fpcm sample size ${pcmSampleSize}.`);
12419
+ track.info.codec = null;
12351
12420
  }
12352
12421
  } else {
12353
12422
  if (pcmSampleSize === 32) {
@@ -12355,7 +12424,8 @@ ${cue.notes ?? ""}`;
12355
12424
  } else if (pcmSampleSize === 64) {
12356
12425
  track.info.codec = "pcm-f64be";
12357
12426
  } else {
12358
- throw new Error(`Invalid fpcm sample size ${pcmSampleSize}.`);
12427
+ console.warn(`Invalid fpcm sample size ${pcmSampleSize}.`);
12428
+ track.info.codec = null;
12359
12429
  }
12360
12430
  }
12361
12431
  }
@@ -12505,9 +12575,24 @@ ${cue.notes ?? ""}`;
12505
12575
  break;
12506
12576
  case "stz2":
12507
12577
  {
12508
- throw new Error("Unsupported.");
12578
+ const track = this.currentTrack;
12579
+ assert(track);
12580
+ if (!track.sampleTable) {
12581
+ break;
12582
+ }
12583
+ this.metadataReader.pos += 4;
12584
+ this.metadataReader.pos += 3;
12585
+ const fieldSize = this.metadataReader.readU8();
12586
+ const sampleCount = this.metadataReader.readU32();
12587
+ const bytes2 = this.metadataReader.readBytes(Math.ceil(sampleCount * fieldSize / 8));
12588
+ const bitstream = new Bitstream(bytes2);
12589
+ for (let i = 0; i < sampleCount; i++) {
12590
+ const sampleSize = bitstream.readBits(fieldSize);
12591
+ track.sampleTable.sampleSizes.push(sampleSize);
12592
+ }
12509
12593
  }
12510
12594
  ;
12595
+ break;
12511
12596
  case "stss":
12512
12597
  {
12513
12598
  const track = this.currentTrack;
@@ -12705,6 +12790,15 @@ ${cue.notes ?? ""}`;
12705
12790
  (x) => x.moofOffset
12706
12791
  );
12707
12792
  this.currentTrack.fragments.splice(insertionIndex + 1, 0, this.currentFragment);
12793
+ const hasKeyFrame = trackData.firstKeyFrameTimestamp !== null;
12794
+ if (hasKeyFrame) {
12795
+ const insertionIndex2 = binarySearchLessOrEqual(
12796
+ this.currentTrack.fragmentsWithKeyFrame,
12797
+ this.currentFragment.moofOffset,
12798
+ (x) => x.moofOffset
12799
+ );
12800
+ this.currentTrack.fragmentsWithKeyFrame.splice(insertionIndex2 + 1, 0, this.currentFragment);
12801
+ }
12708
12802
  const { currentFragmentState } = this.currentTrack;
12709
12803
  assert(currentFragmentState);
12710
12804
  if (currentFragmentState.startTimestamp !== null) {
@@ -12791,7 +12885,8 @@ ${cue.notes ?? ""}`;
12791
12885
  assert(this.currentFragment);
12792
12886
  assert(track.currentFragmentState);
12793
12887
  if (this.currentFragment.trackData.has(track.id)) {
12794
- throw new Error("Can't have two trun boxes for the same track in one fragment.");
12888
+ console.warn("Can't have two trun boxes for the same track in one fragment. Ignoring...");
12889
+ break;
12795
12890
  }
12796
12891
  const version = this.metadataReader.readU8();
12797
12892
  const flags = this.metadataReader.readU24();
@@ -12819,6 +12914,7 @@ ${cue.notes ?? ""}`;
12819
12914
  const trackData = {
12820
12915
  startTimestamp: 0,
12821
12916
  endTimestamp: 0,
12917
+ firstKeyFrameTimestamp: null,
12822
12918
  samples: [],
12823
12919
  presentationTimestamps: [],
12824
12920
  startTimestampIsFinal: false
@@ -12869,11 +12965,16 @@ ${cue.notes ?? ""}`;
12869
12965
  currentTimestamp += sampleDuration;
12870
12966
  }
12871
12967
  trackData.presentationTimestamps = trackData.samples.map((x, i) => ({ presentationTimestamp: x.presentationTimestamp, sampleIndex: i })).sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
12872
- for (let i = 0; i < trackData.presentationTimestamps.length - 1; i++) {
12873
- const current = trackData.presentationTimestamps[i];
12874
- const next = trackData.presentationTimestamps[i + 1];
12875
- const duration = next.presentationTimestamp - current.presentationTimestamp;
12876
- trackData.samples[current.sampleIndex].duration = duration;
12968
+ for (let i = 0; i < trackData.presentationTimestamps.length; i++) {
12969
+ const currentEntry = trackData.presentationTimestamps[i];
12970
+ const currentSample = trackData.samples[currentEntry.sampleIndex];
12971
+ if (trackData.firstKeyFrameTimestamp === null && currentSample.isKeyFrame) {
12972
+ trackData.firstKeyFrameTimestamp = currentSample.presentationTimestamp;
12973
+ }
12974
+ if (i < trackData.presentationTimestamps.length - 1) {
12975
+ const nextEntry = trackData.presentationTimestamps[i + 1];
12976
+ currentSample.duration = nextEntry.presentationTimestamp - currentEntry.presentationTimestamp;
12977
+ }
12877
12978
  }
12878
12979
  const firstSample = trackData.samples[trackData.presentationTimestamps[0].sampleIndex];
12879
12980
  const lastSample = trackData.samples[last(trackData.presentationTimestamps).sampleIndex];
@@ -13072,7 +13173,7 @@ ${cue.notes ?? ""}`;
13072
13173
  while (currentFragment.nextFragment) {
13073
13174
  currentFragment = currentFragment.nextFragment;
13074
13175
  const trackData2 = currentFragment.trackData.get(this.internalTrack.id);
13075
- if (trackData2) {
13176
+ if (trackData2 && trackData2.firstKeyFrameTimestamp !== null) {
13076
13177
  const fragmentIndex2 = binarySearchExact(
13077
13178
  this.internalTrack.fragments,
13078
13179
  currentFragment.moofOffset,
@@ -13080,9 +13181,7 @@ ${cue.notes ?? ""}`;
13080
13181
  );
13081
13182
  assert(fragmentIndex2 !== -1);
13082
13183
  const keyFrameIndex = trackData2.samples.findIndex((x) => x.isKeyFrame);
13083
- if (keyFrameIndex === -1) {
13084
- throw new Error("Not supported: Fragment does not contain key sample.");
13085
- }
13184
+ assert(keyFrameIndex !== -1);
13086
13185
  return {
13087
13186
  fragmentIndex: fragmentIndex2,
13088
13187
  sampleIndex: keyFrameIndex,
@@ -13195,24 +13294,29 @@ ${cue.notes ?? ""}`;
13195
13294
  return { fragmentIndex, sampleIndex, correctSampleFound };
13196
13295
  }
13197
13296
  findKeySampleInFragmentsForTimestamp(timestampInTimescale) {
13198
- const fragmentIndex = binarySearchLessOrEqual(
13297
+ const indexInKeyFrameFragments = binarySearchLessOrEqual(
13199
13298
  // This array is technically not sorted by start timestamp, but for any reasonable file, it basically is.
13200
- this.internalTrack.fragments,
13299
+ this.internalTrack.fragmentsWithKeyFrame,
13201
13300
  timestampInTimescale,
13202
13301
  (x) => x.trackData.get(this.internalTrack.id).startTimestamp
13203
13302
  );
13303
+ let fragmentIndex = -1;
13204
13304
  let sampleIndex = -1;
13205
13305
  let correctSampleFound = false;
13206
- if (fragmentIndex !== -1) {
13207
- const fragment = this.internalTrack.fragments[fragmentIndex];
13306
+ if (indexInKeyFrameFragments !== -1) {
13307
+ const fragment = this.internalTrack.fragmentsWithKeyFrame[indexInKeyFrameFragments];
13308
+ fragmentIndex = binarySearchExact(
13309
+ this.internalTrack.fragments,
13310
+ fragment.moofOffset,
13311
+ (x) => x.moofOffset
13312
+ );
13313
+ assert(fragmentIndex !== -1);
13208
13314
  const trackData = fragment.trackData.get(this.internalTrack.id);
13209
13315
  const index = findLastIndex(trackData.presentationTimestamps, (x) => {
13210
13316
  const sample = trackData.samples[x.sampleIndex];
13211
13317
  return sample.isKeyFrame && x.presentationTimestamp <= timestampInTimescale;
13212
13318
  });
13213
- if (index === -1) {
13214
- throw new Error("Not supported: Fragment does not begin with a key sample.");
13215
- }
13319
+ assert(index !== -1);
13216
13320
  const entry = trackData.presentationTimestamps[index];
13217
13321
  sampleIndex = entry.sampleIndex;
13218
13322
  correctSampleFound = timestampInTimescale < trackData.endTimestamp;
@@ -13735,20 +13839,15 @@ ${cue.notes ?? ""}`;
13735
13839
  trackData.blocks = sortBlocksByReferences(trackData.blocks);
13736
13840
  }
13737
13841
  trackData.presentationTimestamps = trackData.blocks.map((block, i) => ({ timestamp: block.timestamp, blockIndex: i })).sort((a, b) => a.timestamp - b.timestamp);
13738
- let hasKeyFrame = false;
13739
13842
  for (let i = 0; i < trackData.presentationTimestamps.length; i++) {
13740
- const entry = trackData.presentationTimestamps[i];
13741
- const block = trackData.blocks[entry.blockIndex];
13742
- if (block.isKeyFrame) {
13743
- hasKeyFrame = true;
13744
- if (trackData.firstKeyFrameTimestamp === null && block.isKeyFrame) {
13745
- trackData.firstKeyFrameTimestamp = block.timestamp;
13746
- }
13843
+ const currentEntry = trackData.presentationTimestamps[i];
13844
+ const currentBlock = trackData.blocks[currentEntry.blockIndex];
13845
+ if (trackData.firstKeyFrameTimestamp === null && currentBlock.isKeyFrame) {
13846
+ trackData.firstKeyFrameTimestamp = currentBlock.timestamp;
13747
13847
  }
13748
13848
  if (i < trackData.presentationTimestamps.length - 1) {
13749
13849
  const nextEntry = trackData.presentationTimestamps[i + 1];
13750
- const nextBlock = trackData.blocks[nextEntry.blockIndex];
13751
- block.duration = nextBlock.timestamp - block.timestamp;
13850
+ currentBlock.duration = nextEntry.timestamp - currentBlock.timestamp;
13752
13851
  }
13753
13852
  }
13754
13853
  const firstBlock = trackData.blocks[trackData.presentationTimestamps[0].blockIndex];
@@ -13763,6 +13862,7 @@ ${cue.notes ?? ""}`;
13763
13862
  (x) => x.elementStartPos
13764
13863
  );
13765
13864
  track.clusters.splice(insertionIndex2 + 1, 0, cluster);
13865
+ const hasKeyFrame = trackData.firstKeyFrameTimestamp !== null;
13766
13866
  if (hasKeyFrame) {
13767
13867
  const insertionIndex3 = binarySearchLessOrEqual(
13768
13868
  track.clustersWithKeyFrame,
@@ -14801,7 +14901,7 @@ ${cue.notes ?? ""}`;
14801
14901
  readNextFrameHeader(until) {
14802
14902
  assert(this.fileSize);
14803
14903
  until ??= this.fileSize;
14804
- while (this.pos < until - FRAME_HEADER_SIZE) {
14904
+ while (this.pos <= until - FRAME_HEADER_SIZE) {
14805
14905
  const word = this.readU32();
14806
14906
  this.pos -= 4;
14807
14907
  const header = readFrameHeader(word, this);
@@ -15843,7 +15943,8 @@ ${cue.notes ?? ""}`;
15843
15943
  return true;
15844
15944
  }
15845
15945
  mp3Reader.pos = firstHeader.startPos + firstHeader.totalSize;
15846
- const secondHeader = mp3Reader.readNextFrameHeader(Math.min(framesStartPos + 4096, sourceSize));
15946
+ await mp3Reader.reader.loadRange(mp3Reader.pos, mp3Reader.pos + FRAME_HEADER_SIZE);
15947
+ const secondHeader = mp3Reader.readNextFrameHeader(mp3Reader.pos + FRAME_HEADER_SIZE);
15847
15948
  if (!secondHeader) {
15848
15949
  return false;
15849
15950
  }