style: run format:apply

This commit is contained in:
2026-01-13 17:50:33 -03:00
parent 02949e8b16
commit 83569a27e7
+154 -142
View File
@@ -1,182 +1,194 @@
import { AudioPlayer, createAudioPlayer, createAudioResource, StreamType, VoiceConnection } from "@discordjs/voice"; import {
import { AudioMixer } from "node-audio-mixer"; AudioPlayer,
import { PassThrough, Readable } from "stream"; createAudioPlayer,
createAudioResource,
StreamType,
VoiceConnection
} from '@discordjs/voice';
import { AudioMixer } from 'node-audio-mixer';
import { PassThrough, Readable } from 'stream';
import prism from "prism-media"; import prism from 'prism-media';
export class StreamQueue { export class StreamQueue {
private queue: Readable[] = []; private queue: Readable[] = [];
private isPlaying = false; private isPlaying = false;
private mixer: MixedStream; private mixer: MixedStream;
constructor(mixer: MixedStream) { constructor(mixer: MixedStream) {
this.mixer = mixer; this.mixer = mixer;
} }
public enqueue(resource: Readable) { public enqueue(resource: Readable) {
this.queue.push(resource); this.queue.push(resource);
this.processQueue(); this.processQueue();
} }
private async processQueue() { private async processQueue() {
if (this.isPlaying || this.queue.length === 0) return; if (this.isPlaying || this.queue.length === 0) return;
this.isPlaying = true; this.isPlaying = true;
const nextStream = this.queue.shift(); const nextStream = this.queue.shift();
try { try {
if (nextStream) { if (nextStream) {
await this.mixer.playStream(nextStream); await this.mixer.playStream(nextStream);
} }
} catch (e) { } catch (e) {
console.error("Queue error:", e); console.error('Queue error:', e);
} finally { } finally {
this.isPlaying = false; this.isPlaying = false;
this.processQueue(); this.processQueue();
} }
} }
public clear() { public clear() {
this.queue = []; this.queue = [];
} }
} }
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;
private silenceInterval: NodeJS.Timeout; private silenceInterval: NodeJS.Timeout;
private queues: Map<string, StreamQueue> = new Map(); private queues: Map<string, StreamQueue> = new Map();
public constructor() { public constructor() {
this.player = createAudioPlayer(); this.player = createAudioPlayer();
this.mixer = new AudioMixer({ this.mixer = new AudioMixer({
channels: 2, channels: 2,
bitDepth: 16, bitDepth: 16,
sampleRate: 48000, sampleRate: 48000,
autoClose: false, autoClose: false,
generateSilence: false // does not work :< generateSilence: false // does not work :<
}); });
const silenceInput = this.mixer.createAudioInput({ const silenceInput = this.mixer.createAudioInput({
channels: 2, channels: 2,
sampleRate: 48000, sampleRate: 48000,
bitDepth: 16, bitDepth: 16,
volume: 100 volume: 100
}) });
const chunk = Buffer.alloc(3840); const chunk = Buffer.alloc(3840);
this.silenceInterval = setInterval(() => { this.silenceInterval = setInterval(() => {
if (silenceInput.writable && silenceInput.writableLength < 3840 * 10) { if (silenceInput.writable && silenceInput.writableLength < 3840 * 10) {
silenceInput.write(chunk); silenceInput.write(chunk);
} }
}, 20); }, 20);
this.output = new PassThrough({ highWaterMark: 1024 * 16 }); this.output = new PassThrough({ highWaterMark: 1024 * 16 });
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);
this.player.on('error', error => { this.player.on('error', (error) => {
console.error('Error: ', error.message); console.error('Error: ', error.message);
}); });
} }
public getQueue(name: string): StreamQueue { public getQueue(name: string): StreamQueue {
let queue = this.queues.get(name); let queue = this.queues.get(name);
if (!queue) { if (!queue) {
queue = new StreamQueue(this); queue = new StreamQueue(this);
this.queues.set(name, queue); this.queues.set(name, queue);
} }
return queue; return queue;
} }
public playStream(source: Readable): Promise<void> { public playStream(source: Readable): Promise<void> {
return new Promise((resolve) => { return new Promise((resolve) => {
const mixerInput = this.mixer.createAudioInput({ const mixerInput = this.mixer.createAudioInput({
channels: 2, channels: 2,
sampleRate: 48000, sampleRate: 48000,
bitDepth: 16, bitDepth: 16,
volume: 100, volume: 100
}); });
const transcoder = new prism.FFmpeg({ const transcoder = new prism.FFmpeg({
args: [ args: [
'-analyzeduration', '0', '-analyzeduration',
'-loglevel', '0', '0',
'-f', 's16le', '-loglevel',
'-ar', '48000', '0',
'-ac', '2', '-f',
], 's16le',
}); '-ar',
let totalBytes = 0; '48000',
'-ac',
'2'
]
});
let totalBytes = 0;
transcoder.on('data', (chunk: Buffer) => { transcoder.on('data', (chunk: Buffer) => {
totalBytes += chunk.length; totalBytes += chunk.length;
}); });
transcoder.on('end', () => { transcoder.on('end', () => {
const durationMs = (totalBytes / 192000) * 1000; const durationMs = (totalBytes / 192000) * 1000;
setTimeout(() => { setTimeout(() => {
source.unpipe(transcoder); source.unpipe(transcoder);
transcoder.unpipe(mixerInput); transcoder.unpipe(mixerInput);
this.mixer.removeAudioinput(mixerInput); this.mixer.removeAudioinput(mixerInput);
transcoder.destroy(); transcoder.destroy();
resolve(); resolve();
}, durationMs); }, durationMs);
}) });
transcoder.on('error', () => { transcoder.on('error', () => {
this.mixer.removeAudioinput(mixerInput); this.mixer.removeAudioinput(mixerInput);
resolve(); resolve();
}); });
source.pipe(transcoder).pipe(mixerInput); source.pipe(transcoder).pipe(mixerInput);
}); });
} }
public destroy(): void { public destroy(): void {
this.player.stop(); this.player.stop();
this.output.destroy(); this.output.destroy();
this.mixer.destroy(); this.mixer.destroy();
clearInterval(this.silenceInterval); clearInterval(this.silenceInterval);
} }
} }
export class AudioStreamManager { export class AudioStreamManager {
private streams = new WeakMap<VoiceConnection, MixedStream>(); private streams = new WeakMap<VoiceConnection, MixedStream>();
public getOrCreateStream(conn: VoiceConnection): MixedStream { public getOrCreateStream(conn: VoiceConnection): MixedStream {
let stream = this.streams.get(conn); let stream = this.streams.get(conn);
if (stream) return stream; if (stream) return stream;
stream = new MixedStream(); stream = new MixedStream();
this.streams.set(conn, stream); this.streams.set(conn, stream);
conn.subscribe(stream.player); conn.subscribe(stream.player);
return stream; return stream;
} }
public destroyStream(conn: VoiceConnection): void { public destroyStream(conn: VoiceConnection): void {
const stream = this.streams.get(conn); const stream = this.streams.get(conn);
if (stream) { if (stream) {
stream.destroy(); stream.destroy();
this.streams.delete(conn); this.streams.delete(conn);
} }
} }
/* /*
singleton logic singleton logic
*/ */
static #instance: AudioStreamManager | null = null; static #instance: AudioStreamManager | null = null;
public static get get(): AudioStreamManager { public static get get(): AudioStreamManager {
if (!AudioStreamManager.#instance) AudioStreamManager.#instance = new AudioStreamManager(); if (!AudioStreamManager.#instance)
return AudioStreamManager.#instance; AudioStreamManager.#instance = new AudioStreamManager();
} return AudioStreamManager.#instance;
}
} }