I’m working on a Discord bot that can search and play music from YouTube. The basic functionality works fine - it searches YouTube and plays the first result. But I’m having trouble setting up a proper queue system where songs play automatically one after another.
My current approach tries to use the ‘speaking’ event to detect when audio finishes, but this doesn’t work reliably since the voice channel reference isn’t available at startup. Also, users can switch channels with commands, which breaks this setup.
const YOUTUBE_API = "<api_key>";
var discordjs = require("discord.js");
var youtubedl = require('ytdl-core');
var httpRequest = require('superagent');
var client = new discordjs.Client();
var currentVoiceChannel = null;
var musicQueue = [];
client.on('ready', function() {
console.log('Bot is online');
});
client.on('message', function(msg) {
var args = msg.content.split(' ');
var cmd = args[0].toLowerCase();
var params = args.slice(1);
switch (cmd) {
case "hello":
msg.reply("Hi there!");
break;
case "!connect":
ConnectToChannel(params[0], msg);
break;
case "!queue":
AddToQueue(params.join(" "), msg);
break;
}
});
function ConnectToChannel(channelName) {
if (currentVoiceChannel) {
currentVoiceChannel.disconnect();
}
var targetChannel = FindChannelByName(channelName);
return targetChannel.join();
}
function AddToQueue(searchQuery) {
SearchYoutube(searchQuery);
}
function SearchYoutube(keywords) {
var apiUrl = 'https://www.googleapis.com/youtube/v3/search' + `?part=snippet&q=${escape(keywords)}&key=${YOUTUBE_API}`;
httpRequest(apiUrl, (err, res) => {
if (!err && res.statusCode == 200) {
var data = res.body;
for (var result of data.items) {
if (result.id.kind === 'youtube#video') {
AddSongToQueue(result.id.videoId);
break;
}
}
}
});
}
function AddSongToQueue(videoId) {
var videoUrl = `https://www.youtube.com/watch?v=${videoId}`;
musicQueue.push(videoUrl);
if (musicQueue.length === 1) {
PlayNextSong();
}
}
function PlayNextSong() {
if (musicQueue.length === 0) return;
var nextSong = musicQueue[0];
var audioStream = youtubedl(nextSong, {filter: 'audioonly'});
var dispatcher = client.voiceConnections.first().playStream(audioStream);
dispatcher.on('end', () => {
musicQueue.shift();
PlayNextSong();
});
}
What’s the best way to handle automatic queue playback when songs finish? Should I be using different events or a completely different approach?