You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 15:45:04