mediabunny 1.0.2 → 1.0.4

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 (188) hide show
  1. package/README.md +4 -4
  2. package/dist/mediabunny.d.ts +4 -2
  3. package/dist/modules/codec-data.d.ts +95 -0
  4. package/dist/modules/codec-data.d.ts.map +1 -0
  5. package/dist/modules/codec-data.js +1103 -0
  6. package/dist/modules/codec.d.ts +250 -0
  7. package/dist/modules/codec.d.ts.map +1 -0
  8. package/dist/modules/codec.js +1100 -0
  9. package/dist/modules/conversion.d.ts +158 -0
  10. package/dist/modules/conversion.d.ts.map +1 -0
  11. package/dist/modules/conversion.js +866 -0
  12. package/dist/modules/custom-coder.d.ts +119 -0
  13. package/dist/modules/custom-coder.d.ts.map +1 -0
  14. package/dist/modules/custom-coder.js +91 -0
  15. package/dist/modules/demuxer.d.ts +17 -0
  16. package/dist/modules/demuxer.d.ts.map +1 -0
  17. package/dist/modules/demuxer.js +12 -0
  18. package/dist/modules/index.d.ts +25 -0
  19. package/dist/modules/index.d.ts.map +1 -0
  20. package/dist/modules/index.js +24 -0
  21. package/dist/modules/input-format.d.ts +121 -0
  22. package/dist/modules/input-format.d.ts.map +1 -0
  23. package/dist/modules/input-format.js +333 -0
  24. package/dist/modules/input-track.d.ts +138 -0
  25. package/dist/modules/input-track.d.ts.map +1 -0
  26. package/dist/modules/input-track.js +230 -0
  27. package/dist/modules/input.d.ts +55 -0
  28. package/dist/modules/input.d.ts.map +1 -0
  29. package/dist/modules/input.js +104 -0
  30. package/dist/modules/isobmff/isobmff-boxes.d.ts +202 -0
  31. package/dist/modules/isobmff/isobmff-boxes.d.ts.map +1 -0
  32. package/dist/modules/isobmff/isobmff-boxes.js +1111 -0
  33. package/dist/modules/isobmff/isobmff-demuxer.d.ts +163 -0
  34. package/dist/modules/isobmff/isobmff-demuxer.d.ts.map +1 -0
  35. package/dist/modules/isobmff/isobmff-demuxer.js +2080 -0
  36. package/dist/modules/isobmff/isobmff-misc.d.ts +14 -0
  37. package/dist/modules/isobmff/isobmff-misc.d.ts.map +1 -0
  38. package/dist/modules/isobmff/isobmff-misc.js +20 -0
  39. package/dist/modules/isobmff/isobmff-muxer.d.ts +142 -0
  40. package/dist/modules/isobmff/isobmff-muxer.d.ts.map +1 -0
  41. package/dist/modules/isobmff/isobmff-muxer.js +858 -0
  42. package/dist/modules/isobmff/isobmff-reader.d.ts +36 -0
  43. package/dist/modules/isobmff/isobmff-reader.d.ts.map +1 -0
  44. package/dist/modules/isobmff/isobmff-reader.js +105 -0
  45. package/dist/modules/matroska/ebml.d.ts +156 -0
  46. package/dist/modules/matroska/ebml.d.ts.map +1 -0
  47. package/dist/modules/matroska/ebml.js +529 -0
  48. package/dist/modules/matroska/matroska-demuxer.d.ts +119 -0
  49. package/dist/modules/matroska/matroska-demuxer.d.ts.map +1 -0
  50. package/dist/modules/matroska/matroska-demuxer.js +1330 -0
  51. package/dist/modules/matroska/matroska-misc.d.ts +14 -0
  52. package/dist/modules/matroska/matroska-misc.d.ts.map +1 -0
  53. package/dist/modules/matroska/matroska-misc.js +20 -0
  54. package/dist/modules/matroska/matroska-muxer.d.ts +71 -0
  55. package/dist/modules/matroska/matroska-muxer.d.ts.map +1 -0
  56. package/dist/modules/matroska/matroska-muxer.js +729 -0
  57. package/dist/modules/media-sink.d.ts +272 -0
  58. package/dist/modules/media-sink.d.ts.map +1 -0
  59. package/dist/modules/media-sink.js +1264 -0
  60. package/dist/modules/media-source.d.ts +230 -0
  61. package/dist/modules/media-source.d.ts.map +1 -0
  62. package/dist/modules/media-source.js +1095 -0
  63. package/dist/modules/misc.d.ts +127 -0
  64. package/dist/modules/misc.d.ts.map +1 -0
  65. package/dist/modules/misc.js +462 -0
  66. package/dist/modules/mp3/mp3-demuxer.d.ts +32 -0
  67. package/dist/modules/mp3/mp3-demuxer.d.ts.map +1 -0
  68. package/dist/modules/mp3/mp3-demuxer.js +160 -0
  69. package/dist/modules/mp3/mp3-misc.d.ts +37 -0
  70. package/dist/modules/mp3/mp3-misc.d.ts.map +1 -0
  71. package/dist/modules/mp3/mp3-misc.js +122 -0
  72. package/dist/modules/mp3/mp3-muxer.d.ts +27 -0
  73. package/dist/modules/mp3/mp3-muxer.d.ts.map +1 -0
  74. package/dist/modules/mp3/mp3-muxer.js +112 -0
  75. package/dist/modules/mp3/mp3-reader.d.ts +25 -0
  76. package/dist/modules/mp3/mp3-reader.d.ts.map +1 -0
  77. package/dist/modules/mp3/mp3-reader.js +73 -0
  78. package/dist/modules/mp3/mp3-writer.d.ts +30 -0
  79. package/dist/modules/mp3/mp3-writer.d.ts.map +1 -0
  80. package/dist/modules/mp3/mp3-writer.js +72 -0
  81. package/dist/modules/muxer.d.ts +32 -0
  82. package/dist/modules/muxer.d.ts.map +1 -0
  83. package/dist/modules/muxer.js +50 -0
  84. package/dist/modules/ogg/ogg-demuxer.d.ts +53 -0
  85. package/dist/modules/ogg/ogg-demuxer.d.ts.map +1 -0
  86. package/dist/modules/ogg/ogg-demuxer.js +701 -0
  87. package/dist/modules/ogg/ogg-misc.d.ts +27 -0
  88. package/dist/modules/ogg/ogg-misc.d.ts.map +1 -0
  89. package/dist/modules/ogg/ogg-misc.js +78 -0
  90. package/dist/modules/ogg/ogg-muxer.d.ts +58 -0
  91. package/dist/modules/ogg/ogg-muxer.d.ts.map +1 -0
  92. package/dist/modules/ogg/ogg-muxer.js +345 -0
  93. package/dist/modules/ogg/ogg-reader.d.ts +37 -0
  94. package/dist/modules/ogg/ogg-reader.d.ts.map +1 -0
  95. package/dist/modules/ogg/ogg-reader.js +104 -0
  96. package/dist/modules/output-format.d.ts +285 -0
  97. package/dist/modules/output-format.d.ts.map +1 -0
  98. package/dist/modules/output-format.js +385 -0
  99. package/dist/modules/output.d.ts +134 -0
  100. package/dist/modules/output.d.ts.map +1 -0
  101. package/dist/modules/output.js +269 -0
  102. package/dist/modules/packet.d.ts +86 -0
  103. package/dist/modules/packet.d.ts.map +1 -0
  104. package/dist/modules/packet.js +133 -0
  105. package/dist/modules/pcm.d.ts +12 -0
  106. package/dist/modules/pcm.d.ts.map +1 -0
  107. package/dist/modules/pcm.js +85 -0
  108. package/dist/modules/reader.d.ts +40 -0
  109. package/dist/modules/reader.d.ts.map +1 -0
  110. package/dist/modules/reader.js +153 -0
  111. package/dist/modules/sample.d.ts +186 -0
  112. package/dist/modules/sample.d.ts.map +1 -0
  113. package/dist/modules/sample.js +798 -0
  114. package/dist/modules/source.d.ts +76 -0
  115. package/dist/modules/source.d.ts.map +1 -0
  116. package/dist/modules/source.js +183 -0
  117. package/dist/modules/subtitles.d.ts +37 -0
  118. package/dist/modules/subtitles.d.ts.map +1 -0
  119. package/dist/modules/subtitles.js +89 -0
  120. package/dist/modules/target.d.ts +58 -0
  121. package/dist/modules/target.d.ts.map +1 -0
  122. package/dist/modules/target.js +63 -0
  123. package/dist/modules/wave/riff-reader.d.ts +19 -0
  124. package/dist/modules/wave/riff-reader.d.ts.map +1 -0
  125. package/dist/modules/wave/riff-reader.js +38 -0
  126. package/dist/modules/wave/riff-writer.d.ts +18 -0
  127. package/dist/modules/wave/riff-writer.d.ts.map +1 -0
  128. package/dist/modules/wave/riff-writer.js +25 -0
  129. package/dist/modules/wave/wave-demuxer.d.ts +42 -0
  130. package/dist/modules/wave/wave-demuxer.d.ts.map +1 -0
  131. package/dist/modules/wave/wave-demuxer.js +239 -0
  132. package/dist/modules/wave/wave-muxer.d.ts +27 -0
  133. package/dist/modules/wave/wave-muxer.d.ts.map +1 -0
  134. package/dist/modules/wave/wave-muxer.js +108 -0
  135. package/dist/modules/writer.d.ts +85 -0
  136. package/dist/modules/writer.d.ts.map +1 -0
  137. package/dist/modules/writer.js +347 -0
  138. package/package.json +14 -10
  139. package/src/codec-data.ts +1394 -0
  140. package/src/codec.ts +1392 -0
  141. package/src/conversion.ts +1199 -0
  142. package/src/custom-coder.ts +162 -0
  143. package/src/demuxer.ts +22 -0
  144. package/src/index.ts +148 -0
  145. package/src/input-format.ts +386 -0
  146. package/src/input-track.ts +341 -0
  147. package/src/input.ts +138 -0
  148. package/src/isobmff/isobmff-boxes.ts +1365 -0
  149. package/src/isobmff/isobmff-demuxer.ts +2646 -0
  150. package/src/isobmff/isobmff-misc.ts +29 -0
  151. package/src/isobmff/isobmff-muxer.ts +1210 -0
  152. package/src/isobmff/isobmff-reader.ts +137 -0
  153. package/src/matroska/ebml.ts +595 -0
  154. package/src/matroska/matroska-demuxer.ts +1677 -0
  155. package/src/matroska/matroska-misc.ts +29 -0
  156. package/src/matroska/matroska-muxer.ts +999 -0
  157. package/src/media-sink.ts +1577 -0
  158. package/src/media-source.ts +1406 -0
  159. package/src/misc.ts +584 -0
  160. package/src/mp3/mp3-demuxer.ts +228 -0
  161. package/src/mp3/mp3-misc.ts +154 -0
  162. package/src/mp3/mp3-muxer.ts +153 -0
  163. package/src/mp3/mp3-reader.ts +93 -0
  164. package/src/mp3/mp3-writer.ts +112 -0
  165. package/src/muxer.ts +88 -0
  166. package/src/ogg/ogg-demuxer.ts +985 -0
  167. package/src/ogg/ogg-misc.ts +116 -0
  168. package/src/ogg/ogg-muxer.ts +480 -0
  169. package/src/ogg/ogg-reader.ts +143 -0
  170. package/src/output-format.ts +689 -0
  171. package/src/output.ts +427 -0
  172. package/src/packet.ts +180 -0
  173. package/src/pcm.ts +112 -0
  174. package/src/reader.ts +205 -0
  175. package/src/sample.ts +1029 -0
  176. package/src/source.ts +279 -0
  177. package/src/subtitles.ts +131 -0
  178. package/src/target.ts +106 -0
  179. package/src/tsconfig.json +13 -0
  180. package/src/wave/riff-reader.ts +48 -0
  181. package/src/wave/riff-writer.ts +30 -0
  182. package/src/wave/wave-demuxer.ts +317 -0
  183. package/src/wave/wave-muxer.ts +145 -0
  184. package/src/writer.ts +461 -0
  185. /package/dist/{mediabunny.js → bundles/mediabunny.cjs} +0 -0
  186. /package/dist/{mediabunny.min.js → bundles/mediabunny.min.cjs} +0 -0
  187. /package/dist/{mediabunny.min.mjs → bundles/mediabunny.min.mjs} +0 -0
  188. /package/dist/{mediabunny.mjs → bundles/mediabunny.mjs} +0 -0
