145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
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 { type 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 {
|
|
const 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 voiceBufferList: Buffer[];
|
|
if (voice == "TypeCast") {
|
|
const content = TTSTypecastModel.instance.ttsify(text);
|
|
|
|
if (!content.length) throw new Error("Empty content");
|
|
|
|
voiceBufferList = await Promise.all(
|
|
content
|
|
.split("\n")
|
|
.map((content) =>
|
|
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");
|
|
|
|
voiceBufferList = await Promise.all(
|
|
content
|
|
.split("\n")
|
|
.map((content) =>
|
|
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");
|
|
|
|
voiceBufferList = await Promise.all(
|
|
content
|
|
.split("\n")
|
|
.map((content) =>
|
|
TTSPapagoModel.instance.getMemcachedVoice(
|
|
TTSPapagoModel.instance.createRequestId(content),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
throw new Error(`Unknown voice type: ${voice}`);
|
|
}
|
|
|
|
for (const voiceBuffer of voiceBufferList) {
|
|
VoiceQueue.fromConnection(connection).enqueue(
|
|
TTSModelBase.bufferToAudioResource(voiceBuffer),
|
|
);
|
|
}
|
|
} catch (err) {
|
|
OutputHandler.errorLog("[PlayVoice Error]", err);
|
|
throw new Error(err as any, { cause: err });
|
|
}
|
|
}
|
|
|
|
export async function skipCurrentVoice(guild: Guild): Promise<boolean> {
|
|
const connection = await getOrCreateVoiceConnection(guild);
|
|
if (!connection) throw new Error("YaejuNyang is not joined VoiceChat");
|
|
|
|
const vqueue = VoiceQueue.fromConnection(connection);
|
|
if (vqueue.hasNext()) {
|
|
vqueue.next();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|