const { createAudioPlayer, createAudioResource, AudioPlayerStatus, joinVoiceChannel } = require('@discordjs/voice'); const fs = require('fs'); const { spawn } = require('child_process') const queueMap = new Map(); const playerMap = new Map(); const currentTrackMap = new Map(); const repeatMap = new Map(); const voiceChannelMap = new Map(); function addToQueue(guildId, filePath, title, voiceChannel, url, requester, avatarURL, thumbnailUrl) { if (!queueMap.has(guildId)) { queueMap.set(guildId, []); } queueMap.get(guildId).push({ filePath, title, url, requester, avatarURL, thumbnailUrl }); if (voiceChannel) { voiceChannelMap.set(guildId, voiceChannel); } const audioPlayer = playerMap.get(guildId); if (!audioPlayer || audioPlayer.state.status !== AudioPlayerStatus.Playing) { playNextInQueue(guildId); } } function removeFromQueue(guildId, index) { const queue = getQueue(guildId); if (index >= 0 && index < queue.length) { return queue.splice(index, 1)[0]; } return null; } function getQueue(guildId) { return queueMap.get(guildId) || []; } function getPlayer(guildId) { return playerMap.get(guildId); } function getCurrentTrack(guildId) { return currentTrackMap.get(guildId) || null; } function toggleRepeat(guildId) { const currentRepeat = repeatMap.get(guildId) || false; repeatMap.set(guildId, !currentRepeat); return !currentRepeat; } function playNextInQueue(guildId) { const queue = getQueue(guildId); const currentTrack = getCurrentTrack(guildId); const repeat = repeatMap.get(guildId); const voiceChannel = voiceChannelMap.get(guildId); if (!voiceChannel) { console.error("Voice channel is undefined. Cannot play track."); return false; } if (currentTrack && !repeat) { return false; } let trackToPlay = currentTrack; if (!trackToPlay) { if (queue.length > 0) { trackToPlay = queue.shift(); currentTrackMap.set(guildId, trackToPlay); } else if (repeat && currentTrack) { trackToPlay = currentTrack; } else { const connection = playerMap.get(guildId)?._state?.subscription?.connection; if (connection && connection.state.status !== 'destroyed') { connection.destroy(); } return false; } } playTrack(guildId, voiceChannel, trackToPlay); } function playTrack(guildId, voiceChannel, track) { const connection = joinVoiceChannel({ channelId: voiceChannel.id, guildId: guildId, adapterCreator: voiceChannel.guild.voiceAdapterCreator, }); let audioPlayer = playerMap.get(guildId); if (!audioPlayer) { audioPlayer = createAudioPlayer(); playerMap.set(guildId, audioPlayer); audioPlayer.on(AudioPlayerStatus.Idle, () => { if (!repeatMap.get(guildId)) { currentTrackMap.delete(guildId); fs.unlink(track.filePath, (err) => { if (err) console.error('Error deleting file:', track.filePath, err); }); } const queue = getQueue(guildId); if (queue.length > 0 || repeatMap.get(guildId)) { playNextInQueue(guildId); } else { if (connection && connection.state.status !== 'destroyed') { connection.destroy(); } } }); audioPlayer.on('error', (err) => { console.error('AudioPlayer error:', err); currentTrackMap.delete(guildId); if (connection && connection.state.status !== 'destroyed') { connection.destroy(); } }); } if (track.ffmpegProcess) { try { track.ffmpegProcess.kill(); console.log('Killed existing ffmpeg process.'); } catch (err) { console.error('Error killing existing ffmpeg process:', err); } } if (!fs.existsSync(track.filePath)) { console.error('Audio file not found:', track.filePath); return false; } track.ffmpegProcess = spawn('ffmpeg', [ '-i', track.filePath, '-analyzeduration', '0', '-loglevel', '0', '-acodec', 'libopus', '-f', 'opus', '-ar', '48000', '-ac', '2', 'pipe:1' ]); const resource = createAudioResource(track.ffmpegProcess.stdout); audioPlayer.play(resource); connection.subscribe(audioPlayer); currentTrackMap.set(guildId, { ...track, resource }); } function skipTrack(guildId) { const player = playerMap.get(guildId); const queue = getQueue(guildId); if (!player) { console.error('No player found for this guild.'); return; } currentTrackMap.delete(guildId); player.stop(); if (queue.length > 0) { playNextInQueue(guildId); } else { const connection = playerMap.get(guildId)?._state?.subscription?.connection; if (connection && connection.state.status !== 'destroyed') { connection.destroy(); } } } function clearQueue(guildId) { queueMap.set(guildId, []); } module.exports = { addToQueue, getQueue, getCurrentTrack, playNextInQueue, skipTrack, toggleRepeat, clearQueue, removeFromQueue, getPlayer };