mediabunny 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +4 -4
  2. package/dist/mediabunny.d.ts +4 -2
  3. package/dist/modules/codec-data.d.ts +95 -0
  4. package/dist/modules/codec-data.d.ts.map +1 -0
  5. package/dist/modules/codec-data.js +1103 -0
  6. package/dist/modules/codec.d.ts +250 -0
  7. package/dist/modules/codec.d.ts.map +1 -0
  8. package/dist/modules/codec.js +1100 -0
  9. package/dist/modules/conversion.d.ts +158 -0
  10. package/dist/modules/conversion.d.ts.map +1 -0
  11. package/dist/modules/conversion.js +866 -0
  12. package/dist/modules/custom-coder.d.ts +119 -0
  13. package/dist/modules/custom-coder.d.ts.map +1 -0
  14. package/dist/modules/custom-coder.js +91 -0
  15. package/dist/modules/demuxer.d.ts +17 -0
  16. package/dist/modules/demuxer.d.ts.map +1 -0
  17. package/dist/modules/demuxer.js +12 -0
  18. package/dist/modules/index.d.ts +25 -0
  19. package/dist/modules/index.d.ts.map +1 -0
  20. package/dist/modules/index.js +24 -0
  21. package/dist/modules/input-format.d.ts +121 -0
  22. package/dist/modules/input-format.d.ts.map +1 -0
  23. package/dist/modules/input-format.js +333 -0
  24. package/dist/modules/input-track.d.ts +138 -0
  25. package/dist/modules/input-track.d.ts.map +1 -0
  26. package/dist/modules/input-track.js +230 -0
  27. package/dist/modules/input.d.ts +55 -0
  28. package/dist/modules/input.d.ts.map +1 -0
  29. package/dist/modules/input.js +104 -0
  30. package/dist/modules/isobmff/isobmff-boxes.d.ts +202 -0
  31. package/dist/modules/isobmff/isobmff-boxes.d.ts.map +1 -0
  32. package/dist/modules/isobmff/isobmff-boxes.js +1111 -0
  33. package/dist/modules/isobmff/isobmff-demuxer.d.ts +163 -0
  34. package/dist/modules/isobmff/isobmff-demuxer.d.ts.map +1 -0
  35. package/dist/modules/isobmff/isobmff-demuxer.js +2080 -0
  36. package/dist/modules/isobmff/isobmff-misc.d.ts +14 -0
  37. package/dist/modules/isobmff/isobmff-misc.d.ts.map +1 -0
  38. package/dist/modules/isobmff/isobmff-misc.js +20 -0
  39. package/dist/modules/isobmff/isobmff-muxer.d.ts +142 -0
  40. package/dist/modules/isobmff/isobmff-muxer.d.ts.map +1 -0
  41. package/dist/modules/isobmff/isobmff-muxer.js +858 -0
  42. package/dist/modules/isobmff/isobmff-reader.d.ts +36 -0
  43. package/dist/modules/isobmff/isobmff-reader.d.ts.map +1 -0
  44. package/dist/modules/isobmff/isobmff-reader.js +105 -0
  45. package/dist/modules/matroska/ebml.d.ts +156 -0
  46. package/dist/modules/matroska/ebml.d.ts.map +1 -0
  47. package/dist/modules/matroska/ebml.js +529 -0
  48. package/dist/modules/matroska/matroska-demuxer.d.ts +119 -0
  49. package/dist/modules/matroska/matroska-demuxer.d.ts.map +1 -0
  50. package/dist/modules/matroska/matroska-demuxer.js +1330 -0
  51. package/dist/modules/matroska/matroska-misc.d.ts +14 -0
  52. package/dist/modules/matroska/matroska-misc.d.ts.map +1 -0
  53. package/dist/modules/matroska/matroska-misc.js +20 -0
  54. package/dist/modules/matroska/matroska-muxer.d.ts +71 -0
  55. package/dist/modules/matroska/matroska-muxer.d.ts.map +1 -0
  56. package/dist/modules/matroska/matroska-muxer.js +729 -0
  57. package/dist/modules/media-sink.d.ts +272 -0
  58. package/dist/modules/media-sink.d.ts.map +1 -0
  59. package/dist/modules/media-sink.js +1264 -0
  60. package/dist/modules/media-source.d.ts +230 -0
  61. package/dist/modules/media-source.d.ts.map +1 -0
  62. package/dist/modules/media-source.js +1095 -0
  63. package/dist/modules/misc.d.ts +127 -0
  64. package/dist/modules/misc.d.ts.map +1 -0
  65. package/dist/modules/misc.js +462 -0
  66. package/dist/modules/mp3/mp3-demuxer.d.ts +32 -0
  67. package/dist/modules/mp3/mp3-demuxer.d.ts.map +1 -0
  68. package/dist/modules/mp3/mp3-demuxer.js +160 -0
  69. package/dist/modules/mp3/mp3-misc.d.ts +37 -0
  70. package/dist/modules/mp3/mp3-misc.d.ts.map +1 -0
  71. package/dist/modules/mp3/mp3-misc.js +122 -0
  72. package/dist/modules/mp3/mp3-muxer.d.ts +27 -0
  73. package/dist/modules/mp3/mp3-muxer.d.ts.map +1 -0
  74. package/dist/modules/mp3/mp3-muxer.js +112 -0
  75. package/dist/modules/mp3/mp3-reader.d.ts +25 -0
  76. package/dist/modules/mp3/mp3-reader.d.ts.map +1 -0
  77. package/dist/modules/mp3/mp3-reader.js +73 -0
  78. package/dist/modules/mp3/mp3-writer.d.ts +30 -0
  79. package/dist/modules/mp3/mp3-writer.d.ts.map +1 -0
  80. package/dist/modules/mp3/mp3-writer.js +72 -0
  81. package/dist/modules/muxer.d.ts +32 -0
  82. package/dist/modules/muxer.d.ts.map +1 -0
  83. package/dist/modules/muxer.js +50 -0
  84. package/dist/modules/ogg/ogg-demuxer.d.ts +53 -0
  85. package/dist/modules/ogg/ogg-demuxer.d.ts.map +1 -0
  86. package/dist/modules/ogg/ogg-demuxer.js +701 -0
  87. package/dist/modules/ogg/ogg-misc.d.ts +27 -0
  88. package/dist/modules/ogg/ogg-misc.d.ts.map +1 -0
  89. package/dist/modules/ogg/ogg-misc.js +78 -0
  90. package/dist/modules/ogg/ogg-muxer.d.ts +58 -0
  91. package/dist/modules/ogg/ogg-muxer.d.ts.map +1 -0
  92. package/dist/modules/ogg/ogg-muxer.js +345 -0
  93. package/dist/modules/ogg/ogg-reader.d.ts +37 -0
  94. package/dist/modules/ogg/ogg-reader.d.ts.map +1 -0
  95. package/dist/modules/ogg/ogg-reader.js +104 -0
  96. package/dist/modules/output-format.d.ts +285 -0
  97. package/dist/modules/output-format.d.ts.map +1 -0
  98. package/dist/modules/output-format.js +385 -0
  99. package/dist/modules/output.d.ts +134 -0
  100. package/dist/modules/output.d.ts.map +1 -0
  101. package/dist/modules/output.js +269 -0
  102. package/dist/modules/packet.d.ts +86 -0
  103. package/dist/modules/packet.d.ts.map +1 -0
  104. package/dist/modules/packet.js +133 -0
  105. package/dist/modules/pcm.d.ts +12 -0
  106. package/dist/modules/pcm.d.ts.map +1 -0
  107. package/dist/modules/pcm.js +85 -0
  108. package/dist/modules/reader.d.ts +40 -0
  109. package/dist/modules/reader.d.ts.map +1 -0
  110. package/dist/modules/reader.js +153 -0
  111. package/dist/modules/sample.d.ts +186 -0
  112. package/dist/modules/sample.d.ts.map +1 -0
  113. package/dist/modules/sample.js +798 -0
  114. package/dist/modules/source.d.ts +76 -0
  115. package/dist/modules/source.d.ts.map +1 -0
  116. package/dist/modules/source.js +183 -0
  117. package/dist/modules/subtitles.d.ts +37 -0
  118. package/dist/modules/subtitles.d.ts.map +1 -0
  119. package/dist/modules/subtitles.js +89 -0
  120. package/dist/modules/target.d.ts +58 -0
  121. package/dist/modules/target.d.ts.map +1 -0
  122. package/dist/modules/target.js +63 -0
  123. package/dist/modules/wave/riff-reader.d.ts +19 -0
  124. package/dist/modules/wave/riff-reader.d.ts.map +1 -0
  125. package/dist/modules/wave/riff-reader.js +38 -0
  126. package/dist/modules/wave/riff-writer.d.ts +18 -0
  127. package/dist/modules/wave/riff-writer.d.ts.map +1 -0
  128. package/dist/modules/wave/riff-writer.js +25 -0
  129. package/dist/modules/wave/wave-demuxer.d.ts +42 -0
  130. package/dist/modules/wave/wave-demuxer.d.ts.map +1 -0
  131. package/dist/modules/wave/wave-demuxer.js +239 -0
  132. package/dist/modules/wave/wave-muxer.d.ts +27 -0
  133. package/dist/modules/wave/wave-muxer.d.ts.map +1 -0
  134. package/dist/modules/wave/wave-muxer.js +108 -0
  135. package/dist/modules/writer.d.ts +85 -0
  136. package/dist/modules/writer.d.ts.map +1 -0
  137. package/dist/modules/writer.js +347 -0
  138. package/package.json +14 -10
  139. package/src/codec-data.ts +1394 -0
  140. package/src/codec.ts +1392 -0
  141. package/src/conversion.ts +1199 -0
  142. package/src/custom-coder.ts +162 -0
  143. package/src/demuxer.ts +22 -0
  144. package/src/index.ts +148 -0
  145. package/src/input-format.ts +386 -0
  146. package/src/input-track.ts +341 -0
  147. package/src/input.ts +138 -0
  148. package/src/isobmff/isobmff-boxes.ts +1365 -0
  149. package/src/isobmff/isobmff-demuxer.ts +2646 -0
  150. package/src/isobmff/isobmff-misc.ts +29 -0
  151. package/src/isobmff/isobmff-muxer.ts +1210 -0
  152. package/src/isobmff/isobmff-reader.ts +137 -0
  153. package/src/matroska/ebml.ts +595 -0
  154. package/src/matroska/matroska-demuxer.ts +1677 -0
  155. package/src/matroska/matroska-misc.ts +29 -0
  156. package/src/matroska/matroska-muxer.ts +999 -0
  157. package/src/media-sink.ts +1577 -0
  158. package/src/media-source.ts +1406 -0
  159. package/src/misc.ts +584 -0
  160. package/src/mp3/mp3-demuxer.ts +228 -0
  161. package/src/mp3/mp3-misc.ts +154 -0
  162. package/src/mp3/mp3-muxer.ts +153 -0
  163. package/src/mp3/mp3-reader.ts +93 -0
  164. package/src/mp3/mp3-writer.ts +112 -0
  165. package/src/muxer.ts +88 -0
  166. package/src/ogg/ogg-demuxer.ts +985 -0
  167. package/src/ogg/ogg-misc.ts +116 -0
  168. package/src/ogg/ogg-muxer.ts +480 -0
  169. package/src/ogg/ogg-reader.ts +143 -0
  170. package/src/output-format.ts +689 -0
  171. package/src/output.ts +427 -0
  172. package/src/packet.ts +180 -0
  173. package/src/pcm.ts +112 -0
  174. package/src/reader.ts +205 -0
  175. package/src/sample.ts +1029 -0
  176. package/src/source.ts +279 -0
  177. package/src/subtitles.ts +131 -0
  178. package/src/target.ts +106 -0
  179. package/src/tsconfig.json +13 -0
  180. package/src/wave/riff-reader.ts +48 -0
  181. package/src/wave/riff-writer.ts +30 -0
  182. package/src/wave/wave-demuxer.ts +317 -0
  183. package/src/wave/wave-muxer.ts +145 -0
  184. package/src/writer.ts +461 -0
  185. /package/dist/{mediabunny.js → bundles/mediabunny.cjs} +0 -0
  186. /package/dist/{mediabunny.min.js → bundles/mediabunny.min.cjs} +0 -0
  187. /package/dist/{mediabunny.min.mjs → bundles/mediabunny.min.mjs} +0 -0
  188. /package/dist/{mediabunny.mjs → bundles/mediabunny.mjs} +0 -0
