Discord.py音乐机器人voice_client.play的after参数提前触发队列播放异常
问题根源
你遇到的问题核心是两个用法错误:
ctx.voice_client.play()的after参数要求传入播放结束后才会执行的可调用对象,你代码中写的after=await self.after(ctx)是直接当场执行了after方法,而不是把方法本身传给after参数,所以会出现歌曲还没开始播放就触发下一首逻辑的问题。after参数不支持直接传入异步协程,discord.py的语音播放逻辑运行在独立线程中,直接传入协程不会正常执行。
修复代码
你需要修改play_song和after方法的写法,示例如下:
import asyncio # 省略其余原有代码 async def play_song(self, ctx: Context): # 顺便修复你原代码里先打印track再定义track的NameError问题 track = await self.queue.get() logger.info("getting track [{}]".format(track.title)) logger.info("playing track [{}]".format(track.title)) await ctx.send(content="playing track {}".format(track.title)) # 定义同步回调给after参数,播放结束后才会触发 def after_callback(error): if error: logger.error(f"播放出错: {str(error)}") # 将异步的下一首播放逻辑提交到bot的事件循环中执行 asyncio.run_coroutine_threadsafe(self.after(ctx), self.bot.loop) ctx.voice_client.play(discord.FFmpegPCMAudio(track.filename), after=after_callback) async def after(self, ctx): # 已经是播放结束后触发,不需要额外判断是否正在播放 if not self.queue.empty(): logger.info("looping start") await self.play_song(ctx) logger.info("looping end")
同时建议调整play命令的逻辑,避免每次调用都打断当前播放的歌曲:
@commands.command(name="play", aliases=["p"], pass_context=True, usage=DOCS_PLAY) async def play(self, ctx: Context, *args): # 只有当前没有播放时才启动播放,已经在播放就提示用户用queue命令加歌 if not ctx.voice_client.is_playing() and not self.queue.empty(): await self.play_song(ctx) elif ctx.voice_client.is_playing(): await ctx.send("当前已有歌曲在播放,可使用/q命令将歌曲加入队列") else: await ctx.send("当前队列为空,请先使用/q命令添加歌曲")
更合理的队列处理优化方案
- 将
urlqueue也替换为asyncio.Queue,避免多协程操作普通列表出现竞态问题 - 去掉定时轮询的
load_queue任务,用户提交url后直接异步处理下载、加入播放队列,无需等待5秒轮询,响应速度更快 - 按服务器(guild)隔离队列,支持机器人同时在多个服务器独立播放歌曲,不会出现串队列的问题
- 提前下载队列中后续1-2首歌曲,避免切歌时需要等待下载出现空白期
- 增加队列查询、切歌、清空队列、调整队列顺序的配套命令,提升使用体验
内容的提问来源于stack exchange,提问作者atomic_toast
相关产品推荐
相关产品推荐

