@kenzuya/mediabunny 1.26.0 → 1.28.5

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 (237) hide show
  1. package/README.md +1 -1
  2. package/dist/bundles/{mediabunny.mjs → mediabunny.js} +21963 -21388
  3. package/dist/bundles/mediabunny.min.js +490 -0
  4. package/dist/modules/shared/mp3-misc.d.ts.map +1 -1
  5. package/dist/modules/src/adts/adts-demuxer.d.ts +6 -6
  6. package/dist/modules/src/adts/adts-demuxer.d.ts.map +1 -1
  7. package/dist/modules/src/adts/adts-muxer.d.ts +4 -4
  8. package/dist/modules/src/adts/adts-muxer.d.ts.map +1 -1
  9. package/dist/modules/src/adts/adts-reader.d.ts +1 -1
  10. package/dist/modules/src/adts/adts-reader.d.ts.map +1 -1
  11. package/dist/modules/src/avi/avi-demuxer.d.ts +44 -0
  12. package/dist/modules/src/avi/avi-demuxer.d.ts.map +1 -0
  13. package/dist/modules/src/avi/avi-misc.d.ts +88 -0
  14. package/dist/modules/src/avi/avi-misc.d.ts.map +1 -0
  15. package/dist/modules/src/avi/avi-muxer.d.ts +45 -0
  16. package/dist/modules/src/avi/avi-muxer.d.ts.map +1 -0
  17. package/dist/modules/src/avi/riff-writer.d.ts +26 -0
  18. package/dist/modules/src/avi/riff-writer.d.ts.map +1 -0
  19. package/dist/modules/src/codec-data.d.ts +8 -3
  20. package/dist/modules/src/codec-data.d.ts.map +1 -1
  21. package/dist/modules/src/codec.d.ts +10 -10
  22. package/dist/modules/src/codec.d.ts.map +1 -1
  23. package/dist/modules/src/conversion.d.ts +33 -16
  24. package/dist/modules/src/conversion.d.ts.map +1 -1
  25. package/dist/modules/src/custom-coder.d.ts +8 -8
  26. package/dist/modules/src/custom-coder.d.ts.map +1 -1
  27. package/dist/modules/src/demuxer.d.ts +3 -3
  28. package/dist/modules/src/demuxer.d.ts.map +1 -1
  29. package/dist/modules/src/encode.d.ts +8 -8
  30. package/dist/modules/src/encode.d.ts.map +1 -1
  31. package/dist/modules/src/flac/flac-demuxer.d.ts +7 -7
  32. package/dist/modules/src/flac/flac-demuxer.d.ts.map +1 -1
  33. package/dist/modules/src/flac/flac-misc.d.ts +3 -3
  34. package/dist/modules/src/flac/flac-misc.d.ts.map +1 -1
  35. package/dist/modules/src/flac/flac-muxer.d.ts +5 -5
  36. package/dist/modules/src/flac/flac-muxer.d.ts.map +1 -1
  37. package/dist/modules/src/id3.d.ts +3 -3
  38. package/dist/modules/src/id3.d.ts.map +1 -1
  39. package/dist/modules/src/index.d.ts +20 -20
  40. package/dist/modules/src/index.d.ts.map +1 -1
  41. package/dist/modules/src/input-format.d.ts +22 -0
  42. package/dist/modules/src/input-format.d.ts.map +1 -1
  43. package/dist/modules/src/input-track.d.ts +8 -8
  44. package/dist/modules/src/input-track.d.ts.map +1 -1
  45. package/dist/modules/src/input.d.ts +12 -12
  46. package/dist/modules/src/isobmff/isobmff-boxes.d.ts +2 -2
  47. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  48. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +12 -12
  49. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  50. package/dist/modules/src/isobmff/isobmff-misc.d.ts.map +1 -1
  51. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +11 -11
  52. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  53. package/dist/modules/src/isobmff/isobmff-reader.d.ts +2 -2
  54. package/dist/modules/src/isobmff/isobmff-reader.d.ts.map +1 -1
  55. package/dist/modules/src/matroska/ebml.d.ts +3 -3
  56. package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
  57. package/dist/modules/src/matroska/matroska-demuxer.d.ts +13 -13
  58. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  59. package/dist/modules/src/matroska/matroska-input.d.ts +33 -0
  60. package/dist/modules/src/matroska/matroska-input.d.ts.map +1 -0
  61. package/dist/modules/src/matroska/matroska-misc.d.ts.map +1 -1
  62. package/dist/modules/src/matroska/matroska-muxer.d.ts +5 -5
  63. package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
  64. package/dist/modules/src/media-sink.d.ts +5 -5
  65. package/dist/modules/src/media-sink.d.ts.map +1 -1
  66. package/dist/modules/src/media-source.d.ts +22 -4
  67. package/dist/modules/src/media-source.d.ts.map +1 -1
  68. package/dist/modules/src/metadata.d.ts +2 -2
  69. package/dist/modules/src/metadata.d.ts.map +1 -1
  70. package/dist/modules/src/misc.d.ts +5 -4
  71. package/dist/modules/src/misc.d.ts.map +1 -1
  72. package/dist/modules/src/mp3/mp3-demuxer.d.ts +7 -7
  73. package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +1 -1
  74. package/dist/modules/src/mp3/mp3-muxer.d.ts +4 -4
  75. package/dist/modules/src/mp3/mp3-muxer.d.ts.map +1 -1
  76. package/dist/modules/src/mp3/mp3-reader.d.ts +2 -2
  77. package/dist/modules/src/mp3/mp3-reader.d.ts.map +1 -1
  78. package/dist/modules/src/mp3/mp3-writer.d.ts +1 -1
  79. package/dist/modules/src/mp3/mp3-writer.d.ts.map +1 -1
  80. package/dist/modules/src/muxer.d.ts +4 -4
  81. package/dist/modules/src/muxer.d.ts.map +1 -1
  82. package/dist/modules/src/node.d.ts +1 -1
  83. package/dist/modules/src/ogg/ogg-demuxer.d.ts +7 -7
  84. package/dist/modules/src/ogg/ogg-demuxer.d.ts.map +1 -1
  85. package/dist/modules/src/ogg/ogg-misc.d.ts +1 -1
  86. package/dist/modules/src/ogg/ogg-misc.d.ts.map +1 -1
  87. package/dist/modules/src/ogg/ogg-muxer.d.ts +5 -5
  88. package/dist/modules/src/ogg/ogg-muxer.d.ts.map +1 -1
  89. package/dist/modules/src/ogg/ogg-reader.d.ts +1 -1
  90. package/dist/modules/src/ogg/ogg-reader.d.ts.map +1 -1
  91. package/dist/modules/src/output-format.d.ts +51 -6
  92. package/dist/modules/src/output-format.d.ts.map +1 -1
  93. package/dist/modules/src/output.d.ts +13 -13
  94. package/dist/modules/src/output.d.ts.map +1 -1
  95. package/dist/modules/src/packet.d.ts +1 -1
  96. package/dist/modules/src/packet.d.ts.map +1 -1
  97. package/dist/modules/src/pcm.d.ts.map +1 -1
  98. package/dist/modules/src/reader.d.ts +2 -2
  99. package/dist/modules/src/reader.d.ts.map +1 -1
  100. package/dist/modules/src/sample.d.ts +57 -15
  101. package/dist/modules/src/sample.d.ts.map +1 -1
  102. package/dist/modules/src/source.d.ts +3 -3
  103. package/dist/modules/src/source.d.ts.map +1 -1
  104. package/dist/modules/src/subtitles.d.ts +1 -1
  105. package/dist/modules/src/subtitles.d.ts.map +1 -1
  106. package/dist/modules/src/target.d.ts +2 -2
  107. package/dist/modules/src/target.d.ts.map +1 -1
  108. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  109. package/dist/modules/src/wave/riff-writer.d.ts +1 -1
  110. package/dist/modules/src/wave/riff-writer.d.ts.map +1 -1
  111. package/dist/modules/src/wave/wave-demuxer.d.ts +6 -6
  112. package/dist/modules/src/wave/wave-demuxer.d.ts.map +1 -1
  113. package/dist/modules/src/wave/wave-muxer.d.ts +4 -4
  114. package/dist/modules/src/wave/wave-muxer.d.ts.map +1 -1
  115. package/dist/modules/src/writer.d.ts +1 -1
  116. package/dist/modules/src/writer.d.ts.map +1 -1
  117. package/dist/packages/eac3/eac3.wasm +0 -0
  118. package/dist/packages/eac3/mediabunny-eac3.js +1058 -0
  119. package/dist/packages/eac3/mediabunny-eac3.min.js +44 -0
  120. package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.js +694 -0
  121. package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.min.js +58 -0
  122. package/dist/packages/mpeg4/mediabunny-mpeg4.js +1198 -0
  123. package/dist/packages/mpeg4/mediabunny-mpeg4.min.js +44 -0
  124. package/dist/packages/mpeg4/xvid.wasm +0 -0
  125. package/package.json +18 -57
  126. package/dist/bundles/mediabunny.cjs +0 -26140
  127. package/dist/bundles/mediabunny.min.cjs +0 -147
  128. package/dist/bundles/mediabunny.min.mjs +0 -146
  129. package/dist/mediabunny.d.ts +0 -3319
  130. package/dist/modules/shared/mp3-misc.js +0 -147
  131. package/dist/modules/src/adts/adts-demuxer.js +0 -239
  132. package/dist/modules/src/adts/adts-muxer.js +0 -80
  133. package/dist/modules/src/adts/adts-reader.js +0 -63
  134. package/dist/modules/src/codec-data.js +0 -1730
  135. package/dist/modules/src/codec.js +0 -869
  136. package/dist/modules/src/conversion.js +0 -1459
  137. package/dist/modules/src/custom-coder.js +0 -117
  138. package/dist/modules/src/demuxer.js +0 -12
  139. package/dist/modules/src/encode.js +0 -442
  140. package/dist/modules/src/flac/flac-demuxer.js +0 -504
  141. package/dist/modules/src/flac/flac-misc.js +0 -135
  142. package/dist/modules/src/flac/flac-muxer.js +0 -222
  143. package/dist/modules/src/id3.js +0 -848
  144. package/dist/modules/src/index.js +0 -28
  145. package/dist/modules/src/input-format.js +0 -480
  146. package/dist/modules/src/input-track.js +0 -372
  147. package/dist/modules/src/input.js +0 -188
  148. package/dist/modules/src/isobmff/isobmff-boxes.js +0 -1480
  149. package/dist/modules/src/isobmff/isobmff-demuxer.js +0 -2618
  150. package/dist/modules/src/isobmff/isobmff-misc.js +0 -20
  151. package/dist/modules/src/isobmff/isobmff-muxer.js +0 -966
  152. package/dist/modules/src/isobmff/isobmff-reader.js +0 -72
  153. package/dist/modules/src/matroska/ebml.js +0 -653
  154. package/dist/modules/src/matroska/matroska-demuxer.js +0 -2133
  155. package/dist/modules/src/matroska/matroska-misc.js +0 -20
  156. package/dist/modules/src/matroska/matroska-muxer.js +0 -1017
  157. package/dist/modules/src/media-sink.js +0 -1736
  158. package/dist/modules/src/media-source.js +0 -1825
  159. package/dist/modules/src/metadata.js +0 -193
  160. package/dist/modules/src/misc.js +0 -623
  161. package/dist/modules/src/mp3/mp3-demuxer.js +0 -285
  162. package/dist/modules/src/mp3/mp3-muxer.js +0 -123
  163. package/dist/modules/src/mp3/mp3-reader.js +0 -26
  164. package/dist/modules/src/mp3/mp3-writer.js +0 -78
  165. package/dist/modules/src/muxer.js +0 -50
  166. package/dist/modules/src/node.js +0 -9
  167. package/dist/modules/src/ogg/ogg-demuxer.js +0 -763
  168. package/dist/modules/src/ogg/ogg-misc.js +0 -78
  169. package/dist/modules/src/ogg/ogg-muxer.js +0 -353
  170. package/dist/modules/src/ogg/ogg-reader.js +0 -65
  171. package/dist/modules/src/output-format.js +0 -527
  172. package/dist/modules/src/output.js +0 -300
  173. package/dist/modules/src/packet.js +0 -182
  174. package/dist/modules/src/pcm.js +0 -85
  175. package/dist/modules/src/reader.js +0 -236
  176. package/dist/modules/src/sample.js +0 -1056
  177. package/dist/modules/src/source.js +0 -1182
  178. package/dist/modules/src/subtitles.js +0 -575
  179. package/dist/modules/src/target.js +0 -140
  180. package/dist/modules/src/wave/riff-writer.js +0 -30
  181. package/dist/modules/src/wave/wave-demuxer.js +0 -447
  182. package/dist/modules/src/wave/wave-muxer.js +0 -318
  183. package/dist/modules/src/writer.js +0 -370
  184. package/src/adts/adts-demuxer.ts +0 -331
  185. package/src/adts/adts-muxer.ts +0 -111
  186. package/src/adts/adts-reader.ts +0 -85
  187. package/src/codec-data.ts +0 -2078
  188. package/src/codec.ts +0 -1092
  189. package/src/conversion.ts +0 -2112
  190. package/src/custom-coder.ts +0 -197
  191. package/src/demuxer.ts +0 -24
  192. package/src/encode.ts +0 -739
  193. package/src/flac/flac-demuxer.ts +0 -730
  194. package/src/flac/flac-misc.ts +0 -164
  195. package/src/flac/flac-muxer.ts +0 -320
  196. package/src/id3.ts +0 -925
  197. package/src/index.ts +0 -221
  198. package/src/input-format.ts +0 -541
  199. package/src/input-track.ts +0 -529
  200. package/src/input.ts +0 -235
  201. package/src/isobmff/isobmff-boxes.ts +0 -1719
  202. package/src/isobmff/isobmff-demuxer.ts +0 -3190
  203. package/src/isobmff/isobmff-misc.ts +0 -29
  204. package/src/isobmff/isobmff-muxer.ts +0 -1348
  205. package/src/isobmff/isobmff-reader.ts +0 -91
  206. package/src/matroska/ebml.ts +0 -730
  207. package/src/matroska/matroska-demuxer.ts +0 -2481
  208. package/src/matroska/matroska-misc.ts +0 -29
  209. package/src/matroska/matroska-muxer.ts +0 -1276
  210. package/src/media-sink.ts +0 -2179
  211. package/src/media-source.ts +0 -2243
  212. package/src/metadata.ts +0 -320
  213. package/src/misc.ts +0 -798
  214. package/src/mp3/mp3-demuxer.ts +0 -383
  215. package/src/mp3/mp3-muxer.ts +0 -166
  216. package/src/mp3/mp3-reader.ts +0 -34
  217. package/src/mp3/mp3-writer.ts +0 -120
  218. package/src/muxer.ts +0 -88
  219. package/src/node.ts +0 -11
  220. package/src/ogg/ogg-demuxer.ts +0 -1053
  221. package/src/ogg/ogg-misc.ts +0 -116
  222. package/src/ogg/ogg-muxer.ts +0 -497
  223. package/src/ogg/ogg-reader.ts +0 -93
  224. package/src/output-format.ts +0 -945
  225. package/src/output.ts +0 -488
  226. package/src/packet.ts +0 -263
  227. package/src/pcm.ts +0 -112
  228. package/src/reader.ts +0 -323
  229. package/src/sample.ts +0 -1461
  230. package/src/source.ts +0 -1688
  231. package/src/subtitles.ts +0 -711
  232. package/src/target.ts +0 -204
  233. package/src/tsconfig.json +0 -16
  234. package/src/wave/riff-writer.ts +0 -36
  235. package/src/wave/wave-demuxer.ts +0 -529
  236. package/src/wave/wave-muxer.ts +0 -371
  237. package/src/writer.ts +0 -490
