@remotion/media-parser 4.0.199 → 4.0.200

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 (34) hide show
  1. package/dist/boxes/iso-base-media/mdat/mdat.js +0 -1
  2. package/dist/boxes/webm/ebml.d.ts +2 -0
  3. package/dist/boxes/webm/ebml.js +72 -0
  4. package/dist/boxes/webm/make-header.d.ts +2 -0
  5. package/dist/boxes/webm/make-header.js +44 -0
  6. package/dist/boxes/webm/segments/all-segments.d.ts +5 -0
  7. package/dist/boxes/webm/segments/all-segments.js +5 -0
  8. package/dist/boxes/webm/segments/block-simple-block-flags.d.ts +9 -0
  9. package/dist/boxes/webm/segments/block-simple-block-flags.js +38 -0
  10. package/dist/boxes/webm/segments/track-entry.d.ts +20 -8
  11. package/dist/boxes/webm/segments/track-entry.js +64 -23
  12. package/dist/boxes/webm/segments.d.ts +2 -2
  13. package/dist/boxes/webm/segments.js +35 -6
  14. package/dist/buffer-iterator.d.ts +1 -0
  15. package/dist/buffer-iterator.js +9 -2
  16. package/dist/get-audio-codec.d.ts +1 -1
  17. package/dist/parser-state.d.ts +4 -6
  18. package/dist/parser-state.js +24 -16
  19. package/dist/webcodec-sample-types.d.ts +0 -1
  20. package/package.json +2 -2
  21. package/src/boxes/iso-base-media/mdat/mdat.ts +0 -1
  22. package/src/boxes/webm/ebml.ts +78 -0
  23. package/src/boxes/webm/make-header.ts +48 -0
  24. package/src/boxes/webm/segments/all-segments.ts +5 -0
  25. package/src/boxes/webm/segments/block-simple-block-flags.ts +52 -0
  26. package/src/boxes/webm/segments/track-entry.ts +108 -29
  27. package/src/boxes/webm/segments.ts +71 -9
  28. package/src/buffer-iterator.ts +8 -1
  29. package/src/parser-state.ts +30 -20
  30. package/src/test/create-matroska.test.ts +14 -0
  31. package/src/test/matroska.test.ts +75 -100
  32. package/src/test/stream-local.test.ts +47 -5
  33. package/src/webcodec-sample-types.ts +0 -1
  34. package/tsconfig.tsbuildinfo +1 -1
@@ -21,7 +21,6 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
21
21
  };
22
22
  const videoSampleCallbacks = {};
23
23
  const audioSampleCallbacks = {};
24
- let samplesThatHadToBeQueued = 0;
25
24
  const queuedAudioSamples = {};
26
25
  const queuedVideoSamples = {};
27
26
  const declinedTrackNumbers = [];
@@ -35,6 +34,25 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
35
34
  const setTimescale = (newTimescale) => {
36
35
  timescale = newTimescale;
37
36
  };
37
+ const timestampMap = new Map();
38
+ const setTimestampOffset = (byteOffset, timestamp) => {
39
+ timestampMap.set(byteOffset, timestamp);
40
+ };
41
+ const getTimestampOffsetForByteOffset = (byteOffset) => {
42
+ const entries = Array.from(timestampMap.entries());
43
+ const sortedByByteOffset = entries
44
+ .sort((a, b) => {
45
+ return a[0] - b[0];
46
+ })
47
+ .reverse();
48
+ for (const [offset, timestamp] of sortedByByteOffset) {
49
+ if (offset >= byteOffset) {
50
+ continue;
51
+ }
52
+ return timestamp;
53
+ }
54
+ return timestampMap.get(byteOffset);
55
+ };
38
56
  return {
39
57
  onTrackEntrySegment,
40
58
  getTrackInfoByNumber: (id) => trackEntries[id],
@@ -51,6 +69,8 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
51
69
  }
52
70
  queuedVideoSamples[id] = [];
53
71
  },
