@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
package/src/codec.ts DELETED
@@ -1,1092 +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
-
9
- import {
10
- Av1CodecInfo,
11
- AvcDecoderConfigurationRecord,
12
- HevcDecoderConfigurationRecord,
13
- Vp9CodecInfo,
14
- } from './codec-data';
15
- import {
16
- Bitstream,
17
- COLOR_PRIMARIES_MAP,
18
- MATRIX_COEFFICIENTS_MAP,
19
- TRANSFER_CHARACTERISTICS_MAP,
20
- assert,
21
- bytesToHexString,
22
- isAllowSharedBufferSource,
23
- last,
24
- reverseBitsU32,
25
- toDataView,
26
- } from './misc';
27
- import { SubtitleMetadata } from './subtitles';
28
-
29
- /**
30
- * List of known video codecs, ordered by encoding preference.
31
- * @group Codecs
32
- * @public
33
- */
34
- export const VIDEO_CODECS = [
35
- 'avc',
36
- 'hevc',
37
- 'vp9',
38
- 'av1',
39
- 'vp8',
40
- ] as const;
41
- /**
42
- * List of known PCM (uncompressed) audio codecs, ordered by encoding preference.
43
- * @group Codecs
44
- * @public
45
- */
46
- export const PCM_AUDIO_CODECS = [
47
- 'pcm-s16', // We don't prefix 'le' so we're compatible with the WebCodecs-registered PCM codec strings
48
- 'pcm-s16be',
49
- 'pcm-s24',
50
- 'pcm-s24be',
51
- 'pcm-s32',
52
- 'pcm-s32be',
53
- 'pcm-f32',
54
- 'pcm-f32be',
55
- 'pcm-f64',
56
- 'pcm-f64be',
57
- 'pcm-u8',
58
- 'pcm-s8',
59
- 'ulaw',
60
- 'alaw',
61
- ] as const;
62
- /**
63
- * List of known compressed audio codecs, ordered by encoding preference.
64
- * @group Codecs
65
- * @public
66
- */
67
- export const NON_PCM_AUDIO_CODECS = [
68
- 'aac',
69
- 'opus',
70
- 'mp3',
71
- 'vorbis',
72
- 'flac',
73
- ] as const;
74
- /**
75
- * List of known audio codecs, ordered by encoding preference.
76
- * @group Codecs
77
- * @public
78
- */
79
- export const AUDIO_CODECS = [
80
- ...NON_PCM_AUDIO_CODECS,
81
- ...PCM_AUDIO_CODECS,
82
- ] as const;
83
- /**
84
- * List of known subtitle codecs, ordered by encoding preference.
85
- * @group Codecs
86
- * @public
87
- */
88
- export const SUBTITLE_CODECS = [
89
- 'webvtt',
90
- 'tx3g',
91
- 'ttml',
92
- 'srt',
93
- 'ass',
94
- 'ssa',
95
- ] as const;
96
-
97
- /**
98
- * Union type of known video codecs.
99
- * @group Codecs
100
- * @public
101
- */
102
- export type VideoCodec = typeof VIDEO_CODECS[number];
103
- /**
104
- * Union type of known audio codecs.
105
- * @group Codecs
106
- * @public
107
- */
108
- export type AudioCodec = typeof AUDIO_CODECS[number];
109
- export type PcmAudioCodec = typeof PCM_AUDIO_CODECS[number];
110
- /**
111
- * Union type of known subtitle codecs.
112
- * @group Codecs
113
- * @public
114
- */
115
- export type SubtitleCodec = typeof SUBTITLE_CODECS[number];
116
- /**
117
- * Union type of known media codecs.
118
- * @group Codecs
119
- * @public
120
- */
121
- export type MediaCodec = VideoCodec | AudioCodec | SubtitleCodec;
122
-
123
- // https://en.wikipedia.org/wiki/Advanced_Video_Coding
124
- const AVC_LEVEL_TABLE = [
125
- { maxMacroblocks: 99, maxBitrate: 64000, level: 0x0A }, // Level 1
126
- { maxMacroblocks: 396, maxBitrate: 192000, level: 0x0B }, // Level 1.1
127
- { maxMacroblocks: 396, maxBitrate: 384000, level: 0x0C }, // Level 1.2
128
- { maxMacroblocks: 396, maxBitrate: 768000, level: 0x0D }, // Level 1.3
129
- { maxMacroblocks: 396, maxBitrate: 2000000, level: 0x14 }, // Level 2
130
- { maxMacroblocks: 792, maxBitrate: 4000000, level: 0x15 }, // Level 2.1
131
- { maxMacroblocks: 1620, maxBitrate: 4000000, level: 0x16 }, // Level 2.2
132
- { maxMacroblocks: 1620, maxBitrate: 10000000, level: 0x1E }, // Level 3
133
- { maxMacroblocks: 3600, maxBitrate: 14000000, level: 0x1F }, // Level 3.1
134
- { maxMacroblocks: 5120, maxBitrate: 20000000, level: 0x20 }, // Level 3.2
135
- { maxMacroblocks: 8192, maxBitrate: 20000000, level: 0x28 }, // Level 4
136
- { maxMacroblocks: 8192, maxBitrate: 50000000, level: 0x29 }, // Level 4.1
137
- { maxMacroblocks: 8704, maxBitrate: 50000000, level: 0x2A }, // Level 4.2
138
- { maxMacroblocks: 22080, maxBitrate: 135000000, level: 0x32 }, // Level 5
139
- { maxMacroblocks: 36864, maxBitrate: 240000000, level: 0x33 }, // Level 5.1
140
- { maxMacroblocks: 36864, maxBitrate: 240000000, level: 0x34 }, // Level 5.2
141
- { maxMacroblocks: 139264, maxBitrate: 240000000, level: 0x3C }, // Level 6
142
- { maxMacroblocks: 139264, maxBitrate: 480000000, level: 0x3D }, // Level 6.1
143
- { maxMacroblocks: 139264, maxBitrate: 800000000, level: 0x3E }, // Level 6.2
144
- ];
145
-
146
- // https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding
147
- const HEVC_LEVEL_TABLE = [
148
- { maxPictureSize: 36864, maxBitrate: 128000, tier: 'L', level: 30 }, // Level 1 (Low Tier)
149
- { maxPictureSize: 122880, maxBitrate: 1500000, tier: 'L', level: 60 }, // Level 2 (Low Tier)
150
- { maxPictureSize: 245760, maxBitrate: 3000000, tier: 'L', level: 63 }, // Level 2.1 (Low Tier)
151
- { maxPictureSize: 552960, maxBitrate: 6000000, tier: 'L', level: 90 }, // Level 3 (Low Tier)
152
- { maxPictureSize: 983040, maxBitrate: 10000000, tier: 'L', level: 93 }, // Level 3.1 (Low Tier)
153
- { maxPictureSize: 2228224, maxBitrate: 12000000, tier: 'L', level: 120 }, // Level 4 (Low Tier)
154
- { maxPictureSize: 2228224, maxBitrate: 30000000, tier: 'H', level: 120 }, // Level 4 (High Tier)
155
- { maxPictureSize: 2228224, maxBitrate: 20000000, tier: 'L', level: 123 }, // Level 4.1 (Low Tier)
156
- { maxPictureSize: 2228224, maxBitrate: 50000000, tier: 'H', level: 123 }, // Level 4.1 (High Tier)
157
- { maxPictureSize: 8912896, maxBitrate: 25000000, tier: 'L', level: 150 }, // Level 5 (Low Tier)
158
- { maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 150 }, // Level 5 (High Tier)
159
- { maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'L', level: 153 }, // Level 5.1 (Low Tier)
160
- { maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 153 }, // Level 5.1 (High Tier)
161
- { maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'L', level: 156 }, // Level 5.2 (Low Tier)
162
- { maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 156 }, // Level 5.2 (High Tier)
163
- { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'L', level: 180 }, // Level 6 (Low Tier)
164
- { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 180 }, // Level 6 (High Tier)
165
- { maxPictureSize: 35651584, maxBitrate: 120000000, tier: 'L', level: 183 }, // Level 6.1 (Low Tier)
166
- { maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 183 }, // Level 6.1 (High Tier)
167
- { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'L', level: 186 }, // Level 6.2 (Low Tier)
168
- { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 186 }, // Level 6.2 (High Tier)
169
- ];
170
-
171
- // https://en.wikipedia.org/wiki/VP9
172
- export const VP9_LEVEL_TABLE = [
173
- { maxPictureSize: 36864, maxBitrate: 200000, level: 10 }, // Level 1
174
- { maxPictureSize: 73728, maxBitrate: 800000, level: 11 }, // Level 1.1
175
- { maxPictureSize: 122880, maxBitrate: 1800000, level: 20 }, // Level 2
176
- { maxPictureSize: 245760, maxBitrate: 3600000, level: 21 }, // Level 2.1
177
- { maxPictureSize: 552960, maxBitrate: 7200000, level: 30 }, // Level 3
178
- { maxPictureSize: 983040, maxBitrate: 12000000, level: 31 }, // Level 3.1
179
- { maxPictureSize: 2228224, maxBitrate: 18000000, level: 40 }, // Level 4
180
- { maxPictureSize: 2228224, maxBitrate: 30000000, level: 41 }, // Level 4.1
181
- { maxPictureSize: 8912896, maxBitrate: 60000000, level: 50 }, // Level 5
182
- { maxPictureSize: 8912896, maxBitrate: 120000000, level: 51 }, // Level 5.1
183
- { maxPictureSize: 8912896, maxBitrate: 180000000, level: 52 }, // Level 5.2
184
- { maxPictureSize: 35651584, maxBitrate: 180000000, level: 60 }, // Level 6
185
- { maxPictureSize: 35651584, maxBitrate: 240000000, level: 61 }, // Level 6.1
186
- { maxPictureSize: 35651584, maxBitrate: 480000000, level: 62 }, // Level 6.2
187
- ];
188
-
189
- // https://en.wikipedia.org/wiki/AV1
190
- const AV1_LEVEL_TABLE = [
191
- { maxPictureSize: 147456, maxBitrate: 1500000, tier: 'M', level: 0 }, // Level 2.0 (Main Tier)
192
- { maxPictureSize: 278784, maxBitrate: 3000000, tier: 'M', level: 1 }, // Level 2.1 (Main Tier)
193
- { maxPictureSize: 665856, maxBitrate: 6000000, tier: 'M', level: 4 }, // Level 3.0 (Main Tier)
194
- { maxPictureSize: 1065024, maxBitrate: 10000000, tier: 'M', level: 5 }, // Level 3.1 (Main Tier)
195
- { maxPictureSize: 2359296, maxBitrate: 12000000, tier: 'M', level: 8 }, // Level 4.0 (Main Tier)
196
- { maxPictureSize: 2359296, maxBitrate: 30000000, tier: 'H', level: 8 }, // Level 4.0 (High Tier)
197
- { maxPictureSize: 2359296, maxBitrate: 20000000, tier: 'M', level: 9 }, // Level 4.1 (Main Tier)
198
- { maxPictureSize: 2359296, maxBitrate: 50000000, tier: 'H', level: 9 }, // Level 4.1 (High Tier)
199
- { maxPictureSize: 8912896, maxBitrate: 30000000, tier: 'M', level: 12 }, // Level 5.0 (Main Tier)
200
- { maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 12 }, // Level 5.0 (High Tier)
201
- { maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'M', level: 13 }, // Level 5.1 (Main Tier)
202
- { maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 13 }, // Level 5.1 (High Tier)
203
- { maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'M', level: 14 }, // Level 5.2 (Main Tier)
204
- { maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 14 }, // Level 5.2 (High Tier)
205
- { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 15 }, // Level 5.3 (Main Tier)
206
- { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 15 }, // Level 5.3 (High Tier)
207
- { maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 16 }, // Level 6.0 (Main Tier)
208
- { maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 16 }, // Level 6.0 (High Tier)
209
- { maxPictureSize: 35651584, maxBitrate: 100000000, tier: 'M', level: 17 }, // Level 6.1 (Main Tier)
210
- { maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 17 }, // Level 6.1 (High Tier)
211
- { maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 18 }, // Level 6.2 (Main Tier)
212
- { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 18 }, // Level 6.2 (High Tier)
213
- { maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 19 }, // Level 6.3 (Main Tier)
214
- { maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 19 }, // Level 6.3 (High Tier)
215
- ];
216
-
217
- const VP9_DEFAULT_SUFFIX = '.01.01.01.01.00';
218
- const AV1_DEFAULT_SUFFIX = '.0.110.01.01.01.0';
219
-
220
- export const buildVideoCodecString = (codec: VideoCodec, width: number, height: number, bitrate: number) => {
221
- if (codec === 'avc') {
222
- const profileIndication = 0x64; // High Profile
223
- const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16);
224
-
225
- // Determine the level based on the table
226
- const levelInfo = AVC_LEVEL_TABLE.find(
227
- level => totalMacroblocks <= level.maxMacroblocks && bitrate <= level.maxBitrate,
228
- ) ?? last(AVC_LEVEL_TABLE)!;
229
- const levelIndication = levelInfo ? levelInfo.level : 0;
230
-
231
- const hexProfileIndication = profileIndication.toString(16).padStart(2, '0');
232
- const hexProfileCompatibility = '00';
233
- const hexLevelIndication = levelIndication.toString(16).padStart(2, '0');
234
-
235
- return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`;
236
- } else if (codec === 'hevc') {
237
- const profilePrefix = ''; // Profile space 0
238
- const profileIdc = 1; // Main Profile
239
-
240
- const compatibilityFlags = '6'; // Taken from the example in ISO 14496-15
241
-
242
- const pictureSize = width * height;
243
- const levelInfo = HEVC_LEVEL_TABLE.find(
244
- level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate,
245
- ) ?? last(HEVC_LEVEL_TABLE)!;
246
-
247
- const constraintFlags = 'B0'; // Progressive source flag
248
-
249
- return 'hev1.'
250
- + `${profilePrefix}${profileIdc}.`
251
- + `${compatibilityFlags}.`
252
- + `${levelInfo.tier}${levelInfo.level}.`
253
- + `${constraintFlags}`;
254
- } else if (codec === 'vp8') {
255
- return 'vp8'; // Easy, this one
256
- } else if (codec === 'vp9') {
257
- const profile = '00'; // Profile 0
258
-
259
- const pictureSize = width * height;
260
- const levelInfo = VP9_LEVEL_TABLE.find(
261
- level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate,
262
- ) ?? last(VP9_LEVEL_TABLE)!;
263
-
264
- const bitDepth = '08'; // 8-bit
265
-
266
- return `vp09.${profile}.${levelInfo.level.toString().padStart(2, '0')}.${bitDepth}`;
267
- } else if (codec === 'av1') {
268
- const profile = 0; // Main Profile, single digit
269
-
270
- const pictureSize = width * height;
271
- const levelInfo = AV1_LEVEL_TABLE.find(
272
- level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate,
273
- ) ?? last(AV1_LEVEL_TABLE)!;
274
- const level = levelInfo.level.toString().padStart(2, '0');
275
-
276
- const bitDepth = '08'; // 8-bit
277
-
278
- return `av01.${profile}.${level}${levelInfo.tier}.${bitDepth}`;
279
- }
280
-
281
- // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
282
- throw new TypeError(`Unhandled codec '${codec}'.`);
283
- };
284
-
285
- export const generateVp9CodecConfigurationFromCodecString = (codecString: string) => {
286
- // Reference: https://www.webmproject.org/docs/container/#vp9-codec-feature-metadata-codecprivate
287
-
288
- const parts = codecString.split('.'); // We can derive the required values from the codec string
289
-
290
- const profile = Number(parts[1]);
291
- const level = Number(parts[2]);
292
- const bitDepth = Number(parts[3]);
293
- const chromaSubsampling = parts[4] ? Number(parts[4]) : 1;
294
-
295
- return [
296
- 1, 1, profile,
297
- 2, 1, level,
298
- 3, 1, bitDepth,
299
- 4, 1, chromaSubsampling,
300
- ];
301
- };
302
-
303
- export const generateAv1CodecConfigurationFromCodecString = (codecString: string) => {
304
- // Reference: https://aomediacodec.github.io/av1-isobmff/
305
-
306
- const parts = codecString.split('.'); // We can derive the required values from the codec string
307
-
308
- const marker = 1;
309
- const version = 1;
310
- const firstByte = (marker << 7) + version;
311
-
312
- const profile = Number(parts[1]);
313
- const levelAndTier = parts[2]!;
314
- const level = Number(levelAndTier.slice(0, -1));
315
- const secondByte = (profile << 5) + level;
316
-
317
- const tier = levelAndTier.slice(-1) === 'H' ? 1 : 0;
318
- const bitDepth = Number(parts[3]);
319
- const highBitDepth = bitDepth === 8 ? 0 : 1;
320
- const twelveBit = 0;
321
- const monochrome = parts[4] ? Number(parts[4]) : 0;
322
- const chromaSubsamplingX = parts[5] ? Number(parts[5][0]) : 1;
323
- const chromaSubsamplingY = parts[5] ? Number(parts[5][1]) : 1;
324
- const chromaSamplePosition = parts[5] ? Number(parts[5][2]) : 0; // CSP_UNKNOWN
325
- const thirdByte = (tier << 7)
326
- + (highBitDepth << 6)
327
- + (twelveBit << 5)
328
- + (monochrome << 4)
329
- + (chromaSubsamplingX << 3)
330
- + (chromaSubsamplingY << 2)
331
- + chromaSamplePosition;
332
-
333
- const initialPresentationDelayPresent = 0; // Should be fine
334
- const fourthByte = initialPresentationDelayPresent;
335
-
336
- return [firstByte, secondByte, thirdByte, fourthByte];
337
- };
338
-
339
- export const extractVideoCodecString = (trackInfo: {
340
- width: number;
341
- height: number;
342
- codec: VideoCodec | null;
343
- codecDescription: Uint8Array | null;
344
- colorSpace: VideoColorSpaceInit | null;
345
- avcType: 1 | 3 | null;
346
- avcCodecInfo: AvcDecoderConfigurationRecord | null;
347
- hevcCodecInfo: HevcDecoderConfigurationRecord | null;
348
- vp9CodecInfo: Vp9CodecInfo | null;
349
- av1CodecInfo: Av1CodecInfo | null;
350
- }) => {
351
- const { codec, codecDescription, colorSpace, avcCodecInfo, hevcCodecInfo, vp9CodecInfo, av1CodecInfo } = trackInfo;
352
-
353
- if (codec === 'avc') {
354
- assert(trackInfo.avcType !== null);
355
-
356
- if (avcCodecInfo) {
357
- const bytes = new Uint8Array([
358
- avcCodecInfo.avcProfileIndication,
359
- avcCodecInfo.profileCompatibility,
360
- avcCodecInfo.avcLevelIndication,
361
- ]);
362
-
363
- return `avc${trackInfo.avcType}.${bytesToHexString(bytes)}`;
364
- }
365
-
366
- if (!codecDescription || codecDescription.byteLength < 4) {
367
- throw new TypeError('AVC decoder description is not provided or is not at least 4 bytes long.');
368
- }
369
-
370
- return `avc${trackInfo.avcType}.${bytesToHexString(codecDescription.subarray(1, 4))}`;
371
- } else if (codec === 'hevc') {
372
- let generalProfileSpace: number;
373
- let generalProfileIdc: number;
374
- let compatibilityFlags: number;
375
- let generalTierFlag: number;
376
- let generalLevelIdc: number;
377
- let constraintFlags: number[];
378
-
379
- if (hevcCodecInfo) {
380
- generalProfileSpace = hevcCodecInfo.generalProfileSpace;
381
- generalProfileIdc = hevcCodecInfo.generalProfileIdc;
382
- compatibilityFlags = reverseBitsU32(hevcCodecInfo.generalProfileCompatibilityFlags);
383
- generalTierFlag = hevcCodecInfo.generalTierFlag;
384
- generalLevelIdc = hevcCodecInfo.generalLevelIdc;
385
- constraintFlags = [...hevcCodecInfo.generalConstraintIndicatorFlags];
386
- } else {
387
- if (!codecDescription || codecDescription.byteLength < 23) {
388
- throw new TypeError('HEVC decoder description is not provided or is not at least 23 bytes long.');
389
- }
390
-
391
- const view = toDataView(codecDescription);
392
- const profileByte = view.getUint8(1);
393
-
394
- generalProfileSpace = (profileByte >> 6) & 0x03;
395
- generalProfileIdc = profileByte & 0x1F;
396
- compatibilityFlags = reverseBitsU32(view.getUint32(2));
397
- generalTierFlag = (profileByte >> 5) & 0x01;
398
- generalLevelIdc = view.getUint8(12);
399
-
400
- constraintFlags = [];
401
- for (let i = 0; i < 6; i++) {
402
- constraintFlags.push(view.getUint8(6 + i));
403
- }
404
- }
405
-
406
- let codecString = 'hev1.';
407
-
408
- codecString += ['', 'A', 'B', 'C'][generalProfileSpace]! + generalProfileIdc;
409
- codecString += '.';
410
- codecString += compatibilityFlags.toString(16).toUpperCase();
411
- codecString += '.';
412
- codecString += generalTierFlag === 0 ? 'L' : 'H';
413
- codecString += generalLevelIdc;
414
-
415
- while (constraintFlags.length > 0 && constraintFlags[constraintFlags.length - 1] === 0) {
416
- constraintFlags.pop();
417
- }
418
-
419
- if (constraintFlags.length > 0) {
420
- codecString += '.';
421
- codecString += constraintFlags.map(x => x.toString(16).toUpperCase()).join('.');
422
- }
423
-
424
- return codecString;
425
- } else if (codec === 'vp8') {
426
- return 'vp8'; // Easy, this one
427
- } else if (codec === 'vp9') {
428
- if (!vp9CodecInfo) {
429
- // Calculate level based on dimensions
430
- const pictureSize = trackInfo.width * trackInfo.height;
431
- let level = last(VP9_LEVEL_TABLE)!.level; // Default to highest level
432
- for (const entry of VP9_LEVEL_TABLE) {
433
- if (pictureSize <= entry.maxPictureSize) {
434
- level = entry.level;
435
- break;
436
- }
437
- }
438
-
439
- // We don't really know better, so let's return a general-purpose, common codec string and hope for the best
440
- return `vp09.00.${level.toString().padStart(2, '0')}.08`;
441
- }
442
-
443
- const profile = vp9CodecInfo.profile.toString().padStart(2, '0');
444
- const level = vp9CodecInfo.level.toString().padStart(2, '0');
445
- const bitDepth = vp9CodecInfo.bitDepth.toString().padStart(2, '0');
446
- const chromaSubsampling = vp9CodecInfo.chromaSubsampling.toString().padStart(2, '0');
447
- const colourPrimaries = vp9CodecInfo.colourPrimaries.toString().padStart(2, '0');
448
- const transferCharacteristics = vp9CodecInfo.transferCharacteristics.toString().padStart(2, '0');
449
- const matrixCoefficients = vp9CodecInfo.matrixCoefficients.toString().padStart(2, '0');
450
- const videoFullRangeFlag = vp9CodecInfo.videoFullRangeFlag.toString().padStart(2, '0');
451
-
452
- let string = `vp09.${profile}.${level}.${bitDepth}.${chromaSubsampling}`;
453
- string += `.${colourPrimaries}.${transferCharacteristics}.${matrixCoefficients}.${videoFullRangeFlag}`;
454
-
455
- if (string.endsWith(VP9_DEFAULT_SUFFIX)) {
456
- string = string.slice(0, -VP9_DEFAULT_SUFFIX.length);
457
- }
458
-
459
- return string;
460
- } else if (codec === 'av1') {
461
- if (!av1CodecInfo) {
462
- // Calculate level based on dimensions
463
- const pictureSize = trackInfo.width * trackInfo.height;
464
- let level = last(VP9_LEVEL_TABLE)!.level; // Default to highest level
465
- for (const entry of VP9_LEVEL_TABLE) {
466
- if (pictureSize <= entry.maxPictureSize) {
467
- level = entry.level;
468
- break;
469
- }
470
- }
471
-
472
- // We don't really know better, so let's return a general-purpose, common codec string and hope for the best
473
- return `av01.0.${level.toString().padStart(2, '0')}M.08`;
474
- }
475
-
476
- // https://aomediacodec.github.io/av1-isobmff/#codecsparam
477
- const profile = av1CodecInfo.profile; // Single digit
478
- const level = av1CodecInfo.level.toString().padStart(2, '0');
479
- const tier = av1CodecInfo.tier ? 'H' : 'M';
480
- const bitDepth = av1CodecInfo.bitDepth.toString().padStart(2, '0');
481
- const monochrome = av1CodecInfo.monochrome ? '1' : '0';
482
- const chromaSubsampling = 100 * av1CodecInfo.chromaSubsamplingX
483
- + 10 * av1CodecInfo.chromaSubsamplingY
484
- + 1 * (
485
- av1CodecInfo.chromaSubsamplingX && av1CodecInfo.chromaSubsamplingY
486
- ? av1CodecInfo.chromaSamplePosition
487
- : 0
488
- );
489
-
490
- // The defaults are 1 (ITU-R BT.709)
491
- const colorPrimaries = colorSpace?.primaries ? COLOR_PRIMARIES_MAP[colorSpace.primaries] : 1;
492
- const transferCharacteristics = colorSpace?.transfer ? TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] : 1;
493
- const matrixCoefficients = colorSpace?.matrix ? MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] : 1;
494
-
495
- const videoFullRangeFlag = colorSpace?.fullRange ? 1 : 0;
496
-
497
- let string = `av01.${profile}.${level}${tier}.${bitDepth}`;
498
- string += `.${monochrome}.${chromaSubsampling.toString().padStart(3, '0')}`;
499
- string += `.${colorPrimaries.toString().padStart(2, '0')}`;
500
- string += `.${transferCharacteristics.toString().padStart(2, '0')}`;
501
- string += `.${matrixCoefficients.toString().padStart(2, '0')}`;
502
- string += `.${videoFullRangeFlag}`;
503
-
504
- if (string.endsWith(AV1_DEFAULT_SUFFIX)) {
505
- string = string.slice(0, -AV1_DEFAULT_SUFFIX.length);
506
- }
507
-
508
- return string;
509
- }
510
-
511
- throw new TypeError(`Unhandled codec '${codec}'.`);
512
- };
513
-
514
- export const buildAudioCodecString = (codec: AudioCodec, numberOfChannels: number, sampleRate: number) => {
515
- if (codec === 'aac') {
516
- // If stereo or higher channels and lower sample rate, likely using HE-AAC v2 with PS
517
- if (numberOfChannels >= 2 && sampleRate <= 24000) {
518
- return 'mp4a.40.29'; // HE-AAC v2 (AAC LC + SBR + PS)
519
- }
520
-
521
- // If sample rate is low, likely using HE-AAC v1 with SBR
522
- if (sampleRate <= 24000) {
523
- return 'mp4a.40.5'; // HE-AAC v1 (AAC LC + SBR)
524
- }
525
-
526
- // Default to standard AAC-LC for higher sample rates
527
- return 'mp4a.40.2'; // AAC-LC
528
- } else if (codec === 'mp3') {
529
- return 'mp3';
530
- } else if (codec === 'opus') {
531
- return 'opus';
532
- } else if (codec === 'vorbis') {
533
- return 'vorbis';
534
- } else if (codec === 'flac') {
535
- return 'flac';
536
- } else if ((PCM_AUDIO_CODECS as readonly string[]).includes(codec)) {
537
- return codec;
538
- }
539
-
540
- throw new TypeError(`Unhandled codec '${codec}'.`);
541
- };
542
-
543
- export type AacCodecInfo = {
544
- isMpeg2: boolean;
545
- };
546
-
547
- export const extractAudioCodecString = (trackInfo: {
548
- codec: AudioCodec | null;
549
- codecDescription: Uint8Array | null;
550
- aacCodecInfo: AacCodecInfo | null;
551
- }) => {
552
- const { codec, codecDescription, aacCodecInfo } = trackInfo;
553
-
554
- if (codec === 'aac') {
555
- if (!aacCodecInfo) {
556
- throw new TypeError('AAC codec info must be provided.');
557
- }
558
-
559
- if (aacCodecInfo.isMpeg2) {
560
- return 'mp4a.67';
561
- } else {
562
- const audioSpecificConfig = parseAacAudioSpecificConfig(codecDescription);
563
- return `mp4a.40.${audioSpecificConfig.objectType}`;
564
- }
565
- } else if (codec === 'mp3') {
566
- return 'mp3';
567
- } else if (codec === 'opus') {
568
- return 'opus';
569
- } else if (codec === 'vorbis') {
570
- return 'vorbis';
571
- } else if (codec === 'flac') {
572
- return 'flac';
573
- } else if (codec && (PCM_AUDIO_CODECS as readonly string[]).includes(codec)) {
574
- return codec;
575
- }
576
-
577
- throw new TypeError(`Unhandled codec '${codec}'.`);
578
- };
579
-
580
- export type AacAudioSpecificConfig = {
581
- objectType: number;
582
- frequencyIndex: number;
583
- sampleRate: number | null;
584
- channelConfiguration: number;
585
- numberOfChannels: number | null;
586
- };
587
-
588
- export const aacFrequencyTable = [
589
- 96000, 88200, 64000, 48000, 44100, 32000,
590
- 24000, 22050, 16000, 12000, 11025, 8000, 7350,
591
- ];
592
-
593
- export const aacChannelMap = [-1, 1, 2, 3, 4, 5, 6, 8];
594
-
595
- export const parseAacAudioSpecificConfig = (bytes: Uint8Array | null): AacAudioSpecificConfig => {
596
- if (!bytes || bytes.byteLength < 2) {
597
- throw new TypeError('AAC description must be at least 2 bytes long.');
598
- }
599
-
600
- const bitstream = new Bitstream(bytes);
601
-
602
- let objectType = bitstream.readBits(5);
603
- if (objectType === 31) {
604
- objectType = 32 + bitstream.readBits(6);
605
- }
606
-
607
- const frequencyIndex = bitstream.readBits(4);
608
- let sampleRate: number | null = null;
609
- if (frequencyIndex === 15) {
610
- sampleRate = bitstream.readBits(24);
611
- } else {
612
- if (frequencyIndex < aacFrequencyTable.length) {
613
- sampleRate = aacFrequencyTable[frequencyIndex]!;
614
- }
615
- }
616
-
617
- const channelConfiguration = bitstream.readBits(4);
618
- let numberOfChannels: number | null = null;
619
- if (channelConfiguration >= 1 && channelConfiguration <= 7) {
620
- numberOfChannels = aacChannelMap[channelConfiguration]!;
621
- }
622
-
623
- return {
624
- objectType,
625
- frequencyIndex,
626
- sampleRate,
627
- channelConfiguration,
628
- numberOfChannels,
629
- };
630
- };
631
-
632
- export const buildAacAudioSpecificConfig = (config: {
633
- objectType: number;
634
- sampleRate: number;
635
- numberOfChannels: number;
636
- }) => {
637
- let frequencyIndex = aacFrequencyTable.indexOf(config.sampleRate);
638
- let customSampleRate: number | null = null;
639
-
640
- if (frequencyIndex === -1) {
641
- frequencyIndex = 15;
642
- customSampleRate = config.sampleRate;
643
- }
644
-
645
- const channelConfiguration = aacChannelMap.indexOf(config.numberOfChannels);
646
- if (channelConfiguration === -1) {
647
- throw new TypeError(`Unsupported number of channels: ${config.numberOfChannels}`);
648
- }
649
-
650
- let bitCount = 5 + 4 + 4;
651
- if (config.objectType >= 32) {
652
- bitCount += 6;
653
- }
654
- if (frequencyIndex === 15) {
655
- bitCount += 24;
656
- }
657
-
658
- const byteCount = Math.ceil(bitCount / 8);
659
- const bytes = new Uint8Array(byteCount);
660
- const bitstream = new Bitstream(bytes);
661
-
662
- if (config.objectType < 32) {
663
- bitstream.writeBits(5, config.objectType);
664
- } else {
665
- bitstream.writeBits(5, 31);
666
- bitstream.writeBits(6, config.objectType - 32);
667
- }
668
-
669
- bitstream.writeBits(4, frequencyIndex);
670
-
671
- if (frequencyIndex === 15) {
672
- bitstream.writeBits(24, customSampleRate!);
673
- }
674
-
675
- bitstream.writeBits(4, channelConfiguration);
676
-
677
- return bytes;
678
- };
679
-
680
- export const OPUS_SAMPLE_RATE = 48_000;
681
-
682
- const PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
683
-
684
- export const parsePcmCodec = (codec: PcmAudioCodec) => {
685
- assert(PCM_AUDIO_CODECS.includes(codec));
686
-
687
- if (codec === 'ulaw') {
688
- return { dataType: 'ulaw' as const, sampleSize: 1 as const, littleEndian: true, silentValue: 255 };
689
- } else if (codec === 'alaw') {
690
- return { dataType: 'alaw' as const, sampleSize: 1 as const, littleEndian: true, silentValue: 213 };
691
- }
692
-
693
- const match = PCM_CODEC_REGEX.exec(codec);
694
- assert(match);
695
-
696
- let dataType: 'unsigned' | 'signed' | 'float' | 'ulaw' | 'alaw';
697
- if (match[1] === 'u') {
698
- dataType = 'unsigned';
699
- } else if (match[1] === 's') {
700
- dataType = 'signed';
701
- } else {
702
- dataType = 'float';
703
- }
704
-
705
- const sampleSize = (Number(match[2]) / 8) as 1 | 2 | 3 | 4 | 8;
706
- const littleEndian = match[3] !== 'be';
707
- const silentValue = codec === 'pcm-u8' ? 2 ** 7 : 0;
708
-
709
- return { dataType, sampleSize, littleEndian, silentValue };
710
- };
711
-
712
- export const inferCodecFromCodecString = (codecString: string): MediaCodec | null => {
713
- // Video codecs
714
- if (codecString.startsWith('avc1') || codecString.startsWith('avc3')) {
715
- return 'avc';
716
- } else if (codecString.startsWith('hev1') || codecString.startsWith('hvc1')) {
717
- return 'hevc';
718
- } else if (codecString === 'vp8') {
719
- return 'vp8';
720
- } else if (codecString.startsWith('vp09')) {
721
- return 'vp9';
722
- } else if (codecString.startsWith('av01')) {
723
- return 'av1';
724
- }
725
-
726
- // Audio codecs
727
- if (codecString.startsWith('mp4a.40') || codecString === 'mp4a.67') {
728
- return 'aac';
729
- } else if (
730
- codecString === 'mp3'
731
- || codecString === 'mp4a.69'
732
- || codecString === 'mp4a.6B'
733
- || codecString === 'mp4a.6b'
734
- ) {
735
- return 'mp3';
736
- } else if (codecString === 'opus') {
737
- return 'opus';
738
- } else if (codecString === 'vorbis') {
739
- return 'vorbis';
740
- } else if (codecString === 'flac') {
741
- return 'flac';
742
- } else if (codecString === 'ulaw') {
743
- return 'ulaw';
744
- } else if (codecString === 'alaw') {
745
- return 'alaw';
746
- } else if (PCM_CODEC_REGEX.test(codecString)) {
747
- return codecString as PcmAudioCodec;
748
- }
749
-
750
- // Subtitle codecs
751
- if (codecString === 'webvtt') {
752
- return 'webvtt';
753
- } else if (codecString === 'srt') {
754
- return 'srt';
755
- } else if (codecString === 'ass') {
756
- return 'ass';
757
- } else if (codecString === 'ssa') {
758
- return 'ssa';
759
- }
760
-
761
- return null;
762
- };
763
-
764
- export const getVideoEncoderConfigExtension = (codec: VideoCodec) => {
765
- if (codec === 'avc') {
766
- return {
767
- avc: {
768
- format: 'avc' as const, // Ensure the format is not Annex B
769
- },
770
- };
771
- } else if (codec === 'hevc') {
772
- return {
773
- hevc: {
774
- format: 'hevc' as const, // Ensure the format is not Annex B
775
- },
776
- };
777
- }
778
-
779
- return {};
780
- };
781
-
782
- export const getAudioEncoderConfigExtension = (codec: AudioCodec) => {
783
- if (codec === 'aac') {
784
- return {
785
- aac: {
786
- format: 'aac' as const, // Ensure the format is not ADTS
787
- },
788
- };
789
- } else if (codec === 'opus') {
790
- return {
791
- opus: {
792
- format: 'opus' as const,
793
- },
794
- };
795
- }
796
-
797
- return {};
798
- };
799
-
800
- const VALID_VIDEO_CODEC_STRING_PREFIXES = ['avc1', 'avc3', 'hev1', 'hvc1', 'vp8', 'vp09', 'av01'];
801
- const AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/;
802
- const HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/;
803
- const VP9_CODEC_STRING_REGEX = /^vp09(?:\.\d{2}){3}(?:(?:\.\d{2}){5})?$/;
804
- const AV1_CODEC_STRING_REGEX = /^av01\.\d\.\d{2}[MH]\.\d{2}(?:\.\d\.\d{3}\.\d{2}\.\d{2}\.\d{2}\.\d)?$/;
805
-
806
- export const validateVideoChunkMetadata = (metadata: EncodedVideoChunkMetadata | undefined) => {
807
- if (!metadata) {
808
- throw new TypeError('Video chunk metadata must be provided.');
809
- }
810
- if (typeof metadata !== 'object') {
811
- throw new TypeError('Video chunk metadata must be an object.');
812
- }
813
- if (!metadata.decoderConfig) {
814
- throw new TypeError('Video chunk metadata must include a decoder configuration.');
815
- }
816
- if (typeof metadata.decoderConfig !== 'object') {
817
- throw new TypeError('Video chunk metadata decoder configuration must be an object.');
818
- }
819
- if (typeof metadata.decoderConfig.codec !== 'string') {
820
- throw new TypeError('Video chunk metadata decoder configuration must specify a codec string.');
821
- }
822
- if (!VALID_VIDEO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig!.codec.startsWith(prefix))) {
823
- throw new TypeError(
824
- 'Video chunk metadata decoder configuration codec string must be a valid video codec string as specified in'
825
- + ' the WebCodecs Codec Registry.',
826
- );
827
- }
828
- if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth! <= 0) {
829
- throw new TypeError(
830
- 'Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).',
831
- );
832
- }
833
- if (!Number.isInteger(metadata.decoderConfig.codedHeight) || metadata.decoderConfig.codedHeight! <= 0) {
834
- throw new TypeError(
835
- 'Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).',
836
- );
837
- }
838
- if (metadata.decoderConfig.description !== undefined) {
839
- if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) {
840
- throw new TypeError(
841
- 'Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an'
842
- + ' ArrayBuffer view.',
843
- );
844
- }
845
- }
846
- if (metadata.decoderConfig.colorSpace !== undefined) {
847
- const { colorSpace } = metadata.decoderConfig;
848
-
849
- if (typeof colorSpace !== 'object') {
850
- throw new TypeError(
851
- 'Video chunk metadata decoder configuration colorSpace, when provided, must be an object.',
852
- );
853
- }
854
-
855
- const primariesValues = Object.keys(COLOR_PRIMARIES_MAP);
856
- if (colorSpace.primaries != null && !primariesValues.includes(colorSpace.primaries)) {
857
- throw new TypeError(
858
- `Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of`
859
- + ` ${primariesValues.join(', ')}.`,
860
- );
861
- }
862
-
863
- const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP);
864
- if (colorSpace.transfer != null && !transferValues.includes(colorSpace.transfer)) {
865
- throw new TypeError(
866
- `Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of`
867
- + ` ${transferValues.join(', ')}.`,
868
- );
869
- }
870
-
871
- const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP);
872
- if (colorSpace.matrix != null && !matrixValues.includes(colorSpace.matrix)) {
873
- throw new TypeError(
874
- `Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of`
875
- + ` ${matrixValues.join(', ')}.`,
876
- );
877
- }
878
-
879
- if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== 'boolean') {
880
- throw new TypeError(
881
- 'Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.',
882
- );
883
- }
884
- }
885
-
886
- if (metadata.decoderConfig.codec.startsWith('avc1') || metadata.decoderConfig.codec.startsWith('avc3')) {
887
- // AVC-specific validation
888
-
889
- if (!AVC_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
890
- throw new TypeError(
891
- 'Video chunk metadata decoder configuration codec string for AVC must be a valid AVC codec string as'
892
- + ' specified in Section 3.4 of RFC 6381.',
893
- );
894
- }
895
-
896
- // `description` may or may not be set, depending on if the format is AVCC or Annex B, so don't perform any
897
- // validation for it.
898
- // https://www.w3.org/TR/webcodecs-avc-codec-registration
899
- } else if (metadata.decoderConfig.codec.startsWith('hev1') || metadata.decoderConfig.codec.startsWith('hvc1')) {
900
- // HEVC-specific validation
901
-
902
- if (!HEVC_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
903
- throw new TypeError(
904
- 'Video chunk metadata decoder configuration codec string for HEVC must be a valid HEVC codec string as'
905
- + ' specified in Section E.3 of ISO 14496-15.',
906
- );
907
- }
908
-
909
- // `description` may or may not be set, depending on if the format is HEVC or Annex B, so don't perform any
910
- // validation for it.
911
- // https://www.w3.org/TR/webcodecs-hevc-codec-registration
912
- } else if (metadata.decoderConfig.codec.startsWith('vp8')) {
913
- // VP8-specific validation
914
-
915
- if (metadata.decoderConfig.codec !== 'vp8') {
916
- throw new TypeError('Video chunk metadata decoder configuration codec string for VP8 must be "vp8".');
917
- }
918
- } else if (metadata.decoderConfig.codec.startsWith('vp09')) {
919
- // VP9-specific validation
920
-
921
- if (!VP9_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
922
- throw new TypeError(
923
- 'Video chunk metadata decoder configuration codec string for VP9 must be a valid VP9 codec string as'
924
- + ' specified in Section "Codecs Parameter String" of https://www.webmproject.org/vp9/mp4/.',
925
- );
926
- }
927
- } else if (metadata.decoderConfig.codec.startsWith('av01')) {
928
- // AV1-specific validation
929
-
930
- if (!AV1_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
931
- throw new TypeError(
932
- 'Video chunk metadata decoder configuration codec string for AV1 must be a valid AV1 codec string as'
933
- + ' specified in Section "Codecs Parameter String" of https://aomediacodec.github.io/av1-isobmff/.',
934
- );
935
- }
936
- }
937
- };
938
-
939
- const VALID_AUDIO_CODEC_STRING_PREFIXES = ['mp4a', 'mp3', 'opus', 'vorbis', 'flac', 'ulaw', 'alaw', 'pcm'];
940
-
941
- export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata | undefined) => {
942
- if (!metadata) {
943
- throw new TypeError('Audio chunk metadata must be provided.');
944
- }
945
- if (typeof metadata !== 'object') {
946
- throw new TypeError('Audio chunk metadata must be an object.');
947
- }
948
- if (!metadata.decoderConfig) {
949
- throw new TypeError('Audio chunk metadata must include a decoder configuration.');
950
- }
951
- if (typeof metadata.decoderConfig !== 'object') {
952
- throw new TypeError('Audio chunk metadata decoder configuration must be an object.');
953
- }
954
- if (typeof metadata.decoderConfig.codec !== 'string') {
955
- throw new TypeError('Audio chunk metadata decoder configuration must specify a codec string.');
956
- }
957
- if (!VALID_AUDIO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig!.codec.startsWith(prefix))) {
958
- throw new TypeError(
959
- 'Audio chunk metadata decoder configuration codec string must be a valid audio codec string as specified in'
960
- + ' the WebCodecs Codec Registry.',
961
- );
962
- }
963
- if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) {
964
- throw new TypeError(
965
- 'Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).',
966
- );
967
- }
968
- if (!Number.isInteger(metadata.decoderConfig.numberOfChannels) || metadata.decoderConfig.numberOfChannels <= 0) {
969
- throw new TypeError(
970
- 'Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).',
971
- );
972
- }
973
- if (metadata.decoderConfig.description !== undefined) {
974
- if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) {
975
- throw new TypeError(
976
- 'Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an'
977
- + ' ArrayBuffer view.',
978
- );
979
- }
980
- }
981
-
982
- if (
983
- metadata.decoderConfig.codec.startsWith('mp4a')
984
- // These three refer to MP3:
985
- && metadata.decoderConfig.codec !== 'mp4a.69'
986
- && metadata.decoderConfig.codec !== 'mp4a.6B'
987
- && metadata.decoderConfig.codec !== 'mp4a.6b'
988
- ) {
989
- // AAC-specific validation
990
-
991
- const validStrings = ['mp4a.40.2', 'mp4a.40.02', 'mp4a.40.5', 'mp4a.40.05', 'mp4a.40.29', 'mp4a.67'];
992
- if (!validStrings.includes(metadata.decoderConfig.codec)) {
993
- throw new TypeError(
994
- 'Audio chunk metadata decoder configuration codec string for AAC must be a valid AAC codec string as'
995
- + ' specified in https://www.w3.org/TR/webcodecs-aac-codec-registration/.',
996
- );
997
- }
998
-
999
- if (!metadata.decoderConfig.description) {
1000
- throw new TypeError(
1001
- 'Audio chunk metadata decoder configuration for AAC must include a description, which is expected to be'
1002
- + ' an AudioSpecificConfig as specified in ISO 14496-3.',
1003
- );
1004
- }
1005
- } else if (metadata.decoderConfig.codec.startsWith('mp3') || metadata.decoderConfig.codec.startsWith('mp4a')) {
1006
- // MP3-specific validation
1007
-
1008
- if (
1009
- metadata.decoderConfig.codec !== 'mp3'
1010
- && metadata.decoderConfig.codec !== 'mp4a.69'
1011
- && metadata.decoderConfig.codec !== 'mp4a.6B'
1012
- && metadata.decoderConfig.codec !== 'mp4a.6b'
1013
- ) {
1014
- throw new TypeError(
1015
- 'Audio chunk metadata decoder configuration codec string for MP3 must be "mp3", "mp4a.69" or'
1016
- + ' "mp4a.6B".',
1017
- );
1018
- }
1019
- } else if (metadata.decoderConfig.codec.startsWith('opus')) {
1020
- // Opus-specific validation
1021
-
1022
- if (metadata.decoderConfig.codec !== 'opus') {
1023
- throw new TypeError('Audio chunk metadata decoder configuration codec string for Opus must be "opus".');
1024
- }
1025
-
1026
- if (metadata.decoderConfig.description && metadata.decoderConfig.description.byteLength < 18) {
1027
- // Description is optional for Opus per-spec, so we shouldn't enforce it
1028
- throw new TypeError(
1029
- 'Audio chunk metadata decoder configuration description, when specified, is expected to be an'
1030
- + ' Identification Header as specified in Section 5.1 of RFC 7845.',
1031
- );
1032
- }
1033
- } else if (metadata.decoderConfig.codec.startsWith('vorbis')) {
1034
- // Vorbis-specific validation
1035
-
1036
- if (metadata.decoderConfig.codec !== 'vorbis') {
1037
- throw new TypeError('Audio chunk metadata decoder configuration codec string for Vorbis must be "vorbis".');
1038
- }
1039
-
1040
- if (!metadata.decoderConfig.description) {
1041
- throw new TypeError(
1042
- 'Audio chunk metadata decoder configuration for Vorbis must include a description, which is expected to'
1043
- + ' adhere to the format described in https://www.w3.org/TR/webcodecs-vorbis-codec-registration/.',
1044
- );
1045
- }
1046
- } else if (metadata.decoderConfig.codec.startsWith('flac')) {
1047
- // FLAC-specific validation
1048
-
1049
- if (metadata.decoderConfig.codec !== 'flac') {
1050
- throw new TypeError('Audio chunk metadata decoder configuration codec string for FLAC must be "flac".');
1051
- }
1052
-
1053
- const minDescriptionSize = 4 + 4 + 34; // 'fLaC' + metadata block header + STREAMINFO block
1054
- if (!metadata.decoderConfig.description || metadata.decoderConfig.description.byteLength < minDescriptionSize) {
1055
- throw new TypeError(
1056
- 'Audio chunk metadata decoder configuration for FLAC must include a description, which is expected to'
1057
- + ' adhere to the format described in https://www.w3.org/TR/webcodecs-flac-codec-registration/.',
1058
- );
1059
- }
1060
- } else if (
1061
- metadata.decoderConfig.codec.startsWith('pcm')
1062
- || metadata.decoderConfig.codec.startsWith('ulaw')
1063
- || metadata.decoderConfig.codec.startsWith('alaw')
1064
- ) {
1065
- // PCM-specific validation
1066
-
1067
- if (!(PCM_AUDIO_CODECS as readonly string[]).includes(metadata.decoderConfig.codec)) {
1068
- throw new TypeError(
1069
- 'Audio chunk metadata decoder configuration codec string for PCM must be one of the supported PCM'
1070
- + ` codecs (${PCM_AUDIO_CODECS.join(', ')}).`,
1071
- );
1072
- }
1073
- }
1074
- };
1075
-
1076
- export const validateSubtitleMetadata = (metadata: SubtitleMetadata | undefined) => {
1077
- if (!metadata) {
1078
- throw new TypeError('Subtitle metadata must be provided.');
1079
- }
1080
- if (typeof metadata !== 'object') {
1081
- throw new TypeError('Subtitle metadata must be an object.');
1082
- }
1083
- if (!metadata.config) {
1084
- throw new TypeError('Subtitle metadata must include a config object.');
1085
- }
1086
- if (typeof metadata.config !== 'object') {
1087
- throw new TypeError('Subtitle metadata config must be an object.');
1088
- }
1089
- if (typeof metadata.config.description !== 'string') {
1090
- throw new TypeError('Subtitle metadata config description must be a string.');
1091
- }
1092
- };