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.
- package/README.md +4 -4
- package/dist/mediabunny.d.ts +4 -2
- package/dist/modules/codec-data.d.ts +95 -0
- package/dist/modules/codec-data.d.ts.map +1 -0
- package/dist/modules/codec-data.js +1103 -0
- package/dist/modules/codec.d.ts +250 -0
- package/dist/modules/codec.d.ts.map +1 -0
- package/dist/modules/codec.js +1100 -0
- package/dist/modules/conversion.d.ts +158 -0
- package/dist/modules/conversion.d.ts.map +1 -0
- package/dist/modules/conversion.js +866 -0
- package/dist/modules/custom-coder.d.ts +119 -0
- package/dist/modules/custom-coder.d.ts.map +1 -0
- package/dist/modules/custom-coder.js +91 -0
- package/dist/modules/demuxer.d.ts +17 -0
- package/dist/modules/demuxer.d.ts.map +1 -0
- package/dist/modules/demuxer.js +12 -0
- package/dist/modules/index.d.ts +25 -0
- package/dist/modules/index.d.ts.map +1 -0
- package/dist/modules/index.js +24 -0
- package/dist/modules/input-format.d.ts +121 -0
- package/dist/modules/input-format.d.ts.map +1 -0
- package/dist/modules/input-format.js +333 -0
- package/dist/modules/input-track.d.ts +138 -0
- package/dist/modules/input-track.d.ts.map +1 -0
- package/dist/modules/input-track.js +230 -0
- package/dist/modules/input.d.ts +55 -0
- package/dist/modules/input.d.ts.map +1 -0
- package/dist/modules/input.js +104 -0
- package/dist/modules/isobmff/isobmff-boxes.d.ts +202 -0
- package/dist/modules/isobmff/isobmff-boxes.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-boxes.js +1111 -0
- package/dist/modules/isobmff/isobmff-demuxer.d.ts +163 -0
- package/dist/modules/isobmff/isobmff-demuxer.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-demuxer.js +2080 -0
- package/dist/modules/isobmff/isobmff-misc.d.ts +14 -0
- package/dist/modules/isobmff/isobmff-misc.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-misc.js +20 -0
- package/dist/modules/isobmff/isobmff-muxer.d.ts +142 -0
- package/dist/modules/isobmff/isobmff-muxer.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-muxer.js +858 -0
- package/dist/modules/isobmff/isobmff-reader.d.ts +36 -0
- package/dist/modules/isobmff/isobmff-reader.d.ts.map +1 -0
- package/dist/modules/isobmff/isobmff-reader.js +105 -0
- package/dist/modules/matroska/ebml.d.ts +156 -0
- package/dist/modules/matroska/ebml.d.ts.map +1 -0
- package/dist/modules/matroska/ebml.js +529 -0
- package/dist/modules/matroska/matroska-demuxer.d.ts +119 -0
- package/dist/modules/matroska/matroska-demuxer.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-demuxer.js +1330 -0
- package/dist/modules/matroska/matroska-misc.d.ts +14 -0
- package/dist/modules/matroska/matroska-misc.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-misc.js +20 -0
- package/dist/modules/matroska/matroska-muxer.d.ts +71 -0
- package/dist/modules/matroska/matroska-muxer.d.ts.map +1 -0
- package/dist/modules/matroska/matroska-muxer.js +729 -0
- package/dist/modules/media-sink.d.ts +272 -0
- package/dist/modules/media-sink.d.ts.map +1 -0
- package/dist/modules/media-sink.js +1264 -0
- package/dist/modules/media-source.d.ts +230 -0
- package/dist/modules/media-source.d.ts.map +1 -0
- package/dist/modules/media-source.js +1095 -0
- package/dist/modules/misc.d.ts +127 -0
- package/dist/modules/misc.d.ts.map +1 -0
- package/dist/modules/misc.js +462 -0
- package/dist/modules/mp3/mp3-demuxer.d.ts +32 -0
- package/dist/modules/mp3/mp3-demuxer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-demuxer.js +160 -0
- package/dist/modules/mp3/mp3-misc.d.ts +37 -0
- package/dist/modules/mp3/mp3-misc.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-misc.js +122 -0
- package/dist/modules/mp3/mp3-muxer.d.ts +27 -0
- package/dist/modules/mp3/mp3-muxer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-muxer.js +112 -0
- package/dist/modules/mp3/mp3-reader.d.ts +25 -0
- package/dist/modules/mp3/mp3-reader.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-reader.js +73 -0
- package/dist/modules/mp3/mp3-writer.d.ts +30 -0
- package/dist/modules/mp3/mp3-writer.d.ts.map +1 -0
- package/dist/modules/mp3/mp3-writer.js +72 -0
- package/dist/modules/muxer.d.ts +32 -0
- package/dist/modules/muxer.d.ts.map +1 -0
- package/dist/modules/muxer.js +50 -0
- package/dist/modules/ogg/ogg-demuxer.d.ts +53 -0
- package/dist/modules/ogg/ogg-demuxer.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-demuxer.js +701 -0
- package/dist/modules/ogg/ogg-misc.d.ts +27 -0
- package/dist/modules/ogg/ogg-misc.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-misc.js +78 -0
- package/dist/modules/ogg/ogg-muxer.d.ts +58 -0
- package/dist/modules/ogg/ogg-muxer.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-muxer.js +345 -0
- package/dist/modules/ogg/ogg-reader.d.ts +37 -0
- package/dist/modules/ogg/ogg-reader.d.ts.map +1 -0
- package/dist/modules/ogg/ogg-reader.js +104 -0
- package/dist/modules/output-format.d.ts +285 -0
- package/dist/modules/output-format.d.ts.map +1 -0
- package/dist/modules/output-format.js +385 -0
- package/dist/modules/output.d.ts +134 -0
- package/dist/modules/output.d.ts.map +1 -0
- package/dist/modules/output.js +269 -0
- package/dist/modules/packet.d.ts +86 -0
- package/dist/modules/packet.d.ts.map +1 -0
- package/dist/modules/packet.js +133 -0
- package/dist/modules/pcm.d.ts +12 -0
- package/dist/modules/pcm.d.ts.map +1 -0
- package/dist/modules/pcm.js +85 -0
- package/dist/modules/reader.d.ts +40 -0
- package/dist/modules/reader.d.ts.map +1 -0
- package/dist/modules/reader.js +153 -0
- package/dist/modules/sample.d.ts +186 -0
- package/dist/modules/sample.d.ts.map +1 -0
- package/dist/modules/sample.js +798 -0
- package/dist/modules/source.d.ts +76 -0
- package/dist/modules/source.d.ts.map +1 -0
- package/dist/modules/source.js +183 -0
- package/dist/modules/subtitles.d.ts +37 -0
- package/dist/modules/subtitles.d.ts.map +1 -0
- package/dist/modules/subtitles.js +89 -0
- package/dist/modules/target.d.ts +58 -0
- package/dist/modules/target.d.ts.map +1 -0
- package/dist/modules/target.js +63 -0
- package/dist/modules/wave/riff-reader.d.ts +19 -0
- package/dist/modules/wave/riff-reader.d.ts.map +1 -0
- package/dist/modules/wave/riff-reader.js +38 -0
- package/dist/modules/wave/riff-writer.d.ts +18 -0
- package/dist/modules/wave/riff-writer.d.ts.map +1 -0
- package/dist/modules/wave/riff-writer.js +25 -0
- package/dist/modules/wave/wave-demuxer.d.ts +42 -0
- package/dist/modules/wave/wave-demuxer.d.ts.map +1 -0
- package/dist/modules/wave/wave-demuxer.js +239 -0
- package/dist/modules/wave/wave-muxer.d.ts +27 -0
- package/dist/modules/wave/wave-muxer.d.ts.map +1 -0
- package/dist/modules/wave/wave-muxer.js +108 -0
- package/dist/modules/writer.d.ts +85 -0
- package/dist/modules/writer.d.ts.map +1 -0
- package/dist/modules/writer.js +347 -0
- package/package.json +14 -10
- package/src/codec-data.ts +1394 -0
- package/src/codec.ts +1392 -0
- package/src/conversion.ts +1199 -0
- package/src/custom-coder.ts +162 -0
- package/src/demuxer.ts +22 -0
- package/src/index.ts +148 -0
- package/src/input-format.ts +386 -0
- package/src/input-track.ts +341 -0
- package/src/input.ts +138 -0
- package/src/isobmff/isobmff-boxes.ts +1365 -0
- package/src/isobmff/isobmff-demuxer.ts +2646 -0
- package/src/isobmff/isobmff-misc.ts +29 -0
- package/src/isobmff/isobmff-muxer.ts +1210 -0
- package/src/isobmff/isobmff-reader.ts +137 -0
- package/src/matroska/ebml.ts +595 -0
- package/src/matroska/matroska-demuxer.ts +1677 -0
- package/src/matroska/matroska-misc.ts +29 -0
- package/src/matroska/matroska-muxer.ts +999 -0
- package/src/media-sink.ts +1577 -0
- package/src/media-source.ts +1406 -0
- package/src/misc.ts +584 -0
- package/src/mp3/mp3-demuxer.ts +228 -0
- package/src/mp3/mp3-misc.ts +154 -0
- package/src/mp3/mp3-muxer.ts +153 -0
- package/src/mp3/mp3-reader.ts +93 -0
- package/src/mp3/mp3-writer.ts +112 -0
- package/src/muxer.ts +88 -0
- package/src/ogg/ogg-demuxer.ts +985 -0
- package/src/ogg/ogg-misc.ts +116 -0
- package/src/ogg/ogg-muxer.ts +480 -0
- package/src/ogg/ogg-reader.ts +143 -0
- package/src/output-format.ts +689 -0
- package/src/output.ts +427 -0
- package/src/packet.ts +180 -0
- package/src/pcm.ts +112 -0
- package/src/reader.ts +205 -0
- package/src/sample.ts +1029 -0
- package/src/source.ts +279 -0
- package/src/subtitles.ts +131 -0
- package/src/target.ts +106 -0
- package/src/tsconfig.json +13 -0
- package/src/wave/riff-reader.ts +48 -0
- package/src/wave/riff-writer.ts +30 -0
- package/src/wave/wave-demuxer.ts +317 -0
- package/src/wave/wave-muxer.ts +145 -0
- package/src/writer.ts +461 -0
- /package/dist/{mediabunny.js → bundles/mediabunny.cjs} +0 -0
- /package/dist/{mediabunny.min.js → bundles/mediabunny.min.cjs} +0 -0
- /package/dist/{mediabunny.min.mjs → bundles/mediabunny.min.mjs} +0 -0
- /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
|
+
}
|