@kenzuya/mediabunny 1.26.0 → 1.28.6

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 (238) hide show
  1. package/README.md +1 -1
  2. package/dist/bundles/{mediabunny.mjs → mediabunny.js} +21963 -21390
  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/ogg/ogg-demuxer.d.ts +7 -7
  83. package/dist/modules/src/ogg/ogg-demuxer.d.ts.map +1 -1
  84. package/dist/modules/src/ogg/ogg-misc.d.ts +1 -1
  85. package/dist/modules/src/ogg/ogg-misc.d.ts.map +1 -1
  86. package/dist/modules/src/ogg/ogg-muxer.d.ts +5 -5
  87. package/dist/modules/src/ogg/ogg-muxer.d.ts.map +1 -1
  88. package/dist/modules/src/ogg/ogg-reader.d.ts +1 -1
  89. package/dist/modules/src/ogg/ogg-reader.d.ts.map +1 -1
  90. package/dist/modules/src/output-format.d.ts +51 -6
  91. package/dist/modules/src/output-format.d.ts.map +1 -1
  92. package/dist/modules/src/output.d.ts +13 -13
  93. package/dist/modules/src/output.d.ts.map +1 -1
  94. package/dist/modules/src/packet.d.ts +1 -1
  95. package/dist/modules/src/packet.d.ts.map +1 -1
  96. package/dist/modules/src/pcm.d.ts.map +1 -1
  97. package/dist/modules/src/reader.d.ts +2 -2
  98. package/dist/modules/src/reader.d.ts.map +1 -1
  99. package/dist/modules/src/sample.d.ts +57 -15
  100. package/dist/modules/src/sample.d.ts.map +1 -1
  101. package/dist/modules/src/source.d.ts +3 -3
  102. package/dist/modules/src/source.d.ts.map +1 -1
  103. package/dist/modules/src/subtitles.d.ts +1 -1
  104. package/dist/modules/src/subtitles.d.ts.map +1 -1
  105. package/dist/modules/src/target.d.ts +2 -2
  106. package/dist/modules/src/target.d.ts.map +1 -1
  107. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  108. package/dist/modules/src/wave/riff-writer.d.ts +1 -1
  109. package/dist/modules/src/wave/riff-writer.d.ts.map +1 -1
  110. package/dist/modules/src/wave/wave-demuxer.d.ts +6 -6
  111. package/dist/modules/src/wave/wave-demuxer.d.ts.map +1 -1
  112. package/dist/modules/src/wave/wave-muxer.d.ts +4 -4
  113. package/dist/modules/src/wave/wave-muxer.d.ts.map +1 -1
  114. package/dist/modules/src/writer.d.ts +1 -1
  115. package/dist/modules/src/writer.d.ts.map +1 -1
  116. package/dist/packages/eac3/eac3.wasm +0 -0
  117. package/dist/packages/eac3/mediabunny-eac3.js +1058 -0
  118. package/dist/packages/eac3/mediabunny-eac3.min.js +44 -0
  119. package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.js +694 -0
  120. package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.min.js +58 -0
  121. package/dist/packages/mpeg4/mediabunny-mpeg4.js +1198 -0
  122. package/dist/packages/mpeg4/mediabunny-mpeg4.min.js +44 -0
  123. package/dist/packages/mpeg4/xvid.wasm +0 -0
  124. package/package.json +18 -57
  125. package/dist/bundles/mediabunny.cjs +0 -26140
  126. package/dist/bundles/mediabunny.min.cjs +0 -147
  127. package/dist/bundles/mediabunny.min.mjs +0 -146
  128. package/dist/mediabunny.d.ts +0 -3319
  129. package/dist/modules/shared/mp3-misc.js +0 -147
  130. package/dist/modules/src/adts/adts-demuxer.js +0 -239
  131. package/dist/modules/src/adts/adts-muxer.js +0 -80
  132. package/dist/modules/src/adts/adts-reader.js +0 -63
  133. package/dist/modules/src/codec-data.js +0 -1730
  134. package/dist/modules/src/codec.js +0 -869
  135. package/dist/modules/src/conversion.js +0 -1459
  136. package/dist/modules/src/custom-coder.js +0 -117
  137. package/dist/modules/src/demuxer.js +0 -12
  138. package/dist/modules/src/encode.js +0 -442
  139. package/dist/modules/src/flac/flac-demuxer.js +0 -504
  140. package/dist/modules/src/flac/flac-misc.js +0 -135
  141. package/dist/modules/src/flac/flac-muxer.js +0 -222
  142. package/dist/modules/src/id3.js +0 -848
  143. package/dist/modules/src/index.js +0 -28
  144. package/dist/modules/src/input-format.js +0 -480
  145. package/dist/modules/src/input-track.js +0 -372
  146. package/dist/modules/src/input.js +0 -188
  147. package/dist/modules/src/isobmff/isobmff-boxes.js +0 -1480
  148. package/dist/modules/src/isobmff/isobmff-demuxer.js +0 -2618
  149. package/dist/modules/src/isobmff/isobmff-misc.js +0 -20
  150. package/dist/modules/src/isobmff/isobmff-muxer.js +0 -966
  151. package/dist/modules/src/isobmff/isobmff-reader.js +0 -72
  152. package/dist/modules/src/matroska/ebml.js +0 -653
  153. package/dist/modules/src/matroska/matroska-demuxer.js +0 -2133
  154. package/dist/modules/src/matroska/matroska-misc.js +0 -20
  155. package/dist/modules/src/matroska/matroska-muxer.js +0 -1017
  156. package/dist/modules/src/media-sink.js +0 -1736
  157. package/dist/modules/src/media-source.js +0 -1825
  158. package/dist/modules/src/metadata.js +0 -193
  159. package/dist/modules/src/misc.js +0 -623
  160. package/dist/modules/src/mp3/mp3-demuxer.js +0 -285
  161. package/dist/modules/src/mp3/mp3-muxer.js +0 -123
  162. package/dist/modules/src/mp3/mp3-reader.js +0 -26
  163. package/dist/modules/src/mp3/mp3-writer.js +0 -78
  164. package/dist/modules/src/muxer.js +0 -50
  165. package/dist/modules/src/node.d.ts +0 -9
  166. package/dist/modules/src/node.d.ts.map +0 -1
  167. package/dist/modules/src/node.js +0 -9
  168. package/dist/modules/src/ogg/ogg-demuxer.js +0 -763
  169. package/dist/modules/src/ogg/ogg-misc.js +0 -78
  170. package/dist/modules/src/ogg/ogg-muxer.js +0 -353
  171. package/dist/modules/src/ogg/ogg-reader.js +0 -65
  172. package/dist/modules/src/output-format.js +0 -527
  173. package/dist/modules/src/output.js +0 -300
  174. package/dist/modules/src/packet.js +0 -182
  175. package/dist/modules/src/pcm.js +0 -85
  176. package/dist/modules/src/reader.js +0 -236
  177. package/dist/modules/src/sample.js +0 -1056
  178. package/dist/modules/src/source.js +0 -1182
  179. package/dist/modules/src/subtitles.js +0 -575
  180. package/dist/modules/src/target.js +0 -140
  181. package/dist/modules/src/wave/riff-writer.js +0 -30
  182. package/dist/modules/src/wave/wave-demuxer.js +0 -447
  183. package/dist/modules/src/wave/wave-muxer.js +0 -318
  184. package/dist/modules/src/writer.js +0 -370
  185. package/src/adts/adts-demuxer.ts +0 -331
  186. package/src/adts/adts-muxer.ts +0 -111
  187. package/src/adts/adts-reader.ts +0 -85
  188. package/src/codec-data.ts +0 -2078
  189. package/src/codec.ts +0 -1092
  190. package/src/conversion.ts +0 -2112
  191. package/src/custom-coder.ts +0 -197
  192. package/src/demuxer.ts +0 -24
  193. package/src/encode.ts +0 -739
  194. package/src/flac/flac-demuxer.ts +0 -730
  195. package/src/flac/flac-misc.ts +0 -164
  196. package/src/flac/flac-muxer.ts +0 -320
  197. package/src/id3.ts +0 -925
  198. package/src/index.ts +0 -221
  199. package/src/input-format.ts +0 -541
  200. package/src/input-track.ts +0 -529
  201. package/src/input.ts +0 -235
  202. package/src/isobmff/isobmff-boxes.ts +0 -1719
  203. package/src/isobmff/isobmff-demuxer.ts +0 -3190
  204. package/src/isobmff/isobmff-misc.ts +0 -29
  205. package/src/isobmff/isobmff-muxer.ts +0 -1348
  206. package/src/isobmff/isobmff-reader.ts +0 -91
  207. package/src/matroska/ebml.ts +0 -730
  208. package/src/matroska/matroska-demuxer.ts +0 -2481
  209. package/src/matroska/matroska-misc.ts +0 -29
  210. package/src/matroska/matroska-muxer.ts +0 -1276
  211. package/src/media-sink.ts +0 -2179
  212. package/src/media-source.ts +0 -2243
  213. package/src/metadata.ts +0 -320
  214. package/src/misc.ts +0 -798
  215. package/src/mp3/mp3-demuxer.ts +0 -383
  216. package/src/mp3/mp3-muxer.ts +0 -166
  217. package/src/mp3/mp3-reader.ts +0 -34
  218. package/src/mp3/mp3-writer.ts +0 -120
  219. package/src/muxer.ts +0 -88
  220. package/src/node.ts +0 -11
  221. package/src/ogg/ogg-demuxer.ts +0 -1053
  222. package/src/ogg/ogg-misc.ts +0 -116
  223. package/src/ogg/ogg-muxer.ts +0 -497
  224. package/src/ogg/ogg-reader.ts +0 -93
  225. package/src/output-format.ts +0 -945
  226. package/src/output.ts +0 -488
  227. package/src/packet.ts +0 -263
  228. package/src/pcm.ts +0 -112
  229. package/src/reader.ts +0 -323
  230. package/src/sample.ts +0 -1461
  231. package/src/source.ts +0 -1688
  232. package/src/subtitles.ts +0 -711
  233. package/src/target.ts +0 -204
  234. package/src/tsconfig.json +0 -16
  235. package/src/wave/riff-writer.ts +0 -36
  236. package/src/wave/wave-demuxer.ts +0 -529
  237. package/src/wave/wave-muxer.ts +0 -371
  238. package/src/writer.ts +0 -490
