Discord.py音乐机器人队列异常:多曲目添加后跳至最后曲目求助
解决Discord.py音乐机器人队列播放异常问题(基于Interaction Slash命令)
问题背景
使用discord.py开发Discord音乐机器人,采用全局列表queue = []实现队列功能,通过Interaction Slash命令/play添加曲目。当前仅能正常播放前两首,添加第三首及更多时,第一首结束后所有后续曲目会同时尝试播放,无法依次播放。原代码中通过while循环轮询播放状态的逻辑存在问题,尝试使用voice_client.play的after参数但未正确处理异步逻辑。
问题根源
- 阻塞式轮询导致并发冲突:原代码中
elif voice_client.is_playing()分支的while循环会阻塞当前命令协程,每添加一首新曲目就会启动一个独立协程等待播放结束。当第一首曲目播放完毕后,所有等待的协程会同时触发play_next,导致队列被重复修改、多个曲目同时播放。 - 异步回调处理错误:直接用
lambda e: play_next()作为after参数时,play_next是异步函数,但after回调是同步执行的,无法直接调用异步函数,必须通过事件循环创建任务来执行。
解决方案
核心逻辑调整
- 移除阻塞式的
while轮询,改用voice_client.play的after参数实现链式回调,当前曲目播放完毕后自动触发下一首播放。 - 重构
play_next函数,确保队列操作和播放逻辑原子化,每次仅处理一首曲目,并为下一首设置新的回调。 - 为音频源添加
requester属性,记录请求用户,方便后续发送播放通知。
修正后的完整代码
import asyncio import discord from discord.ext import commands from your_ytdl_module import YTDLSource # 替换为你的YTDLSource导入路径 queue = [] @client.tree.command(name="play", description="播放指定音乐") async def play(interaction: discord.Interaction, query: str): await interaction.response.defer() # 获取音频源并添加到队列 source = await YTDLSource.from_url(query, loop=client.loop) source.requester = interaction.user.mention # 记录请求用户 queue.append(source) # 发送添加到队列的通知 description = f"**{source}**\n{source.data['webpage_url']}\nRequested by: {source.requester}" embed = discord.Embed(title="Added to queue", description=description, color=0x23EBE3) embed.set_thumbnail(url="THUMBNAIL_URL") await interaction.followup.send(embed=embed) # 获取语音客户端(确保已连接到语音频道) voice_client = interaction.guild.voice_client if not voice_client: await interaction.followup.send("未连接到语音频道!") return # 定义播放下一首的异步函数 async def play_next(): if len(queue) > 1: # 移除已播放完毕的曲目(当前播放的是queue[0]) queue.pop(0) next_source = queue[0] # 发送当前播放通知 play_description = f"**{next_source}**\n{next_source.data['webpage_url']}\nRequested by: {next_source.requester}" play_embed = discord.Embed(title="Now playing", description=play_description, color=0x23EBE3) play_embed.set_thumbnail(url="THUMBNAIL_URL") await interaction.followup.send(embed=play_embed) # 播放下一首,并设置after回调(同步回调中用create_task执行异步函数) voice_client.play(next_source, after=lambda e: asyncio.create_task(play_next())) elif len(queue) == 1: # 最后一首播放完毕,清空队列 queue.pop(0) await interaction.followup.send("队列已空,等待新的音乐请求~") # 如果当前未播放,启动第一首并设置回调 if not voice_client.is_playing(): current_source = queue[0] # 发送当前播放通知 current_description = f"**{current_source}**\n{current_source.data['webpage_url']}\nRequested by: {current_source.requester}" current_embed = discord.Embed(title="Now playing", description=current_description, color=0x23EBE3) current_embed.set_thumbnail(url="THUMBNAIL_URL") await interaction.followup.send(embed=current_embed) # 启动播放并绑定after回调 voice_client.play(current_source, after=lambda e: asyncio.create_task(play_next()))
关键细节说明
- 链式回调机制:每首曲目播放时通过
after参数绑定play_next,当前曲目播放完毕后自动触发下一首,避免了轮询带来的并发问题。 - 异步回调处理:由于
after是同步回调函数,使用asyncio.create_task(play_next())将异步函数包装成任务提交给事件循环执行,确保异步逻辑正确运行。 - 队列原子操作:
play_next中每次仅弹出一首已播放的曲目,然后检查队列是否有剩余曲目,保证队列操作的顺序性。
内容的提问来源于stack exchange,提问作者Faizal Zahid
相关产品推荐
相关产品推荐

