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

discord.py音乐机器人播完首曲后无法自动播放队列下一首

discord.py音乐机器人无法自动续播队列歌曲问题修复

问题现象

基于discord.py开发的音乐机器人存在以下异常:

  • 尝试所有常规调整方法后,仍无法自动播放队列中的第二首歌曲
  • 队列内第一首歌曲播放完成后机器人直接停止播放
  • 仅当手动向队列新增歌曲时,才会触发第二首歌曲的播放
  • 预期效果:当前歌曲播放完毕后自动接续播放队列内的后续歌曲

原有问题代码

queue=[]


@commands.command()
async def play(self,ctx,message):
    if len(queue) <=5 :
            if r"https://www.youtube.com/watch\?v=(\S{11})" is message:
                queue.append(message)
                await ctx.reply(message+" added to queue. 🕒")

            else:
                keyword=(message)
                print(keyword)
                htmlurl="https://www.youtube.com/results?search_query=" + str(keyword)
                html=(urllib.request.urlopen(htmlurl))
                pattern=(r"watch\?v=(\S{11})")
                video_ids=re.findall(pattern, html.read().decode())
                print(video_ids, ".")
                urlh=("https://www.youtube.com/watch?v="+video_ids[0])
                print(urlh)
                
                queue.append(urlh)
                await ctx.reply(urlh+" added to queue 🕒")
                    
    else:
        await ctx.send("Queue is full. Limit: 5 videos.")
        return
    
    for oyn in range(len(queue)):
        if ctx.voice_client.is_playing():
            return
        else:
            FFMPEG_OPTIONS = {'options':'-vn', 'before_options':'-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5'}
            YDL_OPTIONS = {"format":"bestaudio",'cachedir': False}
            vc = ctx.voice_client
            await ctx.send(queue[0]+" is now playing. ✨")
            with youtube_dl.YoutubeDL(YDL_OPTIONS) as ydl:
                info = ydl.extract_info(queue[0], download= False)
                url2 = info["formats"][0]["url"]
                source= await discord.FFmpegOpusAudio.from_probe(url2, **FFMPEG_OPTIONS)
            vc.play(source)
            del(queue[0])

问题根因

  • 没有绑定音频播放结束的回调逻辑:所有播放触发逻辑都绑定在play命令上,只有用户手动发指令加歌时才会检查播放状态、启动播放,第一首歌播完后没有任何逻辑触发下一首的加载播放。
  • 播放逻辑的循环设计无效:vc.play()是异步非阻塞调用,执行后会立刻返回,不会等待当前歌曲播放完成,写在play命令里的for循环只会在命令触发瞬间执行一次,根本不会等到歌曲播完再跑下一轮。
  • 链接判断逻辑错误:用is做正则匹配完全不符合语法,is用于判断两个对象的内存地址是否完全一致,无法识别输入的内容是不是合法YouTube链接。
  • 全局队列存在设计缺陷:多服务器同时使用机器人时会出现队列串台、播放混乱的问题。
  • 命令参数定义错误:play命令的搜索关键词参数没有加*前缀,多词搜索只会捕获第一个词,后续内容会被丢弃。
  • 音频源选择逻辑有兼容性问题:直接取info["formats"][0]大概率拿到的是纯视频流,会出现播放无声音的情况。

修复实现

核心逻辑是利用discord.py语音客户端play()方法自带的after参数:当前音频流播放结束(或报错中断)时,after指定的函数会被自动触发,在这个函数里实现队列下一首歌的加载播放即可。

注意:after回调运行在独立的非协程线程中,需要通过bot的事件循环创建协程任务,不能直接在回调里调用异步方法,否则会抛出线程安全错误。

修复后的核心代码参考:

import re
import urllib.request
import urllib.parse
import discord
from discord.ext import commands
import youtube_dl

# 生产环境建议按服务器ID区分队列,用字典存储{guild_id: queue_list},避免多服串扰,示例简化保留全局队列写法
queue = []
FFMPEG_OPTIONS = {'options':'-vn', 'before_options':'-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5'}
YDL_OPTIONS = {"format":"bestaudio",'cachedir': False}

class MusicCog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    # 抽离统一的播放下一首逻辑,避免重复代码
    async def play_next(self, ctx):
        # 队列空了直接退出
        if len(queue) == 0:
            return
        vc = ctx.voice_client
        # 取队列第一首歌
        current_url = queue.pop(0)
        await ctx.send(f"{current_url} is now playing. ✨")
        # 加载音频源
        with youtube_dl.YoutubeDL(YDL_OPTIONS) as ydl:
            info = ydl.extract_info(current_url, download=False)
            # 过滤纯音频格式,避免拿到无声音的视频流
            audio_format = next(f for f in info['formats'] if f.get('acodec') != 'none' and f.get('vcodec') == 'none')
            url2 = audio_format["url"]
            source = await discord.FFmpegOpusAudio.from_probe(url2, **FFMPEG_OPTIONS)
        # 播放时绑定after回调,播完自动触发下一首播放
        vc.play(
            source,
            after=lambda e: self.bot.loop.create_task(self.play_next(ctx)) if not e else print(f"播放出错: {e}")
        )

    @commands.command()
    async def play(self, ctx, *, message):
        # 可自行补充:判断用户是否在语音频道、机器人未入频道时自动加入对应频道的逻辑
        if len(queue) >= 5:
            await ctx.send("Queue is full. Limit: 5 videos.")
            return
        
        # 修正链接判断逻辑,用正则匹配
        yt_pattern = r"https?://(?:www\.)?youtube\.com/watch\?v=(\S{11})"
        if re.match(yt_pattern, message):
            target_url = message
            queue.append(target_url)
            await ctx.reply(f"{target_url} added to queue. 🕒")
        else:
            # 关键词做URL编码,避免特殊字符导致搜索失败
            keyword = urllib.parse.quote(message)
            htmlurl = f"https://www.youtube.com/results?search_query={keyword}"
            html = urllib.request.urlopen(htmlurl)
            video_ids = re.findall(r"watch\?v=(\S{11})", html.read().decode())
            urlh = f"https://www.youtube.com/watch?v={video_ids[0]}"
            queue.append(urlh)
            await ctx.reply(f"{urlh} added to queue 🕒")
        
        # 当前没在播放状态时,启动第一首歌的播放
        if not ctx.voice_client.is_playing():
            await self.play_next(ctx)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:31:01