72
+ setTimestampOffset,
73
+ getTimestampOffsetForByteOffset,
54
74
  registerAudioSampleCallback: async (id, callback) => {
55
75
  var _a;
56
76
  if (callback === null) {
@@ -65,7 +85,6 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
65
85
  queuedAudioSamples[id] = [];
66
86
  },
67
87
  onAudioSample: async (trackId, audioSample) => {
68
- var _a;
69
88
  const callback = audioSampleCallbacks[trackId];
70
89
  if (callback) {
71
90
  await callback(audioSample);
@@ -75,15 +94,11 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
75
94
  return;
76
95
  }
77
96
  if (!hasAudioCallbacks) {
78
- return;
97
+ throw new Error('No audio callbacks registered');
79
98
  }
80
- (_a = queuedAudioSamples[trackId]) !== null && _a !== void 0 ? _a : (queuedAudioSamples[trackId] = []);
81
- queuedAudioSamples[trackId].push(audioSample);
82
- samplesThatHadToBeQueued++;
83
99
  }
84
100
  },
85
101
  onVideoSample: async (trackId, videoSample) => {
86
- var _a;
87
102
  const callback = videoSampleCallbacks[trackId];
88
103
  if (callback) {
89
104
  await callback(videoSample);
@@ -93,18 +108,11 @@ const makeParserState = ({ hasAudioCallbacks, hasVideoCallbacks, }) => {
93
108
  return;
94
109
  }
95
110
  if (!hasVideoCallbacks) {
96
- return;
111
+ throw new Error('No video callbacks registered');
97
112
  }
98
- (_a = queuedVideoSamples[trackId]) !== null && _a !== void 0 ? _a : (queuedVideoSamples[trackId] = []);
99
- queuedVideoSamples[trackId].push(videoSample);
100
- samplesThatHadToBeQueued++;
101
113
  }
102
114
  },
103
- getInternalStats: () => {
104
- return {
105
- samplesThatHadToBeQueued,
106
- };
107
- },
115
+ getInternalStats: () => ({}),
108
116
  getTimescale,
109
117
  setTimescale,
110
118
  };
@@ -2,7 +2,6 @@ import type { AudioTrack, VideoTrack } from './get-tracks';
2
2
  export type AudioSample = {
3
3
  data: Uint8Array;
4
4
  timestamp: number;
5
- offset: number;
6
5
  trackId: number;
7
6
  type: 'key' | 'delta';
8
7
  };
package/package.json CHANGED
@@ -3,11 +3,11 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/media-parser"
4
4
  },
5
5
  "name": "@remotion/media-parser",
6
- "version": "4.0.199",
6
+ "version": "4.0.200",
7
7
  "main": "dist/index.js",
8
8
  "sideEffects": false,
9
9
  "devDependencies": {
10
- "@remotion/renderer": "4.0.199"
10
+ "@remotion/renderer": "4.0.200"
11
11
  },
