fix: refactor AudioMixer logic

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