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
相关产品推荐
相关产品推荐

