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

使用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)

关键调整说明

  1. 移除while循环:不再用循环强制播放下一首,改为通过after回调触发下一首,确保当前歌曲播放完成后才处理队列。
  2. 修正状态管理:is_playing仅在开始播放时设为True,播放完成或队列为空时设为False,避免状态提前重置导致重复触发。
  3. 独立after回调:将回调改为独立函数,用bot.loop.create_task运行异步逻辑,避免嵌套定义的上下文问题。
  4. 连接状态检查:播放前检查语音连接状态,避免重复连接或连接失败的情况。
  5. 播放冲突防护:在play_music中增加当前播放状态检查,若已在播放则直接返回,防止重复调用vc.play。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 19:52:42