@@ -1,2133 +0,0 @@
1
- /*!
2
- * Copyright (c) 2025-present, Vanilagy and contributors
3
- *
4
- * This Source Code Form is subject to the terms of the Mozilla Public
5
- * License, v. 2.0. If a copy of the MPL was not distributed with this
6
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
- */
8
- import { extractAv1CodecInfoFromPacket, extractAvcDecoderConfigurationRecord, extractHevcDecoderConfigurationRecord, extractVp9CodecInfoFromPacket, } from '../codec-data.js';
9
- import { extractAudioCodecString, extractVideoCodecString, OPUS_SAMPLE_RATE, } from '../codec.js';
10
- import { Demuxer } from '../demuxer.js';
11
- import { InputAudioTrack, InputSubtitleTrack, InputVideoTrack, } from '../input-track.js';
12
- import { AttachedFile, DEFAULT_TRACK_DISPOSITION } from '../metadata.js';
13
- import { assert, binarySearchLessOrEqual, COLOR_PRIMARIES_MAP_INVERSE, findLastIndex, isIso639Dash2LanguageCode, last, MATRIX_COEFFICIENTS_MAP_INVERSE, normalizeRotation, roundIfAlmostInteger, TRANSFER_CHARACTERISTICS_MAP_INVERSE, UNDETERMINED_LANGUAGE, } from '../misc.js';
14
- import { EncodedPacket, PLACEHOLDER_DATA } from '../packet.js';
15
- import { assertDefinedSize, CODEC_STRING_MAP, EBMLId, LEVEL_0_AND_1_EBML_IDS, LEVEL_1_EBML_IDS, MAX_HEADER_SIZE, MIN_HEADER_SIZE, readAsciiString, readUnicodeString, readElementHeader, readElementId, readFloat, readUnsignedInt, readVarInt, resync, searchForNextElementId, readUnsignedBigInt, } from './ebml.js';
16
- import { buildMatroskaMimeType } from './matroska-misc.js';
17
- import { FileSlice, readBytes, readI16Be, readU8 } from '../reader.js';
18
- var BlockLacing;
19
- (function (BlockLacing) {
20
- BlockLacing[BlockLacing["None"] = 0] = "None";
21
- BlockLacing[BlockLacing["Xiph"] = 1] = "Xiph";
22
- BlockLacing[BlockLacing["FixedSize"] = 2] = "FixedSize";
23
- BlockLacing[BlockLacing["Ebml"] = 3] = "Ebml";
24
- })(BlockLacing || (BlockLacing = {}));
25
- var ContentEncodingScope;
26
- (function (ContentEncodingScope) {
27
- ContentEncodingScope[ContentEncodingScope["Block"] = 1] = "Block";
28
- ContentEncodingScope[ContentEncodingScope["Private"] = 2] = "Private";
29
- ContentEncodingScope[ContentEncodingScope["Next"] = 4] = "Next";
30
- })(ContentEncodingScope || (ContentEncodingScope = {}));
31
- var ContentCompAlgo;
32
- (function (ContentCompAlgo) {
33
- ContentCompAlgo[ContentCompAlgo["Zlib"] = 0] = "Zlib";
34
- ContentCompAlgo[ContentCompAlgo["Bzlib"] = 1] = "Bzlib";
35
- ContentCompAlgo[ContentCompAlgo["lzo1x"] = 2] = "lzo1x";
36
- ContentCompAlgo[ContentCompAlgo["HeaderStripping"] = 3] = "HeaderStripping";
37
- })(ContentCompAlgo || (ContentCompAlgo = {}));
38
- const METADATA_ELEMENTS = [
39
- { id: EBMLId.SeekHead, flag: 'seekHeadSeen' },
40
- { id: EBMLId.Info, flag: 'infoSeen' },
41
- { id: EBMLId.Tracks, flag: 'tracksSeen' },
42
- { id: EBMLId.Cues, flag: 'cuesSeen' },
43
- ];
44
- const MAX_RESYNC_LENGTH = /* #__PURE__ */ 10 * 2 ** 20; // 10 MiB
45
- export class MatroskaDemuxer extends Demuxer {
46
- constructor(input) {
47
- super(input);
48
- this.readMetadataPromise = null;
49
- this.segments = [];
50
- this.currentSegment = null;
51
- this.currentTrack = null;
52
- this.currentCluster = null;
53
- this.currentBlock = null;
54
- this.currentBlockAdditional = null;
55
- this.currentCueTime = null;
56
- this.currentDecodingInstruction = null;
57
- this.currentTagTargetIsMovie = true;
58
- this.currentSimpleTagName = null;
59
- this.currentAttachedFile = null;
60
- this.isWebM = false;
61
- this.reader = input._reader;
62
- }
63
- async computeDuration() {
64
- const tracks = await this.getTracks();
65
- const trackDurations = await Promise.all(tracks.map(x => x.computeDuration()));
66
- return Math.max(0, ...trackDurations);
67
- }
68
- async getTracks() {
69
- await this.readMetadata();
70
- return this.segments.flatMap(segment => segment.tracks.map(track => track.inputTrack));
71
- }
72
- async getMimeType() {
73
- await this.readMetadata();
74
- const tracks = await this.getTracks();
75
- const codecStrings = await Promise.all(tracks.map(x => x.getCodecParameterString()));
76
- return buildMatroskaMimeType({
77
- isWebM: this.isWebM,
78
- hasVideo: this.segments.some(segment => segment.tracks.some(x => x.info?.type === 'video')),
79
- hasAudio: this.segments.some(segment => segment.tracks.some(x => x.info?.type === 'audio')),
80
- codecStrings: codecStrings.filter(Boolean),
81
- });
82
- }
83
- async getMetadataTags() {
84
- await this.readMetadata();
85
- // Load metadata tags from each segment lazily (only once)
86
- for (const segment of this.segments) {
87
- if (!segment.metadataTagsCollected) {
88
- if (this.reader.fileSize !== null) {
89
- await this.loadSegmentMetadata(segment);
90
- }
91
- else {
92
- // The seeking would be too crazy, let's not
93
- }
94
- segment.metadataTagsCollected = true;
95
- }
96
- }
97
- // This is kinda handwavy, and how we handle multiple segments isn't suuuuper well-defined anyway; so we just
98
- // shallow-merge metadata tags from all (usually just one) segments.
99
- let metadataTags = {};
100
- for (const segment of this.segments) {
101
- metadataTags = { ...metadataTags, ...segment.metadataTags };
102
- }
103
- return metadataTags;
104
- }
105
- readMetadata() {
106
- return this.readMetadataPromise ??= (async () => {
107
- let currentPos = 0;
108
- // Loop over all top-level elements in the file
109
- while (true) {
110
- let slice = this.reader.requestSliceRange(currentPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
111
- if (slice instanceof Promise)
112
- slice = await slice;
113
- if (!slice)
114
- break;
115
- const header = readElementHeader(slice);
116
- if (!header) {
117
- break; // Zero padding at the end of the file triggers this, for example
118
- }
119
- const id = header.id;
120
- let size = header.size;
121
- const dataStartPos = slice.filePos;
122
- if (id === EBMLId.EBML) {
123
- assertDefinedSize(size);
124
- let slice = this.reader.requestSlice(dataStartPos, size);
125
- if (slice instanceof Promise)
126
- slice = await slice;
127
- if (!slice)
128
- break;
129
- this.readContiguousElements(slice);
130
- }
131
- else if (id === EBMLId.Segment) { // Segment found!
132
- await this.readSegment(dataStartPos, size);
133
- if (size === null) {
134
- // Segment sizes can be undefined (common in livestreamed files), so assume this is the last
135
- // and only segment
136
- break;
137
- }
138
- if (this.reader.fileSize === null) {
139
- break; // Stop at the first segment
140
- }
141
- }
142
- else if (id === EBMLId.Cluster) {
143
- if (this.reader.fileSize === null) {
144
- break; // Shouldn't be reached anyway, since we stop at the first segment
145
- }
146
- // Clusters are not a top-level element in Matroska, but some files contain a Segment whose size
147
- // doesn't contain any of the clusters that follow it. In the case, we apply the following logic: if
148
- // we find a top-level cluster, attribute it to the previous segment.
149
- if (size === null) {
150
- // Just in case this is one of those weird sizeless clusters, let's do our best and still try to
151
- // determine its size.
152
- const nextElementPos = await searchForNextElementId(this.reader, dataStartPos, LEVEL_0_AND_1_EBML_IDS, this.reader.fileSize);
153
- size = nextElementPos.pos - dataStartPos;
154
- }
155
- const lastSegment = last(this.segments);
156
- if (lastSegment) {
157
- // Extend the previous segment's size
158
- lastSegment.elementEndPos = dataStartPos + size;
159
- }
160
- }
161
- assertDefinedSize(size);
162
- currentPos = dataStartPos + size;
163
- }
164
- })();
165
- }
166
- async readSegment(segmentDataStart, dataSize) {
167
- this.currentSegment = {
168
- seekHeadSeen: false,
169
- infoSeen: false,
170
- tracksSeen: false,
171
- cuesSeen: false,
172
- tagsSeen: false,
173
- attachmentsSeen: false,
174
- timestampScale: -1,
175
- timestampFactor: -1,
176
- duration: -1,
177
- seekEntries: [],
178
- tracks: [],
179
- cuePoints: [],
180
- dataStartPos: segmentDataStart,
181
- elementEndPos: dataSize === null
182
- ? null // Assume it goes until the end of the file
183
- : segmentDataStart + dataSize,
184
- clusterSeekStartPos: segmentDataStart,
185
- lastReadCluster: null,
186
- metadataTags: {},
187
- metadataTagsCollected: false,
188
- };
189
- this.segments.push(this.currentSegment);
190
- let currentPos = segmentDataStart;
191
- while (this.currentSegment.elementEndPos === null || currentPos < this.currentSegment.elementEndPos) {
192
- let slice = this.reader.requestSliceRange(currentPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
193
- if (slice instanceof Promise)
194
- slice = await slice;
195
- if (!slice)
196
- break;
197
- const elementStartPos = currentPos;
198
- const header = readElementHeader(slice);
199
- if (!header || (!LEVEL_1_EBML_IDS.includes(header.id) && header.id !== EBMLId.Void)) {
200
- // Potential junk. Let's try to resync
201
- const nextPos = await resync(this.reader, elementStartPos, LEVEL_1_EBML_IDS, Math.min(this.currentSegment.elementEndPos ?? Infinity, elementStartPos + MAX_RESYNC_LENGTH));
202
- if (nextPos) {
203
- currentPos = nextPos;
204
- continue;
205
- }
206
- else {
207
- break; // Resync failed
208
- }
209
- }
210
- const { id, size } = header;
211
- const dataStartPos = slice.filePos;
212
- const metadataElementIndex = METADATA_ELEMENTS.findIndex(x => x.id === id);
213
- if (metadataElementIndex !== -1) {
214
- const field = METADATA_ELEMENTS[metadataElementIndex].flag;
215
- this.currentSegment[field] = true;
216
- assertDefinedSize(size);
217
- let slice = this.reader.requestSlice(dataStartPos, size);
218
- if (slice instanceof Promise)
219
- slice = await slice;
220
- if (slice) {
221
- this.readContiguousElements(slice);
222
- }
223
- }
224
- else if (id === EBMLId.Tags || id === EBMLId.Attachments) {
225
- // Metadata found at the beginning of the segment, great, let's parse it
226
- if (id === EBMLId.Tags) {
227
- this.currentSegment.tagsSeen = true;
228
- }
229
- else {
230
- this.currentSegment.attachmentsSeen = true;
231
- }
232
- assertDefinedSize(size);
233
- let slice = this.reader.requestSlice(dataStartPos, size);
234
- if (slice instanceof Promise)
235
- slice = await slice;
236
- if (slice) {
237
- this.readContiguousElements(slice);
238
- }
239
- }
240
- else if (id === EBMLId.Cluster) {
241
- this.currentSegment.clusterSeekStartPos = elementStartPos;
242
- break; // Stop at the first cluster
243
- }
244
- if (size === null) {
245
- break;
246
- }
247
- else {
248
- currentPos = dataStartPos + size;
249
- }
250
- }
251
- // Sort the seek entries by file position so reading them exhibits a sequential pattern
252
- this.currentSegment.seekEntries.sort((a, b) => a.segmentPosition - b.segmentPosition);
253
- if (this.reader.fileSize !== null) {
254
- // Use the seek head to read missing metadata elements
255
- for (const seekEntry of this.currentSegment.seekEntries) {
256
- const target = METADATA_ELEMENTS.find(x => x.id === seekEntry.id);
257
- if (!target) {
258
- continue;
259
- }
260
- if (this.currentSegment[target.flag])
261
- continue;
262
- let slice = this.reader.requestSliceRange(segmentDataStart + seekEntry.segmentPosition, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
263
- if (slice instanceof Promise)
264
- slice = await slice;
265
- if (!slice)
266
- continue;
267
- const header = readElementHeader(slice);
268
- if (!header)
269
- continue;
270
- const { id, size } = header;
271
- if (id !== target.id)
272
- continue;
273
- assertDefinedSize(size);
274
- this.currentSegment[target.flag] = true;
275
- let dataSlice = this.reader.requestSlice(slice.filePos, size);
276
- if (dataSlice instanceof Promise)
277
- dataSlice = await dataSlice;
278
- if (!dataSlice)
279
- continue;
280
- this.readContiguousElements(dataSlice);
281
- }
282
- }
283
- if (this.currentSegment.timestampScale === -1) {
284
- // TimestampScale element is missing. Technically an invalid file, but let's default to the typical value,
285
- // which is 1e6.
286
- this.currentSegment.timestampScale = 1e6;
287
- this.currentSegment.timestampFactor = 1e9 / 1e6;
288
- }
289
- // Put default tracks first
290
- this.currentSegment.tracks.sort((a, b) => Number(b.disposition.default) - Number(a.disposition.default));
291
- // Now, let's distribute the cue points to the tracks
292
- const idToTrack = new Map(this.currentSegment.tracks.map(x => [x.id, x]));
293
- // Assign cue points to their respective tracks
294
- for (const cuePoint of this.currentSegment.cuePoints) {
295
- const track = idToTrack.get(cuePoint.trackId);
296
- if (track) {
297
- track.cuePoints.push(cuePoint);
298
- }
299
- }
300
- for (const track of this.currentSegment.tracks) {
301
- // Sort cue points by time
302
- track.cuePoints.sort((a, b) => a.time - b.time);
303
- // Remove multiple cue points for the same time
304
- for (let i = 0; i < track.cuePoints.length - 1; i++) {
305
- const cuePoint1 = track.cuePoints[i];
306
- const cuePoint2 = track.cuePoints[i + 1];
307
- if (cuePoint1.time === cuePoint2.time) {
308
- track.cuePoints.splice(i + 1, 1);
309
- i--;
310
- }
311
- }
312
- }
313
- let trackWithMostCuePoints = null;
314
- let maxCuePointCount = -Infinity;
315
- for (const track of this.currentSegment.tracks) {
316
- if (track.cuePoints.length > maxCuePointCount) {
317
- maxCuePointCount = track.cuePoints.length;
318
- trackWithMostCuePoints = track;
319
- }
320
- }
321
- // For every track that has received 0 cue points (can happen, often only the video track receives cue points),
322
- // we still want to have better seeking. Therefore, let's give it the cue points of the track with the most cue
323
- // points, which should provide us with the most fine-grained seeking.
324
- for (const track of this.currentSegment.tracks) {
325
- if (track.cuePoints.length === 0) {
326
- track.cuePoints = trackWithMostCuePoints.cuePoints;
327
- }
328
- }
329
- this.currentSegment = null;
330
- }
331
- async readCluster(startPos, segment) {
332
- if (segment.lastReadCluster?.elementStartPos === startPos) {
333
- return segment.lastReadCluster;
334
- }
335
- let headerSlice = this.reader.requestSliceRange(startPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
336
- if (headerSlice instanceof Promise)
337
- headerSlice = await headerSlice;
338
- assert(headerSlice);
339
- const elementStartPos = startPos;
340
- const elementHeader = readElementHeader(headerSlice);
341
- assert(elementHeader);
342
- const id = elementHeader.id;
343
- assert(id === EBMLId.Cluster);
344
- let size = elementHeader.size;
345
- const dataStartPos = headerSlice.filePos;
346
- if (size === null) {
347
- // The cluster's size is undefined (can happen in livestreamed files). We'd still like to know the size of
348
- // it, so we have no other choice but to iterate over the EBML structure until we find an element at level
349
- // 0 or 1, indicating the end of the cluster (all elements inside the cluster are at level 2).
350
- const nextElementPos = await searchForNextElementId(this.reader, dataStartPos, LEVEL_0_AND_1_EBML_IDS, segment.elementEndPos);
351
- size = nextElementPos.pos - dataStartPos;
352
- }
353
- // Load the entire cluster
354
- let dataSlice = this.reader.requestSlice(dataStartPos, size);
355
- if (dataSlice instanceof Promise)
356
- dataSlice = await dataSlice;
357
- const cluster = {
358
- segment,
359
- elementStartPos,
360
- elementEndPos: dataStartPos + size,
361
- dataStartPos,
362
- timestamp: -1,
363
- trackData: new Map(),
364
- };
365
- this.currentCluster = cluster;
366
- if (dataSlice) {
367
- // Read the children of the cluster, stopping early at level 0 or 1 EBML elements. We do this because some
368
- // clusters have incorrect sizes that are too large
369
- const endPos = this.readContiguousElements(dataSlice, LEVEL_0_AND_1_EBML_IDS);
370
- cluster.elementEndPos = endPos;
371
- }
372
- for (const [, trackData] of cluster.trackData) {
373
- const track = trackData.track;
374
- // This must hold, as track datas only get created if a block for that track is encountered
375
- assert(trackData.blocks.length > 0);
376
- let hasLacedBlocks = false;
377
- for (let i = 0; i < trackData.blocks.length; i++) {
378
- const block = trackData.blocks[i];
379
- block.timestamp += cluster.timestamp;
380
- hasLacedBlocks ||= block.lacing !== BlockLacing.None;
381
- }
382
- trackData.presentationTimestamps = trackData.blocks
383
- .map((block, i) => ({ timestamp: block.timestamp, blockIndex: i }))
384
- .sort((a, b) => a.timestamp - b.timestamp);
385
- for (let i = 0; i < trackData.presentationTimestamps.length; i++) {
386
- const currentEntry = trackData.presentationTimestamps[i];
387
- const currentBlock = trackData.blocks[currentEntry.blockIndex];
388
- if (trackData.firstKeyFrameTimestamp === null && currentBlock.isKeyFrame) {
389
- trackData.firstKeyFrameTimestamp = currentBlock.timestamp;
390
- }
391
- if (i < trackData.presentationTimestamps.length - 1) {
392
- // Update block durations based on presentation order
393
- const nextEntry = trackData.presentationTimestamps[i + 1];
394
- currentBlock.duration = nextEntry.timestamp - currentBlock.timestamp;
395
- }
396
- else if (currentBlock.duration === 0) {
397
- if (track.defaultDuration != null) {
398
- if (currentBlock.lacing === BlockLacing.None) {
399
- currentBlock.duration = track.defaultDuration;
400
- }
401
- else {
402
- // Handled by the lace resolution code
403
- }
404
- }
405
- }
406
- }
407
- if (hasLacedBlocks) {
408
- // Perform lace resolution. Here, we expand each laced block into multiple blocks where each contains
409
- // one frame of the lace. We do this after determining block timestamps so we can properly distribute
410
- // the block's duration across the laced frames.
411
- this.expandLacedBlocks(trackData.blocks, track);
412
- // Recompute since blocks have changed
413
- trackData.presentationTimestamps = trackData.blocks
414
- .map((block, i) => ({ timestamp: block.timestamp, blockIndex: i }))
415
- .sort((a, b) => a.timestamp - b.timestamp);
416
- }
417
- const firstBlock = trackData.blocks[trackData.presentationTimestamps[0].blockIndex];
418
- const lastBlock = trackData.blocks[last(trackData.presentationTimestamps).blockIndex];
419
- trackData.startTimestamp = firstBlock.timestamp;
420
- trackData.endTimestamp = lastBlock.timestamp + lastBlock.duration;
421
- // Let's remember that a cluster with a given timestamp is here, speeding up future lookups if no cues exist
422
- const insertionIndex = binarySearchLessOrEqual(track.clusterPositionCache, trackData.startTimestamp, x => x.startTimestamp);
423
- if (insertionIndex === -1
424
- || track.clusterPositionCache[insertionIndex].elementStartPos !== elementStartPos) {
425
- track.clusterPositionCache.splice(insertionIndex + 1, 0, {
426
- elementStartPos: cluster.elementStartPos,
427
- startTimestamp: trackData.startTimestamp,
428
- });
429
- }
430
- }
431
- segment.lastReadCluster = cluster;
432
- return cluster;
433
- }
434
- getTrackDataInCluster(cluster, trackNumber) {
435
- let trackData = cluster.trackData.get(trackNumber);
436
- if (!trackData) {
437
- const track = cluster.segment.tracks.find(x => x.id === trackNumber);
438
- if (!track) {
439
- return null;
440
- }
441
- trackData = {
442
- track,
443
- startTimestamp: 0,
444
- endTimestamp: 0,
445
- firstKeyFrameTimestamp: null,
446
- blocks: [],
447
- presentationTimestamps: [],
448
- };
449
- cluster.trackData.set(trackNumber, trackData);
450
- }
451
- return trackData;
452
- }
453
- expandLacedBlocks(blocks, track) {
454
- // https://www.matroska.org/technical/notes.html#block-lacing
455
- for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
456
- const originalBlock = blocks[blockIndex];
457
- if (originalBlock.lacing === BlockLacing.None) {
458
- continue;
459
- }
460
- // Decode the block data if it hasn't been decoded yet (needed for lacing expansion)
461
- if (!originalBlock.decoded) {
462
- originalBlock.data = this.decodeBlockData(track, originalBlock.data);
463
- originalBlock.decoded = true;
464
- }
465
- const slice = FileSlice.tempFromBytes(originalBlock.data);
466
- const frameSizes = [];
467
- const frameCount = readU8(slice) + 1;
468
- switch (originalBlock.lacing) {
469
- case BlockLacing.Xiph:
470
- {
471
- let totalUsedSize = 0;
472
- // Xiph lacing, just like in Ogg
473
- for (let i = 0; i < frameCount - 1; i++) {
474
- let frameSize = 0;
475
- while (slice.bufferPos < slice.length) {
476
- const value = readU8(slice);
477
- frameSize += value;
478
- if (value < 255) {
479
- frameSizes.push(frameSize);
480
- totalUsedSize += frameSize;
481
- break;
482
- }
483
- }
484
- }
485
- // Compute the last frame's size from whatever's left
486
- frameSizes.push(slice.length - (slice.bufferPos + totalUsedSize));
487
- }
488
- ;
489
- break;
490
- case BlockLacing.FixedSize:
491
- {
492
- // Fixed size lacing: all frames have same size
493
- const totalDataSize = slice.length - 1; // Minus the frame count byte
494
- const frameSize = Math.floor(totalDataSize / frameCount);
495
- for (let i = 0; i < frameCount; i++) {
496
- frameSizes.push(frameSize);
497
- }
498
- }
499
- ;
500
- break;
501
- case BlockLacing.Ebml:
502
- {
503
- // EBML lacing: first size absolute, subsequent ones are coded as signed differences from the last
504
- const firstResult = readVarInt(slice);
505
- assert(firstResult !== null); // Assume it's not an invalid VINT
506
- let currentSize = firstResult;
507
- frameSizes.push(currentSize);
508
- let totalUsedSize = currentSize;
509
- for (let i = 1; i < frameCount - 1; i++) {
510
- const startPos = slice.bufferPos;
511
- const diffResult = readVarInt(slice);
512
- assert(diffResult !== null);
513
- const unsignedDiff = diffResult;
514
- const width = slice.bufferPos - startPos;
515
- const bias = (1 << (width * 7 - 1)) - 1; // Typo-corrected version of 2^((7*n)-1)^-1
516
- const diff = unsignedDiff - bias;
517
- currentSize += diff;
518
- frameSizes.push(currentSize);
519
- totalUsedSize += currentSize;
520
- }
521
- // Compute the last frame's size from whatever's left
522
- frameSizes.push(slice.length - (slice.bufferPos + totalUsedSize));
523
- }
524
- ;
525
- break;
526
- default: assert(false);
527
- }
528
- assert(frameSizes.length === frameCount);
529
- blocks.splice(blockIndex, 1); // Remove the original block
530
- const blockDuration = originalBlock.duration || frameCount * (track.defaultDuration ?? 0);
531
- // Now, let's insert each frame as its own block
532
- for (let i = 0; i < frameCount; i++) {
533
- const frameSize = frameSizes[i];
534
- const frameData = readBytes(slice, frameSize);
535
- // Distribute timestamps evenly across the block duration
536
- const frameTimestamp = originalBlock.timestamp + (blockDuration * i / frameCount);
537
- const frameDuration = blockDuration / frameCount;
538
- blocks.splice(blockIndex + i, 0, {
539
- timestamp: frameTimestamp,
540
- duration: frameDuration,
541
- isKeyFrame: originalBlock.isKeyFrame,
542
- data: frameData,
543
- lacing: BlockLacing.None,
544
- decoded: true,
545
- mainAdditional: originalBlock.mainAdditional,
546
- });
547
- }
548
- blockIndex += frameCount; // Skip the blocks we just added
549
- blockIndex--;
550
- }
551
- }
552
- async loadSegmentMetadata(segment) {
553
- for (const seekEntry of segment.seekEntries) {
554
- if (seekEntry.id === EBMLId.Tags && !segment.tagsSeen) {
555
- // We need to load the tags
556
- }
557
- else if (seekEntry.id === EBMLId.Attachments && !segment.attachmentsSeen) {
558
- // We need to load the attachments
559
- }
560
- else {
561
- continue;
562
- }
563
- let slice = this.reader.requestSliceRange(segment.dataStartPos + seekEntry.segmentPosition, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
564
- if (slice instanceof Promise)
565
- slice = await slice;
566
- if (!slice)
567
- continue;
568
- const header = readElementHeader(slice);
569
- if (!header || header.id !== seekEntry.id)
570
- continue;
571
- const { size } = header;
572
- assertDefinedSize(size);
573
- assert(!this.currentSegment);
574
- this.currentSegment = segment;
575
- let dataSlice = this.reader.requestSlice(slice.filePos, size);
576
- if (dataSlice instanceof Promise)
577
- dataSlice = await dataSlice;
578
- if (dataSlice) {
579
- this.readContiguousElements(dataSlice);
580
- }
581
- this.currentSegment = null;
582
- // Mark as seen
583
- if (seekEntry.id === EBMLId.Tags) {
584
- segment.tagsSeen = true;
585
- }
586
- else if (seekEntry.id === EBMLId.Attachments) {
587
- segment.attachmentsSeen = true;
588
- }
589
- }
590
- }
591
- readContiguousElements(slice, stopIds) {
592
- const startIndex = slice.filePos;
593
- while (slice.filePos - startIndex <= slice.length - MIN_HEADER_SIZE) {
594
- const startPos = slice.filePos;
595
- const foundElement = this.traverseElement(slice, stopIds);
596
- if (!foundElement) {
597
- return startPos;
598
- }
599
- }
600
- return slice.filePos;
601
- }
602
- traverseElement(slice, stopIds) {
603
- const header = readElementHeader(slice);
604
- if (!header) {
605
- return false;
606
- }
607
- if (stopIds && stopIds.includes(header.id)) {
608
- return false;
609
- }
610
- const { id, size } = header;
611
- const dataStartPos = slice.filePos;
612
- assertDefinedSize(size);
613
- switch (id) {
614
- case EBMLId.DocType:
615
- {
616
- this.isWebM = readAsciiString(slice, size) === 'webm';
617
- }
618
- ;
619
- break;
620
- case EBMLId.Seek:
621
- {
622
- if (!this.currentSegment)
623
- break;
624
- const seekEntry = { id: -1, segmentPosition: -1 };
625
- this.currentSegment.seekEntries.push(seekEntry);
626
- this.readContiguousElements(slice.slice(dataStartPos, size));
627
- if (seekEntry.id === -1 || seekEntry.segmentPosition === -1) {
628
- this.currentSegment.seekEntries.pop();
629
- }
630
- }
631
- ;
632
- break;
633
- case EBMLId.SeekID:
634
- {
635
- const lastSeekEntry = this.currentSegment?.seekEntries[this.currentSegment.seekEntries.length - 1];
636
- if (!lastSeekEntry)
637
- break;
638
- lastSeekEntry.id = readUnsignedInt(slice, size);
639
- }
640
- ;
641
- break;
642
- case EBMLId.SeekPosition:
643
- {
644
- const lastSeekEntry = this.currentSegment?.seekEntries[this.currentSegment.seekEntries.length - 1];
645
- if (!lastSeekEntry)
646
- break;
647
- lastSeekEntry.segmentPosition = readUnsignedInt(slice, size);
648
- }
649
- ;
650
- break;
651
- case EBMLId.TimestampScale:
652
- {
653
- if (!this.currentSegment)
654
- break;
655
- this.currentSegment.timestampScale = readUnsignedInt(slice, size);
656
- this.currentSegment.timestampFactor = 1e9 / this.currentSegment.timestampScale;
657
- }
658
- ;
659
- break;
660
- case EBMLId.Duration:
661
- {
662
- if (!this.currentSegment)
663
- break;
664
- this.currentSegment.duration = readFloat(slice, size);
665
- }
666
- ;
667
- break;
668
- case EBMLId.TrackEntry:
669
- {
670
- if (!this.currentSegment)
671
- break;
672
- this.currentTrack = {
673
- id: -1,
674
- segment: this.currentSegment,
675
- demuxer: this,
676
- clusterPositionCache: [],
677
- cuePoints: [],
678
- disposition: {
679
- ...DEFAULT_TRACK_DISPOSITION,
680
- },
681
- inputTrack: null,
682
- codecId: null,
683
- codecPrivate: null,
684
- defaultDuration: null,
685
- name: null,
686
- languageCode: UNDETERMINED_LANGUAGE,
687
- decodingInstructions: [],
688
- info: null,
689
- };
690
- this.readContiguousElements(slice.slice(dataStartPos, size));
691
- if (this.currentTrack.decodingInstructions.some((instruction) => {
692
- return instruction.data?.type !== 'decompress'
693
- || instruction.scope !== ContentEncodingScope.Block
694
- || instruction.data.algorithm !== ContentCompAlgo.HeaderStripping;
695
- })) {
696
- console.warn(`Track #${this.currentTrack.id} has an unsupported content encoding; dropping.`);
697
- this.currentTrack = null;
698
- }
699
- if (this.currentTrack
700
- && this.currentTrack.id !== -1
701
- && this.currentTrack.codecId
702
- && this.currentTrack.info) {
703
- const slashIndex = this.currentTrack.codecId.indexOf('/');
704
- const codecIdWithoutSuffix = slashIndex === -1
705
- ? this.currentTrack.codecId
706
- : this.currentTrack.codecId.slice(0, slashIndex);
707
- if (this.currentTrack.info.type === 'video'
708
- && this.currentTrack.info.width !== -1
709
- && this.currentTrack.info.height !== -1) {
710
- if (this.currentTrack.codecId === CODEC_STRING_MAP.avc) {
711
- this.currentTrack.info.codec = 'avc';
712
- this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
713
- }
714
- else if (this.currentTrack.codecId === CODEC_STRING_MAP.hevc) {
715
- this.currentTrack.info.codec = 'hevc';
716
- this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
717
- }
718
- else if (codecIdWithoutSuffix === CODEC_STRING_MAP.vp8) {
719
- this.currentTrack.info.codec = 'vp8';
720
- }
721
- else if (codecIdWithoutSuffix === CODEC_STRING_MAP.vp9) {
722
- this.currentTrack.info.codec = 'vp9';
723
- }
724
- else if (codecIdWithoutSuffix === CODEC_STRING_MAP.av1) {
725
- this.currentTrack.info.codec = 'av1';
726
- }
727
- const videoTrack = this.currentTrack;
728
- const inputTrack = new InputVideoTrack(this.input, new MatroskaVideoTrackBacking(videoTrack));
729
- this.currentTrack.inputTrack = inputTrack;
730
- this.currentSegment.tracks.push(this.currentTrack);
731
- }
732
- else if (this.currentTrack.info.type === 'audio'
733
- && this.currentTrack.info.numberOfChannels !== -1
734
- && this.currentTrack.info.sampleRate !== -1) {
735
- if (codecIdWithoutSuffix === CODEC_STRING_MAP.aac) {
736
- this.currentTrack.info.codec = 'aac';
737
- this.currentTrack.info.aacCodecInfo = {
738
- isMpeg2: this.currentTrack.codecId.includes('MPEG2'),
739
- };
740
- this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
741
- }
742
- else if (this.currentTrack.codecId === CODEC_STRING_MAP.mp3) {
743
- this.currentTrack.info.codec = 'mp3';
744
- }
745
- else if (codecIdWithoutSuffix === CODEC_STRING_MAP.opus) {
746
- this.currentTrack.info.codec = 'opus';
747
- this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
748
- this.currentTrack.info.sampleRate = OPUS_SAMPLE_RATE; // Always the same
749
- }
750
- else if (codecIdWithoutSuffix === CODEC_STRING_MAP.vorbis) {
751
- this.currentTrack.info.codec = 'vorbis';
752
- this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
753
- }
754
- else if (codecIdWithoutSuffix === CODEC_STRING_MAP.flac) {
755
- this.currentTrack.info.codec = 'flac';
756
- this.currentTrack.info.codecDescription = this.currentTrack.codecPrivate;
757
- }
758
- else if (this.currentTrack.codecId === 'A_PCM/INT/LIT') {
759
- if (this.currentTrack.info.bitDepth === 8) {
760
- this.currentTrack.info.codec = 'pcm-u8';
761
- }
762
- else if (this.currentTrack.info.bitDepth === 16) {
763
- this.currentTrack.info.codec = 'pcm-s16';
764
- }
765
- else if (this.currentTrack.info.bitDepth === 24) {
766
- this.currentTrack.info.codec = 'pcm-s24';
767
- }
768
- else if (this.currentTrack.info.bitDepth === 32) {
769
- this.currentTrack.info.codec = 'pcm-s32';
770
- }
771
- }
772
- else if (this.currentTrack.codecId === 'A_PCM/INT/BIG') {
773
- if (this.currentTrack.info.bitDepth === 8) {
774
- this.currentTrack.info.codec = 'pcm-u8';
775
- }
776
- else if (this.currentTrack.info.bitDepth === 16) {
777
- this.currentTrack.info.codec = 'pcm-s16be';
778
- }
779
- else if (this.currentTrack.info.bitDepth === 24) {
780
- this.currentTrack.info.codec = 'pcm-s24be';
781
- }
782
- else if (this.currentTrack.info.bitDepth === 32) {
783
- this.currentTrack.info.codec = 'pcm-s32be';
784
- }
785
- }
786
- else if (this.currentTrack.codecId === 'A_PCM/FLOAT/IEEE') {
787
- if (this.currentTrack.info.bitDepth === 32) {
788
- this.currentTrack.info.codec = 'pcm-f32';
789
- }
790
- else if (this.currentTrack.info.bitDepth === 64) {
791
- this.currentTrack.info.codec = 'pcm-f64';
792
- }
793
- }
794
- const audioTrack = this.currentTrack;
795
- const inputTrack = new InputAudioTrack(this.input, new MatroskaAudioTrackBacking(audioTrack));
796
- this.currentTrack.inputTrack = inputTrack;
797
- this.currentSegment.tracks.push(this.currentTrack);
798
- }
799
- else if (this.currentTrack.info.type === 'subtitle') {
800
- // Map Matroska codec IDs to our subtitle codecs
801
- const codecId = this.currentTrack.codecId;
802
- if (codecId === 'S_TEXT/UTF8') {
803
- this.currentTrack.info.codec = 'srt';
804
- }
805
- else if (codecId === 'S_TEXT/SSA' || codecId === 'S_SSA') {
806
- this.currentTrack.info.codec = 'ssa';
807
- }
808
- else if (codecId === 'S_TEXT/ASS' || codecId === 'S_ASS') {
809
- this.currentTrack.info.codec = 'ass';
810
- }
811
- else if (codecId === 'S_TEXT/WEBVTT' || codecId === 'D_WEBVTT' || codecId === 'D_WEBVTT/SUBTITLES') {
812
- this.currentTrack.info.codec = 'webvtt';
813
- }
814
- // Store CodecPrivate as text for ASS/SSA headers
815
- if (this.currentTrack.codecPrivate) {
816
- const decoder = new TextDecoder('utf-8');
817
- this.currentTrack.info.codecPrivateText = decoder.decode(this.currentTrack.codecPrivate);
818
- }
819
- const subtitleTrack = this.currentTrack;
820
- const inputTrack = new InputSubtitleTrack(this.input, new MatroskaSubtitleTrackBacking(subtitleTrack));
821
- this.currentTrack.inputTrack = inputTrack;
822
- this.currentSegment.tracks.push(this.currentTrack);
823
- }
824
- }
825
- this.currentTrack = null;
826
- }
827
- ;
828
- break;
829
- case EBMLId.TrackNumber:
830
- {
831
- if (!this.currentTrack)
832
- break;
833
- this.currentTrack.id = readUnsignedInt(slice, size);
834
- }
835
- ;
836
- break;
837
- case EBMLId.TrackType:
838
- {
839
- if (!this.currentTrack)
840
- break;
841
- const type = readUnsignedInt(slice, size);
842
- if (type === 1) {
843
- this.currentTrack.info = {
844
- type: 'video',
845
- width: -1,
846
- height: -1,
847
- rotation: 0,
848
- codec: null,
849
- codecDescription: null,
850
- colorSpace: null,
851
- alphaMode: false,
852
- };
853
- }
854
- else if (type === 2) {
855
- this.currentTrack.info = {
856
- type: 'audio',
857
- numberOfChannels: -1,
858
- sampleRate: -1,
859
- bitDepth: -1,
860
- codec: null,
861
- codecDescription: null,
862
- aacCodecInfo: null,
863
- };
864
- }
865
- else if (type === 17) {
866
- this.currentTrack.info = {
867
- type: 'subtitle',
868
- codec: null,
869
- codecPrivateText: null,
870
- };
871
- }
872
- }
873
- ;
874
- break;
875
- case EBMLId.FlagEnabled:
876
- {
877
- if (!this.currentTrack)
878
- break;
879
- const enabled = readUnsignedInt(slice, size);
880
- if (!enabled) {
881
- this.currentSegment.tracks.pop();
882
- this.currentTrack = null;
883
- }
884
- }
885
- ;
886
- break;
887
- case EBMLId.FlagDefault:
888
- {
889
- if (!this.currentTrack)
890
- break;
891
- this.currentTrack.disposition.default = !!readUnsignedInt(slice, size);
892
- }
893
- ;
894
- break;
895
- case EBMLId.FlagForced:
896
- {
897
- if (!this.currentTrack)
898
- break;
899
- this.currentTrack.disposition.forced = !!readUnsignedInt(slice, size);
900
- }
901
- ;
902
- break;
903
- case EBMLId.FlagOriginal:
904
- {
905
- if (!this.currentTrack)
906
- break;
907
- this.currentTrack.disposition.original = !!readUnsignedInt(slice, size);
908
- }
909
- ;
910
- break;
911
- case EBMLId.FlagHearingImpaired:
912
- {
913
- if (!this.currentTrack)
914
- break;
915
- this.currentTrack.disposition.hearingImpaired = !!readUnsignedInt(slice, size);
916
- }
917
- ;
918
- break;
919
- case EBMLId.FlagVisualImpaired:
920
- {
921
- if (!this.currentTrack)
922
- break;
923
- this.currentTrack.disposition.visuallyImpaired = !!readUnsignedInt(slice, size);
924
- }
925
- ;
926
- break;
927
- case EBMLId.FlagCommentary:
928
- {
929
- if (!this.currentTrack)
930
- break;
931
- this.currentTrack.disposition.commentary = !!readUnsignedInt(slice, size);
932
- }
933
- ;
934
- break;
935
- case EBMLId.CodecID:
936
- {
937
- if (!this.currentTrack)
938
- break;
939
- this.currentTrack.codecId = readAsciiString(slice, size);
940
- }
941
- ;
942
- break;
943
- case EBMLId.CodecPrivate:
944
- {
945
- if (!this.currentTrack)
946
- break;
947
- this.currentTrack.codecPrivate = readBytes(slice, size);
948
- }
949
- ;
950
- break;
951
- case EBMLId.DefaultDuration:
952
- {
953
- if (!this.currentTrack)
954
- break;
955
- this.currentTrack.defaultDuration
956
- = this.currentTrack.segment.timestampFactor * readUnsignedInt(slice, size) / 1e9;
957
- }
958
- ;
959
- break;
960
- case EBMLId.Name:
961
- {
962
- if (!this.currentTrack)
963
- break;
964
- this.currentTrack.name = readUnicodeString(slice, size);
965
- }
966
- ;
967
- break;
968
- case EBMLId.Language:
969
- {
970
- if (!this.currentTrack)
971
- break;
972
- if (this.currentTrack.languageCode !== UNDETERMINED_LANGUAGE) {
973
- // LanguageBCP47 was present, which takes precedence
974
- break;
975
- }
976
- this.currentTrack.languageCode = readAsciiString(slice, size);
977
- if (!isIso639Dash2LanguageCode(this.currentTrack.languageCode)) {
978
- this.currentTrack.languageCode = UNDETERMINED_LANGUAGE;
979
- }
980
- }
981
- ;
982
- break;
983
- case EBMLId.LanguageBCP47:
984
- {
985
- if (!this.currentTrack)
986
- break;
987
- const bcp47 = readAsciiString(slice, size);
988
- const languageSubtag = bcp47.split('-')[0];
989
- if (languageSubtag) {
990
- // Technically invalid, for now: The language subtag might be a language code from ISO 639-1,
991
- // ISO 639-2, ISO 639-3, ISO 639-5 or some other thing (source: Wikipedia). But, `languageCode` is
992
- // documented as ISO 639-2. Changing the definition would be a breaking change. This will get
993
- // cleaned up in the future by defining languageCode to be BCP 47 instead.
994
- this.currentTrack.languageCode = languageSubtag;
995
- }
996
- else {
997
- this.currentTrack.languageCode = UNDETERMINED_LANGUAGE;
998
- }
999
- }
1000
- ;
1001
- break;
1002
- case EBMLId.Video:
1003
- {
1004
- if (this.currentTrack?.info?.type !== 'video')
1005
- break;
1006
- this.readContiguousElements(slice.slice(dataStartPos, size));
1007
- }
1008
- ;
1009
- break;
1010
- case EBMLId.PixelWidth:
1011
- {
1012
- if (this.currentTrack?.info?.type !== 'video')
1013
- break;
1014
- this.currentTrack.info.width = readUnsignedInt(slice, size);
1015
- }
1016
- ;
1017
- break;
1018
- case EBMLId.PixelHeight:
1019
- {
1020
- if (this.currentTrack?.info?.type !== 'video')
1021
- break;
1022
- this.currentTrack.info.height = readUnsignedInt(slice, size);
1023
- }
1024
- ;
1025
- break;
1026
- case EBMLId.AlphaMode:
1027
- {
1028
- if (this.currentTrack?.info?.type !== 'video')
1029
- break;
1030
- this.currentTrack.info.alphaMode = readUnsignedInt(slice, size) === 1;
1031
- }
1032
- ;
1033
- break;
1034
- case EBMLId.Colour:
1035
- {
1036
- if (this.currentTrack?.info?.type !== 'video')
1037
- break;
1038
- this.currentTrack.info.colorSpace = {};
1039
- this.readContiguousElements(slice.slice(dataStartPos, size));
1040
- }
1041
- ;
1042
- break;
1043
- case EBMLId.MatrixCoefficients:
1044
- {
1045
- if (this.currentTrack?.info?.type !== 'video' || !this.currentTrack.info.colorSpace)
1046
- break;
1047
- const matrixCoefficients = readUnsignedInt(slice, size);
1048
- const mapped = MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients] ?? null;
1049
- this.currentTrack.info.colorSpace.matrix = mapped;
1050
- }
1051
- ;
1052
- break;
1053
- case EBMLId.Range:
1054
- {
1055
- if (this.currentTrack?.info?.type !== 'video' || !this.currentTrack.info.colorSpace)
1056
- break;
1057
- this.currentTrack.info.colorSpace.fullRange = readUnsignedInt(slice, size) === 2;
1058
- }
1059
- ;
1060
- break;
1061
- case EBMLId.TransferCharacteristics:
1062
- {
1063
- if (this.currentTrack?.info?.type !== 'video' || !this.currentTrack.info.colorSpace)
1064
- break;
1065
- const transferCharacteristics = readUnsignedInt(slice, size);
1066
- const mapped = TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics] ?? null;
1067
- this.currentTrack.info.colorSpace.transfer = mapped;
1068
- }
1069
- ;
1070
- break;
1071
- case EBMLId.Primaries:
1072
- {
1073
- if (this.currentTrack?.info?.type !== 'video' || !this.currentTrack.info.colorSpace)
1074
- break;
1075
- const primaries = readUnsignedInt(slice, size);
1076
- const mapped = COLOR_PRIMARIES_MAP_INVERSE[primaries] ?? null;
1077
- this.currentTrack.info.colorSpace.primaries = mapped;
1078
- }
1079
- ;
1080
- break;
1081
- case EBMLId.Projection:
1082
- {
1083
- if (this.currentTrack?.info?.type !== 'video')
1084
- break;
1085
- this.readContiguousElements(slice.slice(dataStartPos, size));
1086
- }
1087
- ;
1088
- break;
1089
- case EBMLId.ProjectionPoseRoll:
1090
- {
1091
- if (this.currentTrack?.info?.type !== 'video')
1092
- break;
1093
- const rotation = readFloat(slice, size);
1094
- const flippedRotation = -rotation; // Convert counter-clockwise to clockwise
1095
- try {
1096
- this.currentTrack.info.rotation = normalizeRotation(flippedRotation);
1097
- }
1098
- catch {
1099
- // It wasn't a valid rotation
1100
- }
1101
- }
1102
- ;
1103
- break;
1104
- case EBMLId.Audio:
1105
- {
1106
- if (this.currentTrack?.info?.type !== 'audio')
1107
- break;
1108
- this.readContiguousElements(slice.slice(dataStartPos, size));
1109
- }
1110
- ;
1111
- break;
1112
- case EBMLId.SamplingFrequency:
1113
- {
1114
- if (this.currentTrack?.info?.type !== 'audio')
1115
- break;
1116
- this.currentTrack.info.sampleRate = readFloat(slice, size);
1117
- }
1118
- ;
1119
- break;
1120
- case EBMLId.Channels:
1121
- {
1122
- if (this.currentTrack?.info?.type !== 'audio')
1123
- break;
1124
- this.currentTrack.info.numberOfChannels = readUnsignedInt(slice, size);
1125
- }
1126
- ;
1127
- break;
1128
- case EBMLId.BitDepth:
1129
- {
1130
- if (this.currentTrack?.info?.type !== 'audio')
1131
- break;
1132
- this.currentTrack.info.bitDepth = readUnsignedInt(slice, size);
1133
- }
1134
- ;
1135
- break;
1136
- case EBMLId.CuePoint:
1137
- {
1138
- if (!this.currentSegment)
1139
- break;
1140
- this.readContiguousElements(slice.slice(dataStartPos, size));
1141
- this.currentCueTime = null;
1142
- }
1143
- ;
1144
- break;
1145
- case EBMLId.CueTime:
1146
- {
1147
- this.currentCueTime = readUnsignedInt(slice, size);
1148
- }
1149
- ;
1150
- break;
1151
- case EBMLId.CueTrackPositions:
1152
- {
1153
- if (this.currentCueTime === null)
1154
- break;
1155
- assert(this.currentSegment);
1156
- const cuePoint = { time: this.currentCueTime, trackId: -1, clusterPosition: -1 };
1157
- this.currentSegment.cuePoints.push(cuePoint);
1158
- this.readContiguousElements(slice.slice(dataStartPos, size));
1159
- if (cuePoint.trackId === -1 || cuePoint.clusterPosition === -1) {
1160
- this.currentSegment.cuePoints.pop();
1161
- }
1162
- }
1163
- ;
1164
- break;
1165
- case EBMLId.CueTrack:
1166
- {
1167
- const lastCuePoint = this.currentSegment?.cuePoints[this.currentSegment.cuePoints.length - 1];
1168
- if (!lastCuePoint)
1169
- break;
1170
- lastCuePoint.trackId = readUnsignedInt(slice, size);
1171
- }
1172
- ;
1173
- break;
1174
- case EBMLId.CueClusterPosition:
1175
- {
1176
- const lastCuePoint = this.currentSegment?.cuePoints[this.currentSegment.cuePoints.length - 1];
1177
- if (!lastCuePoint)
1178
- break;
1179
- assert(this.currentSegment);
1180
- lastCuePoint.clusterPosition = this.currentSegment.dataStartPos + readUnsignedInt(slice, size);
1181
- }
1182
- ;
1183
- break;
1184
- case EBMLId.Timestamp:
1185
- {
1186
- if (!this.currentCluster)
1187
- break;
1188
- this.currentCluster.timestamp = readUnsignedInt(slice, size);
1189
- }
1190
- ;
1191
- break;
1192
- case EBMLId.SimpleBlock:
1193
- {
1194
- if (!this.currentCluster)
1195
- break;
1196
- const trackNumber = readVarInt(slice);
1197
- if (trackNumber === null)
1198
- break;
1199
- const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
1200
- if (!trackData)
1201
- break; // Not a track we care about
1202
- const relativeTimestamp = readI16Be(slice);
1203
- const flags = readU8(slice);
1204
- const lacing = (flags >> 1) & 0x3; // If the block is laced, we'll expand it later
1205
- let isKeyFrame = !!(flags & 0x80);
1206
- if (trackData.track.info?.type === 'audio' && trackData.track.info.codec) {
1207
- // Some files don't mark their audio packets as key packets (I'm looking at you, Firefox). But, we
1208
- // can fix this in most cases: if we recognize the codec of the track, then we know every packet is
1209
- // necessarily a key packet, no matter what the container says.
1210
- // https://github.com/Vanilagy/mediabunny/issues/192
1211
- isKeyFrame = true;
1212
- }
1213
- const blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
1214
- const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0;
1215
- trackData.blocks.push({
1216
- timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
1217
- duration: 0, // Will set later
1218
- isKeyFrame,
1219
- data: blockData,
1220
- lacing,
1221
- decoded: !hasDecodingInstructions,
1222
- mainAdditional: null,
1223
- });
1224
- }
1225
- ;
1226
- break;
1227
- case EBMLId.BlockGroup:
1228
- {
1229
- if (!this.currentCluster)
1230
- break;
1231
- this.readContiguousElements(slice.slice(dataStartPos, size));
1232
- this.currentBlock = null;
1233
- }
1234
- ;
1235
- break;
1236
- case EBMLId.Block:
1237
- {
1238
- if (!this.currentCluster)
1239
- break;
1240
- const trackNumber = readVarInt(slice);
1241
- if (trackNumber === null)
1242
- break;
1243
- const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
1244
- if (!trackData)
1245
- break;
1246
- const relativeTimestamp = readI16Be(slice);
1247
- const flags = readU8(slice);
1248
- const lacing = (flags >> 1) & 0x3; // If the block is laced, we'll expand it later
1249
- const blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
1250
- const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0;
1251
- this.currentBlock = {
1252
- timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
1253
- duration: 0, // Will set later
1254
- isKeyFrame: true,
1255
- data: blockData,
1256
- lacing,
1257
- decoded: !hasDecodingInstructions,
1258
- mainAdditional: null,
1259
- };
1260
- trackData.blocks.push(this.currentBlock);
1261
- }
1262
- ;
1263
- break;
1264
- case EBMLId.BlockAdditions:
1265
- {
1266
- this.readContiguousElements(slice.slice(dataStartPos, size));
1267
- }
1268
- ;
1269
- break;
1270
- case EBMLId.BlockMore:
1271
- {
1272
- if (!this.currentBlock)
1273
- break;
1274
- this.currentBlockAdditional = {
1275
- addId: 1,
1276
- data: null,
1277
- };
1278
- this.readContiguousElements(slice.slice(dataStartPos, size));
1279
- if (this.currentBlockAdditional.data && this.currentBlockAdditional.addId === 1) {
1280
- this.currentBlock.mainAdditional = this.currentBlockAdditional.data;
1281
- }
1282
- this.currentBlockAdditional = null;
1283
- }
1284
- ;
1285
- break;
1286
- case EBMLId.BlockAdditional:
1287
- {
1288
- if (!this.currentBlockAdditional)
1289
- break;
1290
- this.currentBlockAdditional.data = readBytes(slice, size);
1291
- }
1292
- ;
1293
- break;
1294
- case EBMLId.BlockAddID:
1295
- {
1296
- if (!this.currentBlockAdditional)
1297
- break;
1298
- this.currentBlockAdditional.addId = readUnsignedInt(slice, size);
1299
- }
1300
- ;
1301
- break;
1302
- case EBMLId.BlockDuration:
1303
- {
1304
- if (!this.currentBlock)
1305
- break;
1306
- this.currentBlock.duration = readUnsignedInt(slice, size);
1307
- }
1308
- ;
1309
- break;
1310
- case EBMLId.ReferenceBlock:
1311
- {
1312
- if (!this.currentBlock)
1313
- break;
1314
- this.currentBlock.isKeyFrame = false;
1315
- // We ignore the actual value here, we just use the reference as an indicator for "not a key frame".
1316
- // This is in line with FFmpeg's behavior.
1317
- }
1318
- ;
1319
- break;
1320
- case EBMLId.Tag:
1321
- {
1322
- this.currentTagTargetIsMovie = true;
1323
- this.readContiguousElements(slice.slice(dataStartPos, size));
1324
- }
1325
- ;
1326
- break;
1327
- case EBMLId.Targets:
1328
- {
1329
- this.readContiguousElements(slice.slice(dataStartPos, size));
1330
- }
1331
- ;
1332
- break;
1333
- case EBMLId.TargetTypeValue:
1334
- {
1335
- const targetTypeValue = readUnsignedInt(slice, size);
1336
- if (targetTypeValue !== 50) {
1337
- this.currentTagTargetIsMovie = false;
1338
- }
1339
- }
1340
- ;
1341
- break;
1342
- case EBMLId.TagTrackUID:
1343
- case EBMLId.TagEditionUID:
1344
- case EBMLId.TagChapterUID:
1345
- case EBMLId.TagAttachmentUID:
1346
- {
1347
- this.currentTagTargetIsMovie = false;
1348
- }
1349
- ;
1350
- break;
1351
- case EBMLId.SimpleTag:
1352
- {
1353
- if (!this.currentTagTargetIsMovie)
1354
- break;
1355
- this.currentSimpleTagName = null;
1356
- this.readContiguousElements(slice.slice(dataStartPos, size));
1357
- }
1358
- ;
1359
- break;
1360
- case EBMLId.TagName:
1361
- {
1362
- this.currentSimpleTagName = readUnicodeString(slice, size);
1363
- }
1364
- ;
1365
- break;
1366
- case EBMLId.TagString:
1367
- {
1368
- if (!this.currentSimpleTagName)
1369
- break;
1370
- const value = readUnicodeString(slice, size);
1371
- this.processTagValue(this.currentSimpleTagName, value);
1372
- }
1373
- ;
1374
- break;
1375
- case EBMLId.TagBinary:
1376
- {
1377
- if (!this.currentSimpleTagName)
1378
- break;
1379
- const value = readBytes(slice, size);
1380
- this.processTagValue(this.currentSimpleTagName, value);
1381
- }
1382
- ;
1383
- break;
1384
- case EBMLId.AttachedFile:
1385
- {
1386
- if (!this.currentSegment)
1387
- break;
1388
- this.currentAttachedFile = {
1389
- fileUid: null,
1390
- fileName: null,
1391
- fileMediaType: null,
1392
- fileData: null,
1393
- fileDescription: null,
1394
- };
1395
- this.readContiguousElements(slice.slice(dataStartPos, size));
1396
- const tags = this.currentSegment.metadataTags;
1397
- if (this.currentAttachedFile.fileUid && this.currentAttachedFile.fileData) {
1398
- // All attached files get surfaced in the `raw` metadata tags
1399
- tags.raw ??= {};
1400
- tags.raw[this.currentAttachedFile.fileUid.toString()] = new AttachedFile(this.currentAttachedFile.fileData, this.currentAttachedFile.fileMediaType ?? undefined, this.currentAttachedFile.fileName ?? undefined, this.currentAttachedFile.fileDescription ?? undefined);
1401
- }
1402
- // Only process image attachments
1403
- if (this.currentAttachedFile.fileMediaType?.startsWith('image/') && this.currentAttachedFile.fileData) {
1404
- const fileName = this.currentAttachedFile.fileName;
1405
- let kind = 'unknown';
1406
- if (fileName) {
1407
- const lowerName = fileName.toLowerCase();
1408
- if (lowerName.startsWith('cover.')) {
1409
- kind = 'coverFront';
1410
- }
1411
- else if (lowerName.startsWith('back.')) {
1412
- kind = 'coverBack';
1413
- }
1414
- }
1415
- tags.images ??= [];
1416
- tags.images.push({
1417
- data: this.currentAttachedFile.fileData,
1418
- mimeType: this.currentAttachedFile.fileMediaType,
1419
- kind,
1420
- name: this.currentAttachedFile.fileName ?? undefined,
1421
- description: this.currentAttachedFile.fileDescription ?? undefined,
1422
- });
1423
- }
1424
- this.currentAttachedFile = null;
1425
- }
1426
- ;
1427
- break;
1428
- case EBMLId.FileUID:
1429
- {
1430
- if (!this.currentAttachedFile)
1431
- break;
1432
- this.currentAttachedFile.fileUid = readUnsignedBigInt(slice, size);
1433
- }
1434
- ;
1435
- break;
1436
- case EBMLId.FileName:
1437
- {
1438
- if (!this.currentAttachedFile)
1439
- break;
1440
- this.currentAttachedFile.fileName = readUnicodeString(slice, size);
1441
- }
1442
- ;
1443
- break;
1444
- case EBMLId.FileMediaType:
1445
- {
1446
- if (!this.currentAttachedFile)
1447
- break;
1448
- this.currentAttachedFile.fileMediaType = readAsciiString(slice, size);
1449
- }
1450
- ;
1451
- break;
1452
- case EBMLId.FileData:
1453
- {
1454
- if (!this.currentAttachedFile)
1455
- break;
1456
- this.currentAttachedFile.fileData = readBytes(slice, size);
1457
- }
1458
- ;
1459
- break;
1460
- case EBMLId.FileDescription:
1461
- {
1462
- if (!this.currentAttachedFile)
1463
- break;
1464
- this.currentAttachedFile.fileDescription = readUnicodeString(slice, size);
1465
- }
1466
- ;
1467
- break;
1468
- case EBMLId.ContentEncodings:
1469
- {
1470
- if (!this.currentTrack)
1471
- break;
1472
- this.readContiguousElements(slice.slice(dataStartPos, size));
1473
- // "**MUST** start with the `ContentEncoding` with the highest `ContentEncodingOrder`"
1474
- this.currentTrack.decodingInstructions.sort((a, b) => b.order - a.order);
1475
- }
1476
- ;
1477
- break;
1478
- case EBMLId.ContentEncoding:
1479
- {
1480
- this.currentDecodingInstruction = {
1481
- order: 0,
1482
- scope: ContentEncodingScope.Block,
1483
- data: null,
1484
- };
1485
- this.readContiguousElements(slice.slice(dataStartPos, size));
1486
- if (this.currentDecodingInstruction.data) {
1487
- this.currentTrack.decodingInstructions.push(this.currentDecodingInstruction);
1488
- }
1489
- this.currentDecodingInstruction = null;
1490
- }
1491
- ;
1492
- break;
1493
- case EBMLId.ContentEncodingOrder:
1494
- {
1495
- if (!this.currentDecodingInstruction)
1496
- break;
1497
- this.currentDecodingInstruction.order = readUnsignedInt(slice, size);
1498
- }
1499
- ;
1500
- break;
1501
- case EBMLId.ContentEncodingScope:
1502
- {
1503
- if (!this.currentDecodingInstruction)
1504
- break;
1505
- this.currentDecodingInstruction.scope = readUnsignedInt(slice, size);
1506
- }
1507
- ;
1508
- break;
1509
- case EBMLId.ContentCompression:
1510
- {
1511
- if (!this.currentDecodingInstruction)
1512
- break;
1513
- this.currentDecodingInstruction.data = {
1514
- type: 'decompress',
1515
- algorithm: ContentCompAlgo.Zlib,
1516
- settings: null,
1517
- };
1518
- this.readContiguousElements(slice.slice(dataStartPos, size));
1519
- }
1520
- ;
1521
- break;
1522
- case EBMLId.ContentCompAlgo:
1523
- {
1524
- if (this.currentDecodingInstruction?.data?.type !== 'decompress')
1525
- break;
1526
- this.currentDecodingInstruction.data.algorithm = readUnsignedInt(slice, size);
1527
- }
1528
- ;
1529
- break;
1530
- case EBMLId.ContentCompSettings:
1531
- {
1532
- if (this.currentDecodingInstruction?.data?.type !== 'decompress')
1533
- break;
1534
- this.currentDecodingInstruction.data.settings = readBytes(slice, size);
1535
- }
1536
- ;
1537
- break;
1538
- case EBMLId.ContentEncryption:
1539
- {
1540
- if (!this.currentDecodingInstruction)
1541
- break;
1542
- this.currentDecodingInstruction.data = {
1543
- type: 'decrypt',
1544
- };
1545
- }
1546
- ;
1547
- break;
1548
- }
1549
- slice.filePos = dataStartPos + size;
1550
- return true;
1551
- }
1552
- decodeBlockData(track, rawData) {
1553
- assert(track.decodingInstructions.length > 0); // This method shouldn't be called otherwise
1554
- let currentData = rawData;
1555
- for (const instruction of track.decodingInstructions) {
1556
- assert(instruction.data);
1557
- switch (instruction.data.type) {
1558
- case 'decompress':
1559
- {
1560
- switch (instruction.data.algorithm) {
1561
- case ContentCompAlgo.HeaderStripping:
1562
- {
1563
- if (instruction.data.settings && instruction.data.settings.length > 0) {
1564
- const prefix = instruction.data.settings;
1565
- const newData = new Uint8Array(prefix.length + currentData.length);
1566
- newData.set(prefix, 0);
1567
- newData.set(currentData, prefix.length);
1568
- currentData = newData;
1569
- }
1570
- }
1571
- ;
1572
- break;
1573
- default:
1574
- {
1575
- // Unhandled
1576
- }
1577
- ;
1578
- }
1579
- }
1580
- ;
1581
- break;
1582
- default:
1583
- {
1584
- // Unhandled
1585
- }
1586
- ;
1587
- }
1588
- }
1589
- return currentData;
1590
- }
1591
- processTagValue(name, value) {
1592
- if (!this.currentSegment?.metadataTags)
1593
- return;
1594
- const metadataTags = this.currentSegment.metadataTags;
1595
- metadataTags.raw ??= {};
1596
- metadataTags.raw[name] ??= value;
1597
- if (typeof value === 'string') {
1598
- switch (name.toLowerCase()) {
1599
- case 'title':
1600
- {
1601
- metadataTags.title ??= value;
1602
- }
1603
- ;
1604
- break;
1605
- case 'description':
1606
- {
1607
- metadataTags.description ??= value;
1608
- }
1609
- ;
1610
- break;
1611
- case 'artist':
1612
- {
1613
- metadataTags.artist ??= value;
1614
- }
1615
- ;
1616
- break;
1617
- case 'album':
1618
- {
1619
- metadataTags.album ??= value;
1620
- }
1621
- ;
1622
- break;
1623
- case 'album_artist':
1624
- {
1625
- metadataTags.albumArtist ??= value;
1626
- }
1627
- ;
1628
- break;
1629
- case 'genre':
1630
- {
1631
- metadataTags.genre ??= value;
1632
- }
1633
- ;
1634
- break;
1635
- case 'comment':
1636
- {
1637
- metadataTags.comment ??= value;
1638
- }
1639
- ;
1640
- break;
1641
- case 'lyrics':
1642
- {
1643
- metadataTags.lyrics ??= value;
1644
- }
1645
- ;
1646
- break;
1647
- case 'date':
1648
- {
1649
- const date = new Date(value);
1650
- if (!Number.isNaN(date.getTime())) {
1651
- metadataTags.date ??= date;
1652
- }
1653
- }
1654
- ;
1655
- break;
1656
- case 'track_number':
1657
- case 'part_number':
1658
- {
1659
- const parts = value.split('/');
1660
- const trackNum = Number.parseInt(parts[0], 10);
1661
- const tracksTotal = parts[1] && Number.parseInt(parts[1], 10);
1662
- if (Number.isInteger(trackNum) && trackNum > 0) {
1663
- metadataTags.trackNumber ??= trackNum;
1664
- }
1665
- if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) {
1666
- metadataTags.tracksTotal ??= tracksTotal;
1667
- }
1668
- }
1669
- ;
1670
- break;
1671
- case 'disc_number':
1672
- case 'disc':
1673
- {
1674
- const discParts = value.split('/');
1675
- const discNum = Number.parseInt(discParts[0], 10);
1676
- const discsTotal = discParts[1] && Number.parseInt(discParts[1], 10);
1677
- if (Number.isInteger(discNum) && discNum > 0) {
1678
- metadataTags.discNumber ??= discNum;
1679
- }
1680
- if (discsTotal && Number.isInteger(discsTotal) && discsTotal > 0) {
1681
- metadataTags.discsTotal ??= discsTotal;
1682
- }
1683
- }
1684
- ;
1685
- break;
1686
- }
1687
- }
1688
- }
1689
- }
1690
- class MatroskaTrackBacking {
1691
- constructor(internalTrack) {
1692
- this.internalTrack = internalTrack;
1693
- this.packetToClusterLocation = new WeakMap();
1694
- }
1695
- getId() {
1696
- return this.internalTrack.id;
1697
- }
1698
- getCodec() {
1699
- throw new Error('Not implemented on base class.');
1700
- }
1701
- getInternalCodecId() {
1702
- return this.internalTrack.codecId;
1703
- }
1704
- async computeDuration() {
1705
- const lastPacket = await this.getPacket(Infinity, { metadataOnly: true });
1706
- return (lastPacket?.timestamp ?? 0) + (lastPacket?.duration ?? 0);
1707
- }
1708
- getName() {
1709
- return this.internalTrack.name;
1710
- }
1711
- getLanguageCode() {
1712
- return this.internalTrack.languageCode;
1713
- }
1714
- async getFirstTimestamp() {
1715
- const firstPacket = await this.getFirstPacket({ metadataOnly: true });
1716
- return firstPacket?.timestamp ?? 0;
1717
- }
1718
- getTimeResolution() {
1719
- return this.internalTrack.segment.timestampFactor;
1720
- }
1721
- getDisposition() {
1722
- return this.internalTrack.disposition;
1723
- }
1724
- async getFirstPacket(options) {
1725
- return this.performClusterLookup(null, (cluster) => {
1726
- const trackData = cluster.trackData.get(this.internalTrack.id);
1727
- if (trackData) {
1728
- return {
1729
- blockIndex: 0,
1730
- correctBlockFound: true,
1731
- };
1732
- }
1733
- return {
1734
- blockIndex: -1,
1735
- correctBlockFound: false,
1736
- };
1737
- }, -Infinity, // Use -Infinity as a search timestamp to avoid using the cues
1738
- Infinity, options);
1739
- }
1740
- intoTimescale(timestamp) {
1741
- // Do a little rounding to catch cases where the result is very close to an integer. If it is, it's likely
1742
- // that the number was originally an integer divided by the timescale. For stability, it's best
1743
- // to return the integer in this case.
1744
- return roundIfAlmostInteger(timestamp * this.internalTrack.segment.timestampFactor);
1745
- }
1746
- async getPacket(timestamp, options) {
1747
- const timestampInTimescale = this.intoTimescale(timestamp);
1748
- return this.performClusterLookup(null, (cluster) => {
1749
- const trackData = cluster.trackData.get(this.internalTrack.id);
1750
- if (!trackData) {
1751
- return { blockIndex: -1, correctBlockFound: false };
1752
- }
1753
- const index = binarySearchLessOrEqual(trackData.presentationTimestamps, timestampInTimescale, x => x.timestamp);
1754
- const blockIndex = index !== -1 ? trackData.presentationTimestamps[index].blockIndex : -1;
1755
- const correctBlockFound = index !== -1 && timestampInTimescale < trackData.endTimestamp;
1756
- return { blockIndex, correctBlockFound };
1757
- }, timestampInTimescale, timestampInTimescale, options);
1758
- }
1759
- async getNextPacket(packet, options) {
1760
- const locationInCluster = this.packetToClusterLocation.get(packet);
1761
- if (locationInCluster === undefined) {
1762
- throw new Error('Packet was not created from this track.');
1763
- }
1764
- return this.performClusterLookup(locationInCluster.cluster, (cluster) => {
1765
- if (cluster === locationInCluster.cluster) {
1766
- const trackData = cluster.trackData.get(this.internalTrack.id);
1767
- if (locationInCluster.blockIndex + 1 < trackData.blocks.length) {
1768
- // We can simply take the next block in the cluster
1769
- return {
1770
- blockIndex: locationInCluster.blockIndex + 1,
1771
- correctBlockFound: true,
1772
- };
1773
- }
1774
- }
1775
- else {
1776
- const trackData = cluster.trackData.get(this.internalTrack.id);
1777
- if (trackData) {
1778
- return {
1779
- blockIndex: 0,
1780
- correctBlockFound: true,
1781
- };
1782
- }
1783
- }
1784
- return {
1785
- blockIndex: -1,
1786
- correctBlockFound: false,
1787
- };
1788
- }, -Infinity, // Use -Infinity as a search timestamp to avoid using the cues
1789
- Infinity, options);
1790
- }
1791
- async getKeyPacket(timestamp, options) {
1792
- const timestampInTimescale = this.intoTimescale(timestamp);
1793
- return this.performClusterLookup(null, (cluster) => {
1794
- const trackData = cluster.trackData.get(this.internalTrack.id);
1795
- if (!trackData) {
1796
- return { blockIndex: -1, correctBlockFound: false };
1797
- }
1798
- const index = findLastIndex(trackData.presentationTimestamps, (x) => {
1799
- const block = trackData.blocks[x.blockIndex];
1800
- return block.isKeyFrame && x.timestamp <= timestampInTimescale;
1801
- });
1802
- const blockIndex = index !== -1 ? trackData.presentationTimestamps[index].blockIndex : -1;
1803
- const correctBlockFound = index !== -1 && timestampInTimescale < trackData.endTimestamp;
1804
- return { blockIndex, correctBlockFound };
1805
- }, timestampInTimescale, timestampInTimescale, options);
1806
- }
1807
- async getNextKeyPacket(packet, options) {
1808
- const locationInCluster = this.packetToClusterLocation.get(packet);
1809
- if (locationInCluster === undefined) {
1810
- throw new Error('Packet was not created from this track.');
1811
- }
1812
- return this.performClusterLookup(locationInCluster.cluster, (cluster) => {
1813
- if (cluster === locationInCluster.cluster) {
1814
- const trackData = cluster.trackData.get(this.internalTrack.id);
1815
- const nextKeyFrameIndex = trackData.blocks.findIndex((x, i) => x.isKeyFrame && i > locationInCluster.blockIndex);
1816
- if (nextKeyFrameIndex !== -1) {
1817
- // We can simply take the next key frame in the cluster
1818
- return {
1819
- blockIndex: nextKeyFrameIndex,
1820
- correctBlockFound: true,
1821
- };
1822
- }
1823
- }
1824
- else {
1825
- const trackData = cluster.trackData.get(this.internalTrack.id);
1826
- if (trackData && trackData.firstKeyFrameTimestamp !== null) {
1827
- const keyFrameIndex = trackData.blocks.findIndex(x => x.isKeyFrame);
1828
- assert(keyFrameIndex !== -1); // There must be one
1829
- return {
1830
- blockIndex: keyFrameIndex,
1831
- correctBlockFound: true,
1832
- };
1833
- }
1834
- }
1835
- return {
1836
- blockIndex: -1,
1837
- correctBlockFound: false,
1838
- };
1839
- }, -Infinity, // Use -Infinity as a search timestamp to avoid using the cues
1840
- Infinity, options);
1841
- }
1842
- async fetchPacketInCluster(cluster, blockIndex, options) {
1843
- if (blockIndex === -1) {
1844
- return null;
1845
- }
1846
- const trackData = cluster.trackData.get(this.internalTrack.id);
1847
- const block = trackData.blocks[blockIndex];
1848
- assert(block);
1849
- // Perform lazy decoding if needed
1850
- if (!block.decoded) {
1851
- block.data = this.internalTrack.demuxer.decodeBlockData(this.internalTrack, block.data);
1852
- block.decoded = true;
1853
- }
1854
- const data = options.metadataOnly ? PLACEHOLDER_DATA : block.data;
1855
- const timestamp = block.timestamp / this.internalTrack.segment.timestampFactor;
1856
- const duration = block.duration / this.internalTrack.segment.timestampFactor;
1857
- const sideData = {};
1858
- if (block.mainAdditional && this.internalTrack.info?.type === 'video' && this.internalTrack.info.alphaMode) {
1859
- sideData.alpha = options.metadataOnly ? PLACEHOLDER_DATA : block.mainAdditional;
1860
- sideData.alphaByteLength = block.mainAdditional.byteLength;
1861
- }
1862
- const packet = new EncodedPacket(data, block.isKeyFrame ? 'key' : 'delta', timestamp, duration, cluster.dataStartPos + blockIndex, block.data.byteLength, sideData);
1863
- this.packetToClusterLocation.set(packet, { cluster, blockIndex });
1864
- return packet;
1865
- }
1866
- /** Looks for a packet in the clusters while trying to load as few clusters as possible to retrieve it. */
1867
- async performClusterLookup(
1868
- // The cluster where we start looking
1869
- startCluster,
1870
- // This function returns the best-matching block in a given cluster
1871
- getMatchInCluster,
1872
- // The timestamp with which we can search the lookup table
1873
- searchTimestamp,
1874
- // The timestamp for which we know the correct block will not come after it
1875
- latestTimestamp, options) {
1876
- const { demuxer, segment } = this.internalTrack;
1877
- let currentCluster = null;
1878
- let bestCluster = null;
1879
- let bestBlockIndex = -1;
1880
- if (startCluster) {
1881
- const { blockIndex, correctBlockFound } = getMatchInCluster(startCluster);
1882
- if (correctBlockFound) {
1883
- return this.fetchPacketInCluster(startCluster, blockIndex, options);
1884
- }
1885
- if (blockIndex !== -1) {
1886
- bestCluster = startCluster;
1887
- bestBlockIndex = blockIndex;
1888
- }
1889
- }
1890
- // Search for a cue point; this way, we won't need to start searching from the start of the file
1891
- // but can jump right into the correct cluster (or at least nearby).
1892
- const cuePointIndex = binarySearchLessOrEqual(this.internalTrack.cuePoints, searchTimestamp, x => x.time);
1893
- const cuePoint = cuePointIndex !== -1
1894
- ? this.internalTrack.cuePoints[cuePointIndex]
1895
- : null;
1896
- // Also check the position cache
1897
- const positionCacheIndex = binarySearchLessOrEqual(this.internalTrack.clusterPositionCache, searchTimestamp, x => x.startTimestamp);
1898
- const positionCacheEntry = positionCacheIndex !== -1
1899
- ? this.internalTrack.clusterPositionCache[positionCacheIndex]
1900
- : null;
1901
- const lookupEntryPosition = Math.max(cuePoint?.clusterPosition ?? 0, positionCacheEntry?.elementStartPos ?? 0) || null;
1902
- let currentPos;
1903
- if (!startCluster) {
1904
- currentPos = lookupEntryPosition ?? segment.clusterSeekStartPos;
1905
- }
1906
- else {
1907
- if (lookupEntryPosition === null || startCluster.elementStartPos >= lookupEntryPosition) {
1908
- currentPos = startCluster.elementEndPos;
1909
- currentCluster = startCluster;
1910
- }
1911
- else {
1912
- // Use the lookup entry
1913
- currentPos = lookupEntryPosition;
1914
- }
1915
- }
1916
- while (segment.elementEndPos === null || currentPos <= segment.elementEndPos - MIN_HEADER_SIZE) {
1917
- if (currentCluster) {
1918
- const trackData = currentCluster.trackData.get(this.internalTrack.id);
1919
- if (trackData && trackData.startTimestamp > latestTimestamp) {
1920
- // We're already past the upper bound, no need to keep searching
1921
- break;
1922
- }
1923
- }
1924
- // Load the header
1925
- let slice = demuxer.reader.requestSliceRange(currentPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
1926
- if (slice instanceof Promise)
1927
- slice = await slice;
1928
- if (!slice)
1929
- break;
1930
- const elementStartPos = currentPos;
1931
- const elementHeader = readElementHeader(slice);
1932
- if (!elementHeader
1933
- || (!LEVEL_1_EBML_IDS.includes(elementHeader.id) && elementHeader.id !== EBMLId.Void)) {
1934
- // There's an element here that shouldn't be here. Might be garbage. In this case, let's
1935
- // try and resync to the next valid element.
1936
- const nextPos = await resync(demuxer.reader, elementStartPos, LEVEL_1_EBML_IDS, Math.min(segment.elementEndPos ?? Infinity, elementStartPos + MAX_RESYNC_LENGTH));
1937
- if (nextPos) {
1938
- currentPos = nextPos;
1939
- continue;
1940
- }
1941
- else {
1942
- break; // Resync failed
1943
- }
1944
- }
1945
- const id = elementHeader.id;
1946
- let size = elementHeader.size;
1947
- const dataStartPos = slice.filePos;
1948
- if (id === EBMLId.Cluster) {
1949
- currentCluster = await demuxer.readCluster(elementStartPos, segment);
1950
- // readCluster computes the proper size even if it's undefined in the header, so let's use that instead
1951
- size = currentCluster.elementEndPos - dataStartPos;
1952
- const { blockIndex, correctBlockFound } = getMatchInCluster(currentCluster);
1953
- if (correctBlockFound) {
1954
- return this.fetchPacketInCluster(currentCluster, blockIndex, options);
1955
- }
1956
- if (blockIndex !== -1) {
1957
- bestCluster = currentCluster;
1958
- bestBlockIndex = blockIndex;
1959
- }
1960
- }
1961
- if (size === null) {
1962
- // Undefined element size (can happen in livestreamed files). In this case, we need to do some
1963
- // searching to determine the actual size of the element.
1964
- assert(id !== EBMLId.Cluster); // Undefined cluster sizes are fixed further up
1965
- // Search for the next element at level 0 or 1
1966
- const nextElementPos = await searchForNextElementId(demuxer.reader, dataStartPos, LEVEL_0_AND_1_EBML_IDS, segment.elementEndPos);
1967
- size = nextElementPos.pos - dataStartPos;
1968
- }
1969
- const endPos = dataStartPos + size;
1970
- if (segment.elementEndPos === null) {
1971
- // Check the next element. If it's a new segment, we know this segment ends here. The new
1972
- // segment is just ignored, since we're likely in a livestreamed file and thus only care about
1973
- // the first segment.
1974
- let slice = demuxer.reader.requestSliceRange(endPos, MIN_HEADER_SIZE, MAX_HEADER_SIZE);
1975
- if (slice instanceof Promise)
1976
- slice = await slice;
1977
- if (!slice)
1978
- break;
1979
- const elementId = readElementId(slice);
1980
- if (elementId === EBMLId.Segment) {
1981
- segment.elementEndPos = endPos; // We now know the segment's size
1982
- break;
1983
- }
1984
- }
1985
- currentPos = endPos;
1986
- }
1987
- // Catch faulty cue points
1988
- if (cuePoint && (!bestCluster || bestCluster.elementStartPos < cuePoint.clusterPosition)) {
1989
- // The cue point lied to us! We found a cue point but no cluster there that satisfied the match. In this
1990
- // case, let's search again but using the cue point before that.
1991
- const previousCuePoint = this.internalTrack.cuePoints[cuePointIndex - 1];
1992
- assert(!previousCuePoint || previousCuePoint.time < cuePoint.time);
1993
- const newSearchTimestamp = previousCuePoint?.time ?? -Infinity;
1994
- return this.performClusterLookup(null, getMatchInCluster, newSearchTimestamp, latestTimestamp, options);
1995
- }
1996
- if (bestCluster) {
1997
- // If we finished looping but didn't find a perfect match, still return the best match we found
1998
- return this.fetchPacketInCluster(bestCluster, bestBlockIndex, options);
1999
- }
2000
- return null;
2001
- }
2002
- }
2003
- class MatroskaVideoTrackBacking extends MatroskaTrackBacking {
2004
- constructor(internalTrack) {
2005
- super(internalTrack);
2006
- this.decoderConfigPromise = null;
2007
- this.internalTrack = internalTrack;
2008
- }
2009
- getCodec() {
2010
- return this.internalTrack.info.codec;
2011
- }
2012
- getCodedWidth() {
2013
- return this.internalTrack.info.width;
2014
- }
2015
- getCodedHeight() {
2016
- return this.internalTrack.info.height;
2017
- }
2018
- getRotation() {
2019
- return this.internalTrack.info.rotation;
2020
- }
2021
- async getColorSpace() {
2022
- return {
2023
- primaries: this.internalTrack.info.colorSpace?.primaries,
2024
- transfer: this.internalTrack.info.colorSpace?.transfer,
2025
- matrix: this.internalTrack.info.colorSpace?.matrix,
2026
- fullRange: this.internalTrack.info.colorSpace?.fullRange,
2027
- };
2028
- }
2029
- async canBeTransparent() {
2030
- return this.internalTrack.info.alphaMode;
2031
- }
2032
- async getDecoderConfig() {
2033
- if (!this.internalTrack.info.codec) {
2034
- return null;
2035
- }
2036
- return this.decoderConfigPromise ??= (async () => {
2037
- let firstPacket = null;
2038
- const needsPacketForAdditionalInfo = this.internalTrack.info.codec === 'vp9'
2039
- || this.internalTrack.info.codec === 'av1'
2040
- // Packets are in Annex B format:
2041
- || (this.internalTrack.info.codec === 'avc' && !this.internalTrack.info.codecDescription)
2042
- // Packets are in Annex B format:
2043
- || (this.internalTrack.info.codec === 'hevc' && !this.internalTrack.info.codecDescription);
2044
- if (needsPacketForAdditionalInfo) {
2045
- firstPacket = await this.getFirstPacket({});
2046
- }
2047
- return {
2048
- codec: extractVideoCodecString({
2049
- width: this.internalTrack.info.width,
2050
- height: this.internalTrack.info.height,
2051
- codec: this.internalTrack.info.codec,
2052
- codecDescription: this.internalTrack.info.codecDescription,
2053
- colorSpace: this.internalTrack.info.colorSpace,
2054
- avcType: 1, // We don't know better (or do we?) so just assume 'avc1'
2055
- avcCodecInfo: this.internalTrack.info.codec === 'avc' && firstPacket
2056
- ? extractAvcDecoderConfigurationRecord(firstPacket.data)
2057
- : null,
2058
- hevcCodecInfo: this.internalTrack.info.codec === 'hevc' && firstPacket
2059
- ? extractHevcDecoderConfigurationRecord(firstPacket.data)
2060
- : null,
2061
- vp9CodecInfo: this.internalTrack.info.codec === 'vp9' && firstPacket
2062
- ? extractVp9CodecInfoFromPacket(firstPacket.data)
2063
- : null,
2064
- av1CodecInfo: this.internalTrack.info.codec === 'av1' && firstPacket
2065
- ? extractAv1CodecInfoFromPacket(firstPacket.data)
2066
- : null,
2067
- }),
2068
- codedWidth: this.internalTrack.info.width,
2069
- codedHeight: this.internalTrack.info.height,
2070
- description: this.internalTrack.info.codecDescription ?? undefined,
2071
- colorSpace: this.internalTrack.info.colorSpace ?? undefined,
2072
- };
2073
- })();
2074
- }
2075
- }
2076
- class MatroskaAudioTrackBacking extends MatroskaTrackBacking {
2077
- constructor(internalTrack) {
2078
- super(internalTrack);
2079
- this.decoderConfig = null;
2080
- this.internalTrack = internalTrack;
2081
- }
2082
- getCodec() {
2083
- return this.internalTrack.info.codec;
2084
- }
2085
- getNumberOfChannels() {
2086
- return this.internalTrack.info.numberOfChannels;
2087
- }
2088
- getSampleRate() {
2089
- return this.internalTrack.info.sampleRate;
2090
- }
2091
- async getDecoderConfig() {
2092
- if (!this.internalTrack.info.codec) {
2093
- return null;
2094
- }
2095
- return this.decoderConfig ??= {
2096
- codec: extractAudioCodecString({
2097
- codec: this.internalTrack.info.codec,
2098
- codecDescription: this.internalTrack.info.codecDescription,
2099
- aacCodecInfo: this.internalTrack.info.aacCodecInfo,
2100
- }),
2101
- numberOfChannels: this.internalTrack.info.numberOfChannels,
2102
- sampleRate: this.internalTrack.info.sampleRate,
2103
- description: this.internalTrack.info.codecDescription ?? undefined,
2104
- };
2105
- }
2106
- }
2107
- class MatroskaSubtitleTrackBacking extends MatroskaTrackBacking {
2108
- constructor(internalTrack) {
2109
- super(internalTrack);
2110
- this.internalTrack = internalTrack;
2111
- }
2112
- getCodec() {
2113
- return this.internalTrack.info.codec;
2114
- }
2115
- getCodecPrivate() {
2116
- return this.internalTrack.info.codecPrivateText;
2117
- }
2118
- async *getCues() {
2119
- // Use the existing packet reading infrastructure
2120
- let packet = await this.getFirstPacket({});
2121
- while (packet) {
2122
- // Decode subtitle data as UTF-8 text
2123
- const decoder = new TextDecoder('utf-8');
2124
- const text = decoder.decode(packet.data);
2125
- yield {
2126
- timestamp: packet.timestamp,
2127
- duration: packet.duration,
2128
- text,
2129
- };
2130
- packet = await this.getNextPacket(packet, {});
2131
- }
2132
- }
2133
- }