@remotion/media-parser 4.0.199 → 4.0.201

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 (117) hide show
  1. package/dist/av1-codec-string.d.ts +5 -0
  2. package/dist/av1-codec-string.js +18 -1
  3. package/dist/bitstream/av1.d.ts +2 -0
  4. package/dist/bitstream/av1.js +12 -0
  5. package/dist/boxes/iso-base-media/avcc-hvcc.d.ts +20 -0
  6. package/dist/boxes/iso-base-media/avcc-hvcc.js +73 -0
  7. package/dist/boxes/iso-base-media/avcc.d.ts +18 -0
  8. package/dist/boxes/iso-base-media/avcc.js +27 -0
  9. package/dist/boxes/iso-base-media/esds-descriptors.d.ts +21 -0
  10. package/dist/boxes/iso-base-media/esds-descriptors.js +62 -0
  11. package/dist/boxes/iso-base-media/esds.d.ts +15 -0
  12. package/dist/boxes/iso-base-media/esds.js +27 -0
  13. package/dist/boxes/iso-base-media/mdat/mdat.js +2 -2
  14. package/dist/boxes/iso-base-media/moov/moov.js +1 -0
  15. package/dist/boxes/iso-base-media/process-box.d.ts +4 -2
  16. package/dist/boxes/iso-base-media/process-box.js +56 -40
  17. package/dist/boxes/iso-base-media/stsd/mebx.d.ts +2 -1
  18. package/dist/boxes/iso-base-media/stsd/mebx.js +2 -1
  19. package/dist/boxes/iso-base-media/stsd/samples.js +3 -0
  20. package/dist/boxes/iso-base-media/stsd/stco.d.ts +3 -2
  21. package/dist/boxes/iso-base-media/stsd/stco.js +2 -2
  22. package/dist/boxes/iso-base-media/trak/trak.js +1 -0
  23. package/dist/boxes/webm/bitstream/av1.js +10 -1
  24. package/dist/boxes/webm/ebml.d.ts +2 -0
  25. package/dist/boxes/webm/ebml.js +72 -0
  26. package/dist/boxes/webm/make-header.d.ts +9 -0
  27. package/dist/boxes/webm/make-header.js +79 -0
  28. package/dist/boxes/webm/parse-ebml.d.ts +7 -0
  29. package/dist/boxes/webm/parse-ebml.js +66 -0
  30. package/dist/boxes/webm/parse-webm-header.js +8 -9
  31. package/dist/boxes/webm/segments/all-segments.d.ts +262 -0
  32. package/dist/boxes/webm/segments/all-segments.js +130 -1
  33. package/dist/boxes/webm/segments/block-simple-block-flags.d.ts +9 -0
  34. package/dist/boxes/webm/segments/block-simple-block-flags.js +38 -0
  35. package/dist/boxes/webm/segments/seek-position.js +1 -1
  36. package/dist/boxes/webm/segments/seek.d.ts +1 -1
  37. package/dist/boxes/webm/segments/seek.js +8 -2
  38. package/dist/boxes/webm/segments/timestamp-scale.js +1 -1
  39. package/dist/boxes/webm/segments/track-entry.d.ts +25 -9
  40. package/dist/boxes/webm/segments/track-entry.js +73 -33
  41. package/dist/boxes/webm/segments.d.ts +3 -3
  42. package/dist/boxes/webm/segments.js +64 -30
  43. package/dist/boxes/webm/traversal.d.ts +1 -0
  44. package/dist/boxes/webm/traversal.js +12 -1
  45. package/dist/buffer-iterator.d.ts +10 -6
  46. package/dist/buffer-iterator.js +92 -9
  47. package/dist/from-fetch.js +13 -3
  48. package/dist/from-input-type-file.d.ts +2 -0
  49. package/dist/from-input-type-file.js +37 -0
  50. package/dist/from-node.js +9 -2
  51. package/dist/from-web-file.js +6 -1
  52. package/dist/from-web.js +15 -6
  53. package/dist/get-codec.d.ts +4 -0
  54. package/dist/get-codec.js +22 -0
  55. package/dist/get-sample-positions.js +1 -1
  56. package/dist/has-all-info.js +1 -1
  57. package/dist/options.d.ts +3 -2
  58. package/dist/parse-media.js +13 -9
  59. package/dist/parse-video.js +16 -0
  60. package/dist/parser-state.d.ts +8 -9
  61. package/dist/parser-state.js +39 -19
  62. package/dist/reader.d.ts +1 -1
  63. package/dist/web-file.d.ts +2 -0
  64. package/dist/web-file.js +37 -0
  65. package/dist/webcodec-sample-types.d.ts +0 -1
  66. package/package.json +2 -2
  67. package/src/boxes/iso-base-media/mdat/mdat.ts +2 -2
  68. package/src/boxes/iso-base-media/moov/moov.ts +1 -0
  69. package/src/boxes/iso-base-media/process-box.ts +70 -40
  70. package/src/boxes/iso-base-media/stsd/mebx.ts +3 -0
  71. package/src/boxes/iso-base-media/stsd/samples.ts +3 -0
  72. package/src/boxes/iso-base-media/stsd/stco.ts +5 -3
  73. package/src/boxes/iso-base-media/trak/trak.ts +1 -0
  74. package/src/boxes/webm/ebml.ts +78 -0
  75. package/src/boxes/webm/make-header.ts +138 -0
  76. package/src/boxes/webm/parse-ebml.ts +93 -0
  77. package/src/boxes/webm/parse-webm-header.ts +8 -12
  78. package/src/boxes/webm/segments/all-segments.ts +226 -0
  79. package/src/boxes/webm/segments/block-simple-block-flags.ts +52 -0
  80. package/src/boxes/webm/segments/seek-position.ts +1 -1
  81. package/src/boxes/webm/segments/seek.ts +12 -2
  82. package/src/boxes/webm/segments/timestamp-scale.ts +1 -1
  83. package/src/boxes/webm/segments/track-entry.ts +125 -41
  84. package/src/boxes/webm/segments.ts +107 -40
  85. package/src/boxes/webm/traversal.ts +13 -0
  86. package/src/buffer-iterator.ts +110 -10
  87. package/src/from-fetch.ts +22 -3
  88. package/src/from-node.ts +18 -4
  89. package/src/from-web-file.ts +11 -1
  90. package/src/get-sample-positions.ts +1 -1
  91. package/src/has-all-info.ts +1 -1
  92. package/src/options.ts +3 -2
  93. package/src/parse-media.ts +14 -8
  94. package/src/parse-video.ts +17 -0
  95. package/src/parser-state.ts +52 -25
  96. package/src/reader.ts +1 -0
  97. package/src/test/create-matroska.test.ts +48 -0
  98. package/src/test/matroska.test.ts +144 -127
  99. package/src/test/parse-stco.test.ts +2 -0
  100. package/src/test/stream-local.test.ts +70 -14
  101. package/src/test/stream-remote.test.ts +23 -19
  102. package/src/test/stsd.test.ts +2 -0
  103. package/src/webcodec-sample-types.ts +0 -1
  104. package/tsconfig.tsbuildinfo +1 -1
  105. package/dist/boxes/iso-base-media/ftype.d.ts +0 -9
  106. package/dist/boxes/iso-base-media/ftype.js +0 -31
  107. package/dist/get-video-metadata.d.ts +0 -2
  108. package/dist/get-video-metadata.js +0 -44
  109. package/dist/read-and-increment-offset.d.ts +0 -28
  110. package/dist/read-and-increment-offset.js +0 -177
  111. package/dist/understand-vorbis.d.ts +0 -1
  112. package/dist/understand-vorbis.js +0 -12
  113. package/src/boxes/webm/segments/unknown.ts +0 -19
  114. /package/dist/{boxes/webm/bitstream/av1/frame.d.ts → get-samples.d.ts} +0 -0
  115. /package/dist/{boxes/webm/bitstream/av1/frame.js → get-samples.js} +0 -0
  116. /package/dist/{boxes/webm/bitstream/h264/get-h264-descriptor.d.ts → sample-aspect-ratio.d.ts} +0 -0
  117. /package/dist/{boxes/webm/bitstream/h264/get-h264-descriptor.js → sample-aspect-ratio.js} +0 -0
