fix: refactor AudioMixer logic

This commit is contained in:
2026-01-17 20:48:10 -03:00
parent c7ff5d3659
commit 042fde30c4
+24 -68
View File
@@ -1,5 +1,6 @@
import { import {
AudioPlayer, AudioPlayer,
AudioPlayerStatus,
createAudioPlayer, createAudioPlayer,
createAudioResource, createAudioResource,
StreamType, StreamType,
@@ -11,7 +12,7 @@ import { PassThrough, Readable } from 'stream';
import prism from 'prism-media'; import prism from 'prism-media';
const DURATION_EXTRA_MS = 500; const DURATION_EXTRA_MS = 1000;
export class StreamQueue { export class StreamQueue {
private queue: Readable[] = []; private queue: Readable[] = [];
@@ -65,8 +66,7 @@ export class StreamQueue {
export class MixedStream { export class MixedStream {
public readonly player: AudioPlayer; public readonly player: AudioPlayer;
private mixer: AudioMixer; private mixer: AudioMixer;
private output: PassThrough; private output: PassThrough | undefined;
private silenceInterval: NodeJS.Timeout;
private queues: Map<string, StreamQueue> = new Map(); private queues: Map<string, StreamQueue> = new Map();
@@ -78,34 +78,10 @@ export class MixedStream {
bitDepth: 16, bitDepth: 16,
sampleRate: 48000, sampleRate: 48000,
autoClose: false, autoClose: false,
generateSilence: false // does not work :< generateSilence: true
}); });
const silenceInput = this.mixer.createAudioInput({ this.setupPipeline();
channels: 2,
sampleRate: 48000,
bitDepth: 16,
volume: 100
});
const chunk = Buffer.alloc(3840);
this.silenceInterval = setInterval(() => {
if (silenceInput.writable && silenceInput.writableLength < 3840 * 10) {
silenceInput.write(chunk);
}
}, 20);
this.output = new PassThrough({ highWaterMark: 1024 * 16 });
this.mixer.pipe(this.output);
const resource = createAudioResource(this.output, {
inputType: StreamType.Raw
});
this.player.play(resource);
this.player.on('error', (error) => {
console.error('Error: ', error.message);
});
} }
public getQueue(name: string): StreamQueue { public getQueue(name: string): StreamQueue {
@@ -121,6 +97,10 @@ export class MixedStream {
completion: Promise<void>; completion: Promise<void>;
stop: () => void; stop: () => void;
} { } {
if (this.player.state.status === AudioPlayerStatus.Idle) {
this.setupPipeline();
}
let stopCallback: () => void = () => {}; let stopCallback: () => void = () => {};
const completion = new Promise<void>((resolve) => { const completion = new Promise<void>((resolve) => {
const mixerInput = this.mixer.createAudioInput({ const mixerInput = this.mixer.createAudioInput({
@@ -154,41 +134,15 @@ export class MixedStream {
const cleanup = () => { const cleanup = () => {
if (resolved) return; if (resolved) return;
resolved = true; resolved = true;
try {
source.unpipe(transcoder);
source.destroy();
} catch (e) {
console.error('Error destroying source:', e);
}
try { source.unpipe(transcoder);
transcoder.unpipe(mixerInput); source.destroy();
transcoder.destroy();
} catch (e) {
console.error('Error destroying transcoder:', e);
}
try { transcoder.unpipe(mixerInput);
this.mixer.removeAudioinput(mixerInput); transcoder.destroy();
} catch (e) {
console.error('Error removing audio input:', e);
}
try { this.mixer.removeAudioinput(mixerInput);
// eslint-disable-next-line @typescript-eslint/no-explicit-any mixerInput.destroy();
if (typeof (this.mixer as any).removeAudioInput === 'function') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.mixer as any).removeAudioInput(mixerInput);
}
} catch (_) {
/* ignore */
}
try {
mixerInput.destroy();
} catch (e) {
console.error('Error destroying mixer input:', e);
}
resolve(); resolve();
}; };
@@ -216,24 +170,26 @@ export class MixedStream {
public destroy(): void { public destroy(): void {
this.player.stop(); this.player.stop();
this.output.destroy(); if (this.output) this.output.destroy();
this.mixer.destroy(); this.mixer.destroy();
clearInterval(this.silenceInterval);
} }
public flush(): void { public flush(): void {
this.player.stop(); this.player.stop();
this.setupPipeline();
}
this.mixer.unpipe(this.output); private setupPipeline(): void {
this.output.destroy(); if (this.output) {
this.mixer.unpipe(this.output);
this.output = new PassThrough({ highWaterMark: 1024 * 16 }); this.output.destroy();
}
this.output = new PassThrough({ highWaterMark: 1024 * 256 });
this.mixer.pipe(this.output); this.mixer.pipe(this.output);
const resource = createAudioResource(this.output, { const resource = createAudioResource(this.output, {
inputType: StreamType.Raw inputType: StreamType.Raw
}); });
this.player.play(resource); this.player.play(resource);
} }
} }