diff --git a/commands/play.js b/commands/play.js index f987151..7dc0cc2 100644 --- a/commands/play.js +++ b/commands/play.js @@ -5,6 +5,37 @@ const { EmbedBuilder } = require('discord.js'); const fs = require('fs'); const { exec, spawn, execSync } = require('child_process'); +const MAX_RETRIES = 3; +const RETRY_DELAY = 1000; + +function spawnFFmpegProcess(args, callback, retries = 0) { + const ffmpegProcess = spawn('ffmpeg', args); + + ffmpegProcess.on('close', (code) => { + if (code === 0) { + callback(null); + } else if (retries < MAX_RETRIES) { + console.warn(`FFmpeg process failed, retrying (${retries + 1}/${MAX_RETRIES})...`); + setTimeout(() => { + spawnFFmpegProcess(args, callback, retries + 1); + }, RETRY_DELAY); + } else { + callback(new Error(`FFmpeg process failed after ${MAX_RETRIES} retries.`)); + } + }); + + ffmpegProcess.on('error', (err) => { + if (retries < MAX_RETRIES) { + console.warn(`FFmpeg process error, retrying (${retries + 1}/${MAX_RETRIES})...`, err); + setTimeout(() => { + spawnFFmpegProcess(args, callback, retries + 1); + }, RETRY_DELAY); + } else { + callback(err); + } + }); +} + module.exports = { name: 'play', description: 'Play a song from YouTube, a URL, or an uploaded media file', @@ -84,15 +115,13 @@ module.exports = { const tempVideoPath = path.join(__dirname, '../utils/tmp', attachment.name); fs.writeFileSync(tempVideoPath, buffer); - const ffmpegProcess = spawn('ffmpeg', [ - '-i', tempVideoPath, - '-f', 'mp3', - '-ab', '192000', - '-vn', - tempFilePath - ]); + spawnFFmpegProcess(['-i', tempVideoPath, '-f', 'mp3', '-ab', '192000', '-vn', tempFilePath], (err) => { + if (err) { + console.error('Error converting file:', err); + message.reply('Failed to convert the video file.'); + return; + } - ffmpegProcess.on('close', () => { console.log(`Converted and saved: ${tempFilePath}`); fs.unlinkSync(tempVideoPath); @@ -112,11 +141,6 @@ module.exports = { playNextInQueue(message.guild.id); }); - ffmpegProcess.on('error', (err) => { - console.error('Error converting file:', err); - message.reply('Failed to convert the video file.'); - }); - return; } else { console.error('Attachment is not a supported media file.'); @@ -172,15 +196,13 @@ module.exports = { const tempVideoPath = path.join(__dirname, '../utils/tmp', path.basename(searchQuery.split('?')[0])); fs.writeFileSync(tempVideoPath, buffer); - const ffmpegProcess = spawn('ffmpeg', [ - '-i', tempVideoPath, - '-f', 'mp3', - '-ab', '192000', - '-vn', - tempFilePath - ]); + spawnFFmpegProcess(['-i', tempVideoPath, '-f', 'mp3', '-ab', '192000', '-vn', tempFilePath], (err) => { + if (err) { + console.error('Error converting file:', err); + message.reply('Failed to convert the video file.'); + return; + } - ffmpegProcess.on('close', () => { console.log(`Converted and saved: ${tempFilePath}`); fs.unlinkSync(tempVideoPath); @@ -200,11 +222,6 @@ module.exports = { playNextInQueue(message.guild.id); }); - ffmpegProcess.on('error', (err) => { - console.error('Error converting file:', err); - message.reply('Failed to convert the video file.'); - }); - } else { fs.writeFileSync(tempFilePath, buffer); console.log(`Downloaded and saved: ${tempFilePath}`); diff --git a/utils/queueManager.js b/utils/queueManager.js index e03cd79..5e3ce4a 100644 --- a/utils/queueManager.js +++ b/utils/queueManager.js @@ -1,5 +1,6 @@ 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(); @@ -94,45 +95,70 @@ function playTrack(guildId, voiceChannel, track) { adapterCreator: voiceChannel.guild.voiceAdapterCreator, }); - const audioPlayer = playerMap.get(guildId) || createAudioPlayer(); - playerMap.set(guildId, audioPlayer); + 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; } - const resource = createAudioResource(track.filePath); + 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 }); - - 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(); - } - }); } function skipTrack(guildId) {