@remotion/media-parser 4.0.200 → 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 (111) 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 -1
  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 +1 -1
  25. package/dist/boxes/webm/make-header.d.ts +8 -1
  26. package/dist/boxes/webm/make-header.js +65 -30
  27. package/dist/boxes/webm/parse-ebml.d.ts +7 -0
  28. package/dist/boxes/webm/parse-ebml.js +66 -0
  29. package/dist/boxes/webm/parse-webm-header.js +8 -9
  30. package/dist/boxes/webm/segments/all-segments.d.ts +258 -1
  31. package/dist/boxes/webm/segments/all-segments.js +126 -2
  32. package/dist/boxes/webm/segments/seek-position.js +1 -1
  33. package/dist/boxes/webm/segments/seek.d.ts +1 -1
  34. package/dist/boxes/webm/segments/seek.js +8 -2
  35. package/dist/boxes/webm/segments/timestamp-scale.js +1 -1
  36. package/dist/boxes/webm/segments/track-entry.d.ts +5 -1
  37. package/dist/boxes/webm/segments/track-entry.js +19 -20
  38. package/dist/boxes/webm/segments.d.ts +2 -2
  39. package/dist/boxes/webm/segments.js +30 -25
  40. package/dist/boxes/webm/traversal.d.ts +1 -0
  41. package/dist/boxes/webm/traversal.js +12 -1
  42. package/dist/buffer-iterator.d.ts +9 -6
  43. package/dist/buffer-iterator.js +83 -7
  44. package/dist/from-fetch.js +13 -3
  45. package/dist/from-input-type-file.d.ts +2 -0
  46. package/dist/from-input-type-file.js +37 -0
  47. package/dist/from-node.js +9 -2
  48. package/dist/from-web-file.js +6 -1
  49. package/dist/from-web.js +15 -6
  50. package/dist/get-audio-codec.d.ts +1 -1
  51. package/dist/get-codec.d.ts +4 -0
  52. package/dist/get-codec.js +22 -0
  53. package/dist/get-sample-positions.js +1 -1
  54. package/dist/has-all-info.js +1 -1
  55. package/dist/options.d.ts +3 -2
  56. package/dist/parse-media.js +13 -9
  57. package/dist/parse-video.js +16 -0
  58. package/dist/parser-state.d.ts +4 -3
  59. package/dist/parser-state.js +15 -3
  60. package/dist/reader.d.ts +1 -1
  61. package/dist/web-file.d.ts +2 -0
  62. package/dist/web-file.js +37 -0
  63. package/package.json +2 -2
  64. package/src/boxes/iso-base-media/mdat/mdat.ts +2 -1
  65. package/src/boxes/iso-base-media/moov/moov.ts +1 -0
  66. package/src/boxes/iso-base-media/process-box.ts +70 -40
  67. package/src/boxes/iso-base-media/stsd/mebx.ts +3 -0
  68. package/src/boxes/iso-base-media/stsd/samples.ts +3 -0
  69. package/src/boxes/iso-base-media/stsd/stco.ts +5 -3
  70. package/src/boxes/iso-base-media/trak/trak.ts +1 -0
  71. package/src/boxes/webm/make-header.ts +122 -32
  72. package/src/boxes/webm/parse-ebml.ts +93 -0
  73. package/src/boxes/webm/parse-webm-header.ts +8 -12
  74. package/src/boxes/webm/segments/all-segments.ts +222 -1
  75. package/src/boxes/webm/segments/seek-position.ts +1 -1
  76. package/src/boxes/webm/segments/seek.ts +12 -2
  77. package/src/boxes/webm/segments/timestamp-scale.ts +1 -1
  78. package/src/boxes/webm/segments/track-entry.ts +31 -26
  79. package/src/boxes/webm/segments.ts +37 -32
  80. package/src/boxes/webm/traversal.ts +13 -0
  81. package/src/buffer-iterator.ts +102 -9
  82. package/src/from-fetch.ts +22 -3
  83. package/src/from-node.ts +18 -4
  84. package/src/from-web-file.ts +11 -1
  85. package/src/get-sample-positions.ts +1 -1
  86. package/src/has-all-info.ts +1 -1
  87. package/src/options.ts +3 -2
  88. package/src/parse-media.ts +14 -8
  89. package/src/parse-video.ts +17 -0
  90. package/src/parser-state.ts +22 -5
  91. package/src/reader.ts +1 -0
  92. package/src/test/create-matroska.test.ts +36 -2
  93. package/src/test/matroska.test.ts +69 -27
  94. package/src/test/parse-stco.test.ts +2 -0
  95. package/src/test/stream-local.test.ts +23 -9
  96. package/src/test/stream-remote.test.ts +23 -19
  97. package/src/test/stsd.test.ts +2 -0
  98. package/tsconfig.tsbuildinfo +1 -1
  99. package/dist/boxes/iso-base-media/ftype.d.ts +0 -9
  100. package/dist/boxes/iso-base-media/ftype.js +0 -31
  101. package/dist/get-video-metadata.d.ts +0 -2
  102. package/dist/get-video-metadata.js +0 -44
  103. package/dist/read-and-increment-offset.d.ts +0 -28
  104. package/dist/read-and-increment-offset.js +0 -177
  105. package/dist/understand-vorbis.d.ts +0 -1
  106. package/dist/understand-vorbis.js +0 -12
  107. package/src/boxes/webm/segments/unknown.ts +0 -19
  108. /package/dist/{boxes/webm/bitstream/av1/frame.d.ts → get-samples.d.ts} +0 -0
  109. /package/dist/{boxes/webm/bitstream/av1/frame.js → get-samples.js} +0 -0
  110. /package/dist/{boxes/webm/bitstream/h264/get-h264-descriptor.d.ts → sample-aspect-ratio.d.ts} +0 -0
  111. /package/dist/{boxes/webm/bitstream/h264/get-h264-descriptor.js → sample-aspect-ratio.js} +0 -0