12
12
  "publishConfig": {
13
13
  "access": "public"
@@ -91,7 +91,6 @@ export const parseMdat = async ({
91
91
  await options.parserState.onAudioSample(sampleWithIndex.track.trackId, {
92
92
  data: bytes,
93
93
  timestamp: sampleWithIndex.samplePosition.offset,
94
- offset: data.counter.getOffset(),
95
94
  trackId: sampleWithIndex.track.trackId,
96
95
  type: sampleWithIndex.samplePosition.isKeyframe ? 'key' : 'delta',
97
96
  });
@@ -0,0 +1,78 @@
1
+ // https://github.com/Vanilagy/webm-muxer/blob/main/src/ebml.ts#L101
2
+
3
+ export const measureEBMLVarInt = (value: number) => {
4
+ if (value < (1 << 7) - 1) {
5
+ /** Top bit is set, leaving 7 bits to hold the integer, but we can't store
6
+ * 127 because "all bits set to one" is a reserved value. Same thing for the
7
+ * other cases below:
8
+ */
9
+ return 1;
10
+ }
11
+
12
+ if (value < (1 << 14) - 1) {
13
+ return 2;
14
+ }
15
+
16
+ if (value < (1 << 21) - 1) {
17
+ return 3;
18
+ }
19
+
20
+ if (value < (1 << 28) - 1) {
21
+ return 4;
22
+ }
23
+
24
+ if (value < 2 ** 35 - 1) {
25
+ return 5;
26
+ }
27
+
28
+ if (value < 2 ** 42 - 1) {
29
+ return 6;
30
+ }
31
+
32
+ throw new Error('EBML VINT size not supported ' + value);
33
+ };
34
+
35
+ export const getVariableInt = (
36
+ value: number,
37
+ width: number = measureEBMLVarInt(value),
38
+ ) => {
39
+ switch (width) {
40
+ case 1:
41
+ return new Uint8Array([(1 << 7) | value]);
42
+ case 2:
43
+ return new Uint8Array([(1 << 6) | (value >> 8), value]);
44
+ case 3:
45
+ return new Uint8Array([(1 << 5) | (value >> 16), value >> 8, value]);
46
+ case 4:
47
+ return new Uint8Array([
48
+ (1 << 4) | (value >> 24),
49
+ value >> 16,
50
+ value >> 8,
51
+ value,
52
+ ]);
53
+ case 5:
54
+ /**
55
+ * JavaScript converts its doubles to 32-bit integers for bitwise
56
+ * operations, so we need to do a division by 2^32 instead of a
57
+ * right-shift of 32 to retain those top 3 bits
58
+ */
59
+ return new Uint8Array([
60
+ (1 << 3) | ((value / 2 ** 32) & 0x7),
61
+ value >> 24,
62
+ value >> 16,
63
+ value >> 8,
64
+ value,
65
+ ]);
66
+ case 6:
67
+ return new Uint8Array([
68
+ (1 << 2) | ((value / 2 ** 40) & 0x3),
69
+ (value / 2 ** 32) | 0,
70
+ value >> 24,
71
+ value >> 16,
72
+ value >> 8,
73
+ value,
74
+ ]);
75
+ default:
76
+ throw new Error('Bad EBML VINT size ' + width);
77
+ }
78
+ };
@@ -0,0 +1,48 @@
1
+ import {getVariableInt} from './ebml';
2
+ import {matroskaElements} from './segments/all-segments';
3
+
4
+ export const webmPattern = new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]);
5
+
6
+ const matroskaToHex = (
7
+ matrId: (typeof matroskaElements)[keyof typeof matroskaElements],
8
+ ) => {
9
+ const numbers: number[] = [];
10
+ for (let i = 2; i < matrId.length; i += 2) {
11
+ const hex = matrId.substr(i, 2);
12
+ numbers.push(parseInt(hex, 16));
13
+ }
14
+
15
+ return numbers;
16
+ };
17
+
18
+ export const makeMatroskaHeader = () => {
19
+ const size = 0x23;
20
+
21
+ const array = new Uint8Array([
22
+ ...webmPattern,
23
+ ...getVariableInt(size),
24
+ ...matroskaToHex(matroskaElements.EBMLVersion),
25
+ ...getVariableInt(1),
26
+ 1,
27
+ ...matroskaToHex(matroskaElements.EBMLReadVersion),
28
+ ...getVariableInt(1),
29
+ 1,
30
+ ...matroskaToHex(matroskaElements.EBMLMaxIDLength),
31
+ ...getVariableInt(1),
32
+ 4,
33
+ ...matroskaToHex(matroskaElements.EBMLMaxSizeLength),
34
+ ...getVariableInt(1),
35
+ 8,
36
+ ...matroskaToHex(matroskaElements.DocType),
37
+ ...getVariableInt(8),
38
+ ...new TextEncoder().encode('matroska'),
39
+ ...matroskaToHex(matroskaElements.DocTypeVersion),
40
+ ...getVariableInt(1),
41
+ 4,
42
+ ...matroskaToHex(matroskaElements.DocTypeReadVersion),
43
+ ...getVariableInt(1),
44
+ 2,
45
+ ]);
46
+
47
+ return array;
48
+ };
@@ -1,6 +1,11 @@
1
1
  export const matroskaElements = {
2
2
  EBMLMaxIDLength: '0x42f2',
3
+ EBMLVersion: '0x4286',
4
+ EBMLReadVersion: '0x42F7',
3
5
  EBMLMaxSizeLength: '0x42f3',
6
+ DocType: '0x4282',
7
+ DocTypeVersion: '0x4287',
8
+ DocTypeReadVersion: '0x4285',
4
9
  Segment: '0x18538067',
5
10
  SeekHead: '0x114d9b74',
6
11
  Seek: '0x4dbb',
@@ -0,0 +1,52 @@
1
+ import type {BufferIterator} from '../../../buffer-iterator';
2
+ import {matroskaElements} from './all-segments';
3
+
4
+ type BlockFlags = {
5
+ invisible: boolean;
6
+ lacing: number;
7
+ keyframe: boolean | null;
8
+ };
9
+
10
+ export const parseBlockFlags = (
11
+ iterator: BufferIterator,
12
+ type:
13
+ | (typeof matroskaElements)['Block']
14
+ | (typeof matroskaElements)['SimpleBlock'],
15
+ ): BlockFlags => {
16
+ if (type === matroskaElements.Block) {
17
+ iterator.startReadingBits();
18
+ // Reserved
19
+ iterator.getBits(4);
20
+ const invisible = Boolean(iterator.getBits(1));
21
+ const lacing = iterator.getBits(2);
22
+ // unused
23
+ iterator.getBits(1);
24
+ iterator.stopReadingBits();
25
+ return {
26
+ invisible,
27
+ lacing,
28
+ keyframe: null,
29
+ };
30
+ }
31
+
32
+ if (type === matroskaElements.SimpleBlock) {
33
+ iterator.startReadingBits();
34
+
35
+ const keyframe = Boolean(iterator.getBits(1));
36
+ // Reserved
37
+ iterator.getBits(3);
38
+ const invisible = Boolean(iterator.getBits(1));
39
+ const lacing = iterator.getBits(2);
40
+ iterator.getBits(1);
41
+
42
+ iterator.stopReadingBits();
43
+
44
+ return {
45
+ invisible,
46
+ lacing,
47
+ keyframe,
48
+ };
49
+ }
50
+
51
+ throw new Error('Unexpected type');
52
+ };
@@ -1,6 +1,9 @@
1
1
  import type {BufferIterator} from '../../../buffer-iterator';
2
2
  import type {ParserContext} from '../../../parser-context';
3
+ import type {VideoSample} from '../../../webcodec-sample-types';
3
4
  import type {MatroskaSegment} from '../segments';
5
+ import type {matroskaElements} from './all-segments';
6
+ import {parseBlockFlags} from './block-simple-block-flags';
4
7
  import {expectChildren} from './parse-children';
5
8
 
6
9
  export type TrackEntrySegment = {
@@ -566,8 +569,18 @@ export const parseTimestampSegment = (
566
569
  iterator: BufferIterator,
567
570
  length: number,
568
571
  ): TimestampSegment => {
569
- if (length > 2) {
570
- throw new Error('Expected timestamp segment to be 1 byte or 2 bytes');
572
+ if (length > 3) {
573
+ throw new Error(
574
+ 'Expected timestamp segment to be 1 byte or 2 bytes, but is ' + length,
575
+ );
576
+ }
577
+
578
+ if (length === 3) {
579
+ const val = iterator.getUint24();
580
+ return {
581
+ type: 'timestamp-segment',
582
+ timestamp: val,
583
+ };
571
584
  }
572
585
 
573
586
  const value = length === 2 ? iterator.getUint16() : iterator.getUint8();
@@ -578,66 +591,86 @@ export const parseTimestampSegment = (
578
591
  };
579
592
  };
580
593
 
581
- export type SimpleBlockSegment = {
582
- type: 'simple-block-segment';
594
+ export type SimpleBlockOrBlockSegment = {
595
+ type: 'simple-block-or-block-segment';
583
596
  length: number;
584
597
  trackNumber: number;
585
598
  timecode: number;
586
- headerFlags: number;
587
- keyframe: boolean;
588
- lacing: [number, number];
599
+ keyframe: boolean | null;
600
+ lacing: number;
589
601
  invisible: boolean;
590
- children: MatroskaSegment[];
602
+ videoSample: Omit<VideoSample, 'type'> | null;
591
603
  };
592
604
 
593
605
  export type GetTracks = () => TrackEntrySegment[];
594
606
 
595
- export const parseSimpleBlockSegment = async ({
607
+ export const parseSimpleBlockOrBlockSegment = async ({
596
608
  iterator,
597
609
  length,
598
610
  parserContext,
611
+ type,
599
612
  }: {
600
613
  iterator: BufferIterator;
601
614
  length: number;
602
615
  parserContext: ParserContext;
603
- }): Promise<SimpleBlockSegment> => {
616
+ type:
617
+ | (typeof matroskaElements)['Block']
618
+ | (typeof matroskaElements)['SimpleBlock'];
619
+ }): Promise<SimpleBlockOrBlockSegment> => {
604
620
  const start = iterator.counter.getOffset();
605
621
  const trackNumber = iterator.getVint();
606
- const timecode = iterator.getUint16();
607
- const headerFlags = iterator.getUint8();
622
+ const timecodeRelativeToCluster = iterator.getUint16();
608
623
 
609
- const invisible = Boolean((headerFlags >> 5) & 1);
610
- const pos6 = (headerFlags >> 6) & 1;
611
- const pos7 = (headerFlags >> 6) & 1;
612
- const keyframe = Boolean((headerFlags >> 7) & 1);
624
+ const {invisible, lacing, keyframe} = parseBlockFlags(iterator, type);
613
625
 
614
626
  const codec = parserContext.parserState.getTrackInfoByNumber(trackNumber);
627
+ const clusterOffset =
628
+ parserContext.parserState.getTimestampOffsetForByteOffset(
629
+ iterator.counter.getOffset(),
630
+ );
631
+
632
+ if (clusterOffset === undefined) {
633
+ throw new Error(
634
+ 'Could not find offset for byte offset ' + iterator.counter.getOffset(),
635
+ );
636
+ }
637
+
638
+ const timecode = timecodeRelativeToCluster + clusterOffset;
615
639
 
616
640
  if (!codec) {
617
641
  throw new Error('Could not find codec for track ' + trackNumber);
618
642
  }
619
643
 
620
- const children: MatroskaSegment[] = [];
644
+ const remainingNow = length - (iterator.counter.getOffset() - start);
621
645
 
622
- if (codec.codec.startsWith('V_')) {
623
- const remainingNow = length - (iterator.counter.getOffset() - start);
646
+ let videoSample: Omit<VideoSample, 'type'> | null = null;
624
647
 
625
- await parserContext.parserState.onVideoSample(trackNumber, {
648
+ if (codec.codec.startsWith('V_')) {
649
+ const partialVideoSample: Omit<VideoSample, 'type'> = {
626
650
  data: iterator.getSlice(remainingNow),
627
651
  cts: null,
628
652
  dts: null,
629
653
  duration: undefined,
630
- type: keyframe ? 'key' : 'delta',
631
654
  trackId: trackNumber,
632
655
  timestamp: timecode,
633
- });
656
+ };
657
+
658
+ if (keyframe === null) {
659
+ // If we don't know if this is a keyframe, we know after we emit the BlockGroup
660
+ videoSample = partialVideoSample;
661
+ } else {
662
+ const sample: VideoSample = {
663
+ ...partialVideoSample,
664
+ type: keyframe ? 'key' : 'delta',
665
+ };
666
+
667
+ await parserContext.parserState.onVideoSample(trackNumber, sample);
668
+ }
634
669
  }
635
670
 
636
671
  if (codec.codec.startsWith('A_')) {
637
- const vorbisRemaining = length - (iterator.counter.getOffset() - start);
638
672
  await parserContext.parserState.onAudioSample(trackNumber, {
639
- data: iterator.getSlice(vorbisRemaining),
640
- offset: timecode,
673
+ data: iterator.getSlice(remainingNow),
641
674
  trackId: trackNumber,
642
675
  timestamp: timecode,
643
676
  type: 'key',
@@ -650,15 +683,14 @@ export const parseSimpleBlockSegment = async ({
650
683
  }
651
684
 
652
685
  return {
653
- type: 'simple-block-segment',
686
+ type: 'simple-block-or-block-segment',
654
687
  length,
655
688
  trackNumber,
656
689
  timecode,
657
- headerFlags,
658
690
  keyframe,
659
- lacing: [pos6, pos7],
691
+ lacing,
660
692
  invisible,
661
- children,
693
+ videoSample,
662
694
  };
663
695
  };
664
696
 
@@ -705,6 +737,53 @@ export const parseBlockGroupSegment = async (
705
737
  };
706
738
  };
707
739
 
740
+ export type ReferenceBlockSegment = {
741
+ type: 'reference-block-segment';
742
+ referenceBlock: number;
743
+ };
744
+
745
+ export const parseReferenceBlockSegment = (
746
+ iterator: BufferIterator,
747
+ length: number,
748
+ ): ReferenceBlockSegment => {
749
+ if (length > 4) {
750
+ throw new Error(
751
+ `Expected reference block segment to be 4 bytes, but got ${length}`,
752
+ );
753
+ }
754
+
755
+ const referenceBlock =
756
+ length === 4
757
+ ? iterator.getUint32()
758
+ : length === 3
759
+ ? iterator.getUint24()
760
+ : length === 2
761
+ ? iterator.getUint16()
762
+ : iterator.getUint8();
763
+
764
+ return {
765
+ type: 'reference-block-segment',
766
+ referenceBlock,
767
+ };
768
+ };
769
+
770
+ export type BlockAdditionsSegment = {
771
+ type: 'block-additions-segment';
772
+ blockAdditions: Uint8Array;
773
+ };
774
+
775
+ export const parseBlockAdditionsSegment = (
776
+ iterator: BufferIterator,
777
+ length: number,
778
+ ): BlockAdditionsSegment => {
779
+ const blockAdditions = iterator.getSlice(length);
780
+
781
+ return {
782
+ type: 'block-additions-segment',
783
+ blockAdditions,
784
+ };
785
+ };
786
+
708
787
  export type BlockElement = {
709
788
  type: 'block-element-segment';
710
789
  length: number;
@@ -2,6 +2,7 @@ import {registerTrack} from '../../add-new-matroska-tracks';
2
2
  import type {BufferIterator} from '../../buffer-iterator';
3
3
  import type {ParseResult} from '../../parse-result';
4
4
  import type {ParserContext} from '../../parser-context';
5
+ import type {VideoSample} from '../../webcodec-sample-types';
5
6
  import {getTrack} from './get-track';
6
7
  import {matroskaElements} from './segments/all-segments';
7
8
  import type {DurationSegment} from './segments/duration';
@@ -29,6 +30,7 @@ import type {
29
30
  AlphaModeSegment,
30
31
  AudioSegment,
31
32
  BitDepthSegment,
33
+ BlockAdditionsSegment,
32
34
  BlockElement,
33
35
  BlockGroupSegment,
34
36
  ChannelsSegment,
@@ -46,9 +48,10 @@ import type {
46
48
  InterlacedSegment,
47
49
  LanguageSegment,
48
50
  MaxBlockAdditionId,
51
+ ReferenceBlockSegment,
49
52
  SamplingFrequencySegment,
50
53
  SegmentUUIDSegment,
51
- SimpleBlockSegment,
54
+ SimpleBlockOrBlockSegment,
52
55
  TagSegment,
53
56
  TagsSegment,
54
57
  TimestampSegment,
@@ -64,6 +67,7 @@ import {
64
67
  parseAlphaModeSegment,
65
68
  parseAudioSegment,
66
69
  parseBitDepthSegment,
70
+ parseBlockAdditionsSegment,
67
71
  parseBlockElementSegment,
68
72
  parseBlockGroupSegment,
69
73
  parseChannelsSegment,
@@ -80,9 +84,10 @@ import {
80
84
  parseInterlacedSegment,
81
85
  parseLanguageSegment,
82
86
  parseMaxBlockAdditionId,
87
+ parseReferenceBlockSegment,
83
88
  parseSamplingFrequencySegment,
84
89
  parseSegmentUUIDSegment,
85
- parseSimpleBlockSegment,
90
+ parseSimpleBlockOrBlockSegment,
86
91
  parseTagSegment,
87
92
  parseTagsSegment,
88
93
  parseTimestampSegment,
@@ -142,14 +147,16 @@ export type MatroskaSegment =
142
147
  | TagSegment
143
148
  | ClusterSegment
144
149
  | TimestampSegment
145
- | SimpleBlockSegment
150
+ | SimpleBlockOrBlockSegment
146
151
  | BlockGroupSegment
147
152
  | BlockElement
148
153
  | SeekIdSegment
149
154
  | AudioSegment
150
155
  | SamplingFrequencySegment
151
156
  | ChannelsSegment
152
- | BitDepthSegment;
157
+ | BitDepthSegment
158
+ | ReferenceBlockSegment
159
+ | BlockAdditionsSegment;
153
160
 
154
161
  export type OnTrackEntrySegment = (trackEntry: TrackEntrySegment) => void;
155
162
 
@@ -360,20 +367,75 @@ const parseSegment = async ({
360
367
  return parseBitDepthSegment(iterator, length);
361
368
  }
362
369
 
363
- if (segmentId === '0xe7') {
364
- return parseTimestampSegment(iterator, length);
370
+ if (segmentId === matroskaElements.Timestamp) {
371
+ const offset = iterator.counter.getOffset();
372
+ const timestampSegment = parseTimestampSegment(iterator, length);
373
+
374
+ parserContext.parserState.setTimestampOffset(
375
+ offset,
376
+ timestampSegment.timestamp,
377
+ );
378
+
379
+ return timestampSegment;
365
380
  }
366
381
 
367
- if (segmentId === '0xa3') {
368
- return parseSimpleBlockSegment({
382
+ if (
383
+ segmentId === matroskaElements.SimpleBlock ||
384
+ segmentId === matroskaElements.Block
385
+ ) {
386
+ return parseSimpleBlockOrBlockSegment({
369
387
  iterator,
370
388
  length,
371
389
  parserContext,
390
+ type: segmentId,
372
391
  });
373
392
  }
374
393
 
394
+ if (segmentId === matroskaElements.ReferenceBlock) {
395
+ return parseReferenceBlockSegment(iterator, length);
396
+ }
397
+
398
+ if (segmentId === matroskaElements.BlockAdditions) {
399
+ return parseBlockAdditionsSegment(iterator, length);
400
+ }
401
+
375
402
  if (segmentId === '0xa0') {
376
- return parseBlockGroupSegment(iterator, length, parserContext);
403
+ const blockGroup = await parseBlockGroupSegment(
404
+ iterator,
405
+ length,
406
+ parserContext,
407
+ );
408
+
409
+ // Blocks don't have information about keyframes.
410
+ // https://ffmpeg.org/pipermail/ffmpeg-devel/2015-June/173825.html
411
+ // "For Blocks, keyframes is
412
+ // inferred by the absence of ReferenceBlock element (as done by matroskadec).""
413
+
414
+ const block = blockGroup.children.find(
415
+ (c) => c.type === 'simple-block-or-block-segment',
416
+ );
417
+ if (!block || block.type !== 'simple-block-or-block-segment') {
418
+ throw new Error('Expected block segment');
419
+ }
420
+
421
+ const hasReferenceBlock = blockGroup.children.find(
422
+ (c) => c.type === 'reference-block-segment',
423
+ );
424
+
425
+ const partialVideoSample = block.videoSample;
426
+
427
+ if (partialVideoSample) {
428
+ const completeFrame: VideoSample = {
429
+ ...partialVideoSample,
430
+ type: hasReferenceBlock ? 'delta' : 'key',
431
+ };
432
+ await parserContext.parserState.onVideoSample(
433
+ partialVideoSample.trackId,
434
+ completeFrame,
435
+ );
436
+ }
437
+
438
+ return blockGroup;
377
439
  }
378
440
 
379
441
  if (segmentId === '0xa1') {
@@ -1,3 +1,4 @@
1
+ import {webmPattern} from './boxes/webm/make-header';
1
2
  import {
2
3
  knownIdsWithOneLength,
3
4
  knownIdsWithThreeLength,
@@ -46,7 +47,6 @@ 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
51
 
52
52
  const matchesPattern = (pattern: Uint8Array) => {
@@ -388,6 +388,13 @@ export const getArrayBufferIterator = (
388
388
  counter.increment(2);
389
389
  return val;
390
390
  },
391
+ getUint24: () => {
392
+ const val1 = view.getUint8(counter.getDiscardedOffset());
393
+ const val2 = view.getUint8(counter.getDiscardedOffset());
394
+ const val3 = view.getUint8(counter.getDiscardedOffset());
395
+ counter.increment(3);
396
+ return (val1 << 16) | (val2 << 8) | val3;
397
+ },
391
398
 
392
399
  getInt16: () => {
393
400
  const val = view.getInt16(counter.getDiscardedOffset());