fix: refactor AudioMixer logic
This commit is contained in:
+17
-61
@@ -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.unpipe(transcoder);
|
||||||
source.destroy();
|
source.destroy();
|
||||||
} catch (e) {
|
|
||||||
console.error('Error destroying source:', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
transcoder.unpipe(mixerInput);
|
transcoder.unpipe(mixerInput);
|
||||||
transcoder.destroy();
|
transcoder.destroy();
|
||||||
} catch (e) {
|
|
||||||
console.error('Error destroying transcoder:', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.mixer.removeAudioinput(mixerInput);
|
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();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupPipeline(): void {
|
||||||
|
if (this.output) {
|
||||||
this.mixer.unpipe(this.output);
|
this.mixer.unpipe(this.output);
|
||||||
this.output.destroy();
|
this.output.destroy();
|
||||||
|
}
|
||||||
this.output = new PassThrough({ highWaterMark: 1024 * 16 });
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user