使用discord.py开发音乐机器人时队列播放报错discord.errors.ClientException
Discord音乐机器人队列播放报错问题
错误信息
[2023-08-22 12:08:14] [ERROR ] discord.ext.commands.bot: Ignoring exception in command play Traceback (most recent call last): File "D:\python shits\discord bot\v2\venv\Lib\site-packages\discord\ext\commands\core.py", line 235, in wrapped ret = await coro(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "D:\python shits\discord bot\v2\music_cog.py", line 70, in play await self.play_music(ctx) File "D:\python shits\discord bot\v2\music_cog.py", line 46, in play_music self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS),after=after_play) File "D:\python shits\discord bot\v2\venv\Lib\site-packages\discord\voice_client.py", line 608, in play raise ClientException('Already playing audio.') discord.errors.ClientException: Already playing audio.
机器人代码
import discord from discord.ext import commands from youtube_dl import YoutubeDL print("IMPORTED") class music_cog(commands.Cog): def __init__(self,bot): self.bot = bot # all the music related stuff self.is_playing = False self.is_paused = False # 2d array containing [song, channel] self.music_queue = [] self.YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist': 'True'} self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'} self.vc = None def search_yt(self, item): try: ydl = YoutubeDL(self.YDL_OPTIONS) info = ydl.extract_info("ytsearch:%s" % item, download=False)['entries'][0] except Exception: return False return {'source': info['formats'][0]['url'], 'title': info['title']} async def play_music(self, ctx): while len(self.music_queue) > 0: self.is_playing = True m_url = self.music_queue[0][0]['source'] # Now connect to the voice channel try: self.vc = await self.music_queue[0][1].connect() except: print("Bot already connected") self.music_queue.pop(0) async def after_play(error): await self.play_music(ctx) self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS),after=after_play) self.is_playing = False else: print("huh") self.is_playing = False @commands.command(name="play") async def play(self, ctx, *args): query = " ".join(args) voice_channel = "a" #anti spam system if ctx.author.voice is None: # you need to be connected so that the bot knows where to go await ctx.send("Connect to a voice channel!") else: voice_channel = ctx.author.voice.channel song = self.search_yt(query) if type(song) == type(True): await ctx.send("Could not download the song.") else: await ctx.send("Song added to the queue") self.music_queue.append([song, voice_channel]) print(self.music_queue) if self.is_playing == False: await self.play_music(ctx) else: print("normal")
问题说明:机器人可正常播放单首歌曲,但添加歌曲到队列时会触发上述错误,推测是程序尝试同时播放多首歌曲,需要实现歌曲等待上一首播放完成后再播放的逻辑。
解决方法
问题核心在于play_music里的while循环会直接尝试播放下一首,且is_playing状态被提前设为False,导致新歌曲加入时重复触发play_music,进而重复调用vc.play引发冲突。
修改后的代码如下,关键调整已标注:
import discord from discord.ext import commands from youtube_dl import YoutubeDL print("IMPORTED") class music_cog(commands.Cog): def __init__(self,bot): self.bot = bot self.is_playing = False self.is_paused = False self.music_queue = [] self.YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist': 'True'} self.FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'} self.vc = None def search_yt(self, item): try: ydl = YoutubeDL(self.YDL_OPTIONS) info = ydl.extract_info("ytsearch:%s" % item, download=False)['entries'][0] except Exception: return False return {'source': info['formats'][0]['url'], 'title': info['title']} # 独立的after回调函数,避免嵌套上下文问题 def after_play(self, ctx): self.bot.loop.create_task(self.play_next(ctx)) async def play_next(self, ctx): if len(self.music_queue) > 0: self.is_playing = True m_url = self.music_queue[0][0]['source'] self.music_queue.pop(0) self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.after_play(ctx)) else: self.is_playing = False await self.vc.disconnect() self.vc = None async def play_music(self, ctx): if len(self.music_queue) == 0: self.is_playing = False return self.is_playing = True m_url = self.music_queue[0][0]['source'] voice_channel = self.music_queue[0][1] # 检查语音连接状态,避免重复连接 if self.vc is None or not self.vc.is_connected(): self.vc = await voice_channel.connect() if self.vc is None: await ctx.send("无法连接到语音频道") self.is_playing = False return elif self.vc.is_playing(): return self.music_queue.pop(0) self.vc.play(discord.FFmpegPCMAudio(m_url, **self.FFMPEG_OPTIONS), after=lambda e: self.after_play(ctx)) @commands.command(name="play") async def play(self, ctx, *args): query = " ".join(args) if ctx.author.voice is None: await ctx.send("请先连接到语音频道!") return voice_channel = ctx.author.voice.channel song = self.search_yt(query) if type(song) == type(True): await ctx.send("无法下载该歌曲,请检查输入或网络状态。") else: await ctx.send(f"歌曲《{song['title']}》已添加到队列") self.music_queue.append([song, voice_channel]) if not self.is_playing: await self.play_music(ctx)
关键调整说明
- 移除while循环:不再用循环强制播放下一首,改为通过
after回调触发下一首,确保当前歌曲播放完成后才处理队列。 - 修正状态管理:
is_playing仅在开始播放时设为True,播放完成或队列为空时设为False,避免状态提前重置导致重复触发。 - 独立after回调:将回调改为独立函数,用
bot.loop.create_task运行异步逻辑,避免嵌套定义的上下文问题。 - 连接状态检查:播放前检查语音连接状态,避免重复连接或连接失败的情况。
- 播放冲突防护:在
play_music中增加当前播放状态检查,若已在播放则直接返回,防止重复调用vc.play。
内容的提问来源于stack exchange,提问作者treedai
相关产品推荐
相关产品推荐

