Discord.js音乐bot无报错随机停播切歌直到队列清空问题咨询
问题核心原因
你已经定位到的监听器重复注册是直接诱因,除此之外原有代码还有两个隐藏问题也会触发随机断流:
- 每次调用
videoPlayer都重新创建AudioPlayer实例,旧实例没有正确销毁,不仅会占用内存,还会触发多个实例的状态事件冲突,导致意外进入Idle状态触发切歌逻辑 ytdl的highWaterMark设置不合理,你用16384 * song.duration的计算方式完全没有依据,过大会导致内存溢出,过小会导致网络波动时缓冲区提前耗尽触发流结束,误判为歌曲播放完毕
修复方案
1. 调整AudioPlayer实例逻辑
不要在videoPlayer函数内部创建AudioPlayer和注册监听器,改为每个服务器队列仅保留一个AudioPlayer实例,和队列绑定存储:
// 初始化队列时就给每个guild的队列绑定唯一player const videoPlayer = async function (message, song) { const guild = message.guild; const songQueue = queue.get(guild.id); if(!song) { // 销毁player避免内存泄漏 songQueue.player?.stop(); songQueue.connection.disconnect(); queue.delete(guild.id); songDisplay = undefined; songList = undefined; return message.channel.send("There are no more songs in queue."); } // 队列不存在player时才创建,只创建一次 if (!songQueue.player) { songQueue.player = Voice.createAudioPlayer(); // 监听器只注册一次 songQueue.player.on(Voice.AudioPlayerStatus.Idle, async () => { songQueue.songs.shift(); videoPlayer(message, songQueue.songs[0]); }) // 额外加错误监听捕获流异常,避免静默崩溃 songQueue.player.on('error', error => { console.error(`播放器出错: ${error.message}`); // 出错后跳转到下一首,避免卡住 songQueue.songs.shift(); videoPlayer(message, songQueue.songs[0]); }) songQueue.connection.subscribe(songQueue.player); } const stream = ytdl(song.url, { filter: 'audioonly', // 固定设置highWaterMark为1MB即可,不需要和时长绑定 highWaterMark: 1 << 20, requestOptions: { headers: { cookie: process.env.COOKIE } } }); // 加流错误监听 stream.on('error', err => console.error(`流获取出错: ${err.message}`)) const resource = Voice.createAudioResource(stream); songQueue.player.play(resource); songPlayer = songQueue.player; // 后面的发送embed逻辑保留即可 let songDetails = new MessageEmbed() .setAuthor(song.authorName) .setTitle(`Playing \`${song.title}\``) .setDescription(`Video Link: ${song.url}\nChannel Link: ${song.authorChannel}`) .setThumbnail(song.thumbnail); if(songDisplay != undefined) songDisplay.delete(); message.channel.send('Initializing...') .then(display => { songDisplay = display; songDisplay.edit( { content: null, embeds: [songDetails] } ) }) }
2. 请求监控方法
你可以给ytdl的requestOptions加事件监听来监控请求状态,适合排查网络问题:
const stream = ytdl(song.url, { filter: 'audioonly', highWaterMark: 1 << 20, requestOptions: { headers: { cookie: process.env.COOKIE }, // 监听请求响应 transform: req => { req.on('response', res => { console.log(`请求${song.title}响应状态码: ${res.statusCode}`); res.on('data', chunk => { // 可以打印每次收到的字节数,排查是不是断流了 console.log(`收到${chunk.length}字节数据`); }) res.on('error', err => console.log(`响应出错: ${err.message}`)) }) req.on('error', err => console.log(`请求出错: ${err.message}`)) return req; } } });
额外优化建议
- 不要用全局变量存储
songPlayer、songDisplay,改为和每个服务器的队列绑定,否则多个服务器同时播放时会出现逻辑冲突 - 可以用
play-dl库替代ytdl-core,前者针对YouTube的反爬适配更新更及时,断流概率更低
内容的提问来源于stack exchange,提问作者Shirayuki Yumi
相关产品推荐
相关产品推荐