@@ -48,6 +48,7 @@ export class OffsetCounter {
48
48
 
49
49
  const isoBaseMediaMp4Pattern = new TextEncoder().encode('ftyp');
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
 
@@ -402,6 +494,7 @@ export const getArrayBufferIterator = (
402
494
  return val;
403
495
  },
404
496
  getUint32,
497
+ getUint64,
405
498
  // https://developer.apple.com/documentation/quicktime-file-format/sound_sample_description_version_1
406
499
  // A 32-bit unsigned fixed-point number (16.16) that indicates the rate at which the sound samples were obtained.
407
500
  getFixedPointUnsigned1616Number: () => {
@@ -420,7 +513,7 @@ export const getArrayBufferIterator = (
420
513
  const val = getSlice(32);
421
514
  return [...Array.from(new Uint8Array(val))];
422
515
  },
423
- getDecimalBytes(length: number): number {
516
+ getUint(length: number): number {
424
517
  const bytes = getSlice(length);
425
518
  const numbers = [...Array.from(new Uint8Array(bytes))];
426
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,
@@ -14,11 +14,13 @@ export type InternalStats = {};
14
14
  export const makeParserState = ({
15
15
  hasAudioCallbacks,
16
16
  hasVideoCallbacks,
17
+ signal,
17
18
  }: {
18
19
  hasAudioCallbacks: boolean;
19
20
  hasVideoCallbacks: boolean;
21
+ signal: AbortSignal | undefined;
20
22
  }) => {
21
- const trackEntries: Record<number, CodecSegment> = {};
23
+ const trackEntries: Record<number, TrackInfo> = {};
22
24
 
23
25
  const onTrackEntrySegment: OnTrackEntrySegment = (trackEntry) => {
24
26
  const trackId = getTrackId(trackEntry);
@@ -35,7 +37,12 @@ export const makeParserState = ({
35
37
  throw new Error('Expected codec');
36
38
  }
37
39
 
38
- trackEntries[trackId] = codec;
40
+ const trackTimescale = getTrackTimestampScale(trackEntry);
41
+
42
+ trackEntries[trackId] = {
43
+ codec: codec.codec,
44
+ trackTimescale,
45
+ };
39
46
  };
40
47
 
41
48
  const videoSampleCallbacks: Record<number, OnVideoSample> = {};
@@ -49,8 +56,10 @@ export const makeParserState = ({
49
56
  let timescale: number | null = null;
50
57
 
51
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.
52
61
  if (timescale === null) {
53
- throw new Error('Timescale not set');
62
+ return 1_000_000;
54
63
  }
55
64
 
56
65
  return timescale;
@@ -125,6 +134,10 @@ export const makeParserState = ({
125
134
  queuedAudioSamples[id] = [];
126
135
  },
127
136
  onAudioSample: async (trackId: number, audioSample: AudioSample) => {
137
+ if (signal?.aborted) {
138
+ throw new Error('Aborted');
139
+ }
140
+
128
141
  const callback = audioSampleCallbacks[trackId];
129
142
  if (callback) {
130
143
  await callback(audioSample);
@@ -139,6 +152,10 @@ export const makeParserState = ({
139
152
  }
140
153
  },
141
154
  onVideoSample: async (trackId: number, videoSample: VideoSample) => {
155
+ if (signal?.aborted) {
156
+ throw new Error('Aborted');
157
+ }
158
+
142
159
  const callback = videoSampleCallbacks[trackId];
143
160
  if (callback) {
144
161
  await callback(videoSample);
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
 
@@ -1,14 +1,48 @@
1
1
  import {RenderInternals} from '@remotion/renderer';
2
2
  import {expect, test} from 'bun:test';
3
3
  import {makeMatroskaHeader} from '../boxes/webm/make-header';
4
+ import {
5
+ matroskaHeader,
6
+ seek,
7
+ seekId,
8
+ } from '../boxes/webm/segments/all-segments';
4
9
 
5
10
  test('Should make Matroska header that is same as input', async () => {
6
11
  const exampleVideo = RenderInternals.exampleVideos.matroskaMp3;
7
12
  const file = await Bun.file(exampleVideo).arrayBuffer();
8
13
 
9
- const headerInput = new Uint8Array(file.slice(0, 0x23 + 5));
14
+ const headerInput = new Uint8Array(file.slice(0, 0x28));
10
15
 
11
- const headerOutput = makeMatroskaHeader();
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
+ });
12
25
 
13
26
  expect(headerInput).toEqual(headerOutput);
14
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
+ });