如何将协程用作闭包实现Discord音乐机器人队列功能?
解决Discord音乐机器人队列递归播放的问题
首先明确:disnake中VoiceClient.play()的after参数只接受同步函数,不能直接传入协程。要执行异步逻辑,需要在同步回调里通过机器人的事件循环创建异步任务。同时你的原代码存在队列重复添加的bug(同一首歌被append两次),下面是修正后的完整实现:
修改后的代码
async def play(self, inter: disnake.CmdInter, url): """Plays a song from a given YouTube URL.""" async with inter.channel.typing(): player = await YTDLSource.from_url( url, loop=self.bot.loop, stream=True ) # 获取当前语音客户端 voice_client = self.bot.voice_clients[0] if self.bot.voice_clients else None # 将歌曲信息存入队列(仅添加一次) self.queue.append({"channel": inter.channel, "player": player}) if not voice_client or not voice_client.is_playing(): # 无正在播放的歌曲,直接启动播放流程 await self.play_next() await inter.send(f"Now playing: `{player.title}`") else: # 已有歌曲播放,提示队列位置 queue_pos = len(self.queue) await inter.send(f"Added `{player.title}` to queue (Nr. {queue_pos})") async def play_next(self): """递归播放队列中的下一首歌""" if not self.queue: # 队列为空,终止播放流程 return # 取出队列首项歌曲 next_song = self.queue.pop(0) channel = next_song["channel"] player = next_song["player"] def after_playing(error): # 同步回调函数,通过事件循环启动异步的下一首播放 if error: print(f"播放出错: {error}") self.bot.loop.create_task(self.play_next()) # 启动播放并绑定结束回调 self.bot.voice_clients[0].play(player, after=after_playing) # 发送当前播放提示 await channel.send(f"Now playing: `{player.title}`")
关键说明
- 队列结构优化:将原有的
[频道, URL]结构改为存储已解析的player对象,避免重复解析同一URL,提升性能。 - 修复重复添加bug:原代码中
try和except块均执行了append操作,导致同一首歌被加入队列两次,现在调整为仅添加一次。 - 同步回调转异步逻辑:
after_playing是同步函数,通过self.bot.loop.create_task()将异步的play_next()包装成可在同步环境中执行的任务,实现递归播放。 - 基础错误处理:回调中加入错误打印,方便排查播放过程中的异常问题。
内容的提问来源于stack exchange,提问作者Asgard
相关产品推荐
相关产品推荐

