@remotion/media-parser 4.0.191 → 4.0.193

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 (66) hide show
  1. package/boxes.json +1 -0
  2. package/dist/boxes/iso-base-media/esds/esds-descriptors.d.ts +21 -0
  3. package/dist/boxes/iso-base-media/esds/esds-descriptors.js +62 -0
  4. package/dist/boxes/iso-base-media/esds/esds.d.ts +15 -0
  5. package/dist/boxes/iso-base-media/esds/esds.js +27 -0
  6. package/dist/boxes/iso-base-media/ftype.d.ts +9 -0
  7. package/dist/boxes/iso-base-media/ftype.js +31 -0
  8. package/dist/boxes/iso-base-media/mdhd.d.ts +14 -0
  9. package/dist/boxes/iso-base-media/mdhd.js +33 -0
  10. package/dist/boxes/iso-base-media/process-box.js +30 -0
  11. package/dist/boxes/iso-base-media/stsd/samples.d.ts +2 -0
  12. package/dist/boxes/iso-base-media/stsd/samples.js +28 -8
  13. package/dist/boxes/webm/parse-webm-header.js +4 -4
  14. package/dist/boxes/webm/segments/track-entry.d.ts +30 -0
  15. package/dist/boxes/webm/segments/track-entry.js +59 -8
  16. package/dist/boxes/webm/segments.d.ts +2 -2
  17. package/dist/boxes/webm/segments.js +18 -0
  18. package/dist/buffer-iterator.d.ts +2 -1
  19. package/dist/buffer-iterator.js +29 -8
  20. package/dist/from-node.js +6 -2
  21. package/dist/from-web.js +6 -1
  22. package/dist/get-audio-codec.d.ts +4 -0
  23. package/dist/get-audio-codec.js +106 -0
  24. package/dist/get-dimensions.js +6 -2
  25. package/dist/get-fps.d.ts +8 -0
  26. package/dist/get-fps.js +117 -9
  27. package/dist/get-video-codec.d.ts +4 -0
  28. package/dist/get-video-codec.js +79 -0
  29. package/dist/get-video-metadata.d.ts +2 -0
  30. package/dist/get-video-metadata.js +44 -0
  31. package/dist/has-all-info.d.ts +1 -1
  32. package/dist/has-all-info.js +8 -0
  33. package/dist/options.d.ts +11 -3
  34. package/dist/parse-media.js +27 -6
  35. package/dist/parse-result.d.ts +3 -1
  36. package/dist/read-and-increment-offset.d.ts +28 -0
  37. package/dist/read-and-increment-offset.js +177 -0
  38. package/dist/reader.d.ts +5 -1
  39. package/package.json +2 -2
  40. package/src/boxes/iso-base-media/esds/esds-descriptors.ts +104 -0
  41. package/src/boxes/iso-base-media/esds/esds.ts +49 -0
  42. package/src/boxes/iso-base-media/mdhd.ts +56 -0
  43. package/src/boxes/iso-base-media/process-box.ts +35 -0
  44. package/src/boxes/iso-base-media/stsd/samples.ts +36 -8
  45. package/src/boxes/webm/parse-webm-header.ts +4 -4
  46. package/src/boxes/webm/segments/track-entry.ts +103 -11
  47. package/src/boxes/webm/segments.ts +43 -1
  48. package/src/buffer-iterator.ts +36 -10
  49. package/src/from-node.ts +6 -4
  50. package/src/from-web.ts +8 -1
  51. package/src/get-audio-codec.ts +143 -0
  52. package/src/get-dimensions.ts +11 -4
  53. package/src/get-fps.ts +175 -9
  54. package/src/get-video-codec.ts +104 -0
  55. package/src/has-all-info.ts +19 -2
  56. package/src/options.ts +43 -3
  57. package/src/parse-media.ts +35 -7
  58. package/src/parse-result.ts +5 -1
  59. package/src/reader.ts +5 -1
  60. package/src/test/matroska.test.ts +6 -7
  61. package/src/test/parse-esds.test.ts +75 -0
  62. package/src/test/parse-webm.test.ts +2 -0
  63. package/src/test/stream-local.test.ts +93 -5
  64. package/src/test/stream-remote.test.ts +41 -0
  65. package/src/test/stsd.test.ts +52 -5
  66. package/tsconfig.tsbuildinfo +1 -1
