g711-web-stream-player 0.1.0
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/LICENSE +22 -0
- package/README.md +273 -0
- package/dist/index.cjs +492 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +170 -0
- package/dist/index.d.ts +170 -0
- package/dist/index.js +482 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +518 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +16 -0
- package/dist/react.d.ts +16 -0
- package/dist/react.js +516 -0
- package/dist/react.js.map +1 -0
- package/dist/vue.cjs +517 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.cts +17 -0
- package/dist/vue.d.ts +17 -0
- package/dist/vue.js +515 -0
- package/dist/vue.js.map +1 -0
- package/package.json +92 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/core/decoder.ts
|
|
4
|
+
var ALAW_DECODE_TABLE = new Int16Array(256);
|
|
5
|
+
for (let encoded = 0; encoded < 256; encoded += 1) {
|
|
6
|
+
const value = encoded ^ 85;
|
|
7
|
+
let sample = (value & 15) << 4;
|
|
8
|
+
const segment = (value & 112) >> 4;
|
|
9
|
+
if (segment === 0) {
|
|
10
|
+
sample += 8;
|
|
11
|
+
} else if (segment === 1) {
|
|
12
|
+
sample += 264;
|
|
13
|
+
} else {
|
|
14
|
+
sample += 264;
|
|
15
|
+
sample <<= segment - 1;
|
|
16
|
+
}
|
|
17
|
+
ALAW_DECODE_TABLE[encoded] = value & 128 ? sample : -sample;
|
|
18
|
+
}
|
|
19
|
+
var MULAW_DECODE_TABLE = new Int16Array(256);
|
|
20
|
+
for (let encoded = 0; encoded < 256; encoded += 1) {
|
|
21
|
+
const value = ~encoded & 255;
|
|
22
|
+
const magnitude = ((value & 15) << 3) + 132;
|
|
23
|
+
const sample = (magnitude << ((value & 112) >> 4)) - 132;
|
|
24
|
+
MULAW_DECODE_TABLE[encoded] = value & 128 ? -sample : sample;
|
|
25
|
+
}
|
|
26
|
+
function decodeALaw(encoded) {
|
|
27
|
+
return ALAW_DECODE_TABLE[encoded & 255];
|
|
28
|
+
}
|
|
29
|
+
function decodeMuLaw(encoded) {
|
|
30
|
+
return MULAW_DECODE_TABLE[encoded & 255];
|
|
31
|
+
}
|
|
32
|
+
function decodeG711Chunk(data, codec) {
|
|
33
|
+
const table = codec === "pcma" ? ALAW_DECODE_TABLE : MULAW_DECODE_TABLE;
|
|
34
|
+
const pcm = new Int16Array(data.length);
|
|
35
|
+
for (let index = 0; index < data.length; index += 1) {
|
|
36
|
+
pcm[index] = table[data[index]];
|
|
37
|
+
}
|
|
38
|
+
return pcm;
|
|
39
|
+
}
|
|
40
|
+
function pcm16ToFloat32(pcm) {
|
|
41
|
+
const output = new Float32Array(pcm.length);
|
|
42
|
+
for (let index = 0; index < pcm.length; index += 1) {
|
|
43
|
+
output[index] = pcm[index] / 32768;
|
|
44
|
+
}
|
|
45
|
+
return output;
|
|
46
|
+
}
|
|
47
|
+
function decodeG711ToFloat32(data, codec) {
|
|
48
|
+
return pcm16ToFloat32(decodeG711Chunk(data, codec));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/core/player.ts
|
|
52
|
+
var DEFAULT_SAMPLE_RATE = 8e3;
|
|
53
|
+
function clamp(value, minimum, maximum) {
|
|
54
|
+
return Math.max(minimum, Math.min(maximum, value));
|
|
55
|
+
}
|
|
56
|
+
function normalizeOptions(options) {
|
|
57
|
+
return {
|
|
58
|
+
inputSampleRate: Math.max(1, options.inputSampleRate ?? DEFAULT_SAMPLE_RATE),
|
|
59
|
+
bufferDurationMs: Math.max(250, options.bufferDurationMs ?? 4e3),
|
|
60
|
+
prebufferMs: Math.max(0, options.prebufferMs ?? 120),
|
|
61
|
+
overflowDropMs: Math.max(1, options.overflowDropMs ?? 150),
|
|
62
|
+
processorBufferSize: options.processorBufferSize ?? 2048,
|
|
63
|
+
cubicInterpolation: options.cubicInterpolation ?? true,
|
|
64
|
+
noiseGateThreshold: Math.max(0, options.noiseGateThreshold ?? 8e-3),
|
|
65
|
+
noiseGateFloor: clamp(options.noiseGateFloor ?? 0.18, 0, 1),
|
|
66
|
+
noiseGateRelease: clamp(options.noiseGateRelease ?? 0.35, 0, 1),
|
|
67
|
+
highpassHz: Math.max(0, options.highpassHz ?? 180),
|
|
68
|
+
highshelfHz: Math.max(0, options.highshelfHz ?? 2600),
|
|
69
|
+
highshelfGainDb: options.highshelfGainDb ?? 2,
|
|
70
|
+
volume: clamp(options.volume ?? 1, 0, 1),
|
|
71
|
+
muted: options.muted ?? false,
|
|
72
|
+
onDiagnostic: options.onDiagnostic
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
var G711AudioPlayer = class {
|
|
76
|
+
constructor(options = {}) {
|
|
77
|
+
this.context = null;
|
|
78
|
+
this.gainNode = null;
|
|
79
|
+
this.scriptNode = null;
|
|
80
|
+
this.nodes = [];
|
|
81
|
+
this.outputSampleRate = 44100;
|
|
82
|
+
this.resampleRatio = 1;
|
|
83
|
+
this.sourcePhase = 0;
|
|
84
|
+
this.ring = new Int16Array(0);
|
|
85
|
+
this.writeIndex = 0;
|
|
86
|
+
this.readIndex = 0;
|
|
87
|
+
this.gateGain = 1;
|
|
88
|
+
this.waitingForBuffer = true;
|
|
89
|
+
this.destroyed = false;
|
|
90
|
+
this.inputChunkCount = 0;
|
|
91
|
+
this.inputBytes = 0;
|
|
92
|
+
this.processCallbackCount = 0;
|
|
93
|
+
this.underflowCount = 0;
|
|
94
|
+
this.overflowCount = 0;
|
|
95
|
+
this.options = normalizeOptions(options);
|
|
96
|
+
}
|
|
97
|
+
get isReady() {
|
|
98
|
+
return this.context !== null && this.context.state !== "closed";
|
|
99
|
+
}
|
|
100
|
+
getDiagnosticState() {
|
|
101
|
+
return {
|
|
102
|
+
contextState: this.context?.state ?? "not-created",
|
|
103
|
+
muted: this.options.muted,
|
|
104
|
+
volume: this.options.volume,
|
|
105
|
+
outputSampleRate: this.outputSampleRate,
|
|
106
|
+
queuedSamples: this.readable,
|
|
107
|
+
queuedMilliseconds: Math.round(this.readable / this.options.inputSampleRate * 1e3),
|
|
108
|
+
inputChunkCount: this.inputChunkCount,
|
|
109
|
+
inputBytes: this.inputBytes,
|
|
110
|
+
processCallbackCount: this.processCallbackCount,
|
|
111
|
+
underflowCount: this.underflowCount,
|
|
112
|
+
overflowCount: this.overflowCount,
|
|
113
|
+
waitingForBuffer: this.waitingForBuffer,
|
|
114
|
+
destroyed: this.destroyed
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Initialize Web Audio. Call this from a click/tap handler when possible. */
|
|
118
|
+
init() {
|
|
119
|
+
if (this.context) return;
|
|
120
|
+
if (this.destroyed) throw new Error("This G711AudioPlayer has already been destroyed");
|
|
121
|
+
if (typeof AudioContext === "undefined") {
|
|
122
|
+
throw new Error("G711AudioPlayer requires a browser with the Web Audio API");
|
|
123
|
+
}
|
|
124
|
+
this.context = new AudioContext();
|
|
125
|
+
this.outputSampleRate = this.context.sampleRate;
|
|
126
|
+
this.resampleRatio = this.outputSampleRate / this.options.inputSampleRate;
|
|
127
|
+
const capacity = Math.ceil(this.options.inputSampleRate * this.options.bufferDurationMs / 1e3);
|
|
128
|
+
this.ring = new Int16Array(Math.max(capacity, 8));
|
|
129
|
+
this.context.onstatechange = () => this.emit("context-state");
|
|
130
|
+
this.gainNode = this.context.createGain();
|
|
131
|
+
this.gainNode.gain.value = this.options.muted ? 0 : this.options.volume;
|
|
132
|
+
let previous;
|
|
133
|
+
this.scriptNode = this.context.createScriptProcessor(this.options.processorBufferSize, 0, 1);
|
|
134
|
+
this.scriptNode.onaudioprocess = (event) => this.process(event);
|
|
135
|
+
previous = this.scriptNode;
|
|
136
|
+
if (this.options.highpassHz > 0) {
|
|
137
|
+
const highpass = this.context.createBiquadFilter();
|
|
138
|
+
highpass.type = "highpass";
|
|
139
|
+
highpass.frequency.value = this.options.highpassHz;
|
|
140
|
+
highpass.Q.value = 0.7;
|
|
141
|
+
previous.connect(highpass);
|
|
142
|
+
this.nodes.push(highpass);
|
|
143
|
+
previous = highpass;
|
|
144
|
+
}
|
|
145
|
+
if (this.options.highshelfHz > 0) {
|
|
146
|
+
const highshelf = this.context.createBiquadFilter();
|
|
147
|
+
highshelf.type = "highshelf";
|
|
148
|
+
highshelf.frequency.value = this.options.highshelfHz;
|
|
149
|
+
highshelf.gain.value = this.options.highshelfGainDb;
|
|
150
|
+
previous.connect(highshelf);
|
|
151
|
+
this.nodes.push(highshelf);
|
|
152
|
+
previous = highshelf;
|
|
153
|
+
}
|
|
154
|
+
previous.connect(this.gainNode);
|
|
155
|
+
this.gainNode.connect(this.context.destination);
|
|
156
|
+
this.emit("initialized");
|
|
157
|
+
}
|
|
158
|
+
/** Resume an AudioContext suspended by the browser autoplay policy. */
|
|
159
|
+
async resume() {
|
|
160
|
+
if (!this.context) this.init();
|
|
161
|
+
await this.context?.resume();
|
|
162
|
+
}
|
|
163
|
+
/** Decode and enqueue one raw PCMA or PCMU packet. */
|
|
164
|
+
pushChunk(data, codec) {
|
|
165
|
+
if (!this.context) this.init();
|
|
166
|
+
if (this.destroyed || data.length === 0) return;
|
|
167
|
+
if (this.context?.state === "suspended") void this.context.resume();
|
|
168
|
+
const pcm = decodeG711Chunk(data, codec);
|
|
169
|
+
this.inputChunkCount += 1;
|
|
170
|
+
this.inputBytes += data.byteLength;
|
|
171
|
+
let sourceIndex = 0;
|
|
172
|
+
while (sourceIndex < pcm.length) {
|
|
173
|
+
if (this.writable === 0) {
|
|
174
|
+
const drop = Math.min(
|
|
175
|
+
this.readable,
|
|
176
|
+
Math.max(1, Math.floor(this.options.inputSampleRate * this.options.overflowDropMs / 1e3))
|
|
177
|
+
);
|
|
178
|
+
this.readIndex = (this.readIndex + drop) % this.ring.length;
|
|
179
|
+
this.sourcePhase = 0;
|
|
180
|
+
this.overflowCount += 1;
|
|
181
|
+
this.emit("overflow");
|
|
182
|
+
}
|
|
183
|
+
const count = Math.min(pcm.length - sourceIndex, this.ring.length - this.writeIndex, this.writable);
|
|
184
|
+
this.ring.set(pcm.subarray(sourceIndex, sourceIndex + count), this.writeIndex);
|
|
185
|
+
this.writeIndex = (this.writeIndex + count) % this.ring.length;
|
|
186
|
+
sourceIndex += count;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Smoothly mute or unmute the output. */
|
|
190
|
+
setMuted(muted) {
|
|
191
|
+
this.options.muted = muted;
|
|
192
|
+
this.rampGain(muted ? 0 : this.options.volume);
|
|
193
|
+
}
|
|
194
|
+
/** Set output volume in the inclusive range 0..1. */
|
|
195
|
+
setVolume(volume) {
|
|
196
|
+
this.options.volume = clamp(volume, 0, 1);
|
|
197
|
+
if (!this.options.muted) this.rampGain(this.options.volume);
|
|
198
|
+
}
|
|
199
|
+
/** Drop queued audio and rebuild the initial jitter buffer. */
|
|
200
|
+
flush() {
|
|
201
|
+
this.writeIndex = 0;
|
|
202
|
+
this.readIndex = 0;
|
|
203
|
+
this.sourcePhase = 0;
|
|
204
|
+
this.gateGain = 1;
|
|
205
|
+
this.waitingForBuffer = true;
|
|
206
|
+
}
|
|
207
|
+
/** Disconnect audio nodes and close the underlying AudioContext. */
|
|
208
|
+
destroy() {
|
|
209
|
+
if (this.destroyed) return;
|
|
210
|
+
this.destroyed = true;
|
|
211
|
+
this.scriptNode?.disconnect();
|
|
212
|
+
if (this.scriptNode) this.scriptNode.onaudioprocess = null;
|
|
213
|
+
for (const node of this.nodes) node.disconnect();
|
|
214
|
+
this.gainNode?.disconnect();
|
|
215
|
+
if (this.context && this.context.state !== "closed") {
|
|
216
|
+
this.context.onstatechange = null;
|
|
217
|
+
void this.context.close();
|
|
218
|
+
}
|
|
219
|
+
this.scriptNode = null;
|
|
220
|
+
this.nodes = [];
|
|
221
|
+
this.gainNode = null;
|
|
222
|
+
this.context = null;
|
|
223
|
+
this.emit("destroyed");
|
|
224
|
+
}
|
|
225
|
+
get readable() {
|
|
226
|
+
if (this.ring.length === 0) return 0;
|
|
227
|
+
return this.writeIndex >= this.readIndex ? this.writeIndex - this.readIndex : this.ring.length - this.readIndex + this.writeIndex;
|
|
228
|
+
}
|
|
229
|
+
get writable() {
|
|
230
|
+
return this.ring.length - this.readable - 1;
|
|
231
|
+
}
|
|
232
|
+
sampleAt(offset) {
|
|
233
|
+
return this.ring[(this.readIndex + offset) % this.ring.length];
|
|
234
|
+
}
|
|
235
|
+
process(event) {
|
|
236
|
+
this.processCallbackCount += 1;
|
|
237
|
+
const output = event.outputBuffer.getChannelData(0);
|
|
238
|
+
const prebufferSamples = Math.floor(this.options.inputSampleRate * this.options.prebufferMs / 1e3);
|
|
239
|
+
if (this.options.muted) {
|
|
240
|
+
output.fill(0);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (this.waitingForBuffer) {
|
|
244
|
+
if (this.readable < Math.max(4, prebufferSamples)) {
|
|
245
|
+
output.fill(0);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
this.waitingForBuffer = false;
|
|
249
|
+
}
|
|
250
|
+
if (this.readable < 4) {
|
|
251
|
+
output.fill(0);
|
|
252
|
+
this.waitingForBuffer = true;
|
|
253
|
+
this.sourcePhase = 0;
|
|
254
|
+
this.underflowCount += 1;
|
|
255
|
+
this.emit("underflow");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const available = this.readable;
|
|
259
|
+
let produced = 0;
|
|
260
|
+
for (; produced < output.length; produced += 1) {
|
|
261
|
+
const position = this.sourcePhase + produced / this.resampleRatio;
|
|
262
|
+
const index = Math.floor(position);
|
|
263
|
+
const fraction = position - index;
|
|
264
|
+
if (index >= available) break;
|
|
265
|
+
if (this.options.cubicInterpolation && index >= 1 && index + 2 < available) {
|
|
266
|
+
output[produced] = this.cubic(
|
|
267
|
+
this.sampleAt(index - 1),
|
|
268
|
+
this.sampleAt(index),
|
|
269
|
+
this.sampleAt(index + 1),
|
|
270
|
+
this.sampleAt(index + 2),
|
|
271
|
+
fraction
|
|
272
|
+
) / 32768;
|
|
273
|
+
} else if (index + 1 < available) {
|
|
274
|
+
const current = this.sampleAt(index);
|
|
275
|
+
output[produced] = (current + (this.sampleAt(index + 1) - current) * fraction) / 32768;
|
|
276
|
+
} else {
|
|
277
|
+
output[produced] = this.sampleAt(index) / 32768;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (produced < output.length) {
|
|
281
|
+
output.fill(0, produced);
|
|
282
|
+
this.waitingForBuffer = true;
|
|
283
|
+
this.underflowCount += 1;
|
|
284
|
+
this.emit("underflow");
|
|
285
|
+
}
|
|
286
|
+
const nextPosition = this.sourcePhase + produced / this.resampleRatio;
|
|
287
|
+
const consumed = Math.min(Math.floor(nextPosition), available);
|
|
288
|
+
this.readIndex = (this.readIndex + consumed) % this.ring.length;
|
|
289
|
+
this.sourcePhase = produced === output.length ? nextPosition - consumed : 0;
|
|
290
|
+
this.applyNoiseGate(output);
|
|
291
|
+
}
|
|
292
|
+
applyNoiseGate(output) {
|
|
293
|
+
if (this.options.noiseGateThreshold <= 0) return;
|
|
294
|
+
let sumOfSquares = 0;
|
|
295
|
+
for (let index = 0; index < output.length; index += 1) {
|
|
296
|
+
sumOfSquares += output[index] * output[index];
|
|
297
|
+
}
|
|
298
|
+
const rms = Math.sqrt(sumOfSquares / output.length);
|
|
299
|
+
const target = rms < this.options.noiseGateThreshold ? this.options.noiseGateFloor : 1;
|
|
300
|
+
this.gateGain = target > this.gateGain ? target : this.gateGain + (target - this.gateGain) * this.options.noiseGateRelease;
|
|
301
|
+
if (this.gateGain < 0.999) {
|
|
302
|
+
for (let index = 0; index < output.length; index += 1) output[index] *= this.gateGain;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
cubic(p0, p1, p2, p3, amount) {
|
|
306
|
+
const a = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3;
|
|
307
|
+
const b = p0 - 2.5 * p1 + 2 * p2 - 0.5 * p3;
|
|
308
|
+
const c = -0.5 * p0 + 0.5 * p2;
|
|
309
|
+
return ((a * amount + b) * amount + c) * amount + p1;
|
|
310
|
+
}
|
|
311
|
+
rampGain(value) {
|
|
312
|
+
if (!this.context || !this.gainNode) return;
|
|
313
|
+
const now = this.context.currentTime;
|
|
314
|
+
this.gainNode.gain.setValueAtTime(this.gainNode.gain.value, now);
|
|
315
|
+
this.gainNode.gain.linearRampToValueAtTime(value, now + 0.02);
|
|
316
|
+
}
|
|
317
|
+
emit(type) {
|
|
318
|
+
this.options.onDiagnostic?.({ type, state: this.getDiagnosticState() });
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// src/core/flv.ts
|
|
323
|
+
var FLV_HEADER_SIZE = 9;
|
|
324
|
+
var FLV_TAG_HEADER_SIZE = 11;
|
|
325
|
+
var PREVIOUS_TAG_SIZE_LENGTH = 4;
|
|
326
|
+
var AUDIO_TAG_TYPE = 8;
|
|
327
|
+
var FlvG711Demuxer = class {
|
|
328
|
+
constructor(callbacks = {}) {
|
|
329
|
+
this.callbacks = callbacks;
|
|
330
|
+
this.buffer = new Uint8Array(0);
|
|
331
|
+
this.headerParsed = false;
|
|
332
|
+
this.detectedCodec = null;
|
|
333
|
+
this.unsupportedFormats = /* @__PURE__ */ new Set();
|
|
334
|
+
}
|
|
335
|
+
append(chunk) {
|
|
336
|
+
if (chunk.length === 0) return;
|
|
337
|
+
const merged = new Uint8Array(this.buffer.length + chunk.length);
|
|
338
|
+
merged.set(this.buffer);
|
|
339
|
+
merged.set(chunk, this.buffer.length);
|
|
340
|
+
this.buffer = merged;
|
|
341
|
+
this.parse();
|
|
342
|
+
}
|
|
343
|
+
reset() {
|
|
344
|
+
this.buffer = new Uint8Array(0);
|
|
345
|
+
this.headerParsed = false;
|
|
346
|
+
this.detectedCodec = null;
|
|
347
|
+
this.unsupportedFormats.clear();
|
|
348
|
+
}
|
|
349
|
+
parse() {
|
|
350
|
+
let offset = 0;
|
|
351
|
+
if (!this.headerParsed) {
|
|
352
|
+
if (this.buffer.length < FLV_HEADER_SIZE) return;
|
|
353
|
+
if (this.buffer[0] !== 70 || this.buffer[1] !== 76 || this.buffer[2] !== 86) {
|
|
354
|
+
throw new Error("Invalid FLV stream: missing FLV signature");
|
|
355
|
+
}
|
|
356
|
+
const dataOffset = this.buffer[5] * 16777216 + (this.buffer[6] << 16) + (this.buffer[7] << 8) + this.buffer[8] >>> 0;
|
|
357
|
+
if (dataOffset < FLV_HEADER_SIZE) throw new Error("Invalid FLV stream: bad data offset");
|
|
358
|
+
if (this.buffer.length < dataOffset + PREVIOUS_TAG_SIZE_LENGTH) return;
|
|
359
|
+
offset = dataOffset + PREVIOUS_TAG_SIZE_LENGTH;
|
|
360
|
+
this.headerParsed = true;
|
|
361
|
+
}
|
|
362
|
+
while (this.buffer.length - offset >= FLV_TAG_HEADER_SIZE) {
|
|
363
|
+
const tagType = this.buffer[offset];
|
|
364
|
+
const dataSize = this.buffer[offset + 1] << 16 | this.buffer[offset + 2] << 8 | this.buffer[offset + 3];
|
|
365
|
+
const totalSize = FLV_TAG_HEADER_SIZE + dataSize + PREVIOUS_TAG_SIZE_LENGTH;
|
|
366
|
+
if (this.buffer.length - offset < totalSize) break;
|
|
367
|
+
if (tagType === AUDIO_TAG_TYPE && dataSize > 1) {
|
|
368
|
+
const dataOffset = offset + FLV_TAG_HEADER_SIZE;
|
|
369
|
+
const soundFormat = this.buffer[dataOffset] >> 4 & 15;
|
|
370
|
+
const timestamp = this.buffer[offset + 7] * 16777216 + (this.buffer[offset + 4] << 16) + (this.buffer[offset + 5] << 8) + this.buffer[offset + 6] >>> 0;
|
|
371
|
+
this.emitAudio(dataOffset, dataSize, soundFormat, timestamp);
|
|
372
|
+
}
|
|
373
|
+
offset += totalSize;
|
|
374
|
+
}
|
|
375
|
+
if (offset > 0) this.buffer = this.buffer.slice(offset);
|
|
376
|
+
}
|
|
377
|
+
emitAudio(dataOffset, dataSize, soundFormat, timestamp) {
|
|
378
|
+
const codec = soundFormat === 7 ? "pcma" : soundFormat === 8 ? "pcmu" : null;
|
|
379
|
+
if (!codec) {
|
|
380
|
+
if (!this.unsupportedFormats.has(soundFormat)) {
|
|
381
|
+
this.unsupportedFormats.add(soundFormat);
|
|
382
|
+
this.callbacks.onUnsupportedAudioFormat?.(soundFormat);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (codec !== this.detectedCodec) {
|
|
387
|
+
this.detectedCodec = codec;
|
|
388
|
+
this.callbacks.onCodecDetected?.(codec);
|
|
389
|
+
}
|
|
390
|
+
const audio = this.buffer.slice(dataOffset + 1, dataOffset + dataSize);
|
|
391
|
+
if (audio.length > 0) this.callbacks.onAudioData?.(audio, codec, timestamp);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
var FlvAudioExtractor = class {
|
|
395
|
+
constructor(options = {}) {
|
|
396
|
+
this.options = options;
|
|
397
|
+
this.controller = null;
|
|
398
|
+
this.stopped = true;
|
|
399
|
+
}
|
|
400
|
+
async start(url, callbacks = {}) {
|
|
401
|
+
this.stop();
|
|
402
|
+
this.stopped = false;
|
|
403
|
+
const controller = new AbortController();
|
|
404
|
+
this.controller = controller;
|
|
405
|
+
const demuxer = new FlvG711Demuxer(callbacks);
|
|
406
|
+
const fetchImplementation = this.options.fetch ?? globalThis.fetch;
|
|
407
|
+
if (!fetchImplementation) throw new Error("FlvAudioExtractor requires the Fetch API");
|
|
408
|
+
try {
|
|
409
|
+
const response = await fetchImplementation(url, {
|
|
410
|
+
...this.options.requestInit,
|
|
411
|
+
cache: this.options.requestInit?.cache ?? "no-store",
|
|
412
|
+
signal: controller.signal
|
|
413
|
+
});
|
|
414
|
+
if (!response.ok) throw new Error(`FLV stream request failed with HTTP ${response.status}`);
|
|
415
|
+
const reader = response.body?.getReader();
|
|
416
|
+
if (!reader) throw new Error("The FLV response does not expose a readable body");
|
|
417
|
+
while (!this.stopped) {
|
|
418
|
+
const { done, value } = await reader.read();
|
|
419
|
+
if (done) break;
|
|
420
|
+
try {
|
|
421
|
+
demuxer.append(value);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
await reader.cancel();
|
|
424
|
+
throw error;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
429
|
+
if (this.stopped || normalized.name === "AbortError") return;
|
|
430
|
+
callbacks.onError?.(normalized);
|
|
431
|
+
throw normalized;
|
|
432
|
+
} finally {
|
|
433
|
+
if (this.controller === controller) this.controller = null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
stop() {
|
|
437
|
+
this.stopped = true;
|
|
438
|
+
this.controller?.abort();
|
|
439
|
+
this.controller = null;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
var FlvAudioStreamer = class {
|
|
443
|
+
constructor(options = {}) {
|
|
444
|
+
this.player = new G711AudioPlayer(options.player);
|
|
445
|
+
this.extractor = new FlvAudioExtractor(options.extractor);
|
|
446
|
+
}
|
|
447
|
+
get isReady() {
|
|
448
|
+
return this.player.isReady;
|
|
449
|
+
}
|
|
450
|
+
async start(url, callbacks = {}) {
|
|
451
|
+
this.player.init();
|
|
452
|
+
return this.extractor.start(url, {
|
|
453
|
+
...callbacks,
|
|
454
|
+
onAudioData: (data, codec, timestamp) => {
|
|
455
|
+
this.player.pushChunk(data, codec);
|
|
456
|
+
callbacks.onAudioData?.(data, codec, timestamp);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
async resume() {
|
|
461
|
+
await this.player.resume();
|
|
462
|
+
}
|
|
463
|
+
setMuted(muted) {
|
|
464
|
+
this.player.setMuted(muted);
|
|
465
|
+
}
|
|
466
|
+
setVolume(volume) {
|
|
467
|
+
this.player.setVolume(volume);
|
|
468
|
+
}
|
|
469
|
+
flush() {
|
|
470
|
+
this.player.flush();
|
|
471
|
+
}
|
|
472
|
+
stop() {
|
|
473
|
+
this.extractor.stop();
|
|
474
|
+
this.player.flush();
|
|
475
|
+
}
|
|
476
|
+
destroy() {
|
|
477
|
+
this.extractor.stop();
|
|
478
|
+
this.player.destroy();
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
exports.FlvAudioExtractor = FlvAudioExtractor;
|
|
483
|
+
exports.FlvAudioStreamer = FlvAudioStreamer;
|
|
484
|
+
exports.FlvG711Demuxer = FlvG711Demuxer;
|
|
485
|
+
exports.G711AudioPlayer = G711AudioPlayer;
|
|
486
|
+
exports.decodeALaw = decodeALaw;
|
|
487
|
+
exports.decodeG711Chunk = decodeG711Chunk;
|
|
488
|
+
exports.decodeG711ToFloat32 = decodeG711ToFloat32;
|
|
489
|
+
exports.decodeMuLaw = decodeMuLaw;
|
|
490
|
+
exports.pcm16ToFloat32 = pcm16ToFloat32;
|
|
491
|
+
//# sourceMappingURL=index.cjs.map
|
|
492
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/decoder.ts","../src/core/player.ts","../src/core/flv.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/** Supported ITU-T G.711 companding variants. */\nexport type G711CodecType = 'pcma' | 'pcmu'\n\n/**\n * Build the A-law lookup table once at module initialization.\n * Decoding through a table keeps the streaming hot path allocation-free.\n */\nconst ALAW_DECODE_TABLE = new Int16Array(256)\nfor (let encoded = 0; encoded < 256; encoded += 1) {\n const value = encoded ^ 0x55\n let sample = (value & 0x0f) << 4\n const segment = (value & 0x70) >> 4\n\n if (segment === 0) {\n sample += 8\n } else if (segment === 1) {\n sample += 0x108\n } else {\n sample += 0x108\n sample <<= segment - 1\n }\n\n ALAW_DECODE_TABLE[encoded] = value & 0x80 ? sample : -sample\n}\n\n/** Precomputed μ-law to signed 16-bit PCM lookup table. */\nconst MULAW_DECODE_TABLE = new Int16Array(256)\nfor (let encoded = 0; encoded < 256; encoded += 1) {\n const value = (~encoded) & 0xff\n const magnitude = ((value & 0x0f) << 3) + 0x84\n const sample = (magnitude << ((value & 0x70) >> 4)) - 0x84\n MULAW_DECODE_TABLE[encoded] = value & 0x80 ? -sample : sample\n}\n\n/** Decode one G.711 A-law byte to a signed 16-bit PCM sample. */\nexport function decodeALaw(encoded: number): number {\n return ALAW_DECODE_TABLE[encoded & 0xff]\n}\n\n/** Decode one G.711 μ-law byte to a signed 16-bit PCM sample. */\nexport function decodeMuLaw(encoded: number): number {\n return MULAW_DECODE_TABLE[encoded & 0xff]\n}\n\n/** Decode a complete G.711 packet into signed 16-bit PCM samples. */\nexport function decodeG711Chunk(data: Uint8Array, codec: G711CodecType): Int16Array {\n const table = codec === 'pcma' ? ALAW_DECODE_TABLE : MULAW_DECODE_TABLE\n const pcm = new Int16Array(data.length)\n\n for (let index = 0; index < data.length; index += 1) {\n pcm[index] = table[data[index]]\n }\n\n return pcm\n}\n\n/** Convert signed 16-bit PCM into Web Audio's normalized Float32 format. */\nexport function pcm16ToFloat32(pcm: Int16Array): Float32Array {\n const output = new Float32Array(pcm.length)\n for (let index = 0; index < pcm.length; index += 1) {\n output[index] = pcm[index] / 32768\n }\n return output\n}\n\n/** Decode a G.711 packet directly into samples accepted by the Web Audio API. */\nexport function decodeG711ToFloat32(data: Uint8Array, codec: G711CodecType): Float32Array {\n return pcm16ToFloat32(decodeG711Chunk(data, codec))\n}\n\n","import { decodeG711Chunk, type G711CodecType } from './decoder'\n\nconst DEFAULT_SAMPLE_RATE = 8000\n\nexport interface G711AudioPlayerOptions {\n /** G.711 input rate. Telephony streams normally use 8000 Hz. */\n inputSampleRate?: number\n /** Maximum queued audio, in milliseconds. */\n bufferDurationMs?: number\n /** Audio collected before playback starts or resumes after an underflow. */\n prebufferMs?: number\n /** Old audio discarded when the ring buffer overflows. */\n overflowDropMs?: number\n /** ScriptProcessor buffer size; must be one of the Web Audio supported sizes. */\n processorBufferSize?: 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384\n /** Enable Catmull-Rom interpolation instead of linear interpolation. */\n cubicInterpolation?: boolean\n /** RMS value below which the noise gate closes. Set to 0 to disable it. */\n noiseGateThreshold?: number\n /** Gain used while the gate is closed, from 0 to 1. */\n noiseGateFloor?: number\n /** Gate closing speed, from 0 (slow) to 1 (immediate). */\n noiseGateRelease?: number\n /** High-pass cutoff in hertz. Set to 0 to disable the filter. */\n highpassHz?: number\n /** High-shelf start frequency. Set to 0 to disable the filter. */\n highshelfHz?: number\n /** High-shelf gain in dB. */\n highshelfGainDb?: number\n /** Initial output volume. */\n volume?: number\n /** Start muted. */\n muted?: boolean\n /** Emit diagnostic events without logging stream URLs or packet contents. */\n onDiagnostic?: (event: G711DiagnosticEvent) => void\n}\n\nexport interface G711DiagnosticState {\n contextState: AudioContextState | 'not-created'\n muted: boolean\n volume: number\n outputSampleRate: number\n queuedSamples: number\n queuedMilliseconds: number\n inputChunkCount: number\n inputBytes: number\n processCallbackCount: number\n underflowCount: number\n overflowCount: number\n waitingForBuffer: boolean\n destroyed: boolean\n}\n\nexport interface G711DiagnosticEvent {\n type: 'initialized' | 'context-state' | 'underflow' | 'overflow' | 'destroyed'\n state: G711DiagnosticState\n}\n\ninterface NormalizedOptions {\n inputSampleRate: number\n bufferDurationMs: number\n prebufferMs: number\n overflowDropMs: number\n processorBufferSize: 256 | 512 | 1024 | 2048 | 4096 | 8192 | 16384\n cubicInterpolation: boolean\n noiseGateThreshold: number\n noiseGateFloor: number\n noiseGateRelease: number\n highpassHz: number\n highshelfHz: number\n highshelfGainDb: number\n volume: number\n muted: boolean\n onDiagnostic?: (event: G711DiagnosticEvent) => void\n}\n\nfunction clamp(value: number, minimum: number, maximum: number): number {\n return Math.max(minimum, Math.min(maximum, value))\n}\n\nfunction normalizeOptions(options: G711AudioPlayerOptions): NormalizedOptions {\n return {\n inputSampleRate: Math.max(1, options.inputSampleRate ?? DEFAULT_SAMPLE_RATE),\n bufferDurationMs: Math.max(250, options.bufferDurationMs ?? 4000),\n prebufferMs: Math.max(0, options.prebufferMs ?? 120),\n overflowDropMs: Math.max(1, options.overflowDropMs ?? 150),\n processorBufferSize: options.processorBufferSize ?? 2048,\n cubicInterpolation: options.cubicInterpolation ?? true,\n noiseGateThreshold: Math.max(0, options.noiseGateThreshold ?? 0.008),\n noiseGateFloor: clamp(options.noiseGateFloor ?? 0.18, 0, 1),\n noiseGateRelease: clamp(options.noiseGateRelease ?? 0.35, 0, 1),\n highpassHz: Math.max(0, options.highpassHz ?? 180),\n highshelfHz: Math.max(0, options.highshelfHz ?? 2600),\n highshelfGainDb: options.highshelfGainDb ?? 2,\n volume: clamp(options.volume ?? 1, 0, 1),\n muted: options.muted ?? false,\n onDiagnostic: options.onDiagnostic,\n }\n}\n\n/**\n * Browser G.711 player with jitter buffering, resampling and speech-oriented\n * noise filtering. Create one instance per independently controlled stream.\n */\nexport class G711AudioPlayer {\n private readonly options: NormalizedOptions\n private context: AudioContext | null = null\n private gainNode: GainNode | null = null\n private scriptNode: ScriptProcessorNode | null = null\n private nodes: AudioNode[] = []\n\n private outputSampleRate = 44100\n private resampleRatio = 1\n private sourcePhase = 0\n private ring = new Int16Array(0)\n private writeIndex = 0\n private readIndex = 0\n private gateGain = 1\n private waitingForBuffer = true\n private destroyed = false\n private inputChunkCount = 0\n private inputBytes = 0\n private processCallbackCount = 0\n private underflowCount = 0\n private overflowCount = 0\n\n constructor(options: G711AudioPlayerOptions = {}) {\n this.options = normalizeOptions(options)\n }\n\n get isReady(): boolean {\n return this.context !== null && this.context.state !== 'closed'\n }\n\n getDiagnosticState(): G711DiagnosticState {\n return {\n contextState: this.context?.state ?? 'not-created',\n muted: this.options.muted,\n volume: this.options.volume,\n outputSampleRate: this.outputSampleRate,\n queuedSamples: this.readable,\n queuedMilliseconds: Math.round((this.readable / this.options.inputSampleRate) * 1000),\n inputChunkCount: this.inputChunkCount,\n inputBytes: this.inputBytes,\n processCallbackCount: this.processCallbackCount,\n underflowCount: this.underflowCount,\n overflowCount: this.overflowCount,\n waitingForBuffer: this.waitingForBuffer,\n destroyed: this.destroyed,\n }\n }\n\n /** Initialize Web Audio. Call this from a click/tap handler when possible. */\n init(): void {\n if (this.context) return\n if (this.destroyed) throw new Error('This G711AudioPlayer has already been destroyed')\n if (typeof AudioContext === 'undefined') {\n throw new Error('G711AudioPlayer requires a browser with the Web Audio API')\n }\n\n this.context = new AudioContext()\n this.outputSampleRate = this.context.sampleRate\n this.resampleRatio = this.outputSampleRate / this.options.inputSampleRate\n const capacity = Math.ceil((this.options.inputSampleRate * this.options.bufferDurationMs) / 1000)\n this.ring = new Int16Array(Math.max(capacity, 8))\n\n this.context.onstatechange = () => this.emit('context-state')\n this.gainNode = this.context.createGain()\n this.gainNode.gain.value = this.options.muted ? 0 : this.options.volume\n\n let previous: AudioNode\n this.scriptNode = this.context.createScriptProcessor(this.options.processorBufferSize, 0, 1)\n this.scriptNode.onaudioprocess = (event) => this.process(event)\n previous = this.scriptNode\n\n if (this.options.highpassHz > 0) {\n const highpass = this.context.createBiquadFilter()\n highpass.type = 'highpass'\n highpass.frequency.value = this.options.highpassHz\n highpass.Q.value = 0.7\n previous.connect(highpass)\n this.nodes.push(highpass)\n previous = highpass\n }\n\n if (this.options.highshelfHz > 0) {\n const highshelf = this.context.createBiquadFilter()\n highshelf.type = 'highshelf'\n highshelf.frequency.value = this.options.highshelfHz\n highshelf.gain.value = this.options.highshelfGainDb\n previous.connect(highshelf)\n this.nodes.push(highshelf)\n previous = highshelf\n }\n\n previous.connect(this.gainNode)\n this.gainNode.connect(this.context.destination)\n this.emit('initialized')\n }\n\n /** Resume an AudioContext suspended by the browser autoplay policy. */\n async resume(): Promise<void> {\n if (!this.context) this.init()\n await this.context?.resume()\n }\n\n /** Decode and enqueue one raw PCMA or PCMU packet. */\n pushChunk(data: Uint8Array, codec: G711CodecType): void {\n if (!this.context) this.init()\n if (this.destroyed || data.length === 0) return\n\n if (this.context?.state === 'suspended') void this.context.resume()\n const pcm = decodeG711Chunk(data, codec)\n this.inputChunkCount += 1\n this.inputBytes += data.byteLength\n\n let sourceIndex = 0\n while (sourceIndex < pcm.length) {\n if (this.writable === 0) {\n const drop = Math.min(\n this.readable,\n Math.max(1, Math.floor((this.options.inputSampleRate * this.options.overflowDropMs) / 1000)),\n )\n this.readIndex = (this.readIndex + drop) % this.ring.length\n this.sourcePhase = 0\n this.overflowCount += 1\n this.emit('overflow')\n }\n\n const count = Math.min(pcm.length - sourceIndex, this.ring.length - this.writeIndex, this.writable)\n this.ring.set(pcm.subarray(sourceIndex, sourceIndex + count), this.writeIndex)\n this.writeIndex = (this.writeIndex + count) % this.ring.length\n sourceIndex += count\n }\n }\n\n /** Smoothly mute or unmute the output. */\n setMuted(muted: boolean): void {\n this.options.muted = muted\n this.rampGain(muted ? 0 : this.options.volume)\n }\n\n /** Set output volume in the inclusive range 0..1. */\n setVolume(volume: number): void {\n this.options.volume = clamp(volume, 0, 1)\n if (!this.options.muted) this.rampGain(this.options.volume)\n }\n\n /** Drop queued audio and rebuild the initial jitter buffer. */\n flush(): void {\n this.writeIndex = 0\n this.readIndex = 0\n this.sourcePhase = 0\n this.gateGain = 1\n this.waitingForBuffer = true\n }\n\n /** Disconnect audio nodes and close the underlying AudioContext. */\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.scriptNode?.disconnect()\n if (this.scriptNode) this.scriptNode.onaudioprocess = null\n for (const node of this.nodes) node.disconnect()\n this.gainNode?.disconnect()\n if (this.context && this.context.state !== 'closed') {\n this.context.onstatechange = null\n void this.context.close()\n }\n this.scriptNode = null\n this.nodes = []\n this.gainNode = null\n this.context = null\n this.emit('destroyed')\n }\n\n private get readable(): number {\n if (this.ring.length === 0) return 0\n return this.writeIndex >= this.readIndex\n ? this.writeIndex - this.readIndex\n : this.ring.length - this.readIndex + this.writeIndex\n }\n\n private get writable(): number {\n return this.ring.length - this.readable - 1\n }\n\n private sampleAt(offset: number): number {\n return this.ring[(this.readIndex + offset) % this.ring.length]\n }\n\n private process(event: AudioProcessingEvent): void {\n this.processCallbackCount += 1\n const output = event.outputBuffer.getChannelData(0)\n const prebufferSamples = Math.floor((this.options.inputSampleRate * this.options.prebufferMs) / 1000)\n\n if (this.options.muted) {\n output.fill(0)\n return\n }\n\n if (this.waitingForBuffer) {\n if (this.readable < Math.max(4, prebufferSamples)) {\n output.fill(0)\n return\n }\n this.waitingForBuffer = false\n }\n\n if (this.readable < 4) {\n output.fill(0)\n this.waitingForBuffer = true\n this.sourcePhase = 0\n this.underflowCount += 1\n this.emit('underflow')\n return\n }\n\n const available = this.readable\n let produced = 0\n for (; produced < output.length; produced += 1) {\n const position = this.sourcePhase + produced / this.resampleRatio\n const index = Math.floor(position)\n const fraction = position - index\n if (index >= available) break\n\n if (this.options.cubicInterpolation && index >= 1 && index + 2 < available) {\n output[produced] = this.cubic(\n this.sampleAt(index - 1),\n this.sampleAt(index),\n this.sampleAt(index + 1),\n this.sampleAt(index + 2),\n fraction,\n ) / 32768\n } else if (index + 1 < available) {\n const current = this.sampleAt(index)\n output[produced] = (current + (this.sampleAt(index + 1) - current) * fraction) / 32768\n } else {\n output[produced] = this.sampleAt(index) / 32768\n }\n }\n\n if (produced < output.length) {\n output.fill(0, produced)\n this.waitingForBuffer = true\n this.underflowCount += 1\n this.emit('underflow')\n }\n\n const nextPosition = this.sourcePhase + produced / this.resampleRatio\n const consumed = Math.min(Math.floor(nextPosition), available)\n this.readIndex = (this.readIndex + consumed) % this.ring.length\n this.sourcePhase = produced === output.length ? nextPosition - consumed : 0\n this.applyNoiseGate(output)\n }\n\n private applyNoiseGate(output: Float32Array): void {\n if (this.options.noiseGateThreshold <= 0) return\n let sumOfSquares = 0\n for (let index = 0; index < output.length; index += 1) {\n sumOfSquares += output[index] * output[index]\n }\n const rms = Math.sqrt(sumOfSquares / output.length)\n const target = rms < this.options.noiseGateThreshold ? this.options.noiseGateFloor : 1\n this.gateGain = target > this.gateGain\n ? target\n : this.gateGain + (target - this.gateGain) * this.options.noiseGateRelease\n\n if (this.gateGain < 0.999) {\n for (let index = 0; index < output.length; index += 1) output[index] *= this.gateGain\n }\n }\n\n private cubic(p0: number, p1: number, p2: number, p3: number, amount: number): number {\n const a = -0.5 * p0 + 1.5 * p1 - 1.5 * p2 + 0.5 * p3\n const b = p0 - 2.5 * p1 + 2 * p2 - 0.5 * p3\n const c = -0.5 * p0 + 0.5 * p2\n return ((a * amount + b) * amount + c) * amount + p1\n }\n\n private rampGain(value: number): void {\n if (!this.context || !this.gainNode) return\n const now = this.context.currentTime\n this.gainNode.gain.setValueAtTime(this.gainNode.gain.value, now)\n this.gainNode.gain.linearRampToValueAtTime(value, now + 0.02)\n }\n\n private emit(type: G711DiagnosticEvent['type']): void {\n this.options.onDiagnostic?.({ type, state: this.getDiagnosticState() })\n }\n}\n\n","import type { G711CodecType } from './decoder'\nimport { G711AudioPlayer, type G711AudioPlayerOptions } from './player'\n\nconst FLV_HEADER_SIZE = 9\nconst FLV_TAG_HEADER_SIZE = 11\nconst PREVIOUS_TAG_SIZE_LENGTH = 4\nconst AUDIO_TAG_TYPE = 0x08\n\nexport interface FlvAudioCallbacks {\n onAudioData?: (data: Uint8Array, codec: G711CodecType, timestamp: number) => void\n onCodecDetected?: (codec: G711CodecType) => void\n onUnsupportedAudioFormat?: (soundFormat: number) => void\n onError?: (error: Error) => void\n}\n\n/**\n * Incremental FLV demuxer focused on G.711 audio tags.\n * Feed it arbitrary network chunks; incomplete headers/tags remain buffered.\n */\nexport class FlvG711Demuxer {\n private buffer = new Uint8Array(0)\n private headerParsed = false\n private detectedCodec: G711CodecType | null = null\n private readonly unsupportedFormats = new Set<number>()\n\n constructor(private readonly callbacks: FlvAudioCallbacks = {}) {}\n\n append(chunk: Uint8Array): void {\n if (chunk.length === 0) return\n const merged = new Uint8Array(this.buffer.length + chunk.length)\n merged.set(this.buffer)\n merged.set(chunk, this.buffer.length)\n this.buffer = merged\n this.parse()\n }\n\n reset(): void {\n this.buffer = new Uint8Array(0)\n this.headerParsed = false\n this.detectedCodec = null\n this.unsupportedFormats.clear()\n }\n\n private parse(): void {\n let offset = 0\n\n if (!this.headerParsed) {\n if (this.buffer.length < FLV_HEADER_SIZE) return\n if (this.buffer[0] !== 0x46 || this.buffer[1] !== 0x4c || this.buffer[2] !== 0x56) {\n throw new Error('Invalid FLV stream: missing FLV signature')\n }\n\n const dataOffset = (\n this.buffer[5] * 0x1000000\n + (this.buffer[6] << 16)\n + (this.buffer[7] << 8)\n + this.buffer[8]\n ) >>> 0\n if (dataOffset < FLV_HEADER_SIZE) throw new Error('Invalid FLV stream: bad data offset')\n if (this.buffer.length < dataOffset + PREVIOUS_TAG_SIZE_LENGTH) return\n offset = dataOffset + PREVIOUS_TAG_SIZE_LENGTH\n this.headerParsed = true\n }\n\n while (this.buffer.length - offset >= FLV_TAG_HEADER_SIZE) {\n const tagType = this.buffer[offset]\n const dataSize = (this.buffer[offset + 1] << 16)\n | (this.buffer[offset + 2] << 8)\n | this.buffer[offset + 3]\n const totalSize = FLV_TAG_HEADER_SIZE + dataSize + PREVIOUS_TAG_SIZE_LENGTH\n if (this.buffer.length - offset < totalSize) break\n\n if (tagType === AUDIO_TAG_TYPE && dataSize > 1) {\n const dataOffset = offset + FLV_TAG_HEADER_SIZE\n const soundFormat = (this.buffer[dataOffset] >> 4) & 0x0f\n const timestamp = (\n this.buffer[offset + 7] * 0x1000000\n + (this.buffer[offset + 4] << 16)\n + (this.buffer[offset + 5] << 8)\n + this.buffer[offset + 6]\n ) >>> 0\n this.emitAudio(dataOffset, dataSize, soundFormat, timestamp)\n }\n\n offset += totalSize\n }\n\n if (offset > 0) this.buffer = this.buffer.slice(offset)\n }\n\n private emitAudio(dataOffset: number, dataSize: number, soundFormat: number, timestamp: number): void {\n const codec: G711CodecType | null = soundFormat === 7 ? 'pcma' : soundFormat === 8 ? 'pcmu' : null\n if (!codec) {\n if (!this.unsupportedFormats.has(soundFormat)) {\n this.unsupportedFormats.add(soundFormat)\n this.callbacks.onUnsupportedAudioFormat?.(soundFormat)\n }\n return\n }\n\n if (codec !== this.detectedCodec) {\n this.detectedCodec = codec\n this.callbacks.onCodecDetected?.(codec)\n }\n\n const audio = this.buffer.slice(dataOffset + 1, dataOffset + dataSize)\n if (audio.length > 0) this.callbacks.onAudioData?.(audio, codec, timestamp)\n }\n}\n\nexport interface FlvAudioExtractorOptions {\n fetch?: typeof fetch\n requestInit?: Omit<RequestInit, 'signal'>\n}\n\n/** Fetch and incrementally demux a live HTTP-FLV stream. */\nexport class FlvAudioExtractor {\n private controller: AbortController | null = null\n private stopped = true\n\n constructor(private readonly options: FlvAudioExtractorOptions = {}) {}\n\n async start(url: string, callbacks: FlvAudioCallbacks = {}): Promise<void> {\n this.stop()\n this.stopped = false\n const controller = new AbortController()\n this.controller = controller\n const demuxer = new FlvG711Demuxer(callbacks)\n const fetchImplementation = this.options.fetch ?? globalThis.fetch\n if (!fetchImplementation) throw new Error('FlvAudioExtractor requires the Fetch API')\n\n try {\n const response = await fetchImplementation(url, {\n ...this.options.requestInit,\n cache: this.options.requestInit?.cache ?? 'no-store',\n signal: controller.signal,\n })\n if (!response.ok) throw new Error(`FLV stream request failed with HTTP ${response.status}`)\n const reader = response.body?.getReader()\n if (!reader) throw new Error('The FLV response does not expose a readable body')\n\n while (!this.stopped) {\n const { done, value } = await reader.read()\n if (done) break\n try {\n demuxer.append(value)\n } catch (error) {\n await reader.cancel()\n throw error\n }\n }\n } catch (error) {\n const normalized = error instanceof Error ? error : new Error(String(error))\n if (this.stopped || normalized.name === 'AbortError') return\n callbacks.onError?.(normalized)\n throw normalized\n } finally {\n // A newer start() call may already own a different controller.\n if (this.controller === controller) this.controller = null\n }\n }\n\n stop(): void {\n this.stopped = true\n this.controller?.abort()\n this.controller = null\n }\n}\n\nexport interface FlvAudioStreamerOptions {\n player?: G711AudioPlayerOptions\n extractor?: FlvAudioExtractorOptions\n}\n\n/** One-step HTTP-FLV → G.711 → Web Audio streaming pipeline. */\nexport class FlvAudioStreamer {\n readonly player: G711AudioPlayer\n private readonly extractor: FlvAudioExtractor\n\n constructor(options: FlvAudioStreamerOptions = {}) {\n this.player = new G711AudioPlayer(options.player)\n this.extractor = new FlvAudioExtractor(options.extractor)\n }\n\n get isReady(): boolean {\n return this.player.isReady\n }\n\n async start(url: string, callbacks: FlvAudioCallbacks = {}): Promise<void> {\n this.player.init()\n return this.extractor.start(url, {\n ...callbacks,\n onAudioData: (data, codec, timestamp) => {\n this.player.pushChunk(data, codec)\n callbacks.onAudioData?.(data, codec, timestamp)\n },\n })\n }\n\n async resume(): Promise<void> {\n await this.player.resume()\n }\n\n setMuted(muted: boolean): void {\n this.player.setMuted(muted)\n }\n\n setVolume(volume: number): void {\n this.player.setVolume(volume)\n }\n\n flush(): void {\n this.player.flush()\n }\n\n stop(): void {\n this.extractor.stop()\n this.player.flush()\n }\n\n destroy(): void {\n this.extractor.stop()\n this.player.destroy()\n }\n}\n"]}
|