import { AudioPlayer, AudioPlayerStatus, AudioResource, createAudioPlayer, VoiceConnection } from "@discordjs/voice"; import { Voice } from "../db/generated/prisma/enums"; import TTSTypecastModel from "../tts/typecast"; import TTSPapagoModel from "../tts/papago"; import { Guild } from "discord.js"; import { getOrCreateVoiceConnection } from "./util"; import TTSModelBase from "../tts"; import { DiscordUserProfile } from "../db/generated/prisma/client"; import { nyaize } from "../utils/nyaize"; import { OutputHandler } from "../utils/outputHandler"; import TTSSupertonicModel from "../tts/supertonic"; class VoiceQueue { private connection: VoiceConnection; private list: AudioResource[]; private currentPlayer?: AudioPlayer; constructor(connection: VoiceConnection) { this.connection = connection; this.list = []; } public static fromConnection(connection: VoiceConnection): VoiceQueue { return (connection as any).queue ??= new VoiceQueue(connection); } private play() { if (!this.list[0]) return; const player = this.currentPlayer = createAudioPlayer(); this.connection.subscribe(player); player.once(AudioPlayerStatus.Idle, this.next.bind(this)); player.play(this.list[0]); } public enqueue(resource: AudioResource) { this.list.push(resource); if (this.list.length == 1) { this.play(); return; } } public next() { this.currentPlayer?.removeAllListeners(AudioPlayerStatus.Idle); this.list.shift(); this.play(); } public hasNext(): boolean { return !!this.list[0]; } } export type PlayVoiceOptions = { supertonicStyleId?: string, }; export async function playVoice( guild: Guild, profile: DiscordUserProfile, voice: Voice, text: string, options?: PlayVoiceOptions, ) { if (profile.nya) text = nyaize(text); try { let connection = await getOrCreateVoiceConnection(guild); if (!connection) throw new Error("Yaeju is not joined VoiceChat"); if (voice == "TypeCast" && !profile.canTypecast) { throw new Error(`the user ${profile.userId} is can't use typecast voice`); } let voiceBuffer: Buffer if (voice == "TypeCast") { const content = TTSTypecastModel.instance.ttsify(text); if (!content.length) throw new Error("Empty content"); voiceBuffer = await TTSTypecastModel.instance.getMemcachedVoice( TTSTypecastModel.instance.createRequestId(content) ); } else if (voice == "Supertonic") { const content = TTSSupertonicModel.instance.ttsify(text); if (!content.length) throw new Error("Empty content"); voiceBuffer = await TTSSupertonicModel.instance.getMemcachedVoice( TTSSupertonicModel.instance.createRequestId(content, options?.supertonicStyleId) ); } else if (voice == "Papago") { const content = TTSPapagoModel.instance.ttsify(text); if (!content.length) throw new Error("Empty content"); voiceBuffer = await TTSPapagoModel.instance.getMemcachedVoice( TTSPapagoModel.instance.createRequestId(content) ); } else { throw new Error(`Unknown voice type: ${voice}`); } VoiceQueue.fromConnection(connection).enqueue( TTSModelBase.bufferToAudioResource(voiceBuffer) ); } catch(err) { OutputHandler.errorLog("[PlayVoice Error]", err); throw new Error(err as any); } } export async function skipCurrentVoice(guild: Guild): Promise { let connection = await getOrCreateVoiceConnection(guild); if (!connection) throw new Error("Yaeju is not joined VoiceChat"); const vqueue = VoiceQueue.fromConnection(connection); if (vqueue.hasNext()) { vqueue.next(); return true; } return false; }