@@ -5,14 +5,14 @@ import {expectSegment} from './segments';
5
5
  // Parsing according to https://darkcoding.net/software/reading-mediarecorders-webm-opus-output/
6
6
  export const parseWebm = (counter: BufferIterator): ParseResult => {
7
7
  counter.discard(4);
8
- const length = counter.getEBML();
8
+ const length = counter.getVint();
9
9
 
10
- if (length !== 31) {
11
- throw new Error(`Expected header length 31, got ${length}`);
10
+ if (length !== 31 && length !== 35) {
11
+ throw new Error(`Expected header length 31 or 25, got ${length}`);
12
12
  }
13
13
 
14
14
  // Discard header for now
15
- counter.discard(31);
15
+ counter.discard(length);
16
16
 
17
17
  return {status: 'done', segments: [expectSegment(counter)]};
18
18
  };
@@ -51,10 +51,6 @@ export type TrackUIDSegment = {
51
51
 
52
52
  export const parseTrackUID = (iterator: BufferIterator): TrackUIDSegment => {
53
53
  const length = iterator.getVint();
54
- // Observation: AV1 has 8 bytes, WebM has 7
55
- if (length !== 8 && length !== 7) {
56
- throw new Error('Expected track number to be 8 byte');
57
- }
58
54
 
59
55
  const bytes = iterator.getSlice(length);
60
56
 
@@ -187,16 +183,11 @@ export type VideoSegment = {
187
183
  };
188
184
 
189
185
  export const parseVideoSegment = (iterator: BufferIterator): VideoSegment => {
190
- const offset = iterator.counter.getOffset();
191
-
192
186
  const length = iterator.getVint();
193
187
 
194
188
  return {
195
189
  type: 'video-segment',
196
- children: expectChildren(
197
- iterator,
198
- length - (iterator.counter.getOffset() - offset),
199
- ),
190
+ children: expectChildren(iterator, length),
200
191
  };
201
192
  };
202
193
 
@@ -287,9 +278,110 @@ export type ColorSegment = {
287
278
  export const parseColorSegment = (iterator: BufferIterator): ColorSegment => {
288
279
  const length = iterator.getVint();
289
280
 
290
- iterator.discard(length - 1);
281
+ iterator.discard(length);
291
282
 
292
283
  return {
293
284
  type: 'color-segment',
294
285
  };
295
286
  };
287
+
288
+ export type TitleSegment = {
289
+ type: 'title-segment';
290
+ title: string;
291
+ };
292
+
293
+ export const parseTitleSegment = (iterator: BufferIterator): TitleSegment => {
294
+ const length = iterator.getVint();
295
+ const title = iterator.getByteString(length);
296
+
297
+ return {
298
+ type: 'title-segment',
299
+ title,
300
+ };
301
+ };
302
+
303
+ export type InterlacedSegment = {
304
+ type: 'interlaced-segment';
305
+ interlaced: boolean;
306
+ };
307
+
308
+ export const parseInterlacedSegment = (
309
+ iterator: BufferIterator,
310
+ ): InterlacedSegment => {
311
+ const length = iterator.getVint();
312
+ if (length !== 1) {
313
+ throw new Error('Expected interlaced segment to be 1 byte');
314
+ }
315
+
316
+ const interlaced = iterator.getUint8();
317
+
318
+ return {
319
+ type: 'interlaced-segment',
320
+ interlaced: Boolean(interlaced),
321
+ };
322
+ };
323
+
324
+ export type CodecPrivateSegment = {
325
+ type: 'codec-private-segment';
326
+ codecPrivateData: number[];
327
+ };
328
+
329
+ export const parseCodecPrivateSegment = (
330
+ iterator: BufferIterator,
331
+ ): CodecPrivateSegment => {
332
+ const length = iterator.getVint();
333
+
334
+ return {
335
+ type: 'codec-private-segment',
336
+ codecPrivateData: [...iterator.getSlice(length)],
337
+ };
338
+ };
339
+
340
+ export type Crc32Segment = {
341
+ type: 'crc32-segment';
342
+ crc32: number[];
343
+ };
344
+
345
+ export const parseCrc32Segment = (iterator: BufferIterator): Crc32Segment => {
346
+ const length = iterator.getVint();
347
+
348
+ return {
349
+ type: 'crc32-segment',
350
+ crc32: [...iterator.getSlice(length)],
351
+ };
352
+ };
353
+
354
+ export type SegmentUUIDSegment = {
355
+ type: 'segment-uuid-segment';
356
+ segmentUUID: string;
357
+ };
358
+
359
+ export const parseSegmentUUIDSegment = (
360
+ iterator: BufferIterator,
361
+ ): SegmentUUIDSegment => {
362
+ const length = iterator.getVint();
363
+
364
+ return {
365
+ type: 'segment-uuid-segment',
366
+ segmentUUID: iterator.getSlice(length).toString(),
367
+ };
368
+ };
369
+
370
+ export type DefaultFlagSegment = {
371
+ type: 'default-flag-segment';
372
+ defaultFlag: boolean;
373
+ };
374
+
375
+ export const parseDefaultFlagSegment = (
376
+ iterator: BufferIterator,
377
+ ): DefaultFlagSegment => {
378
+ const length = iterator.getVint();
379
+ if (length !== 1) {
380
+ throw new Error('Expected default flag segment to be 1 byte');
381
+ }
382
+
383
+ return {
384
+ type: 'default-flag-segment',
385
+ defaultFlag: Boolean(iterator.getUint8()),
386
+ };
387
+ };
@@ -17,13 +17,19 @@ import type {TimestampScaleSegment} from './segments/timestamp-scale';
17
17
  import {parseTimestampScaleSegment} from './segments/timestamp-scale';
18
18
  import type {
19
19
  AlphaModeSegment,
20
+ CodecPrivateSegment,
20
21
  CodecSegment,
21
22
  ColorSegment,
23
+ Crc32Segment,
22
24
  DefaultDurationSegment,
25
+ DefaultFlagSegment,
23
26
  FlagLacingSegment,
24
27
  HeightSegment,
28
+ InterlacedSegment,
25
29
  LanguageSegment,
26
30
  MaxBlockAdditionId,
31
+ SegmentUUIDSegment,
32
+ TitleSegment,
27
33
  TrackEntrySegment,
28
34
  TrackNumberSegment,
29
35
  TrackTypeSegment,
@@ -33,13 +39,19 @@ import type {
33
39
  } from './segments/track-entry';
34
40
  import {
35
41
  parseAlphaModeSegment,
42
+ parseCodecPrivateSegment,
36
43
  parseCodecSegment,
37
44
  parseColorSegment,
45
+ parseCrc32Segment,
38
46
  parseDefaultDurationSegment,
47
+ parseDefaultFlagSegment,
39
48
  parseFlagLacing,
40
49
  parseHeightSegment,
50
+ parseInterlacedSegment,
41
51
  parseLanguageSegment,
42
52
  parseMaxBlockAdditionId,
53
+ parseSegmentUUIDSegment,
54
+ parseTitleSegment,
43
55
  parseTrackEntry,
44
56
  parseTrackNumber,
45
57
  parseTrackTypeSegment,
@@ -82,7 +94,13 @@ export type MatroskaSegment =
82
94
  | HeightSegment
83
95
  | AlphaModeSegment
84
96
  | MaxBlockAdditionId
85
- | ColorSegment;
97
+ | ColorSegment
98
+ | TitleSegment
99
+ | InterlacedSegment
100
+ | CodecPrivateSegment
101
+ | Crc32Segment
102
+ | SegmentUUIDSegment
103
+ | DefaultFlagSegment;
86
104
 
87
105
  export const expectSegment = (iterator: BufferIterator): MatroskaSegment => {
88
106
  const segmentId = iterator.getMatroskaSegmentId();
@@ -189,10 +207,34 @@ export const expectSegment = (iterator: BufferIterator): MatroskaSegment => {
189
207
  return parseHeightSegment(iterator);
190
208
  }
191
209
 
210
+ if (segmentId === '0x9a') {
211
+ return parseInterlacedSegment(iterator);
212
+ }
213
+
192
214
  if (segmentId === '0x53c0') {
193
215
  return parseAlphaModeSegment(iterator);
194
216
  }
195
217
 
218
+ if (segmentId === '0x63a2') {
219
+ return parseCodecPrivateSegment(iterator);
220
+ }
221
+
222
+ if (segmentId === '0x7ba9') {
223
+ return parseTitleSegment(iterator);
224
+ }
225
+
226
+ if (segmentId === '0xbf') {
227
+ return parseCrc32Segment(iterator);
228
+ }
229
+
230
+ if (segmentId === '0x73a4') {
231
+ return parseSegmentUUIDSegment(iterator);
232
+ }
233
+
234
+ if (segmentId === '0x88') {
235
+ return parseDefaultFlagSegment(iterator);
236
+ }
237
+
196
238
  const length = iterator.getVint();
197
239
 
198
240
  const bytesRemaining = iterator.byteLength() - iterator.counter.getOffset();
@@ -48,8 +48,16 @@ const makeOffsetCounter = (): OffsetCounter => {
48
48
  return new OffsetCounter(0);
49
49
  };
50
50
 
51
- export const getArrayBufferIterator = (initialData: Uint8Array) => {
52
- let data = initialData;
51
+ export const getArrayBufferIterator = (
52
+ initialData: Uint8Array,
53
+ maxBytes?: number,
54
+ ) => {
55
+ const buf = new ArrayBuffer(initialData.byteLength, {
56
+ maxByteLength: maxBytes ?? 1_000_000_000,
57
+ });
58
+ let data = new Uint8Array(buf);
59
+ data.set(initialData);
60
+
53
61
  let view = new DataView(data.buffer);
54
62
  const counter = makeOffsetCounter();
55
63
 
@@ -76,6 +84,15 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
76
84
  );
77
85
  };
78
86
 
87
+ const getPaddedFourByteNumber = () => {
88
+ let lastInt = 128;
89
+ while (((lastInt = getUint8()), lastInt === 128)) {
90
+ // Do nothing
91
+ }
92
+
93
+ return lastInt;
94
+ };
95
+
79
96
  const getUint32 = () => {
80
97
  const val = view.getUint32(counter.getDiscardedOffset());
81
98
  counter.increment(4);
@@ -83,11 +100,11 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
83
100
  };
84
101
 
85
102
  const addData = (newData: Uint8Array) => {
86
- const newArray = new Uint8Array(
87
- data.buffer.byteLength + newData.byteLength,
88
- );
89
- newArray.set(data);
90
- newArray.set(new Uint8Array(newData), data.byteLength);
103
+ const oldLength = buf.byteLength;
104
+ const newLength = oldLength + newData.byteLength;
105
+ buf.resize(newLength);
106
+ const newArray = new Uint8Array(buf);
107
+ newArray.set(newData, oldLength);
91
108
  data = newArray;
92
109
  view = new DataView(data.buffer);
93
110
  };
@@ -111,9 +128,9 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
111
128
  const removeBytesRead = () => {
112
129
  const bytesToRemove = counter.getDiscardedOffset();
113
130
  counter.discardBytes(bytesToRemove);
114
- const newArray = new Uint8Array(data.buffer.byteLength - bytesToRemove);
115
- newArray.set(data.slice(bytesToRemove));
116
- data = newArray;
131
+ const newData = data.slice(bytesToRemove);
132
+ data.set(newData);
133
+ buf.resize(newData.byteLength);
117
134
  view = new DataView(data.buffer);
118
135
  };
119
136
 
@@ -134,6 +151,7 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
134
151
  const atom = getSlice(4);
135
152
  return new TextDecoder().decode(atom);
136
153
  },
154
+ getPaddedFourByteNumber,
137
155
  getMatroskaSegmentId: () => {
138
156
  const first = getSlice(1);
139
157
  const firstOneString = `0x${Array.from(new Uint8Array(first))
@@ -154,6 +172,10 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
154
172
  '0xe0',
155
173
  '0xb0',
156
174
  '0xba',
175
+ '0x9a',
176
+ '0xe1',
177
+ '0xbf',
178
+ '0x88',
157
179
  ];
158
180
  if (knownIdsWithOneLength.includes(firstOneString)) {
159
181
  return firstOneString;
@@ -172,6 +194,9 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
172
194
  '0x4489',
173
195
  '0x55ee',
174
196
  '0x55b0',
197
+ '0x7ba9',
198
+ '0x63a2',
199
+ '0x73a4',
175
200
  ];
176
201
 
177
202
  const firstTwoString = `${firstOneString}${Array.from(
@@ -265,6 +290,7 @@ export const getArrayBufferIterator = (initialData: Uint8Array) => {
265
290
  counter.increment(2);
266
291
  return val;
267
292
  },
293
+
268
294
  getInt16: () => {
269
295
  const val = view.getInt16(counter.getDiscardedOffset());
270
296
  counter.increment(2);
package/src/from-node.ts CHANGED
@@ -4,16 +4,18 @@ import {Readable} from 'stream';
4
4
  import type {ReaderInterface} from './reader';
5
5
 
6
6
  export const nodeReader: ReaderInterface = {
7
- read: (src, range) => {
7
+ read: async (src, range) => {
8
8
  const stream = createReadStream(src, {
9
9
  start: range === null ? 0 : range[0],
10
10
  end: range === null ? Infinity : range[1],
11
11
  });
12
- return Promise.resolve(
13
- Readable.toWeb(
12
+ const stats = await stat(src);
13
+ return {
14
+ reader: Readable.toWeb(
14
15
  stream,
15
16
  ).getReader() as ReadableStreamDefaultReader<Uint8Array>,
16
- );
17
+ contentLength: stats.size,
18
+ };
17
19
  },
18
20
  getLength: async (src) => {
19
21
  const stats = await stat(src);
package/src/from-web.ts CHANGED
@@ -31,9 +31,16 @@ export const webReader: ReaderInterface = {
31
31
  throw new Error('No body');
32
32
  }
33
33
 
34
+ const length = res.headers.get('content-length');
35
+ if (!length) {
36
+ throw new Error('No content-length');
37
+ }
38
+
39
+ const contentLength = length === null ? null : parseInt(length, 10);
40
+
34
41
  const reader = res.body.getReader();
35
42
 
36
- return reader;
43
+ return {reader, contentLength};
37
44
  },
38
45
  getLength: async (src) => {
39
46
  const res = await fetch(src, {
@@ -0,0 +1,143 @@
1
+ /* eslint-disable max-depth */
2
+ import type {Sample} from './boxes/iso-base-media/stsd/samples';
3
+ import {trakBoxContainsAudio} from './get-fps';
4
+ import type {KnownAudioCodecs} from './options';
5
+ import type {AnySegment} from './parse-result';
6
+
7
+ export const hasAudioCodec = (boxes: AnySegment[]): boolean => {
8
+ try {
9
+ return getAudioCodec(boxes) !== null;
10
+ } catch (e) {
11
+ return false;
12
+ }
13
+ };
14
+
15
+ const onEsdsBox = (child: AnySegment): KnownAudioCodecs | null => {
16
+ if (child && child.type === 'esds-box') {
17
+ const descriptor = child.descriptors.find(
18
+ (d) => d.type === 'decoder-config-descriptor',
19
+ );
20
+ if (descriptor && descriptor.type === 'decoder-config-descriptor') {
21
+ return descriptor.objectTypeIndication;
22
+ }
23
+ }
24
+
25
+ return null;
26
+ };
27
+
28
+ const onSample = (sample: Sample): KnownAudioCodecs | null | undefined => {
29
+ if (!sample) {
30
+ return null;
31
+ }
32
+
33
+ if (sample.type !== 'audio') {
34
+ return null;
35
+ }
36
+
37
+ if (sample.format === 'sowt') {
38
+ return 'aiff';
39
+ }
40
+
41
+ const child = sample.children.find((c) => c.type === 'esds-box');
42
+
43
+ if (child && child.type === 'esds-box') {
44
+ const ret = onEsdsBox(child);
45
+ if (ret) {
46
+ return ret;
47
+ }
48
+ }
49
+ };
50
+
51
+ export const getAudioCodec = (boxes: AnySegment[]): KnownAudioCodecs | null => {
52
+ const moovBox = boxes.find((b) => b.type === 'moov-box');
53
+ if (moovBox && moovBox.type === 'moov-box') {
54
+ const trakBox = moovBox.children.find(
55
+ (b) => b.type === 'trak-box' && trakBoxContainsAudio(b),
56
+ );
57
+ if (trakBox && trakBox.type === 'trak-box') {
58
+ const mdiaBox = trakBox.children.find(
59
+ (b) => b.type === 'regular-box' && b.boxType === 'mdia',
60
+ );
61
+ if (
62
+ mdiaBox &&
63
+ mdiaBox.type === 'regular-box' &&
64
+ mdiaBox.boxType === 'mdia'
65
+ ) {
66
+ const minfBox = mdiaBox?.children.find(
67
+ (b) => b.type === 'regular-box' && b.boxType === 'minf',
68
+ );
69
+ if (
70
+ minfBox &&
71
+ minfBox.type === 'regular-box' &&
72
+ minfBox.boxType === 'minf'
73
+ ) {
74
+ const stblBox = minfBox?.children.find(
75
+ (b) => b.type === 'regular-box' && b.boxType === 'stbl',
76
+ );
77
+ if (stblBox && stblBox.type === 'regular-box') {
78
+ const stsdBox = stblBox?.children.find(
79
+ (b) => b.type === 'stsd-box',
80
+ );
81
+ if (stsdBox && stsdBox.type === 'stsd-box') {
82
+ const sample = stsdBox.samples.find((s) => s.type === 'audio');
83
+ if (sample && sample.type === 'audio') {
84
+ const ret = onSample(sample);
85
+ if (ret) {
86
+ return ret;
87
+ }
88
+
89
+ const waveBox = sample.children.find(
90
+ (b) => b.type === 'regular-box' && b.boxType === 'wave',
91
+ );
92
+ if (
93
+ waveBox &&
94
+ waveBox.type === 'regular-box' &&
95
+ waveBox.boxType === 'wave'
96
+ ) {
97
+ const esdsBox = waveBox.children.find(
98
+ (b) => b.type === 'esds-box',
99
+ );
100
+ if (esdsBox && esdsBox.type === 'esds-box') {
101
+ const ret2 = onEsdsBox(esdsBox);
102
+ if (ret2) {
103
+ return ret2;
104
+ }
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ const mainSegment = boxes.find((b) => b.type === 'main-segment');
116
+ if (!mainSegment || mainSegment.type !== 'main-segment') {
117
+ return null;
118
+ }
119
+
120
+ const tracksSegment = mainSegment.children.find(
121
+ (b) => b.type === 'tracks-segment',
122
+ );
123
+ if (!tracksSegment || tracksSegment.type !== 'tracks-segment') {
124
+ return null;
125
+ }
126
+
127
+ for (const track of tracksSegment.children) {
128
+ if (track.type === 'track-entry-segment') {
129
+ const trackType = track.children.find((b) => b.type === 'codec-segment');
130
+ if (trackType && trackType.type === 'codec-segment') {
131
+ if (trackType.codec === 'A_OPUS') {
132
+ return 'opus';
133
+ }
134
+
135
+ if (trackType.codec === 'A_PCM/INT/LIT') {
136
+ return 'pcm';
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ return null;
143
+ };
@@ -14,10 +14,17 @@ const getDimensionsFromMatroska = (segments: MainSegment): Dimensions => {
14
14
  throw new Error('No tracks segment');
15
15
  }
16
16
 
17
- // TODO: What if there are multiple video tracks, or audio track is first?
18
- const trackEntrySegment = tracksSegment.children.find(
19
- (b) => b.type === 'track-entry-segment',
20
- );
17
+ const trackEntrySegment = tracksSegment.children.find((b) => {
18
+ if (b.type !== 'track-entry-segment') {
19
+ return false;
20
+ }
21
+
22
+ return (
23
+ b.children.find(
24
+ (c) => c.type === 'codec-segment' && c.codec.startsWith('V_'),
25
+ ) !== undefined
26
+ );
27
+ });
21
28
  if (!trackEntrySegment || trackEntrySegment.type !== 'track-entry-segment') {
22
29
  throw new Error('No track entry segment');
23
30
  }