@@ -1,116 +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 { parseOpusTocByte } from '../codec-data';
10
- import { assert, ilog, toDataView } from '../misc';
11
-
12
- export const OGGS = 0x5367674f; // 'OggS'
13
-
14
- const OGG_CRC_POLYNOMIAL = 0x04c11db7;
15
- const OGG_CRC_TABLE = new Uint32Array(256);
16
- for (let n = 0; n < 256; n++) {
17
- let crc = n << 24;
18
-
19
- for (let k = 0; k < 8; k++) {
20
- crc = (crc & 0x80000000)
21
- ? ((crc << 1) ^ OGG_CRC_POLYNOMIAL)
22
- : (crc << 1);
23
- }
24
-
25
- OGG_CRC_TABLE[n] = (crc >>> 0) & 0xffffffff;
26
- }
27
-
28
- export const computeOggPageCrc = (bytes: Uint8Array) => {
29
- const view = toDataView(bytes);
30
-
31
- const originalChecksum = view.getUint32(22, true);
32
- view.setUint32(22, 0, true); // Zero out checksum field
33
-
34
- let crc = 0;
35
- for (let i = 0; i < bytes.length; i++) {
36
- const byte = bytes[i]!;
37
- crc = ((crc << 8) ^ OGG_CRC_TABLE[(crc >>> 24) ^ byte]!) >>> 0;
38
- }
39
-
40
- view.setUint32(22, originalChecksum, true); // Restore checksum field
41
-
42
- return crc;
43
- };
44
-
45
- export type OggCodecInfo = {
46
- codec: 'vorbis' | 'opus' | null;
47
- vorbisInfo: {
48
- blocksizes: number[];
49
- modeBlockflags: number[];
50
- } | null;
51
- opusInfo: {
52
- preSkip: number;
53
- } | null;
54
- };
55
-
56
- export const extractSampleMetadata = (
57
- data: Uint8Array,
58
- codecInfo: OggCodecInfo,
59
- vorbisLastBlocksize: number | null,
60
- ) => {
61
- let durationInSamples = 0;
62
- let currentBlocksize: number | null = null;
63
-
64
- if (data.length > 0) {
65
- // To know sample duration, we'll need to peak inside the packet
66
- if (codecInfo.codec === 'vorbis') {
67
- assert(codecInfo.vorbisInfo);
68
-
69
- const vorbisModeCount = codecInfo.vorbisInfo.modeBlockflags.length;
70
- const bitCount = ilog(vorbisModeCount - 1);
71
- const modeMask = ((1 << bitCount) - 1) << 1;
72
- const modeNumber = (data[0]! & modeMask) >> 1;
73
-
74
- if (modeNumber >= codecInfo.vorbisInfo.modeBlockflags.length) {
75
- throw new Error('Invalid mode number.');
76
- }
77
-
78
- // In Vorbis, packet duration also depends on the blocksize of the previous packet
79
- let prevBlocksize = vorbisLastBlocksize;
80
-
81
- const blockflag = codecInfo.vorbisInfo.modeBlockflags[modeNumber]!;
82
- currentBlocksize = codecInfo.vorbisInfo.blocksizes[blockflag]!;
83
-
84
- if (blockflag === 1) {
85
- const prevMask = (modeMask | 0x1) + 1;
86
- const flag = data[0]! & prevMask ? 1 : 0;
87
- prevBlocksize = codecInfo.vorbisInfo.blocksizes[flag]!;
88
- }
89
-
90
- durationInSamples = prevBlocksize !== null
91
- ? (prevBlocksize + currentBlocksize) >> 2
92
- : 0; // The first sample outputs no audio data and therefore has a duration of 0
93
- } else if (codecInfo.codec === 'opus') {
94
- const toc = parseOpusTocByte(data);
95
- durationInSamples = toc.durationInSamples;
96
- }
97
- }
98
-
99
- return {
100
- durationInSamples,
101
- vorbisBlockSize: currentBlocksize,
102
- };
103
- };
104
-
105
- export const buildOggMimeType = (info: {
106
- codecStrings: string[];
107
- }) => {
108
- let string = 'audio/ogg';
109
-
110
- if (info.codecStrings) {
111
- const uniqueCodecMimeTypes = [...new Set(info.codecStrings)];
112
- string += `; codecs="${uniqueCodecMimeTypes.join(', ')}"`;
113
- }
114
-
115
- return string;
116
- };
@@ -1,497 +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 { OPUS_SAMPLE_RATE, validateAudioChunkMetadata } from '../codec';
10
- import { createVorbisComments, parseModesFromVorbisSetupPacket, parseOpusIdentificationHeader } from '../codec-data';
11
- import {
12
- assert,
13
- promiseWithResolvers,
14
- setInt64,
15
- toDataView,
16
- toUint8Array,
17
- } from '../misc';
18
- import { Muxer } from '../muxer';
19
- import { Output,
20
- OutputAudioTrack } from '../output';
21
- import { OggOutputFormat } from '../output-format';
22
- import { EncodedPacket } from '../packet';
23
- import { Writer } from '../writer';
24
- import {
25
- buildOggMimeType,
26
- computeOggPageCrc,
27
- extractSampleMetadata,
28
- OggCodecInfo,
29
- OGGS,
30
- } from './ogg-misc';
31
- import { MAX_PAGE_SIZE } from './ogg-reader';
32
-
33
- const PAGE_SIZE_TARGET = 8192;
34
-
35
- type OggTrackData = {
36
- track: OutputAudioTrack;
37
- serialNumber: number;
38
- internalSampleRate: number;
39
- codecInfo: OggCodecInfo;
40
- vorbisLastBlocksize: number | null;
41
- packetQueue: Packet[];
42
- currentTimestampInSamples: number;
43
- pagesWritten: number;
44
-
45
- currentGranulePosition: number;
46
- currentLacingValues: number[];
47
- currentPageData: Uint8Array[];
48
- currentPageSize: number;
49
- currentPageStartsWithFreshPacket: boolean;
50
- };
51
-
52
- type Packet = {
53
- data: Uint8Array;
54
- endGranulePosition: number;
55
- timestamp: number;
56
- forcePageFlush: boolean;
57
- };
58
-
59
- export class OggMuxer extends Muxer {
60
- private format: OggOutputFormat;
61
- private writer: Writer;
62
-
63
- private trackDatas: OggTrackData[] = [];
64
- private bosPagesWritten = false;
65
- private allTracksKnown = promiseWithResolvers();
66
-
67
- private pageBytes = new Uint8Array(MAX_PAGE_SIZE);
68
- private pageView = new DataView(this.pageBytes.buffer);
69
-
70
- constructor(output: Output, format: OggOutputFormat) {
71
- super(output);
72
-
73
- this.format = format;
74
- this.writer = output._writer;
75
-
76
- this.writer.ensureMonotonicity = true; // Ogg is always monotonically written!
77
- }
78
-
79
- async start() {
80
- // Nothin'
81
- }
82
-
83
- async getMimeType() {
84
- await this.allTracksKnown.promise;
85
-
86
- return buildOggMimeType({
87
- codecStrings: this.trackDatas.map(x => x.codecInfo.codec!),
88
- });
89
- }
90
-
91
- addEncodedVideoPacket(): never {
92
- throw new Error('Video tracks are not supported.');
93
- }
94
-
95
- private getTrackData(track: OutputAudioTrack, meta?: EncodedAudioChunkMetadata) {
96
- const existingTrackData = this.trackDatas.find(td => td.track === track);
97
- if (existingTrackData) {
98
- return existingTrackData;
99
- }
100
-
101
- // Give the track a unique random serial number
102
- let serialNumber: number;
103
- do {
104
- serialNumber = Math.floor(2 ** 32 * Math.random());
105
- } while (this.trackDatas.some(td => td.serialNumber === serialNumber));
106
-
107
- assert(track.source._codec === 'vorbis' || track.source._codec === 'opus');
108
-
109
- validateAudioChunkMetadata(meta);
110
-
111
- assert(meta);
112
- assert(meta.decoderConfig);
113
-
114
- const newTrackData: OggTrackData = {
115
- track,
116
- serialNumber,
117
- internalSampleRate: track.source._codec === 'opus'
118
- ? OPUS_SAMPLE_RATE
119
- : meta.decoderConfig.sampleRate,
120
- codecInfo: {
121
- codec: track.source._codec,
122
- vorbisInfo: null,
123
- opusInfo: null,
124
- },
125
- vorbisLastBlocksize: null,
126
- packetQueue: [],
127
- currentTimestampInSamples: 0,
128
- pagesWritten: 0,
129
-
130
- currentGranulePosition: 0,
131
- currentLacingValues: [],
132
- currentPageData: [],
133
- currentPageSize: 27,
134
- currentPageStartsWithFreshPacket: true,
135
- };
136
-
137
- this.queueHeaderPackets(newTrackData, meta);
138
-
139
- this.trackDatas.push(newTrackData);
140
-
141
- if (this.allTracksAreKnown()) {
142
- this.allTracksKnown.resolve();
143
- }
144
-
145
- return newTrackData;
146
- }
147
-
148
- private queueHeaderPackets(trackData: OggTrackData, meta: EncodedAudioChunkMetadata) {
149
- assert(meta.decoderConfig);
150
-
151
- if (trackData.track.source._codec === 'vorbis') {
152
- assert(meta.decoderConfig.description);
153
-
154
- const bytes = toUint8Array(meta.decoderConfig.description);
155
- if (bytes[0] !== 2) {
156
- throw new TypeError('First byte of Vorbis decoder description must be 2.');
157
- }
158
-
159
- let pos = 1;
160
- const readPacketLength = () => {
161
- let length = 0;
162
-
163
- while (true) {
164
- const value = bytes[pos++];
165
- if (value === undefined) {
166
- throw new TypeError('Vorbis decoder description is too short.');
167
- }
168
-
169
- length += value;
170
-
171
- if (value < 255) {
172
- return length;
173
- }
174
- }
175
- };
176
-
177
- const identificationHeaderLength = readPacketLength();
178
- const commentHeaderLength = readPacketLength();
179
- const setupHeaderLength = bytes.length - pos; // Setup header fills the remaining bytes
180
-
181
- if (setupHeaderLength <= 0) {
182
- throw new TypeError('Vorbis decoder description is too short.');
183
- }
184
-
185
- const identificationHeader = bytes.subarray(pos, pos += identificationHeaderLength);
186
- pos += commentHeaderLength; // Skip the comment header, we'll build our own
187
- const setupHeader = bytes.subarray(pos);
188
-
189
- const commentHeaderHeader = new Uint8Array(7);
190
- commentHeaderHeader[0] = 3; // Packet type
191
- commentHeaderHeader[1] = 0x76; // 'v'
192
- commentHeaderHeader[2] = 0x6f; // 'o'
193
- commentHeaderHeader[3] = 0x72; // 'r'
194
- commentHeaderHeader[4] = 0x62; // 'b'
195
- commentHeaderHeader[5] = 0x69; // 'i'
196
- commentHeaderHeader[6] = 0x73; // 's'
197
-
198
- const commentHeader = createVorbisComments(commentHeaderHeader, this.output._metadataTags, true);
199
-
200
- trackData.packetQueue.push({
201
- data: identificationHeader,
202
- endGranulePosition: 0,
203
- timestamp: 0,
204
- forcePageFlush: true,
205
- }, {
206
- data: commentHeader,
207
- endGranulePosition: 0,
208
- timestamp: 0,
209
- forcePageFlush: false,
210
- }, {
211
- data: setupHeader,
212
- endGranulePosition: 0,
213
- timestamp: 0,
214
- forcePageFlush: true, // The last header packet must flush the page
215
- });
216
-
217
- const view = toDataView(identificationHeader);
218
- const blockSizeByte = view.getUint8(28);
219
-
220
- trackData.codecInfo.vorbisInfo = {
221
- blocksizes: [
222
- 1 << (blockSizeByte & 0xf),
223
- 1 << (blockSizeByte >> 4),
224
- ],
225
- modeBlockflags: parseModesFromVorbisSetupPacket(setupHeader).modeBlockflags,
226
- };
227
- } else if (trackData.track.source._codec === 'opus') {
228
- if (!meta.decoderConfig.description) {
229
- throw new TypeError('For Ogg, Opus decoder description is required.');
230
- }
231
-
232
- const identificationHeader = toUint8Array(meta.decoderConfig.description);
233
-
234
- const commentHeaderHeader = new Uint8Array(8);
235
- const commentHeaderHeaderView = toDataView(commentHeaderHeader);
236
- commentHeaderHeaderView.setUint32(0, 0x4f707573, false); // 'Opus'
237
- commentHeaderHeaderView.setUint32(4, 0x54616773, false); // 'Tags'
238
- const commentHeader = createVorbisComments(commentHeaderHeader, this.output._metadataTags, true);
239
-
240
- trackData.packetQueue.push({
241
- data: identificationHeader,
242
- endGranulePosition: 0,
243
- timestamp: 0,
244
- forcePageFlush: true,
245
- }, {
246
- data: commentHeader,
247
- endGranulePosition: 0,
248
- timestamp: 0,
249
- forcePageFlush: true, // The last header packet must flush the page
250
- });
251
-
252
- trackData.codecInfo.opusInfo = {
253
- preSkip: parseOpusIdentificationHeader(identificationHeader).preSkip,
254
- };
255
- }
256
- }
257
-
258
- async addEncodedAudioPacket(track: OutputAudioTrack, packet: EncodedPacket, meta?: EncodedAudioChunkMetadata) {
259
- const release = await this.mutex.acquire();
260
-
261
- try {
262
- const trackData = this.getTrackData(track, meta);
263
-
264
- this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, packet.type === 'key');
265
-
266
- const currentTimestampInSamples = trackData.currentTimestampInSamples;
267
-
268
- const { durationInSamples, vorbisBlockSize } = extractSampleMetadata(
269
- packet.data,
270
- trackData.codecInfo,
271
- trackData.vorbisLastBlocksize,
272
- );
273
- trackData.currentTimestampInSamples += durationInSamples;
274
- trackData.vorbisLastBlocksize = vorbisBlockSize;
275
-
276
- trackData.packetQueue.push({
277
- data: packet.data,
278
- endGranulePosition: trackData.currentTimestampInSamples,
279
- timestamp: currentTimestampInSamples / trackData.internalSampleRate,
280
- forcePageFlush: false,
281
- });
282
-
283
- await this.interleavePages();
284
- } finally {
285
- release();
286
- }
287
- }
288
-
289
- addSubtitleCue(): never {
290
- throw new Error('Subtitle tracks are not supported.');
291
- }
292
-
293
- allTracksAreKnown() {
294
- for (const track of this.output._tracks) {
295
- if (!track.source._closed && !this.trackDatas.some(x => x.track === track)) {
296
- return false; // We haven't seen a sample from this open track yet
297
- }
298
- }
299
-
300
- return true;
301
- }
302
-
303
- async interleavePages(isFinalCall = false) {
304
- if (!this.bosPagesWritten) {
305
- if (!this.allTracksAreKnown()) {
306
- return; // We can't interleave yet as we don't yet know how many tracks we'll truly have
307
- }
308
-
309
- // Write the header page for all bitstreams
310
- for (const trackData of this.trackDatas) {
311
- while (trackData.packetQueue.length > 0) {
312
- const packet = trackData.packetQueue.shift()!;
313
- this.writePacket(trackData, packet, false);
314
-
315
- if (packet.forcePageFlush) {
316
- // We say the header page ends once the first packet is encountered that forces a page flush
317
- break;
318
- }
319
- }
320
- }
321
-
322
- this.bosPagesWritten = true;
323
- }
324
-
325
- outer:
326
- while (true) {
327
- let trackWithMinTimestamp: OggTrackData | null = null;
328
- let minTimestamp = Infinity;
329
-
330
- for (const trackData of this.trackDatas) {
331
- if (
332
- !isFinalCall
333
- && trackData.packetQueue.length <= 1 // Limit is 1, not 0, for correct EOS flag logic
334
- && !trackData.track.source._closed
335
- ) {
336
- break outer;
337
- }
338
-
339
- if (
340
- trackData.packetQueue.length > 0
341
- && trackData.packetQueue[0]!.timestamp < minTimestamp
342
- ) {
343
- trackWithMinTimestamp = trackData;
344
- minTimestamp = trackData.packetQueue[0]!.timestamp;
345
- }
346
- }
347
-
348
- if (!trackWithMinTimestamp) {
349
- break;
350
- }
351
-
352
- const packet = trackWithMinTimestamp.packetQueue.shift()!;
353
- const isFinalPacket = trackWithMinTimestamp.packetQueue.length === 0;
354
-
355
- this.writePacket(trackWithMinTimestamp, packet, isFinalPacket);
356
- }
357
-
358
- if (!isFinalCall) {
359
- await this.writer.flush();
360
- }
361
- }
362
-
363
- writePacket(trackData: OggTrackData, packet: Packet, isFinalPacket: boolean) {
364
- let remainingLength = packet.data.length;
365
- let dataStartOffset = 0;
366
- let dataOffset = 0;
367
-
368
- while (true) {
369
- if (trackData.currentLacingValues.length === 0 && dataStartOffset > 0) {
370
- // This is a packet spanning multiple pages
371
- trackData.currentPageStartsWithFreshPacket = false;
372
- }
373
-
374
- const segmentSize = Math.min(255, remainingLength);
375
- trackData.currentLacingValues.push(segmentSize);
376
- trackData.currentPageSize++;
377
- dataOffset += segmentSize;
378
-
379
- const segmentIsLastOfPacket = remainingLength < 255;
380
-
381
- if (trackData.currentLacingValues.length === 255) {
382
- // The page is full, we need to add part of the packet data and then flush the page
383
- const slice = packet.data.subarray(dataStartOffset, dataOffset);
384
- dataStartOffset = dataOffset;
385
- trackData.currentPageData.push(slice);
386
- trackData.currentPageSize += slice.length;
387
-
388
- this.writePage(trackData, isFinalPacket && segmentIsLastOfPacket);
389
-
390
- if (segmentIsLastOfPacket) {
391
- return;
392
- }
393
- }
394
-
395
- if (segmentIsLastOfPacket) {
396
- break;
397
- }
398
- remainingLength -= 255;
399
- }
400
-
401
- const slice = packet.data.subarray(dataStartOffset);
402
- trackData.currentPageData.push(slice);
403
- trackData.currentPageSize += slice.length;
404
- trackData.currentGranulePosition = packet.endGranulePosition;
405
-
406
- if (trackData.currentPageSize >= PAGE_SIZE_TARGET || packet.forcePageFlush) {
407
- this.writePage(trackData, isFinalPacket);
408
- }
409
- }
410
-
411
- writePage(trackData: OggTrackData, isEos: boolean) {
412
- this.pageView.setUint32(0, OGGS, true); // Capture pattern
413
- this.pageView.setUint8(4, 0); // Version
414
-
415
- let headerType = 0;
416
- if (!trackData.currentPageStartsWithFreshPacket) {
417
- headerType |= 1;
418
- }
419
- if (trackData.pagesWritten === 0) {
420
- headerType |= 2; // Beginning of stream
421
- }
422
- if (isEos) {
423
- headerType |= 4; // End of stream
424
- }
425
- this.pageView.setUint8(5, headerType); // Header type
426
-
427
- const granulePosition = trackData.currentLacingValues.every(x => x === 255)
428
- ? -1 // No packets end on this page
429
- : trackData.currentGranulePosition;
430
- setInt64(this.pageView, 6, granulePosition, true); // Granule position
431
-
432
- this.pageView.setUint32(14, trackData.serialNumber, true); // Serial number
433
- this.pageView.setUint32(18, trackData.pagesWritten, true); // Page sequence number
434
- this.pageView.setUint32(22, 0, true); // Checksum placeholder
435
-
436
- this.pageView.setUint8(26, trackData.currentLacingValues.length); // Number of page segments
437
- this.pageBytes.set(trackData.currentLacingValues, 27);
438
-
439
- let pos = 27 + trackData.currentLacingValues.length;
440
- for (const data of trackData.currentPageData) {
441
- this.pageBytes.set(data, pos);
442
- pos += data.length;
443
- }
444
-
445
- const slice = this.pageBytes.subarray(0, pos);
446
-
447
- const crc = computeOggPageCrc(slice);
448
- this.pageView.setUint32(22, crc, true); // Checksum
449
-
450
- trackData.pagesWritten++;
451
- trackData.currentLacingValues.length = 0;
452
- trackData.currentPageData.length = 0;
453
- trackData.currentPageSize = 27;
454
- trackData.currentPageStartsWithFreshPacket = true;
455
-
456
- if (this.format._options.onPage) {
457
- this.writer.startTrackingWrites();
458
- }
459
-
460
- this.writer.write(slice);
461
-
462
- if (this.format._options.onPage) {
463
- const { data, start } = this.writer.stopTrackingWrites();
464
- this.format._options.onPage(data, start, trackData.track.source);
465
- }
466
- }
467
-
468
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
469
- override async onTrackClose() {
470
- const release = await this.mutex.acquire();
471
-
472
- if (this.allTracksAreKnown()) {
473
- this.allTracksKnown.resolve();
474
- }
475
-
476
- // Since a track is now closed, we may be able to write out chunks that were previously waiting
477
- await this.interleavePages();
478
-
479
- release();
480
- }
481
-
482
- async finalize() {
483
- const release = await this.mutex.acquire();
484
-
485
- this.allTracksKnown.resolve();
486
-
487
- await this.interleavePages(true);
488
-
489
- for (const trackData of this.trackDatas) {
490
- if (trackData.currentLacingValues.length > 0) {
491
- this.writePage(trackData, true);
492
- }
493
- }
494
-
495
- release();
496
- }
497
- }
@@ -1,93 +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 { FileSlice, readI64Le, readU32Le, readU8 } from '../reader';
10
- import { OGGS } from './ogg-misc';
11
-
12
- export const MIN_PAGE_HEADER_SIZE = 27;
13
- export const MAX_PAGE_HEADER_SIZE = 27 + 255;
14
- export const MAX_PAGE_SIZE = /* #__PURE__ */ MAX_PAGE_HEADER_SIZE + 255 * 255;
15
-
16
- export type Page = {
17
- headerStartPos: number;
18
- totalSize: number;
19
- dataStartPos: number;
20
- dataSize: number;
21
- headerType: number;
22
- granulePosition: number;
23
- serialNumber: number;
24
- sequenceNumber: number;
25
- checksum: number;
26
- lacingValues: Uint8Array;
27
- };
28
-
29
- export const readPageHeader = (slice: FileSlice): Page | null => {
30
- const startPos = slice.filePos;
31
-
32
- const capturePattern = readU32Le(slice);
33
- if (capturePattern !== OGGS) {
34
- return null;
35
- }
36
-
37
- slice.skip(1); // Version
38
- const headerType = readU8(slice);
39
- const granulePosition = readI64Le(slice);
40
- const serialNumber = readU32Le(slice);
41
- const sequenceNumber = readU32Le(slice);
42
- const checksum = readU32Le(slice);
43
-
44
- const numberPageSegments = readU8(slice);
45
- const lacingValues = new Uint8Array(numberPageSegments);
46
-
47
- for (let i = 0; i < numberPageSegments; i++) {
48
- lacingValues[i] = readU8(slice);
49
- }
50
-
51
- const headerSize = 27 + numberPageSegments;
52
- const dataSize = lacingValues.reduce((a, b) => a + b, 0);
53
- const totalSize = headerSize + dataSize;
54
-
55
- return {
56
- headerStartPos: startPos,
57
- totalSize,
58
- dataStartPos: startPos + headerSize,
59
- dataSize,
60
- headerType,
61
- granulePosition,
62
- serialNumber,
63
- sequenceNumber,
64
- checksum,
65
- lacingValues,
66
- };
67
- };
68
-
69
- export const findNextPageHeader = (slice: FileSlice, until: number) => {
70
- while (slice.filePos < until - (4 - 1)) { // Size of word minus 1
71
- const word = readU32Le(slice);
72
- const firstByte = word & 0xff;
73
- const secondByte = (word >>> 8) & 0xff;
74
- const thirdByte = (word >>> 16) & 0xff;
75
- const fourthByte = (word >>> 24) & 0xff;
76
-
77
- const O = 0x4f; // 'O'
78
- if (firstByte !== O && secondByte !== O && thirdByte !== O && fourthByte !== O) {
79
- continue;
80
- }
81
-
82
- slice.skip(-4);
83
-
84
- if (word === OGGS) {
85
- // We have found the capture pattern
86
- return true;
87
- }
88
-
89
- slice.skip(1);
90
- }
91
-
92
- return false;
93
- };