@@ -0,0 +1,595 @@
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
+
9
+ import { MediaCodec } from '../codec';
10
+ import { Reader } from '../reader';
11
+ import { Writer } from '../writer';
12
+
13
+ export interface EBMLElement {
14
+ id: number;
15
+ size?: number;
16
+ data: number | string | Uint8Array | EBMLFloat32 | EBMLFloat64 | EBMLSignedInt | (EBML | null)[];
17
+ }
18
+
19
+ export type EBML = EBMLElement | Uint8Array | (EBML | null)[];
20
+
21
+ /** Wrapper around a number to be able to differentiate it in the writer. */
22
+ export class EBMLFloat32 {
23
+ value: number;
24
+
25
+ constructor(value: number) {
26
+ this.value = value;
27
+ }
28
+ }
29
+
30
+ /** Wrapper around a number to be able to differentiate it in the writer. */
31
+ export class EBMLFloat64 {
32
+ value: number;
33
+
34
+ constructor(value: number) {
35
+ this.value = value;
36
+ }
37
+ }
38
+
39
+ /** Wrapper around a number to be able to differentiate it in the writer. */
40
+ export class EBMLSignedInt {
41
+ value: number;
42
+
43
+ constructor(value: number) {
44
+ this.value = value;
45
+ }
46
+ }
47
+
48
+ /** Defines some of the EBML IDs used by Matroska files. */
49
+ export enum EBMLId {
50
+ EBML = 0x1a45dfa3,
51
+ EBMLVersion = 0x4286,
52
+ EBMLReadVersion = 0x42f7,
53
+ EBMLMaxIDLength = 0x42f2,
54
+ EBMLMaxSizeLength = 0x42f3,
55
+ DocType = 0x4282,
56
+ DocTypeVersion = 0x4287,
57
+ DocTypeReadVersion = 0x4285,
58
+ SeekHead = 0x114d9b74,
59
+ Seek = 0x4dbb,
60
+ SeekID = 0x53ab,
61
+ SeekPosition = 0x53ac,
62
+ Duration = 0x4489,
63
+ Info = 0x1549a966,
64
+ TimestampScale = 0x2ad7b1,
65
+ MuxingApp = 0x4d80,
66
+ WritingApp = 0x5741,
67
+ Tracks = 0x1654ae6b,
68
+ TrackEntry = 0xae,
69
+ TrackNumber = 0xd7,
70
+ TrackUID = 0x73c5,
71
+ TrackType = 0x83,
72
+ FlagEnabled = 0xb9,
73
+ FlagDefault = 0x88,
74
+ FlagForced = 0x55aa,
75
+ FlagLacing = 0x9c,
76
+ Language = 0x22b59c,
77
+ CodecID = 0x86,
78
+ CodecPrivate = 0x63a2,
79
+ CodecDelay = 0x56aa,
80
+ SeekPreRoll = 0x56bb,
81
+ DefaultDuration = 0x23e383,
82
+ Video = 0xe0,
83
+ PixelWidth = 0xb0,
84
+ PixelHeight = 0xba,
85
+ Audio = 0xe1,
86
+ SamplingFrequency = 0xb5,
87
+ Channels = 0x9f,
88
+ BitDepth = 0x6264,
89
+ Segment = 0x18538067,
90
+ SimpleBlock = 0xa3,
91
+ BlockGroup = 0xa0,
92
+ Block = 0xa1,
93
+ BlockAdditions = 0x75a1,
94
+ BlockMore = 0xa6,
95
+ BlockAdditional = 0xa5,
96
+ BlockAddID = 0xee,
97
+ BlockDuration = 0x9b,
98
+ ReferenceBlock = 0xfb,
99
+ Cluster = 0x1f43b675,
100
+ Timestamp = 0xe7,
101
+ Cues = 0x1c53bb6b,
102
+ CuePoint = 0xbb,
103
+ CueTime = 0xb3,
104
+ CueTrackPositions = 0xb7,
105
+ CueTrack = 0xf7,
106
+ CueClusterPosition = 0xf1,
107
+ Colour = 0x55b0,
108
+ MatrixCoefficients = 0x55b1,
109
+ TransferCharacteristics = 0x55ba,
110
+ Primaries = 0x55bb,
111
+ Range = 0x55b9,
112
+ Projection = 0x7670,
113
+ ProjectionType = 0x7671,
114
+ ProjectionPoseRoll = 0x7675,
115
+ Attachments = 0x1941a469,
116
+ Chapters = 0x1043a770,
117
+ Tags = 0x1254c367,
118
+ }
119
+
120
+ export const LEVEL_0_EBML_IDS: EBMLId[] = [
121
+ EBMLId.EBML,
122
+ EBMLId.Segment,
123
+ ];
124
+
125
+ export const LEVEL_1_EBML_IDS: EBMLId[] = [
126
+ EBMLId.EBMLMaxIDLength,
127
+ EBMLId.EBMLMaxSizeLength,
128
+ EBMLId.SeekHead,
129
+ EBMLId.Info,
130
+ EBMLId.Cluster,
131
+ EBMLId.Tracks,
132
+ EBMLId.Cues,
133
+ EBMLId.Attachments,
134
+ EBMLId.Chapters,
135
+ EBMLId.Tags,
136
+ ];
137
+
138
+ export const LEVEL_0_AND_1_EBML_IDS = [
139
+ ...LEVEL_0_EBML_IDS,
140
+ ...LEVEL_1_EBML_IDS,
141
+ ];
142
+
143
+ export const measureUnsignedInt = (value: number) => {
144
+ if (value < (1 << 8)) {
145
+ return 1;
146
+ } else if (value < (1 << 16)) {
147
+ return 2;
148
+ } else if (value < (1 << 24)) {
149
+ return 3;
150
+ } else if (value < 2 ** 32) {
151
+ return 4;
152
+ } else if (value < 2 ** 40) {
153
+ return 5;
154
+ } else {
155
+ return 6;
156
+ }
157
+ };
158
+
159
+ export const measureSignedInt = (value: number) => {
160
+ if (value >= -(1 << 6) && value < (1 << 6)) {
161
+ return 1;
162
+ } else if (value >= -(1 << 13) && value < (1 << 13)) {
163
+ return 2;
164
+ } else if (value >= -(1 << 20) && value < (1 << 20)) {
165
+ return 3;
166
+ } else if (value >= -(1 << 27) && value < (1 << 27)) {
167
+ return 4;
168
+ } else if (value >= -(2 ** 34) && value < 2 ** 34) {
169
+ return 5;
170
+ } else {
171
+ return 6;
172
+ }
173
+ };
174
+
175
+ export const measureVarInt = (value: number) => {
176
+ if (value < (1 << 7) - 1) {
177
+ /** Top bit is set, leaving 7 bits to hold the integer, but we can't store
178
+ * 127 because "all bits set to one" is a reserved value. Same thing for the
179
+ * other cases below:
180
+ */
181
+ return 1;
182
+ } else if (value < (1 << 14) - 1) {
183
+ return 2;
184
+ } else if (value < (1 << 21) - 1) {
185
+ return 3;
186
+ } else if (value < (1 << 28) - 1) {
187
+ return 4;
188
+ } else if (value < 2 ** 35 - 1) {
189
+ return 5;
190
+ } else if (value < 2 ** 42 - 1) {
191
+ return 6;
192
+ } else {
193
+ throw new Error('EBML varint size not supported ' + value);
194
+ }
195
+ };
196
+
197
+ export class EBMLWriter {
198
+ helper = new Uint8Array(8);
199
+ helperView = new DataView(this.helper.buffer);
200
+
201
+ /**
202
+ * Stores the position from the start of the file to where EBML elements have been written. This is used to
203
+ * rewrite/edit elements that were already added before, and to measure sizes of things.
204
+ */
205
+ offsets = new WeakMap<EBML, number>();
206
+ /** Same as offsets, but stores position where the element's data starts (after ID and size fields). */
207
+ dataOffsets = new WeakMap<EBML, number>();
208
+
209
+ constructor(private writer: Writer) {}
210
+
211
+ writeByte(value: number) {
212
+ this.helperView.setUint8(0, value);
213
+ this.writer.write(this.helper.subarray(0, 1));
214
+ }
215
+
216
+ writeFloat32(value: number) {
217
+ this.helperView.setFloat32(0, value, false);
218
+ this.writer.write(this.helper.subarray(0, 4));
219
+ }
220
+
221
+ writeFloat64(value: number) {
222
+ this.helperView.setFloat64(0, value, false);
223
+ this.writer.write(this.helper);
224
+ }
225
+
226
+ writeUnsignedInt(value: number, width = measureUnsignedInt(value)) {
227
+ let pos = 0;
228
+
229
+ // Each case falls through:
230
+ switch (width) {
231
+ case 6:
232
+ // Need to use division to access >32 bits of floating point var
233
+ this.helperView.setUint8(pos++, (value / 2 ** 40) | 0);
234
+ // eslint-disable-next-line no-fallthrough
235
+ case 5:
236
+ this.helperView.setUint8(pos++, (value / 2 ** 32) | 0);
237
+ // eslint-disable-next-line no-fallthrough
238
+ case 4:
239
+ this.helperView.setUint8(pos++, value >> 24);
240
+ // eslint-disable-next-line no-fallthrough
241
+ case 3:
242
+ this.helperView.setUint8(pos++, value >> 16);
243
+ // eslint-disable-next-line no-fallthrough
244
+ case 2:
245
+ this.helperView.setUint8(pos++, value >> 8);
246
+ // eslint-disable-next-line no-fallthrough
247
+ case 1:
248
+ this.helperView.setUint8(pos++, value);
249
+ break;
250
+ default:
251
+ throw new Error('Bad unsigned int size ' + width);
252
+ }
253
+
254
+ this.writer.write(this.helper.subarray(0, pos));
255
+ }
256
+
257
+ writeSignedInt(value: number, width = measureSignedInt(value)) {
258
+ if (value < 0) {
259
+ // Two's complement stuff
260
+ value += 2 ** (width * 8);
261
+ }
262
+
263
+ this.writeUnsignedInt(value, width);
264
+ }
265
+
266
+ writeVarInt(value: number, width = measureVarInt(value)) {
267
+ let pos = 0;
268
+
269
+ switch (width) {
270
+ case 1:
271
+ this.helperView.setUint8(pos++, (1 << 7) | value);
272
+ break;
273
+ case 2:
274
+ this.helperView.setUint8(pos++, (1 << 6) | (value >> 8));
275
+ this.helperView.setUint8(pos++, value);
276
+ break;
277
+ case 3:
278
+ this.helperView.setUint8(pos++, (1 << 5) | (value >> 16));
279
+ this.helperView.setUint8(pos++, value >> 8);
280
+ this.helperView.setUint8(pos++, value);
281
+ break;
282
+ case 4:
283
+ this.helperView.setUint8(pos++, (1 << 4) | (value >> 24));
284
+ this.helperView.setUint8(pos++, value >> 16);
285
+ this.helperView.setUint8(pos++, value >> 8);
286
+ this.helperView.setUint8(pos++, value);
287
+ break;
288
+ case 5:
289
+ /**
290
+ * JavaScript converts its doubles to 32-bit integers for bitwise
291
+ * operations, so we need to do a division by 2^32 instead of a
292
+ * right-shift of 32 to retain those top 3 bits
293
+ */
294
+ this.helperView.setUint8(pos++, (1 << 3) | ((value / 2 ** 32) & 0x7));
295
+ this.helperView.setUint8(pos++, value >> 24);
296
+ this.helperView.setUint8(pos++, value >> 16);
297
+ this.helperView.setUint8(pos++, value >> 8);
298
+ this.helperView.setUint8(pos++, value);
299
+ break;
300
+ case 6:
301
+ this.helperView.setUint8(pos++, (1 << 2) | ((value / 2 ** 40) & 0x3));
302
+ this.helperView.setUint8(pos++, (value / 2 ** 32) | 0);
303
+ this.helperView.setUint8(pos++, value >> 24);
304
+ this.helperView.setUint8(pos++, value >> 16);
305
+ this.helperView.setUint8(pos++, value >> 8);
306
+ this.helperView.setUint8(pos++, value);
307
+ break;
308
+ default:
309
+ throw new Error('Bad EBML varint size ' + width);
310
+ }
311
+
312
+ this.writer.write(this.helper.subarray(0, pos));
313
+ }
314
+
315
+ // Assumes the string is ASCII
316
+ writeString(str: string) {
317
+ this.writer.write(new Uint8Array(str.split('').map(x => x.charCodeAt(0))));
318
+ }
319
+
320
+ writeEBML(data: EBML | null) {
321
+ if (data === null) return;
322
+
323
+ if (data instanceof Uint8Array) {
324
+ this.writer.write(data);
325
+ } else if (Array.isArray(data)) {
326
+ for (const elem of data) {
327
+ this.writeEBML(elem);
328
+ }
329
+ } else {
330
+ this.offsets.set(data, this.writer.getPos());
331
+
332
+ this.writeUnsignedInt(data.id); // ID field
333
+
334
+ if (Array.isArray(data.data)) {
335
+ const sizePos = this.writer.getPos();
336
+ const sizeSize = data.size === -1 ? 1 : (data.size ?? 4);
337
+
338
+ if (data.size === -1) {
339
+ // Write the reserved all-one-bits marker for unknown/unbounded size.
340
+ this.writeByte(0xff);
341
+ } else {
342
+ this.writer.seek(this.writer.getPos() + sizeSize);
343
+ }
344
+
345
+ const startPos = this.writer.getPos();
346
+ this.dataOffsets.set(data, startPos);
347
+ this.writeEBML(data.data);
348
+
349
+ if (data.size !== -1) {
350
+ const size = this.writer.getPos() - startPos;
351
+ const endPos = this.writer.getPos();
352
+ this.writer.seek(sizePos);
353
+ this.writeVarInt(size, sizeSize);
354
+ this.writer.seek(endPos);
355
+ }
356
+ } else if (typeof data.data === 'number') {
357
+ const size = data.size ?? measureUnsignedInt(data.data);
358
+ this.writeVarInt(size);
359
+ this.writeUnsignedInt(data.data, size);
360
+ } else if (typeof data.data === 'string') {
361
+ this.writeVarInt(data.data.length);
362
+ this.writeString(data.data);
363
+ } else if (data.data instanceof Uint8Array) {
364
+ this.writeVarInt(data.data.byteLength, data.size);
365
+ this.writer.write(data.data);
366
+ } else if (data.data instanceof EBMLFloat32) {
367
+ this.writeVarInt(4);
368
+ this.writeFloat32(data.data.value);
369
+ } else if (data.data instanceof EBMLFloat64) {
370
+ this.writeVarInt(8);
371
+ this.writeFloat64(data.data.value);
372
+ } else if (data.data instanceof EBMLSignedInt) {
373
+ const size = data.size ?? measureSignedInt(data.data.value);
374
+ this.writeVarInt(size);
375
+ this.writeSignedInt(data.data.value, size);
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ const MAX_VAR_INT_SIZE = 8;
382
+ export const MIN_HEADER_SIZE = 2; // 1-byte ID and 1-byte size
383
+ export const MAX_HEADER_SIZE = 4 + MAX_VAR_INT_SIZE; // 4-byte ID and 8-byte size
384
+
385
+ export class EBMLReader {
386
+ pos = 0;
387
+
388
+ constructor(public reader: Reader) {}
389
+
390
+ readBytes(length: number) {
391
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
392
+ this.pos += length;
393
+
394
+ return new Uint8Array(view.buffer, offset, length);
395
+ }
396
+
397
+ readU8() {
398
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
399
+ this.pos++;
400
+
401
+ return view.getUint8(offset);
402
+ }
403
+
404
+ readS16() {
405
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 2);
406
+ this.pos += 2;
407
+
408
+ return view.getInt16(offset, false);
409
+ }
410
+
411
+ readVarIntSize() {
412
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
413
+ const firstByte = view.getUint8(offset);
414
+
415
+ let width = 1;
416
+ let mask = 0x80;
417
+ while ((firstByte & mask) === 0 && width < 8) {
418
+ width++;
419
+ mask >>= 1;
420
+ }
421
+
422
+ return width;
423
+ }
424
+
425
+ readVarInt() {
426
+ // Read the first byte to determine the width of the variable-length integer
427
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
428
+ const firstByte = view.getUint8(offset);
429
+
430
+ // Find the position of VINT_MARKER, which determines the width
431
+ let width = 1;
432
+ let mask = 1 << 7;
433
+ while ((firstByte & mask) === 0 && width < MAX_VAR_INT_SIZE) {
434
+ width++;
435
+ mask >>= 1;
436
+ }
437
+
438
+ const { view: fullView, offset: fullOffset } = this.reader.getViewAndOffset(this.pos, this.pos + width);
439
+
440
+ // First byte's value needs the marker bit cleared
441
+ let value = firstByte & (mask - 1);
442
+
443
+ // Read remaining bytes
444
+ for (let i = 1; i < width; i++) {
445
+ value *= 1 << 8;
446
+ value += fullView.getUint8(fullOffset + i);
447
+ }
448
+
449
+ this.pos += width;
450
+ return value;
451
+ }
452
+
453
+ readUnsignedInt(width: number) {
454
+ if (width < 1 || width > 8) {
455
+ throw new Error('Bad unsigned int size ' + width);
456
+ }
457
+
458
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + width);
459
+ let value = 0;
460
+
461
+ // Read bytes from most significant to least significant
462
+ for (let i = 0; i < width; i++) {
463
+ value *= 1 << 8;
464
+ value += view.getUint8(offset + i);
465
+ }
466
+
467
+ this.pos += width;
468
+ return value;
469
+ }
470
+
471
+ readSignedInt(width: number) {
472
+ let value = this.readUnsignedInt(width);
473
+
474
+ // If the highest bit is set, convert from two's complement
475
+ if (value & (1 << (width * 8 - 1))) {
476
+ value -= 2 ** (width * 8);
477
+ }
478
+
479
+ return value;
480
+ }
481
+
482
+ readFloat(width: number) {
483
+ if (width === 0) {
484
+ return 0;
485
+ }
486
+
487
+ if (width !== 4 && width !== 8) {
488
+ throw new Error('Bad float size ' + width);
489
+ }
490
+
491
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + width);
492
+ const value = width === 4 ? view.getFloat32(offset, false) : view.getFloat64(offset, false);
493
+
494
+ this.pos += width;
495
+ return value;
496
+ }
497
+
498
+ readString(length: number) {
499
+ const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
500
+ this.pos += length;
501
+
502
+ return String.fromCharCode(...new Uint8Array(view.buffer, offset, length));
503
+ }
504
+
505
+ readElementId() {
506
+ const size = this.readVarIntSize();
507
+ const id = this.readUnsignedInt(size);
508
+
509
+ return id;
510
+ }
511
+
512
+ readElementSize() {
513
+ let size: number | null = this.readU8();
514
+
515
+ if (size === 0xff) {
516
+ size = null;
517
+ } else {
518
+ this.pos--;
519
+ size = this.readVarInt();
520
+
521
+ // In some (livestreamed) files, this is the value of the size field. While this technically is just a very
522
+ // large number, it is intended to behave like the reserved size 0xFF, meaning the size is undefined. We
523
+ // catch the number here. Note that it cannot be perfectly represented as a double, but the comparison works
524
+ // nonetheless.
525
+ // eslint-disable-next-line no-loss-of-precision
526
+ if (size === 0x00ffffffffffffff) {
527
+ size = null;
528
+ }
529
+ }
530
+
531
+ return size;
532
+ }
533
+
534
+ readElementHeader() {
535
+ const id = this.readElementId();
536
+ const size = this.readElementSize();
537
+
538
+ return { id, size };
539
+ }
540
+
541
+ /** Returns the byte offset in the file of the next element with a matching ID. */
542
+ async searchForNextElementId(ids: EBMLId[], until: number) {
543
+ const loadChunkSize = 2 ** 20; // 1 MiB
544
+ const idsSet = new Set(ids);
545
+
546
+ while (this.pos < until - MAX_HEADER_SIZE) {
547
+ if (!this.reader.rangeIsLoaded(this.pos, this.pos + MAX_HEADER_SIZE)) {
548
+ await this.reader.loadRange(this.pos, Math.min(this.pos + loadChunkSize, until));
549
+ }
550
+
551
+ const elementStartPos = this.pos;
552
+ const elementHeader = this.readElementHeader();
553
+ if (idsSet.has(elementHeader.id)) {
554
+ return elementStartPos;
555
+ }
556
+
557
+ assertDefinedSize(elementHeader.size);
558
+
559
+ this.pos += elementHeader.size;
560
+ }
561
+
562
+ return null;
563
+ }
564
+ }
565
+
566
+ export const CODEC_STRING_MAP: Partial<Record<MediaCodec, string>> = {
567
+ 'avc': 'V_MPEG4/ISO/AVC',
568
+ 'hevc': 'V_MPEGH/ISO/HEVC',
569
+ 'vp8': 'V_VP8',
570
+ 'vp9': 'V_VP9',
571
+ 'av1': 'V_AV1',
572
+
573
+ 'aac': 'A_AAC',
574
+ 'mp3': 'A_MPEG/L3',
575
+ 'opus': 'A_OPUS',
576
+ 'vorbis': 'A_VORBIS',
577
+ 'flac': 'A_FLAC',
578
+ 'pcm-u8': 'A_PCM/INT/LIT',
579
+ 'pcm-s16': 'A_PCM/INT/LIT',
580
+ 'pcm-s16be': 'A_PCM/INT/BIG',
581
+ 'pcm-s24': 'A_PCM/INT/LIT',
582
+ 'pcm-s24be': 'A_PCM/INT/BIG',
583
+ 'pcm-s32': 'A_PCM/INT/LIT',
584
+ 'pcm-s32be': 'A_PCM/INT/BIG',
585
+ 'pcm-f32': 'A_PCM/FLOAT/IEEE',
586
+ 'pcm-f64': 'A_PCM/FLOAT/IEEE',
587
+
588
+ 'webvtt': 'S_TEXT/WEBVTT',
589
+ };
590
+
591
+ export function assertDefinedSize(size: number | null): asserts size is number {
592
+ if (size === null) {
593
+ throw new Error('Undefined element size is used in a place where it is not supported.');
594
+ }
595
+ };