Discord Music App


Project Background

This project was to create a discord music application that streams youtube audio directly into a voice call, given a link by any user. It also has functionality for music queues, skipping, fetching metadata about the current track, looping, and more. For some other projects, this application was used to integrate Discord with external servers.


File Structure
          
            |-commands/
            |  |-joinvc.js
            |  |-leavevc.js
            |  |-loop.js
            |  |-perms.js
            |  |-play.js
            |  |-playing.js
            |  |-queue.js
            |  |-skip.js
            |-events/
            |  |-interactionCreate.js
            |  |-ready.js
            |-modules/
            |  |-audioQueue.js
            |  |-audioTrack.js
            |-index.js
            |-config.js
            |-util.js
          
        

Code
index.js
          
            // Modules
            const config = require('./config.js');
            const fs = require('fs');
            const path = require('path');
            const { Client, Intents, Collection } = require('discord.js');

            // Define logging function
            const log = console.log;

            // Create discord bot client
            const client = new Client({
              intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_VOICE_STATES],
            });

            // Retrieve command data
            client.commands = new Collection();
            const commandsPath = path.join(__dirname, 'commands');
            const commandFiles = fs
              .readdirSync(commandsPath)
              .filter((file) => file.endsWith('.js'));
            // Initialize commands
            for (const file of commandFiles) {
              const filePath = path.join(commandsPath, file);
              const command = require(filePath);
              client.commands.set(command.meta.name, command);
            }

            // Retrieve event data
            const eventsPath = path.join(__dirname, 'events');
            const eventFiles = fs
              .readdirSync(eventsPath)
              .filter((file) => file.endsWith('.js'));
            // Initialize events
            for (const file of eventFiles) {
              const filePath = path.join(eventsPath, file);
              const event = require(filePath);
              if (event.once) {
                client.once(event.name, (...args) => event.execute(...args));
              } else {
                client.on(event.name, (...args) => event.execute(...args));
              }
            }

            // Login to discord
            client.login(config.meta.token);
          
        