@@ -1,3 +1,4 @@
1
+ import {webmPattern} from './boxes/webm/make-header';
1
2
  import {
2
3
  knownIdsWithOneLength,
3
4
  knownIdsWithThreeLength,
@@ -46,8 +47,8 @@ export class OffsetCounter {
46
47
  }
47
48
 
48
49
  const isoBaseMediaMp4Pattern = new TextEncoder().encode('ftyp');
49
- const webmPattern = new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]);
50
50
  const mpegPattern = new Uint8Array([0xff, 0xf3, 0xe4, 0x64]);
51
+ const riffPattern = new Uint8Array([0x52, 0x49, 0x46, 0x46]);
51
52
 
52
53
  const matchesPattern = (pattern: Uint8Array) => {
53
54
  return (data: Uint8Array) => {
@@ -64,7 +65,9 @@ export const getArrayBufferIterator = (
64
65
  maxBytes?: number,
65
66
  ) => {
66
67
  const buf = new ArrayBuffer(initialData.byteLength, {
67
- maxByteLength: maxBytes ?? 1_000_000_000,
68
+ maxByteLength: maxBytes
69
+ ? Math.min(maxBytes as number, 2 ** 32)
70
+ : 1_000_000_000,
68
71
  });
69
72
  if (!buf.resize) {
70
73
  throw new Error(
@@ -95,7 +98,61 @@ export const getArrayBufferIterator = (
95
98
  return val;
96
99
  };
97
100
 
98
- const getFourByteNumber = () => {
101
+ const getEightByteNumber = (littleEndian = false) => {
102
+ if (littleEndian) {
103
+ const one = getUint8();
104
+ const two = getUint8();
105
+ const three = getUint8();
106
+ const four = getUint8();
107
+ const five = getUint8();
108
+ const six = getUint8();
109
+ const seven = getUint8();
110
+ const eight = getUint8();
111
+
112
+ return (
113
+ (eight << 56) |
114
+ (seven << 48) |
115
+ (six << 40) |
116
+ (five << 32) |
117
+ (four << 24) |
118
+ (three << 16) |
119
+ (two << 8) |
120
+ one
121
+ );
122
+ }
123
+
124
+ function byteArrayToBigInt(byteArray: number[]): BigInt {
125
+ let result = BigInt(0);
126
+ for (let i = 0; i < byteArray.length; i++) {
127
+ result = (result << BigInt(8)) + BigInt(byteArray[i]);
128
+ }
129
+
130
+ return result;
131
+ }
132
+
133
+ const bigInt = byteArrayToBigInt([
134
+ getUint8(),
135
+ getUint8(),
136
+ getUint8(),
137
+ getUint8(),
138
+ getUint8(),
139
+ getUint8(),
140
+ getUint8(),
141
+ getUint8(),
142
+ ]);
143
+
144
+ return Number(bigInt);
145
+ };
146
+
147
+ const getFourByteNumber = (littleEndian = false) => {
148
+ if (littleEndian) {
149
+ const one = getUint8();
150
+ const two = getUint8();
151
+ const three = getUint8();
152
+ const four = getUint8();
153
+ return (four << 24) | (three << 16) | (two << 8) | one;
154
+ }
155
+
99
156
  return (
100
157
  (getUint8() << 24) | (getUint8() << 16) | (getUint8() << 8) | getUint8()
101
158
  );
@@ -110,12 +167,18 @@ export const getArrayBufferIterator = (
110
167
  return lastInt;
111
168
  };
112
169
 
113
- const getUint32 = () => {
114
- const val = view.getUint32(counter.getDiscardedOffset());
170
+ const getUint32 = (littleEndian = false) => {
171
+ const val = view.getUint32(counter.getDiscardedOffset(), littleEndian);
115
172
  counter.increment(4);
116
173
  return val;
117
174
  };
118
175
 
176
+ const getUint64 = (littleEndian = false) => {
177
+ const val = view.getBigUint64(counter.getDiscardedOffset(), littleEndian);
178
+ counter.increment(8);
179
+ return val;
180
+ };
181
+
119
182
  const getUint32Le = () => {
120
183
  const val = view.getUint32(counter.getDiscardedOffset(), true);
121
184
  counter.increment(4);
@@ -156,6 +219,10 @@ export const getArrayBufferIterator = (
156
219
  return matchesPattern(isoBaseMediaMp4Pattern)(data.subarray(4, 8));
157
220
  };
158
221
 
222
+ const isRiff = () => {
223
+ return matchesPattern(riffPattern)(data.subarray(0, 4));
224
+ };
225
+
159
226
  const isWebm = () => {
160
227
  return matchesPattern(webmPattern)(data.subarray(0, 4));
161
228
  };
@@ -186,7 +253,6 @@ export const getArrayBufferIterator = (
186
253
  counter.decrement(counter.getOffset() - offset);
187
254
  counter.setDiscardedOffset(offset);
188
255
  } else {
189
- buf.resize(offset);
190
256
  const currentOffset = counter.getOffset();
191
257
  counter.increment(offset - currentOffset);
192
258
  removeBytesRead();
@@ -276,19 +342,25 @@ export const getArrayBufferIterator = (
276
342
  bytesRemaining,
277
343
  isIsoBaseMedia,
278
344
  leb128,
279
- discardFirstBytes: removeBytesRead,
345
+ removeBytesRead,
280
346
  isWebm,
281
347
  discard: (length: number) => {
282
348
  counter.increment(length);
283
349
  },
350
+ getEightByteNumber,
284
351
  getFourByteNumber,
285
352
  getSlice,
286
353
  getAtom: () => {
287
354
  const atom = getSlice(4);
288
355
  return new TextDecoder().decode(atom);
289
356
  },
357
+ isRiff,
290
358
  getPaddedFourByteNumber,
291
- getMatroskaSegmentId: () => {
359
+ getMatroskaSegmentId: (): string | null => {
360
+ if (bytesRemaining() === 0) {
361
+ return null;
362
+ }
363
+
292
364
  const first = getSlice(1);
293
365
  const firstOneString = `0x${Array.from(new Uint8Array(first))
294
366
  .map((b) => {
@@ -303,6 +375,10 @@ export const getArrayBufferIterator = (
303
375
  return firstOneString;
304
376
  }
305
377
 
378
+ if (bytesRemaining() === 0) {
379
+ return null;
380
+ }
381
+
306
382
  const firstTwo = getSlice(1);
307
383
 
308
384
  const firstTwoString = `${firstOneString}${Array.from(
@@ -317,6 +393,10 @@ export const getArrayBufferIterator = (
317
393
  return firstTwoString;
318
394
  }
319
395
 
396
+ if (bytesRemaining() === 0) {
397
+ return null;
398
+ }
399
+
320
400
  const firstThree = getSlice(1);
321
401
 
322
402
  const firstThreeString = `${firstTwoString}${Array.from(
@@ -331,6 +411,10 @@ export const getArrayBufferIterator = (
331
411
  return firstThreeString;
332
412
  }
333
413
 
414
+ if (bytesRemaining() === 0) {
415
+ return null;
416
+ }
417
+
334
418
  const segmentId = getSlice(1);
335
419
 
336
420
  return `${firstThreeString}${Array.from(new Uint8Array(segmentId))
@@ -339,7 +423,11 @@ export const getArrayBufferIterator = (
339
423
  })
340
424
  .join('')}`;
341
425
  },
342
- getVint: () => {
426
+ getVint: (): number | null => {
427
+ if (bytesRemaining() === 0) {
428
+ return null;
429
+ }
430
+
343
431
  const firstByte = getUint8();
344
432
  const totalLength = firstByte;
345
433
 
@@ -353,6 +441,10 @@ export const getArrayBufferIterator = (
353
441
  actualLength++;
354
442
  }
355
443
 
444
+ if (bytesRemaining() < actualLength) {
445
+ return null;
446
+ }
447
+
356
448
  const slice = getSlice(actualLength);
357
449
  const d = [firstByte, ...Array.from(new Uint8Array(slice))];
358
450
 
@@ -388,6 +480,13 @@ export const getArrayBufferIterator = (
388
480
  counter.increment(2);
389
481
  return val;
390
482
  },
483
+ getUint24: () => {
484
+ const val1 = view.getUint8(counter.getDiscardedOffset());
485
+ const val2 = view.getUint8(counter.getDiscardedOffset());
486
+ const val3 = view.getUint8(counter.getDiscardedOffset());
487
+ counter.increment(3);
488
+ return (val1 << 16) | (val2 << 8) | val3;
489
+ },
391
490
 
392
491
  getInt16: () => {
393
492
  const val = view.getInt16(counter.getDiscardedOffset());
@@ -395,6 +494,7 @@ export const getArrayBufferIterator = (
395
494
  return val;
396
495
  },
397
496
  getUint32,
497
+ getUint64,
398
498
  // https://developer.apple.com/documentation/quicktime-file-format/sound_sample_description_version_1
399
499
  // A 32-bit unsigned fixed-point number (16.16) that indicates the rate at which the sound samples were obtained.
400
500
  getFixedPointUnsigned1616Number: () => {
@@ -413,7 +513,7 @@ export const getArrayBufferIterator = (
413
513
  const val = getSlice(32);
414
514
  return [...Array.from(new Uint8Array(val))];
415
515
  },
416
- getDecimalBytes(length: number): number {
516
+ getUint(length: number): number {
417
517
  const bytes = getSlice(length);
418
518
  const numbers = [...Array.from(new Uint8Array(bytes))];
419
519
  return numbers.reduce(
package/src/from-fetch.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import type {ReaderInterface} from './reader';
2
2
 
3
3
  export const fetchReader: ReaderInterface = {
4
- read: async (src, range) => {
4
+ read: async (src, range, signal) => {
5
5
  if (typeof src !== 'string') {
6
- throw new Error('src must be a string when using `webReader`');
6
+ throw new Error('src must be a string when using `fetchReader`');
7
7
  }
8
8
 
9
9
  const resolvedUrl =
@@ -34,9 +34,18 @@ export const fetchReader: ReaderInterface = {
34
34
  : {
35
35
  Range: `bytes=${`${range[0]}-${range[1]}`}`,
36
36
  },
37
+ signal,
37
38
  // Disable Next.js caching
38
39
  cache: 'no-store',
39
40
  });
41
+
42
+ if (
43
+ res.status.toString().startsWith('4') ||
44
+ res.status.toString().startsWith('5')
45
+ ) {
46
+ throw new Error(`Server returned status code ${res.status} for ${src}`);
47
+ }
48
+
40
49
  if (!res.body) {
41
50
  throw new Error('No body');
42
51
  }
@@ -47,11 +56,21 @@ export const fetchReader: ReaderInterface = {
47
56
 
48
57
  const reader = res.body.getReader();
49
58
 
59
+ if (signal) {
60
+ signal.addEventListener(
61
+ 'abort',
62
+ () => {
63
+ reader.cancel();
64
+ },
65
+ {once: true},
66
+ );
67
+ }
68
+
50
69
  return {reader, contentLength};
51
70
  },
52
71
  getLength: async (src) => {
53
72
  if (typeof src !== 'string') {
54
- throw new Error('src must be a string when using `webReader`');
73
+ throw new Error('src must be a string when using `fetchReader`');
55
74
  }
56
75
 
57
76
  const res = await fetch(src, {
package/src/from-node.ts CHANGED
@@ -4,7 +4,7 @@ import {Readable} from 'stream';
4
4
  import type {ReaderInterface} from './reader';
5
5
 
6
6
  export const nodeReader: ReaderInterface = {
7
- read: async (src, range) => {
7
+ read: async (src, range, signal) => {
8
8
  if (typeof src !== 'string') {
9
9
  throw new Error('src must be a string when using `nodeReader`');
10
10
  }
@@ -17,12 +17,26 @@ export const nodeReader: ReaderInterface = {
17
17
  : typeof range === 'number'
18
18
  ? Infinity
19
19
  : range[1],
20
+ signal,
20
21
  });
21
22
  const stats = await stat(src);
23
+
24
+ const reader = Readable.toWeb(
25
+ stream,
26
+ ).getReader() as ReadableStreamDefaultReader<Uint8Array>;
27
+
28
+ if (signal) {
29
+ signal.addEventListener(
30
+ 'abort',
31
+ () => {
32
+ reader.cancel();
33
+ },
34
+ {once: true},
35
+ );
36
+ }
37
+
22
38
  return {
23
- reader: Readable.toWeb(
24
- stream,
25
- ).getReader() as ReadableStreamDefaultReader<Uint8Array>,
39
+ reader,
26
40
  contentLength: stats.size,
27
41
  };
28
42
  },
@@ -1,7 +1,7 @@
1
1
  import type {ReaderInterface} from './reader';
2
2
 
3
3
  export const webFileReader: ReaderInterface = {
4
- read: (file, range) => {
4
+ read: (file, range, signal) => {
5
5
  if (typeof file === 'string') {
6
6
  throw new Error('`inputTypeFileReader` only supports `File` objects');
7
7
  }
@@ -16,6 +16,16 @@ export const webFileReader: ReaderInterface = {
16
16
  const reader = new FileReader();
17
17
  reader.readAsArrayBuffer(file);
18
18
 
19
+ if (signal) {
20
+ signal.addEventListener(
21
+ 'abort',
22
+ () => {
23
+ reader.abort();
24
+ },
25
+ {once: true},
26
+ );
27
+ }
28
+
19
29
  return new Promise((resolve, reject) => {
20
30
  reader.onload = () => {
21
31
  resolve({
@@ -77,7 +77,7 @@ export const getSamplePositions = ({
77
77
  const cts = dts + ctsOffset;
78
78
 
79
79
  samples.push({
80
- offset: chunks[i] + offsetInThisChunk,
80
+ offset: Number(chunks[i]) + offsetInThisChunk,
81
81
  size,
82
82
  isKeyframe,
83
83
  dts,
@@ -51,7 +51,7 @@ export const hasAllInfo = (
51
51
  if (
52
52
  key === 'dimensions' ||
53
53
  key === 'rotation' ||
54
- key === 'unrotatedDimension'
54
+ key === 'unrotatedDimensions'
55
55
  ) {
56
56
  return hasDimensions(parseResult.segments, state);
57
57
  }
package/src/options.ts CHANGED
@@ -43,7 +43,7 @@ export type Options<
43
43
  audioCodec?: EnableAudioCodec;
44
44
  tracks?: EnableTracks;
45
45
  rotation?: EnableRotation;
46
- unrotatedDimension?: EnableUnrotatedDimensions;
46
+ unrotatedDimensions?: EnableUnrotatedDimensions;
47
47
  internalStats?: EnableInternalStats;
48
48
  };
49
49
 
@@ -69,7 +69,7 @@ export type Metadata<
69
69
  : {}) &
70
70
  (EnableRotation extends true ? {rotation: number | null} : {}) &
71
71
  (EnableUnrotatedDimensions extends true
72
- ? {unrotatedDimension: Dimensions}
72
+ ? {unrotatedDimensions: Dimensions}
73
73
  : {}) &
74
74
  (EnableInternalStats extends true ? {internalStats: InternalStats} : {});
75
75
 
@@ -101,6 +101,7 @@ export type ParseMedia = <
101
101
  reader?: ReaderInterface;
102
102
  onAudioTrack?: OnAudioTrack;
103
103
  onVideoTrack?: OnVideoTrack;
104
+ signal?: AbortSignal;
104
105
  }) => Promise<
105
106
  Metadata<
106
107
  EnableDimensions,
@@ -21,8 +21,14 @@ export const parseMedia: ParseMedia = async ({
21
21
  reader: readerInterface = fetchReader,
22
22
  onAudioTrack,
23
23
  onVideoTrack,
24
+ signal,
24
25
  }) => {
25
- const {reader, contentLength} = await readerInterface.read(src, null);
26
+ const state = makeParserState({
27
+ hasAudioCallbacks: onAudioTrack !== null,
28
+ hasVideoCallbacks: onVideoTrack !== null,
29
+ signal,
30
+ });
31
+ const {reader, contentLength} = await readerInterface.read(src, null, signal);
26
32
  let currentReader = reader;
27
33
 
28
34
  const returnValue = {} as Metadata<
@@ -41,11 +47,6 @@ export const parseMedia: ParseMedia = async ({
41
47
  let iterator: BufferIterator | null = null;
42
48
  let parseResult: ParseResult | null = null;
43
49
 
44
- const state = makeParserState({
45
- hasAudioCallbacks: onAudioTrack !== null,
46
- hasVideoCallbacks: onVideoTrack !== null,
47
- });
48
-
49
50
  const options: ParserContext = {
50
51
  canSkipVideoData: !(onAudioTrack || onVideoTrack),
51
52
  onAudioTrack: onAudioTrack ?? null,
@@ -54,6 +55,10 @@ export const parseMedia: ParseMedia = async ({
54
55
  };
55
56
 
56
57
  while (parseResult === null || parseResult.status === 'incomplete') {
58
+ if (signal?.aborted) {
59
+ throw new Error('Aborted');
60
+ }
61
+
57
62
  const result = await currentReader.read();
58
63
 
59
64
  if (iterator) {
@@ -106,6 +111,7 @@ export const parseMedia: ParseMedia = async ({
106
111
  const {reader: newReader} = await readerInterface.read(
107
112
  src,
108
113
  parseResult.skipTo,
114
+ signal,
109
115
  );
110
116
  currentReader = newReader;
111
117
  iterator.skipTo(parseResult.skipTo);
@@ -124,9 +130,9 @@ export const parseMedia: ParseMedia = async ({
124
130
  };
125
131
  }
126
132
 
127
- if (fields?.unrotatedDimension) {
133
+ if (fields?.unrotatedDimensions) {
128
134
  const dimensions = getDimensions(parseResult.segments, state);
129
- returnValue.unrotatedDimension = {
135
+ returnValue.unrotatedDimensions = {
130
136
  width: dimensions.unrotatedWidth,
131
137
  height: dimensions.unrotatedHeight,
132
138
  };
@@ -43,6 +43,22 @@ export const parseVideo = ({
43
43
  });
44
44
  }
45
45
 
46
+ if (iterator.isRiff()) {
47
+ throw new Error('AVI files are not yet supported');
48
+ /*
49
+ iterator.discard(4);
50
+ return parseBoxes({
51
+ iterator,
52
+ maxBytes: Infinity,
53
+ allowIncompleteBoxes: true,
54
+ initialBoxes: [],
55
+ options,
56
+ continueMdat: false,
57
+ littleEndian: true,
58
+ });
59
+ */
60
+ }
61
+
46
62
  if (iterator.isIsoBaseMedia()) {
47
63
  return parseBoxes({
48
64
  iterator,
@@ -51,6 +67,7 @@ export const parseVideo = ({
51
67
  initialBoxes: [],
52
68
  options,
53
69
  continueMdat: false,
70
+ littleEndian: false,
54
71
  });
55
72
  }
56
73
 
@@ -1,6 +1,6 @@
1
1
  import type {OnTrackEntrySegment} from './boxes/webm/segments';
2
- import type {CodecSegment} from './boxes/webm/segments/track-entry';
3
- import {getTrackCodec} from './boxes/webm/traversal';
2
+ import type {TrackInfo} from './boxes/webm/segments/track-entry';
3
+ import {getTrackCodec, getTrackTimestampScale} from './boxes/webm/traversal';
4
4
  import {getTrackId} from './traversal';
5
5
  import type {
6
6
  AudioSample,
@@ -9,18 +9,18 @@ import type {
9
9
  VideoSample,
10
10
  } from './webcodec-sample-types';
11
11
 
12
- export type InternalStats = {
13
- samplesThatHadToBeQueued: number;
14
- };
12
+ export type InternalStats = {};
15
13
 
16
14
  export const makeParserState = ({
17
15
  hasAudioCallbacks,
18
16
  hasVideoCallbacks,
17
+ signal,
19
18
  }: {
20
19
  hasAudioCallbacks: boolean;
21
20
  hasVideoCallbacks: boolean;
21
+ signal: AbortSignal | undefined;
22
22
  }) => {
23
- const trackEntries: Record<number, CodecSegment> = {};
23
+ const trackEntries: Record<number, TrackInfo> = {};
24
24
 
25
25
  const onTrackEntrySegment: OnTrackEntrySegment = (trackEntry) => {
26
26
  const trackId = getTrackId(trackEntry);
@@ -37,14 +37,17 @@ export const makeParserState = ({
37
37
  throw new Error('Expected codec');
38
38
  }
39
39
 
40
- trackEntries[trackId] = codec;
40
+ const trackTimescale = getTrackTimestampScale(trackEntry);
41
+
42
+ trackEntries[trackId] = {
43
+ codec: codec.codec,
44
+ trackTimescale,
45
+ };
41
46
  };
42
47
 
43
48
  const videoSampleCallbacks: Record<number, OnVideoSample> = {};
44
49
  const audioSampleCallbacks: Record<number, OnAudioSample> = {};
45
50
 
46
- let samplesThatHadToBeQueued = 0;
47
-
48
51
  const queuedAudioSamples: Record<number, AudioSample[]> = {};
49
52
  const queuedVideoSamples: Record<number, VideoSample[]> = {};
50
53
 
@@ -53,8 +56,10 @@ export const makeParserState = ({
53
56
  let timescale: number | null = null;
54
57
 
55
58
  const getTimescale = () => {
59
+ // https://www.matroska.org/technical/notes.html
60
+ // When using the default value of TimestampScale of “1,000,000”, one Segment Tick represents one millisecond.
56
61
  if (timescale === null) {
57
- throw new Error('Timescale not set');
62
+ return 1_000_000;
58
63
  }
59
64
 
60
65
  return timescale;
@@ -64,6 +69,30 @@ export const makeParserState = ({
64
69
  timescale = newTimescale;
65
70
  };
66
71
 
72
+ const timestampMap = new Map<number, number>();
73
+
74
+ const setTimestampOffset = (byteOffset: number, timestamp: number) => {
75
+ timestampMap.set(byteOffset, timestamp);
76
+ };
77
+
78
+ const getTimestampOffsetForByteOffset = (byteOffset: number) => {
79
+ const entries = Array.from(timestampMap.entries());
80
+ const sortedByByteOffset = entries
81
+ .sort((a, b) => {
82
+ return a[0] - b[0];
83
+ })
84
+ .reverse();
85
+ for (const [offset, timestamp] of sortedByByteOffset) {
86
+ if (offset >= byteOffset) {
87
+ continue;
88
+ }
89
+
90
+ return timestamp;
91
+ }
92
+
93
+ return timestampMap.get(byteOffset);
94
+ };
95
+
67
96
  return {
68
97
  onTrackEntrySegment,
69
98
  getTrackInfoByNumber: (id: number) => trackEntries[id],
@@ -85,6 +114,8 @@ export const makeParserState = ({
85
114
 
86
115
  queuedVideoSamples[id] = [];
87
116
  },
117
+ setTimestampOffset,
118
+ getTimestampOffsetForByteOffset,
88
119
  registerAudioSampleCallback: async (
89
120
  id: number,
90
121
  callback: OnAudioSample | null,
@@ -103,6 +134,10 @@ export const makeParserState = ({
103
134
  queuedAudioSamples[id] = [];
104
135
  },
105
136
  onAudioSample: async (trackId: number, audioSample: AudioSample) => {
137
+ if (signal?.aborted) {
138
+ throw new Error('Aborted');
139
+ }
140
+
106
141
  const callback = audioSampleCallbacks[trackId];
107
142
  if (callback) {
108
143
  await callback(audioSample);
@@ -112,15 +147,15 @@ export const makeParserState = ({
112
147
  }
113
148
 
114
149
  if (!hasAudioCallbacks) {
115
- return;
150
+ throw new Error('No audio callbacks registered');
116
151
  }
117
-
118
- queuedAudioSamples[trackId] ??= [];
119
- queuedAudioSamples[trackId].push(audioSample);
120
- samplesThatHadToBeQueued++;
121
152
  }
122
153
  },
123
154
  onVideoSample: async (trackId: number, videoSample: VideoSample) => {
155
+ if (signal?.aborted) {
156
+ throw new Error('Aborted');
157
+ }
158
+
124
159
  const callback = videoSampleCallbacks[trackId];
125
160
  if (callback) {
126
161
  await callback(videoSample);
@@ -130,19 +165,11 @@ export const makeParserState = ({
130
165
  }
131
166
 
132
167
  if (!hasVideoCallbacks) {
133
- return;
168
+ throw new Error('No video callbacks registered');
134
169
  }
135
-
136
- queuedVideoSamples[trackId] ??= [];
137
- queuedVideoSamples[trackId].push(videoSample);
138
- samplesThatHadToBeQueued++;
139
170
  }
140
171
  },
141
- getInternalStats: () => {
142
- return {
143
- samplesThatHadToBeQueued,
144
- };
145
- },
172
+ getInternalStats: () => ({}),
146
173
  getTimescale,
147
174
  setTimescale,
148
175
  };
package/src/reader.ts CHANGED
@@ -5,6 +5,7 @@ type ReadResult = {
5
5
  type ReadContent = (
6
6
  src: string | File,
7
7
  range: [number, number] | number | null,
8
+ signal: AbortSignal | undefined,
8
9
  ) => Promise<ReadResult>;
9
10
  type GetLength = (src: string | File) => Promise<number>;
10
11
 
@@ -0,0 +1,48 @@
1
+ import {RenderInternals} from '@remotion/renderer';
2
+ import {expect, test} from 'bun:test';
3
+ import {makeMatroskaHeader} from '../boxes/webm/make-header';
4
+ import {
5
+ matroskaHeader,
6
+ seek,
7
+ seekId,
8
+ } from '../boxes/webm/segments/all-segments';
9
+
10
+ test('Should make Matroska header that is same as input', async () => {
11
+ const exampleVideo = RenderInternals.exampleVideos.matroskaMp3;
12
+ const file = await Bun.file(exampleVideo).arrayBuffer();
13
+
14
+ const headerInput = new Uint8Array(file.slice(0, 0x28));
15
+
16
+ const headerOutput = makeMatroskaHeader(matroskaHeader, {
17
+ DocType: 'matroska',
18
+ DocTypeVersion: 4,
19
+ DocTypeReadVersion: 2,
20
+ EBMLMaxIDLength: 4,
21
+ EBMLMaxSizeLength: 8,
22
+ EBMLReadVersion: 1,
23
+ EBMLVersion: 1,
24
+ });
25
+
26
+ expect(headerInput).toEqual(headerOutput);
27
+ });
28
+
29
+ test('Should be able to create SeekIdBox', async () => {
30
+ const file = new Uint8Array(
31
+ await Bun.file('vp8-segments/56-0x53ab').arrayBuffer(),
32
+ );
33
+
34
+ const custom = makeMatroskaHeader(seekId, '0x1549a966');
35
+ expect(custom).toEqual(file);
36
+ });
37
+
38
+ test('Should be able to create Seek', async () => {
39
+ const file = new Uint8Array(
40
+ await Bun.file('vp8-segments/53-0x4dbb').arrayBuffer(),
41
+ );
42
+
43
+ const custom = makeMatroskaHeader(seek, {
44
+ SeekID: '0x1549a966',
45
+ SeekPosition: 0x40,
46
+ });
47
+ expect(custom).toEqual(file);
48
+ });