diff --git a/src/modules/audioStreams.ts b/src/modules/audioStreams.ts index c362cb3..95759a5 100644 --- a/src/modules/audioStreams.ts +++ b/src/modules/audioStreams.ts @@ -1,182 +1,194 @@ -import { AudioPlayer, createAudioPlayer, createAudioResource, StreamType, VoiceConnection } from "@discordjs/voice"; -import { AudioMixer } from "node-audio-mixer"; -import { PassThrough, Readable } from "stream"; +import { + AudioPlayer, + 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 { - private queue: Readable[] = []; - private isPlaying = false; - private mixer: MixedStream; + private queue: Readable[] = []; + private isPlaying = false; + private mixer: MixedStream; - constructor(mixer: MixedStream) { - this.mixer = mixer; - } + constructor(mixer: MixedStream) { + this.mixer = mixer; + } - public enqueue(resource: Readable) { - this.queue.push(resource); - this.processQueue(); - } + public enqueue(resource: Readable) { + this.queue.push(resource); + this.processQueue(); + } - private async processQueue() { - if (this.isPlaying || this.queue.length === 0) return; + private async processQueue() { + if (this.isPlaying || this.queue.length === 0) return; - this.isPlaying = true; - const nextStream = this.queue.shift(); + this.isPlaying = true; + const nextStream = this.queue.shift(); - try { - if (nextStream) { - await this.mixer.playStream(nextStream); - } - } catch (e) { - console.error("Queue error:", e); - } finally { - this.isPlaying = false; - this.processQueue(); - } - } + try { + if (nextStream) { + await this.mixer.playStream(nextStream); + } + } catch (e) { + console.error('Queue error:', e); + } finally { + this.isPlaying = false; + this.processQueue(); + } + } - public clear() { - this.queue = []; - } + public clear() { + this.queue = []; + } } export class MixedStream { - public readonly player: AudioPlayer; - private mixer: AudioMixer; - private output: PassThrough; - private silenceInterval: NodeJS.Timeout; + public readonly player: AudioPlayer; + private mixer: AudioMixer; + private output: PassThrough; + private silenceInterval: NodeJS.Timeout; - private queues: Map = new Map(); + private queues: Map = new Map(); - public constructor() { - this.player = createAudioPlayer(); + public constructor() { + this.player = createAudioPlayer(); - this.mixer = new AudioMixer({ - channels: 2, - bitDepth: 16, - sampleRate: 48000, - autoClose: false, - generateSilence: false // does not work :< - }); + this.mixer = new AudioMixer({ + channels: 2, + bitDepth: 16, + sampleRate: 48000, + autoClose: false, + generateSilence: false // does not work :< + }); - const silenceInput = this.mixer.createAudioInput({ - channels: 2, - sampleRate: 48000, - bitDepth: 16, - volume: 100 - }) + 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); + 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); + this.output = new PassThrough({ highWaterMark: 1024 * 16 }); + this.mixer.pipe(this.output); - const resource = createAudioResource(this.output, { - inputType: StreamType.Raw - }); + const resource = createAudioResource(this.output, { + inputType: StreamType.Raw + }); - this.player.play(resource); - this.player.on('error', error => { - console.error('Error: ', error.message); - }); - } + this.player.play(resource); + this.player.on('error', (error) => { + console.error('Error: ', error.message); + }); + } - public getQueue(name: string): StreamQueue { - let queue = this.queues.get(name); - if (!queue) { - queue = new StreamQueue(this); - this.queues.set(name, queue); - } - return queue; - } + public getQueue(name: string): StreamQueue { + let queue = this.queues.get(name); + if (!queue) { + queue = new StreamQueue(this); + this.queues.set(name, queue); + } + return queue; + } - public playStream(source: Readable): Promise { - return new Promise((resolve) => { - const mixerInput = this.mixer.createAudioInput({ - channels: 2, - sampleRate: 48000, - bitDepth: 16, - volume: 100, - }); + public playStream(source: Readable): Promise { + return new Promise((resolve) => { + const mixerInput = this.mixer.createAudioInput({ + channels: 2, + sampleRate: 48000, + bitDepth: 16, + volume: 100 + }); - const transcoder = new prism.FFmpeg({ - args: [ - '-analyzeduration', '0', - '-loglevel', '0', - '-f', 's16le', - '-ar', '48000', - '-ac', '2', - ], - }); - let totalBytes = 0; + const transcoder = new prism.FFmpeg({ + args: [ + '-analyzeduration', + '0', + '-loglevel', + '0', + '-f', + 's16le', + '-ar', + '48000', + '-ac', + '2' + ] + }); + let totalBytes = 0; - transcoder.on('data', (chunk: Buffer) => { - totalBytes += chunk.length; - }); + transcoder.on('data', (chunk: Buffer) => { + totalBytes += chunk.length; + }); - transcoder.on('end', () => { - const durationMs = (totalBytes / 192000) * 1000; + transcoder.on('end', () => { + const durationMs = (totalBytes / 192000) * 1000; - setTimeout(() => { - source.unpipe(transcoder); - transcoder.unpipe(mixerInput); - this.mixer.removeAudioinput(mixerInput); - transcoder.destroy(); - resolve(); - }, durationMs); - }) + setTimeout(() => { + source.unpipe(transcoder); + transcoder.unpipe(mixerInput); + this.mixer.removeAudioinput(mixerInput); + transcoder.destroy(); + resolve(); + }, durationMs); + }); - transcoder.on('error', () => { - this.mixer.removeAudioinput(mixerInput); - resolve(); - }); + transcoder.on('error', () => { + this.mixer.removeAudioinput(mixerInput); + resolve(); + }); - source.pipe(transcoder).pipe(mixerInput); - }); - } + source.pipe(transcoder).pipe(mixerInput); + }); + } - public destroy(): void { - this.player.stop(); - this.output.destroy(); - this.mixer.destroy(); - clearInterval(this.silenceInterval); - } + public destroy(): void { + this.player.stop(); + this.output.destroy(); + this.mixer.destroy(); + clearInterval(this.silenceInterval); + } } export class AudioStreamManager { - private streams = new WeakMap(); + private streams = new WeakMap(); - public getOrCreateStream(conn: VoiceConnection): MixedStream { - let stream = this.streams.get(conn); - if (stream) return stream; + public getOrCreateStream(conn: VoiceConnection): MixedStream { + let stream = this.streams.get(conn); + if (stream) return stream; - stream = new MixedStream(); - this.streams.set(conn, stream); - conn.subscribe(stream.player); - return stream; - } + stream = new MixedStream(); + this.streams.set(conn, stream); + conn.subscribe(stream.player); + return stream; + } - public destroyStream(conn: VoiceConnection): void { - const stream = this.streams.get(conn); - if (stream) { - stream.destroy(); - this.streams.delete(conn); - } - } + public destroyStream(conn: VoiceConnection): void { + const stream = this.streams.get(conn); + if (stream) { + stream.destroy(); + this.streams.delete(conn); + } + } - /* + /* singleton logic */ - static #instance: AudioStreamManager | null = null; + static #instance: AudioStreamManager | null = null; - public static get get(): AudioStreamManager { - if (!AudioStreamManager.#instance) AudioStreamManager.#instance = new AudioStreamManager(); - return AudioStreamManager.#instance; - } + public static get get(): AudioStreamManager { + if (!AudioStreamManager.#instance) + AudioStreamManager.#instance = new AudioStreamManager(); + return AudioStreamManager.#instance; + } }