@@ -0,0 +1,317 @@
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 { AudioCodec } from '../codec';
10
+ import { Demuxer } from '../demuxer';
11
+ import { Input } from '../input';
12
+ import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
13
+ import { PacketRetrievalOptions } from '../media-sink';
14
+ import { assert, UNDETERMINED_LANGUAGE } from '../misc';
15
+ import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
16
+ import { Reader } from '../reader';
17
+ import { RiffReader } from './riff-reader';
18
+
19
+ export enum WaveFormat {
20
+ PCM = 0x0001,
21
+ IEEE_FLOAT = 0x0003,
22
+ ALAW = 0x0006,
23
+ MULAW = 0x0007,
24
+ EXTENSIBLE = 0xFFFE,
25
+ }
26
+
27
+ export class WaveDemuxer extends Demuxer {
28
+ metadataReader: RiffReader;
29
+ chunkReader: RiffReader;
30
+
31
+ metadataPromise: Promise<void> | null = null;
32
+ dataStart = -1;
33
+ dataSize = -1;
34
+ audioInfo: {
35
+ format: number;
36
+ numberOfChannels: number;
37
+ sampleRate: number;
38
+ sampleSizeInBytes: number;
39
+ blockSizeInBytes: number;
40
+ } | null = null;
41
+
42
+ tracks: InputAudioTrack[] = [];
43
+
44
+ constructor(input: Input) {
45
+ super(input);
46
+
47
+ this.metadataReader = new RiffReader(input._mainReader);
48
+ this.chunkReader = new RiffReader(new Reader(input.source, 64 * 2 ** 20));
49
+ }
50
+
51
+ async readMetadata() {
52
+ return this.metadataPromise ??= (async () => {
53
+ const actualFileSize = await this.metadataReader.reader.source.getSize();
54
+
55
+ const riffType = this.metadataReader.readAscii(4);
56
+ this.metadataReader.littleEndian = riffType === 'RIFF';
57
+
58
+ const totalFileSize = Math.min(this.metadataReader.readU32() + 8, actualFileSize);
59
+ const format = this.metadataReader.readAscii(4);
60
+
61
+ if (format !== 'WAVE') {
62
+ throw new Error('Invalid WAVE file - wrong format');
63
+ }
64
+
65
+ this.metadataReader.pos = 12;
66
+ while (this.metadataReader.pos < totalFileSize) {
67
+ await this.metadataReader.reader.loadRange(this.metadataReader.pos, this.metadataReader.pos + 8);
68
+
69
+ const chunkId = this.metadataReader.readAscii(4);
70
+ const chunkSize = this.metadataReader.readU32();
71
+ const startPos = this.metadataReader.pos;
72
+
73
+ if (chunkId === 'fmt ') {
74
+ await this.parseFmtChunk(chunkSize);
75
+ } else if (chunkId === 'data') {
76
+ this.dataStart = this.metadataReader.pos;
77
+ this.dataSize = Math.min(chunkSize, totalFileSize - this.dataStart);
78
+ }
79
+
80
+ this.metadataReader.pos = startPos + chunkSize + (chunkSize & 1); // Handle padding
81
+ }
82
+
83
+ if (!this.audioInfo) {
84
+ throw new Error('Invalid WAVE file - missing "fmt " chunk');
85
+ }
86
+ if (this.dataStart === -1) {
87
+ throw new Error('Invalid WAVE file - missing "data" chunk');
88
+ }
89
+
90
+ const blockSize = this.audioInfo.blockSizeInBytes;
91
+ this.dataSize = Math.floor(this.dataSize / blockSize) * blockSize;
92
+
93
+ this.tracks.push(new InputAudioTrack(new WaveAudioTrackBacking(this)));
94
+ })();
95
+ }
96
+
97
+ private async parseFmtChunk(size: number) {
98
+ await this.metadataReader.reader.loadRange(this.metadataReader.pos, this.metadataReader.pos + size);
99
+
100
+ let formatTag = this.metadataReader.readU16();
101
+ const numChannels = this.metadataReader.readU16();
102
+ const sampleRate = this.metadataReader.readU32();
103
+ this.metadataReader.pos += 4; // Bytes per second
104
+ const blockAlign = this.metadataReader.readU16();
105
+
106
+ let bitsPerSample: number;
107
+
108
+ if (size === 14) { // Plain WAVEFORMAT
109
+ bitsPerSample = 8;
110
+ } else {
111
+ bitsPerSample = this.metadataReader.readU16();
112
+ }
113
+
114
+ // Handle WAVEFORMATEXTENSIBLE
115
+ if (size >= 18 && formatTag !== 0x0165) {
116
+ const cbSize = this.metadataReader.readU16();
117
+ const remainingSize = size - 18;
118
+ const extensionSize = Math.min(remainingSize, cbSize);
119
+
120
+ if (extensionSize >= 22 && formatTag === WaveFormat.EXTENSIBLE) {
121
+ // Parse WAVEFORMATEXTENSIBLE
122
+ this.metadataReader.pos += 2 + 4;
123
+ const subFormat = this.metadataReader.readBytes(16);
124
+
125
+ // Get actual format from subFormat GUID
126
+ formatTag = subFormat[0]! | (subFormat[1]! << 8);
127
+ }
128
+ }
129
+
130
+ if (formatTag === WaveFormat.MULAW || formatTag === WaveFormat.ALAW) {
131
+ bitsPerSample = 8;
132
+ }
133
+
134
+ this.audioInfo = {
135
+ format: formatTag,
136
+ numberOfChannels: numChannels,
137
+ sampleRate,
138
+ sampleSizeInBytes: Math.ceil(bitsPerSample / 8),
139
+ blockSizeInBytes: blockAlign,
140
+ };
141
+ }
142
+
143
+ getCodec(): AudioCodec | null {
144
+ assert(this.audioInfo);
145
+
146
+ if (this.audioInfo.format === WaveFormat.MULAW) {
147
+ return 'ulaw';
148
+ }
149
+ if (this.audioInfo.format === WaveFormat.ALAW) {
150
+ return 'alaw';
151
+ }
152
+ if (this.audioInfo.format === WaveFormat.PCM) {
153
+ // All formats are little-endian
154
+ if (this.audioInfo.sampleSizeInBytes === 1) {
155
+ return 'pcm-u8';
156
+ } else if (this.audioInfo.sampleSizeInBytes === 2) {
157
+ return 'pcm-s16';
158
+ } else if (this.audioInfo.sampleSizeInBytes === 3) {
159
+ return 'pcm-s24';
160
+ } else if (this.audioInfo.sampleSizeInBytes === 4) {
161
+ return 'pcm-s32';
162
+ }
163
+ }
164
+ if (this.audioInfo.format === WaveFormat.IEEE_FLOAT) {
165
+ if (this.audioInfo.sampleSizeInBytes === 4) {
166
+ return 'pcm-f32';
167
+ }
168
+ }
169
+
170
+ return null;
171
+ }
172
+
173
+ async getMimeType() {
174
+ return 'audio/wav';
175
+ }
176
+
177
+ async computeDuration() {
178
+ await this.readMetadata();
179
+ assert(this.audioInfo);
180
+
181
+ const numberOfBlocks = this.dataSize / this.audioInfo.blockSizeInBytes;
182
+ return numberOfBlocks / this.audioInfo.sampleRate;
183
+ }
184
+
185
+ async getTracks() {
186
+ await this.readMetadata();
187
+ return this.tracks;
188
+ }
189
+ }
190
+
191
+ const PACKET_SIZE_IN_FRAMES = 2048;
192
+
193
+ class WaveAudioTrackBacking implements InputAudioTrackBacking {
194
+ constructor(public demuxer: WaveDemuxer) {}
195
+
196
+ getId() {
197
+ return 1;
198
+ }
199
+
200
+ getCodec() {
201
+ return this.demuxer.getCodec();
202
+ }
203
+
204
+ async getDecoderConfig(): Promise<AudioDecoderConfig | null> {
205
+ const codec = this.demuxer.getCodec();
206
+ if (!codec) {
207
+ return null;
208
+ }
209
+
210
+ assert(this.demuxer.audioInfo);
211
+ return {
212
+ codec,
213
+ numberOfChannels: this.demuxer.audioInfo.numberOfChannels,
214
+ sampleRate: this.demuxer.audioInfo.sampleRate,
215
+ };
216
+ }
217
+
218
+ computeDuration() {
219
+ return this.demuxer.computeDuration();
220
+ }
221
+
222
+ getNumberOfChannels() {
223
+ assert(this.demuxer.audioInfo);
224
+ return this.demuxer.audioInfo.numberOfChannels;
225
+ }
226
+
227
+ getSampleRate() {
228
+ assert(this.demuxer.audioInfo);
229
+ return this.demuxer.audioInfo.sampleRate;
230
+ }
231
+
232
+ getTimeResolution() {
233
+ assert(this.demuxer.audioInfo);
234
+ return this.demuxer.audioInfo.sampleRate;
235
+ }
236
+
237
+ getLanguageCode() {
238
+ return UNDETERMINED_LANGUAGE;
239
+ }
240
+
241
+ async getFirstTimestamp() {
242
+ return 0;
243
+ }
244
+
245
+ private async getPacketAtIndex(
246
+ packetIndex: number,
247
+ options: PacketRetrievalOptions,
248
+ ): Promise<EncodedPacket | null> {
249
+ assert(this.demuxer.audioInfo);
250
+ const startOffset = packetIndex * PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes;
251
+ if (startOffset >= this.demuxer.dataSize) {
252
+ return null;
253
+ }
254
+
255
+ const sizeInBytes = Math.min(
256
+ PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes,
257
+ this.demuxer.dataSize - startOffset,
258
+ );
259
+
260
+ let data: Uint8Array;
261
+ if (options.metadataOnly) {
262
+ data = PLACEHOLDER_DATA;
263
+ } else {
264
+ const sizeOfOnePacket = PACKET_SIZE_IN_FRAMES * this.demuxer.audioInfo.blockSizeInBytes;
265
+ const chunkSize = Math.ceil(2 ** 19 / sizeOfOnePacket) * sizeOfOnePacket;
266
+ const chunkStart = Math.floor(startOffset / chunkSize) * chunkSize;
267
+ const chunkEnd = chunkStart + chunkSize;
268
+
269
+ // Always load large 0.5 MiB chunks instead of just the required packet
270
+ await this.demuxer.chunkReader.reader.loadRange(
271
+ this.demuxer.dataStart + chunkStart,
272
+ this.demuxer.dataStart + chunkEnd,
273
+ );
274
+
275
+ this.demuxer.chunkReader.pos = this.demuxer.dataStart + startOffset;
276
+ data = this.demuxer.chunkReader.readBytes(sizeInBytes);
277
+ }
278
+
279
+ const timestamp = packetIndex * PACKET_SIZE_IN_FRAMES / this.demuxer.audioInfo.sampleRate;
280
+ const duration = sizeInBytes / this.demuxer.audioInfo.blockSizeInBytes / this.demuxer.audioInfo.sampleRate;
281
+
282
+ return new EncodedPacket(
283
+ data,
284
+ 'key',
285
+ timestamp,
286
+ duration,
287
+ packetIndex,
288
+ sizeInBytes,
289
+ );
290
+ }
291
+
292
+ getFirstPacket(options: PacketRetrievalOptions) {
293
+ return this.getPacketAtIndex(0, options);
294
+ }
295
+
296
+ getPacket(timestamp: number, options: PacketRetrievalOptions) {
297
+ assert(this.demuxer.audioInfo);
298
+ const packetIndex = Math.floor(timestamp * this.demuxer.audioInfo.sampleRate / PACKET_SIZE_IN_FRAMES);
299
+
300
+ return this.getPacketAtIndex(packetIndex, options);
301
+ }
302
+
303
+ getNextPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
304
+ assert(this.demuxer.audioInfo);
305
+ const packetIndex = Math.round(packet.timestamp * this.demuxer.audioInfo.sampleRate / PACKET_SIZE_IN_FRAMES);
306
+
307
+ return this.getPacketAtIndex(packetIndex + 1, options);
308
+ }
309
+
310
+ getKeyPacket(timestamp: number, options: PacketRetrievalOptions) {
311
+ return this.getPacket(timestamp, options);
312
+ }
313
+
314
+ getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
315
+ return this.getNextPacket(packet, options);
316
+ }
317
+ }
@@ -0,0 +1,145 @@
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 { Muxer } from '../muxer';
10
+ import { Output, OutputAudioTrack } from '../output';
11
+ import { parsePcmCodec, PcmAudioCodec, validateAudioChunkMetadata } from '../codec';
12
+ import { WaveFormat } from './wave-demuxer';
13
+ import { RiffWriter } from './riff-writer';
14
+ import { Writer } from '../writer';
15
+ import { EncodedPacket } from '../packet';
16
+ import { WavOutputFormat } from '../output-format';
17
+ import { assert } from '../misc';
18
+
19
+ export class WaveMuxer extends Muxer {
20
+ private format: WavOutputFormat;
21
+ private writer: Writer;
22
+ private riffWriter: RiffWriter;
23
+ private headerWritten = false;
24
+ private dataSize = 0;
25
+
26
+ constructor(output: Output, format: WavOutputFormat) {
27
+ super(output);
28
+
29
+ this.format = format;
30
+ this.writer = output._writer;
31
+ this.riffWriter = new RiffWriter(output._writer);
32
+ }
33
+
34
+ async start() {
35
+ // Nothing needed here - we'll write the header with the first sample
36
+ }
37
+
38
+ async getMimeType() {
39
+ return 'audio/wav';
40
+ }
41
+
42
+ async addEncodedVideoPacket() {
43
+ throw new Error('WAVE does not support video.');
44
+ }
45
+
46
+ async addEncodedAudioPacket(
47
+ track: OutputAudioTrack,
48
+ packet: EncodedPacket,
49
+ meta?: EncodedAudioChunkMetadata,
50
+ ) {
51
+ const release = await this.mutex.acquire();
52
+
53
+ try {
54
+ if (!this.headerWritten) {
55
+ validateAudioChunkMetadata(meta);
56
+
57
+ assert(meta);
58
+ assert(meta.decoderConfig);
59
+
60
+ this.writeHeader(track, meta.decoderConfig);
61
+ this.headerWritten = true;
62
+ }
63
+
64
+ this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
65
+
66
+ this.writer.write(packet.data);
67
+ this.dataSize += packet.data.byteLength;
68
+
69
+ await this.writer.flush();
70
+ } finally {
71
+ release();
72
+ }
73
+ }
74
+
75
+ async addSubtitleCue() {
76
+ throw new Error('WAVE does not support subtitles.');
77
+ }
78
+
79
+ private writeHeader(track: OutputAudioTrack, config: AudioDecoderConfig) {
80
+ if (this.format._options.onHeader) {
81
+ this.writer.startTrackingWrites();
82
+ }
83
+
84
+ let format: WaveFormat;
85
+
86
+ const codec = track.source._codec;
87
+ const pcmInfo = parsePcmCodec(codec as PcmAudioCodec);
88
+
89
+ if (pcmInfo.dataType === 'ulaw') {
90
+ format = WaveFormat.MULAW;
91
+ } else if (pcmInfo.dataType === 'alaw') {
92
+ format = WaveFormat.ALAW;
93
+ } else if (pcmInfo.dataType === 'float') {
94
+ format = WaveFormat.IEEE_FLOAT;
95
+ } else {
96
+ format = WaveFormat.PCM;
97
+ }
98
+
99
+ const channels = config.numberOfChannels;
100
+ const sampleRate = config.sampleRate;
101
+ const blockSize = pcmInfo.sampleSize * channels;
102
+
103
+ // RIFF header
104
+ this.riffWriter.writeAscii('RIFF');
105
+ this.riffWriter.writeU32(0); // File size placeholder
106
+ this.riffWriter.writeAscii('WAVE');
107
+
108
+ // fmt chunk
109
+ this.riffWriter.writeAscii('fmt ');
110
+ this.riffWriter.writeU32(16); // Chunk size
111
+ this.riffWriter.writeU16(format);
112
+ this.riffWriter.writeU16(channels);
113
+ this.riffWriter.writeU32(sampleRate);
114
+ this.riffWriter.writeU32(sampleRate * blockSize); // Bytes per second
115
+ this.riffWriter.writeU16(blockSize);
116
+ this.riffWriter.writeU16(8 * pcmInfo.sampleSize);
117
+
118
+ // data chunk
119
+ this.riffWriter.writeAscii('data');
120
+ this.riffWriter.writeU32(0); // Data size placeholder
121
+
122
+ if (this.format._options.onHeader) {
123
+ const { data, start } = this.writer.stopTrackingWrites();
124
+ this.format._options.onHeader(data, start);
125
+ }
126
+ }
127
+
128
+ async finalize() {
129
+ const release = await this.mutex.acquire();
130
+
131
+ const endPos = this.writer.getPos();
132
+
133
+ // Write file size
134
+ this.writer.seek(4);
135
+ this.riffWriter.writeU32(this.dataSize + 36); // File size - 8
136
+
137
+ // Write data chunk size
138
+ this.writer.seek(40);
139
+ this.riffWriter.writeU32(this.dataSize);
140
+
141
+ this.writer.seek(endPos);
142
+
143
+ release();
144
+ }
145
+ }