@discordjs/voiceを使用して音声を再生する
ここでは@discordjs/voiceを使用した音声の再生を行ってみる。
code:bash
npm i discord.js @discordjs/voice @discordjs/opus
# yarn add discord.js @discordjs/voice @discordjs/opus
また、ffmpegがインストールされている必要がある
ffmpeg-staticを使うこともできる
code:index.js
const { joinVoiceChannel, entersState, VoiceConnectionStatus, createAudioResource, StreamType, createAudioPlayer, AudioPlayerStatus, NoSubscriberBehavior, generateDependencyReport } = require("@discordjs/voice");
console.log(generateDependencyReport());
const Discord = require("discord.js");
const client = new Discord.Client({
intents: Discord.Intents.FLAGS.GUILDS | Discord.Intents.FLAGS.GUILD_VOICE_STATES //多分これでいい
});
async function play(interaction) {
const guild = interaction.guild;
const member = await guild.members.fetch(interaction.member.id);
const memberVC = member.voice.channel;
if (!memberVC) {
return interaction.reply({
content: "接続先のVCが見つかりません。",
ephemeral: true,
});
}
if (!memberVC.joinable) {
return interaction.reply({
content: "VCに接続できません。",
ephemeral: true,
});
}
if (!memberVC.speakable) {
return interaction.reply({
content: "VCで音声を再生する権限がありません。",
ephemeral: true,
});
}
const status = ["●Loading Sounds...", ●Connecting to ${memberVC}...];
const p = interaction.reply(status.join("\n"));
const connection = joinVoiceChannel({
guildId: guild.id,
channelId: memberVC.id,
adapterCreator: guild.voiceAdapterCreator,
selfMute: false,
});
{
inputType: StreamType.Arbitrary,
});
const player = createAudioPlayer({
behaviors: {
noSubscriber: NoSubscriberBehavior.Pause,
},
});
player.play(resource);
const promises = [];
promises.push(entersState(player, AudioPlayerStatus.AutoPaused, 1000 * 10).then(() => status0 += "Done!")); promises.push(entersState(connection, VoiceConnectionStatus.Ready, 1000 * 10).then(() => status1 += "Done!")); await Promise.race(promises);
await p;
connection.subscribe(player);
await entersState(player, AudioPlayerStatus.Playing, 100);
await interaction.editReply("Playing");
await entersState(player, AudioPlayerStatus.Idle, 2 ** 31 - 1);
await interaction.editReply("End");
connection.destroy();
}
/**
*
* @param {Discord.CommandInteraction} interaction
*/
async function onPlay(interaction) {
try {
await play(interaction);
} catch (err) {
if (interaction.replied) {
interaction.editReply("エラーが発生しました。").catch(() => { });
} else {
interaction.reply("エラーが発生しました。").catch(() => { });
}
throw err;
}
}
/**
*
* @param {Discord.CommandInteraction} interaction
*/
async function onCommandInteraction(interaction) {
if (interaction.commandName === "play") {
return onPlay(interaction);
}
}
/**
*
* @param {Discord.Interaction} interaction
*/
async function onInteraction(interaction) {
if (interaction.isCommand()) {
return onCommandInteraction(interaction);
}
}
client.on("interactionCreate", interaction => onInteraction(interaction).catch(err => console.error(err)));
client.login(process.env.DISCORD_TOKEN)
解説
generateDependencyReport
@discordjs/voiceの使用しているライブラリとそのバージョンが確認できる。
joinVoiceChannel
ボイスチャンネルに接続する
guildId,channelId,adapterCreatorは必須
返り値はVoiceConnection
createAudioResource
第一引数はstringまたはReadableStream
第二引数はオプション
Arbitraryと言われてもよくわからないが、ffmpeg様がいい感じに頑張ってくれる。
この関数の返す、AudioResourceは再利用不可。
createAudioPlayer
引数は省略可能
ここではsubscriberがいなくなった場合にはpauseするよう設定している。
この関数の返すAudioPlayerは再利用前提で設計されている。
entersState
VoiceConnectionとAudioPlayerの状態の変更を監視する関数。
監視している状態に遷移するとPromiseが解決される。
第一引数は監視するオブジェクト
第二引数は期待する状態
その状態になるとPromiseが解決される
第三引数はタイムアウト時間(ms単位)
時間内に期待する状態に遷移しない場合Promiseはrejectされる。
省略できない。
VoiceConnectionStatusがSignallingのまま(Readyにならない場合)
GUILD_VOICE_STATESを忘れている可能性がある
code:register.js
const { Client, ClientApplication } = require("discord.js");
/**
*
* @param {Client} client
* @param {import("discord.js").ApplicationCommandData[]} commands
* @param {import("discord.js").Snowflake} guildID
* @returns {Promise<import("@discordjs/collection").Collection<string,import("discord.js").ApplicationCommand>>}
*/
async function register(client, commands, guildID) {
if (guildID == null) {
return client.application.commands.set(commands);
}
const guild = await client.guilds.fetch(guildID);
return guild.commands.set(commands);
}
const ping = {
name: "play",
description: "play music!",
};
const client = new Client({
intents: 0,
});
client.token = process.env.DISCORD_TOKEN;
async function main() {
client.application = new ClientApplication(client, {});
await client.application.fetch();
await register(client, commands, process.argv2); console.log("registration succeed!");
}
main().catch(err => console.error(err));