joinvc.js
          
            // Modules
            const config = require('../config.js');
            const { SlashCommandBuilder } = require('@discordjs/builders');
            const { joinVoiceChannel, getVoiceConnection } = require('@discordjs/voice');
            const AudioQueue = require('../modules/audioQueue.js');
            const { MessageEmbed } = require('discord.js');

            // Define logging function
            const log = console.log;

            // Command
            module.exports = {
              disabled: false,
              commandPermLevel: 0,
              meta: new SlashCommandBuilder()
                .setName('joinvc')
                .setDescription('Make the bot join your voice channel'),
              async execute(interaction, member) {
                // Find member voice channel if it exists
                const { channelId, guild } = member.voice;
                if (!channelId) {
                  // Member is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('You are not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Get voice channel connection if it exists
                if (getVoiceConnection(guild.id)) {
                  // Bot is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Bot is already in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Create voice channel connection
                const connection = joinVoiceChannel({
                  channelId,
                  guildId: guild.id,
                  adapterCreator: guild.voiceAdapterCreator,
                });
                const queue = new AudioQueue(connection, interaction.channel);
                queue.init();
                connection.queue = queue;

                // Notify user of success
                await interaction.reply({
                  embeds: [
                    new MessageEmbed()
                      .setColor(config.colors.success)
                      .setTitle('Command Execution')
                      .setAuthor({
                        name: member.displayName,
                        iconURL: member.displayAvatarURL({ dynamic: true }),
                      })
                      .setDescription(
                        `Successfully joined \`${member.voice.channel.name}\``
                      )
                      .setTimestamp(),
                  ],
                });
              },
            };
          
        
leavevc.js
          
            // Modules
            const config = require('../config.js');
            const { SlashCommandBuilder } = require('@discordjs/builders');
            const { getVoiceConnection } = require('@discordjs/voice');
            const { MessageEmbed } = require('discord.js');

            // Define logging function
            const log = console.log;

            // Command
            module.exports = {
              disabled: false,
              commandPermLevel: 0,
              meta: new SlashCommandBuilder()
                .setName('leavevc')
                .setDescription('Make the bot leave your voice channel'),
              async execute(interaction, member) {
                // Find member voice channel if it exists
                const { channelId, guild } = member.voice;
                if (!channelId) {
                  // Member is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('You are not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Get voice channel connection if it exists
                const connection = getVoiceConnection(guild.id);
                if (!connection) {
                  // Bot is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Bot is not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Destroy connnection
                connection.destroy();

                // Notify user of success
                await interaction.reply({
                  embeds: [
                    new MessageEmbed()
                      .setColor(config.colors.success)
                      .setTitle('Command Execution')
                      .setAuthor({
                        name: member.displayName,
                        iconURL: member.displayAvatarURL({ dynamic: true }),
                      })
                      .setDescription(`Successfully left \`${member.voice.channel.name}\``)
                      .setTimestamp(),
                  ],
                });
              },
            };
          
        
loop.js
          
            // Modules
            const config = require('../config.js');
            const { SlashCommandBuilder } = require('@discordjs/builders');
            const { getVoiceConnection } = require('@discordjs/voice');
            const { MessageEmbed } = require('discord.js');

            // Define logging function
            const log = console.log;

            // Command
            module.exports = {
              disabled: false,
              commandPermLevel: 0,
              meta: new SlashCommandBuilder()
                .setName('loop')
                .setDescription('Loop the queue or the current track')
                .addStringOption((option) => {
                  return option
                    .setName('input')
                    .setDescription('Whether to loop the queue or the current track')
                    .setRequired(true)
                    .addChoices(
                      { name: 'queue', value: 'queue' },
                      { name: 'track', value: 'track' }
                    );
                }),
              async execute(interaction, member) {
                // Find member voice channel if it exists
                const { channelId, guild } = member.voice;
                if (!channelId) {
                  // Member is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('You are not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Get voice channel connection if it exists
                const connection = getVoiceConnection(guild.id);
                if (!connection) {
                  // Bot is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Bot is not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                const loopTrack = interaction.options.getString('input') === 'track';
                if (loopTrack) {
                  // Loop the current track if one is playing
                  const looped = connection.queue.loopCurrentTrack();
                  if (!looped) {
                    // No track is currently playing
                    await interaction.reply({
                      embeds: [
                        new MessageEmbed()
                          .setColor(config.colors.failure)
                          .setTitle('Command Failure')
                          .setAuthor({
                            name: member.displayName,
                            iconURL: member.displayAvatarURL({ dynamic: true }),
                          })
                          .setDescription('No track is currently playing')
                          .setTimestamp(),
                      ],
                    });
                    return;
                  }

                  // Notify user of success
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.success)
                        .setTitle('Command Execution')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Looped current track'),
                    ],
                  });
                } else {
                  // Loop the entire queue
                  const looped = connection.queue.loopQueue();

                  // Notify user of success
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.success)
                        .setTitle('Command Execution')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription(`${looped ? 'Looped' : 'Un-looped'} the queue`),
                    ],
                  });
                }
              },
            };
          
        
perms.js
          
            // Modules
            const config = require('../config.js');
            const { SlashCommandBuilder } = require('@discordjs/builders');
            const { MessageEmbed } = require('discord.js');

            // Define logging function
            const log = console.log;

            // Command
            module.exports = {
              disabled: false,
              commandPermLevel: 0,
              meta: new SlashCommandBuilder()
                .setName('perms')
                .setDescription('Check your permissions level'),
              async execute(interaction, member) {
                // Check if member has perms
                const { permLevel } = member;
                const hasPerms = permLevel > 0;
                await interaction.reply({
                  embeds: [
                    new MessageEmbed()
                      .setColor(hasPerms ? config.colors.success : config.colors.failure)
                      .setTitle('Permissions Check')
                      .setAuthor({
                        name: member.displayName,
                        iconURL: member.displayAvatarURL({ dynamic: true }),
                      })
                      .setDescription(
                        `You ${hasPerms ? '**do**' : '**do not**'} have permissions`
                      )
                      .addField(
                        'Permissions Level',
                        `[${permLevel}] ${config.authNames[permLevel]}`
                      )
                      .setTimestamp(),
                  ],
                });
              },
            };
          
        
