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

Discord音乐Bot播放完歌曲后清空整个队列而非移除单首已播歌曲问题

问题根因

核心问题是重复绑定了AudioPlayerStatus.Idle事件的监听器。你大概率在每次新增歌曲到队列、或者每次触发播放操作时,都重复执行了player.on(AudioPlayerStatus.Idle, () => { ... })的绑定逻辑,没有做监听器去重处理。

你提供的调试日志可以直接验证这个问题:

  • 第一次触发Idle状态时,多个绑定的回调同时执行,第一次splice(0,1)执行后队列变为song2,song3,后续回调执行splice时,因为你在回调里调用了player.play()让播放器退出了Idle状态,所以没有触发下一轮播放,就出现了连续三次输出song2,song3的情况
  • 第二次触发Idle状态时,多个回调再次连续执行,删除song2后队列只剩song3,同样出现连续三次输出的情况
  • 最后一次触发Idle状态时,回调直接把最后一首歌删除,队列变为空

修复方案

只需要保证Idle事件的回调全局只绑定一次即可,不要在每次操作队列的时候重复绑定,参考实现如下:

// 播放器初始化阶段全局只绑定一次Idle事件监听器
const player = createAudioPlayer();
// 全局唯一的队列变量
const queue = [];

player.on(AudioPlayerStatus.Idle, () => {
  // 移除已播放完成的队首歌曲,shift和splice(0,1)效果一致,写法更简洁
  queue.shift();
  if (queue.length === 0) return;
  const nextResource = createAudioResource(join('./music/', `${queue[0].toLowerCase()}.mp3`));
  player.play(nextResource);
});

// 后续加歌、触发播放的逻辑中不要重复绑定上面的Idle事件
function addSongToQueue(songName) {
  queue.push(songName);
  // 播放器空闲时直接触发播放
  if (player.state.status === AudioPlayerStatus.Idle) {
    const resource = createAudioResource(join('./music/', `${queue[0].toLowerCase()}.mp3`));
    player.play(resource);
  }
}

如果你的业务逻辑无法避免重复绑定的场景,也可以在绑定新监听器前先移除旧监听器,需要先把回调提取为具名函数:

function handlePlayerIdle() {
  queue.shift();
  if (queue.length === 0) return;
  const nextResource = createAudioResource(join('./music/', `${queue[0].toLowerCase()}.mp3`));
  player.play(nextResource);
}

// 每次绑定前先移除旧的监听器,保证同一时间只有一个监听器生效
player.off(AudioPlayerStatus.Idle, handlePlayerIdle);
player.on(AudioPlayerStatus.Idle, handlePlayerIdle);

内容的提问来源于stack exchange,提问作者MegaMix_Craft

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 12:36:04