play.js
          
            // Modules
            const config = require('../config.js');
            const { SlashCommandBuilder } = require('@discordjs/builders');
            const { getVoiceConnection } = require('@discordjs/voice');
            const { MessageEmbed } = require('discord.js');
            const ytdl = require('ytdl-core');
            const validUrl = require('valid-url');
            const luxon = require('luxon');
            const AudioTrack = require('../modules/audioTrack.js');

            // Define logging function
            const log = console.log;

            // Quality helper function
            const opusCodes = [250, 249, 251]; // Priorities of which opus format to choose first
            function isOpusAvailable(info) {
              let availableCode;
              opusCodes.some((code) => {
                try {
                  ytdl.chooseFormat(info.formats, { quality: code });
                  availableCode = code;
                  return true;
                } catch (err) {}
              });
              return availableCode;
            }

            // Duration helper function
            function getDurationString(sec) {
              return luxon.Duration.fromObject({ seconds: sec }).toFormat('hh:mm:ss');
            }

            // Command
            module.exports = {
              disabled: false,
              commandPermLevel: 0,
              meta: new SlashCommandBuilder()
                .setName('play')
                .setDescription('Play audio from a youtube link')
                .addStringOption((option) => {
                  return option
                    .setName('link')
                    .setDescription('The youtube link from which to play audio')
                    .setRequired(true);
                }),
              async execute(interaction, member) {
                // Find member voice channel if it exists
                const { channelId, guild } = member.voice;
                if (!channelId) {
                  // Member is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('You are not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Get voice channel connection if it exists
                const connection = getVoiceConnection(guild.id);
                if (!connection) {
                  // Bot is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Bot is not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Validate url
                const url = interaction.options.getString('link');
                if (!validUrl.isWebUri(url)) {
                  // Link is not a valid url
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Link is not a valid URL')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Validate youtube url
                let info;
                try {
                  info = await ytdl.getInfo(url);
                } catch (err) {
                  log(err);
                  // Link is not a valid youtube url
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Link is not a valid YouTube URL')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Check if opus quality is available
                const opusCode = isOpusAvailable(info);

                // Add metadata
                info.url = url;
                info.opusCode = opusCode;
                const duration = getDurationString(
                  parseInt(info.videoDetails.lengthSeconds)
                );
                info.duration = duration;
                //info.endTime = getEndTime(parseInt(info.videoDetails.lengthSeconds));

                // Add to queue
                connection.queue.addTrack(
                  new AudioTrack({
                    member,
                    metadata: info,
                  })
                );

                // Notify user of success
                await interaction.reply({
                  embeds: [
                    new MessageEmbed()
                      .setColor(config.colors.success)
                      .setTitle('Command Execution')
                      .setAuthor({
                        name: member.displayName,
                        iconURL: member.displayAvatarURL({ dynamic: true }),
                      })
                      .setDescription(`Queued Track`)
                      .setFields(
                        { name: 'URL', value: url },
                        { name: 'Title', value: info.videoDetails.title },
                        {
                          name: 'Author',
                          value: info.videoDetails.author.name,
                          inline: true,
                        },
                        {
                          name: 'Duration',
                          value: duration,
                          inline: true,
                        },
                        {
                          name: 'High Quality',
                          value: opusCode ? config.emojis.success : config.emojis.failure,
                          inline: true,
                        }
                      )
                      .setTimestamp(),
                  ],
                });
              },
            };
          
        
playing.js
          
            // Modules
            const config = require('../config.js');
            const { SlashCommandBuilder } = require('@discordjs/builders');
            const { getVoiceConnection } = require('@discordjs/voice');
            const { MessageEmbed } = require('discord.js');
            const luxon = require('luxon');

            // Define logging function
            const log = console.log;

            // Time left helper function
            function getTimeLeft(endTime) {
            return endTime.diff(luxon.DateTime.now()).toFormat('hh:mm:ss');
            }

            // Command
            module.exports = {
            disabled: false,
            commandPermLevel: 0,
            meta: new SlashCommandBuilder()
              .setName('playing')
              .setDescription('Get metadata about the playing track'),
            async execute(interaction, member) {
              // Find member voice channel if it exists
              const { channelId, guild } = member.voice;
              if (!channelId) {
                // Member is not in voice channel
                await interaction.reply({
                  embeds: [
                    new MessageEmbed()
                      .setColor(config.colors.failure)
                      .setTitle('Command Failure')
                      .setAuthor({
                        name: member.displayName,
                        iconURL: member.displayAvatarURL({ dynamic: true }),
                      })
                      .setDescription('You are not currently in a voice channel')
                      .setTimestamp(),
                  ],
                });
                return;
              }

              // Get voice channel connection if it exists
              const connection = getVoiceConnection(guild.id);
              if (!connection) {
                // Bot is not in voice channel
                await interaction.reply({
                  embeds: [
                    new MessageEmbed()
                      .setColor(config.colors.failure)
                      .setTitle('Command Failure')
                      .setAuthor({
                        name: member.displayName,
                        iconURL: member.displayAvatarURL({ dynamic: true }),
                      })
                      .setDescription('Bot is not currently in a voice channel')
                      .setTimestamp(),
                  ],
                });
                return;
              }

              // Get playing track from queue
              const firstTrack = connection.queue.getNextTrack();
              if (!firstTrack) {
                // No audio resource is playing
                await interaction.reply({
                  embeds: [
                    new MessageEmbed()
                      .setColor(config.colors.failure)
                      .setTitle('Command Failure')
                      .setAuthor({
                        name: member.displayName,
                        iconURL: member.displayAvatarURL({ dynamic: true }),
                      })
                      .setDescription('No track is currently playing')
                      .setTimestamp(),
                  ],
                });
                return;
              }
              const { metadata: data } = firstTrack;

              // Notify user of success
              await interaction.reply({
                embeds: [
                  new MessageEmbed()
                    .setColor(config.colors.success)
                    .setTitle('Command Execution')
                    .setAuthor({
                      name: member.displayName,
                      iconURL: member.displayAvatarURL({ dynamic: true }),
                    })
                    .setDescription(`Track information`)
                    .setFields(
                      { name: 'URL', value: data.url },
                      { name: 'Title', value: data.videoDetails.title },
                      {
                        name: 'Author',
                        value: data.videoDetails.author.name,
                        inline: true,
                      },
                      {
                        name: 'Time Left',
                        value: getTimeLeft(data.endTime),
                        inline: true,
                      },
                      {
                        name: 'High Quality',
                        value: data.opusCode
                          ? config.emojis.success
                          : config.emojis.failure,
                        inline: true,
                      }
                    )
                    .setTimestamp(),
                ],
              });
            },
            };
          
        
queue.js
          
            // Modules
            const config = require('../config.js');
            const { SlashCommandBuilder } = require('@discordjs/builders');
            const { getVoiceConnection } = require('@discordjs/voice');
            const { MessageEmbed } = require('discord.js');

            // Define logging function
            const log = console.log;

            // Command
            module.exports = {
              disabled: false,
              commandPermLevel: 0,
              meta: new SlashCommandBuilder()
                .setName('queue')
                .setDescription('Get the current queue'),
              async execute(interaction, member) {
                // Find member voice channel if it exists
                const { channelId, guild } = member.voice;
                if (!channelId) {
                  // Member is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('You are not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Get voice channel connection if it exists
                const connection = getVoiceConnection(guild.id);
                if (!connection) {
                  // Bot is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Bot is not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Get queue and construct response
                const fields = [];
                const queuedTracks = connection.queue.getTracks();
                queuedTracks.forEach((track, index) => {
                  const { metadata: data, member } = track;
                  fields.push({
                    name: `**${index + 1}**${track.playing ? ' - Now Playing' : ''}`,
                    value: `Title: \`${data.videoDetails.title}\` | Duration: \`${data.duration}\` | User: ${member}`,
                  });
                });

                // Notify user of success
                const embed = new MessageEmbed()
                  .setColor(config.colors.success)
                  .setTitle('Command Execution')
                  .setAuthor({
                    name: member.displayName,
                    iconURL: member.displayAvatarURL({ dynamic: true }),
                  })
                  .setTimestamp();
                if (fields.length > 0) {
                  embed.setDescription(`Current queue`).setFields(fields);
                } else {
                  embed.setDescription('No queued tracks');
                }
                await interaction.reply({
                  embeds: [embed],
                });
              },
            };
          
        
queue.js
          
            // Modules
            const config = require('../config.js');
            const { SlashCommandBuilder } = require('@discordjs/builders');
            const { getVoiceConnection } = require('@discordjs/voice');
            const { MessageEmbed } = require('discord.js');

            // Define logging function
            const log = console.log;

            // Command
            module.exports = {
              disabled: false,
              commandPermLevel: 0,
              meta: new SlashCommandBuilder()
                .setName('skip')
                .setDescription('Skip the current track'),
              async execute(interaction, member) {
                // Find member voice channel if it exists
                const { channelId, guild } = member.voice;
                if (!channelId) {
                  // Member is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('You are not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Get voice channel connection if it exists
                const connection = getVoiceConnection(guild.id);
                if (!connection) {
                  // Bot is not in voice channel
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('Bot is not currently in a voice channel')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Skip the current track if one is playing
                const skipped = connection.queue.skipCurrentTrack();
                if (!skipped) {
                  // No track is currently playing
                  await interaction.reply({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Command Failure')
                        .setAuthor({
                          name: member.displayName,
                          iconURL: member.displayAvatarURL({ dynamic: true }),
                        })
                        .setDescription('No track is currently playing')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }

                // Notify user of success
                await interaction.reply({
                  embeds: [
                    new MessageEmbed()
                      .setColor(config.colors.success)
                      .setTitle('Command Execution')
                      .setAuthor({
                        name: member.displayName,
                        iconURL: member.displayAvatarURL({ dynamic: true }),
                      })
                      .setDescription(`Skipped current track`),
                  ],
                });
              },
            };
          
        
audioQueue.js
          
            // Modules
            const config = require('../config.js');
            const {
              demuxProbe,
              createAudioPlayer,
              createAudioResource,
              AudioPlayerStatus,
              VoiceConnectionStatus,
            } = require('@discordjs/voice');
            const { MessageEmbed } = require('discord.js');
            const ytdl = require('ytdl-core');
            const luxon = require('luxon');

            // End time helper function
            function getEndTime(sec) {
              return luxon.DateTime.now().plus({ seconds: sec });
            }

            // Audio queue
            module.exports = class AudioQueue {
              constructor(connection, channel) {
                // Create internal structures
                this.initialized = false;
                this.connection = connection;
                this.channel = channel;
                this.queue = [];
                this.looped = false;
              }

              init() {
                if (this.initialized) {
                  log('Audio queue already initialized');
                  return;
                }

                // Create audio player
                const player = createAudioPlayer();
                this.connection.subscribe(player);
                this.player = player;

                // Attach error handler
                player.on('error', (err) => {
                  log(`Error with audio playback: ${err}`);

                  // Stop audio playback
                  player.stop(true);

                  // Notify user of error
                  this.channel.send({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.failure)
                        .setTitle('Audio Failure')
                        .setDescription('An error has occurred with audio playback')
                        .setTimestamp(),
                    ],
                  });
                });

                this.connection.on('stateChange', (oldState, newState) => {
                  // console.log(
                  //   `Voice connection transitioned from ${oldState.status} to ${newState.status}`
                  // );

                  if (
                    oldState.status === VoiceConnectionStatus.Ready &&
                    newState.status === VoiceConnectionStatus.Connecting
                  ) {
                    this.connection.configureNetworking();
                  }
                });

                // player.on('stateChange', (oldState, newState) => {
                //   console.log(
                //     `Audio player transitioned from ${oldState.status} to ${newState.status}`
                //   );
                // });

                // Attach idle handler
                player.on(AudioPlayerStatus.Idle, () => {
                  // Remove first track if not looped
                  if (!this.getNextTrack().looped) {
                    const lastTrack = this.removeFirstTrack();

                    // Add the track to the end of the queue if queue is looped
                    if (this.looped) {
                      lastTrack.playing = false;
                      this.addTrack(lastTrack);
                    }
                  }

                  // Play next track
                  this.playNextTrack();
                });
              }

              async playNextTrack() {
                // Get next track if it exists
                const nextTrack = this.getNextTrack();
                if (!nextTrack) {
                  // Notify user that queue has finished
                  this.channel.send({
                    embeds: [
                      new MessageEmbed()
                        .setColor(config.colors.info)
                        .setTitle('Queue Status')
                        .setDescription('All tracks in the queue have finished playing')
                        .setTimestamp(),
                    ],
                  });
                  return;
                }
                nextTrack.metadata.endTime = getEndTime(
                  parseInt(nextTrack.metadata.videoDetails.lengthSeconds)
                );
                nextTrack.playing = true;

                // Check for opus quality
                const ytdlOptions = { highWaterMark: 1048576 * 32 };
                if (nextTrack.metadata.opusCode) {
                  ytdlOptions.quality = nextTrack.metadata.opusCode.toString();
                }

                // Download video from youtube link
                const { stream, type } = await demuxProbe(
                  ytdl.downloadFromInfo(nextTrack.metadata, ytdlOptions)
                );
                const resource = createAudioResource(stream, {
                  inputType: type,
                  metadata: nextTrack.metadata,
                });

                // Play resource
                this.player.play(resource);
              }

              addTrack(info) {
                this.queue.push(info);

                // Play track if first track
                if (this.queue.length === 1) {
                  this.playNextTrack();
                }
              }

              getNextTrack() {
                return this.queue[0];
              }

              getTracks() {
                return this.queue;
              }

              skipCurrentTrack() {
                if (this.queue.length <= 0) {
                  return false;
                }

                // Unloop the current track (just in case)
                this.getNextTrack().looped = false;

                // Stop the player (the idle handler will automatically move to the next track)
                this.player.stop(true);
                return true;
              }

              loopCurrentTrack() {
                if (this.queue.length <= 0) {
                  return false;
                }

                // Stop the player (the idle handler will automatically move to the next track)
                this.getNextTrack().looped = true;
                return true;
              }

              loopQueue() {
                this.looped = !this.looped;
                return this.looped;
              }

              removeFirstTrack() {
                return this.queue.shift();
              }

              removeTrackAt(pos) {
                return this.queue.splice(pos, 1);
              }

              clear() {
                this.queue = [];
              }
            };
          
        
audioTrack.js
          
            // Audio track
            module.exports = class AudioTrack {
              constructor(options) {
                // Create internal structures
                const { member, metadata } = options;
                this.member = member;
                this.metadata = metadata;
                this.playing = false;
                this.looped = false;
              }
            };
          
        

Images
Thingy