discord.py 2.0音乐机器人youtube-dl/ffmpeg播放故障排查
故障原因
- 核心依赖失效:你使用的
youtube_dl自2021年12月后已停止维护,无法适配Youtube当前的网页反爬规则,日志开头的error [youtube] QxYdBvB8sOY: Downloading webpage就是该依赖拉取视频流失败的直接报错。拿不到有效音频地址时FFmpeg会直接退出,触发discord.py 2.0版本内置的FFmpeg进程回收逻辑,打印你看到的进程终止日志。 - API适配问题:discord.py 2.0重构了语音连接模块的逻辑,1.x版本的无参数
connect()调用默认跳过部分权限、连接状态校验,2.0版本下如果机器人缺少语音权限、用户未在语音频道,连接会直接失败。你代码里把连接异常用print("error")直接吞掉,没有中断后续逻辑,才会出现“机器人连语音频道都进不去”的现象——连接失败后代码依然尝试读取不存在的语音客户端对象,直接触发异常被捕获。 - FFmpeg参数缺失:discord.py 2.0的音频播放器对网络流媒体的容错要求更高,你原本只配置了
-vn去除视频流,没有配置流媒体自动重连参数,Youtube返回的音频直链本身带过期时间,网络稍有波动就会导致FFmpeg进程意外退出。 - 逻辑漏洞:代码没有判断语音客户端的存在状态、播放状态,重复连接、重复播放时也会触发无意义的异常。
修复方案
- 替换失效依赖:卸载停更的
youtube_dl,安装社区持续维护的兼容分支yt-dlp,执行以下命令:
pip uninstall youtube_dl -y pip install yt-dlp
yt-dlp完全兼容原youtube_dl的API,会持续更新Youtube及其他视频站点的解析规则,可正常拉取音频流地址。
- 补全语音连接校验逻辑:连接前先判断用户是否在语音频道,连接过程不吞异常,连接失败直接中断后续播放逻辑,同时增加重复连接判断,避免机器人重复进入语音频道。
- 补全FFmpeg流媒体适配参数:新增自动重连配置,解决网络波动、直链过期导致的播放中断,最终参数为
-vn -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5,分别对应去除视频流、开启流重连、流媒体场景生效、最大重连等待时长5秒。 - 补全播放逻辑校验:增加播放状态判断,正在播放时先停止原有音频再播放新内容,同时兼容搜索结果、单条视频的返回结构,避免解析结果格式不统一导致的报错。
修复后可直接运行的核心代码
import discord import asyncio import yt_dlp token = "替换为你的机器人Token" prefix = "j!" blocked_words = ["替换为需要屏蔽的词汇列表"] voice_clients = {} # yt-dlp解析配置,关闭冗余日志,禁止自动拉取整个播放列表 yt_dl_opts = { 'format': 'bestaudio/best', 'noplaylist': True, 'quiet': True, 'no_warnings': True } ytdl = yt_dlp.YoutubeDL(yt_dl_opts) # FFmpeg流媒体适配参数 ffmpeg_options = { 'options': '-vn -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5' } intents = discord.Intents.default() intents.message_content = True client = discord.Client(intents=intents) programmer_role = "987018590152699964" @client.event async def on_ready(): print(f"Bot logged in as {client.user}") @client.event async def on_message(msg): if msg.author == client.user: return if msg.content.lower().startswith(f"{prefix}info"): await msg.channel.send(f"Hi, Im JoksysBot Made By Joksy!") for text in blocked_words: if text in str(msg.content.lower()): await msg.delete() await msg.channel.send("Hey, Dont Say That!") return if msg.content.startswith(f"{prefix}play"): # 校验用户是否在语音频道 if not msg.author.voice: await msg.channel.send("请先加入语音频道再使用播放指令") return # 连接语音频道 try: if msg.guild.id not in voice_clients or not voice_clients[msg.guild.id].is_connected(): voice_client = await msg.author.voice.channel.connect() voice_clients[msg.guild.id] = voice_client else: voice_client = voice_clients[msg.guild.id] except Exception as e: await msg.channel.send(f"连接语音频道失败:{str(e)}") return try: url = msg.content.split()[1] loop = asyncio.get_event_loop() data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=False)) # 兼容搜索结果、播放列表单条的返回结构 if 'entries' in data: data = data['entries'][0] song_url = data['url'] player = discord.FFmpegPCMAudio(song_url, **ffmpeg_options) # 正在播放时先停止当前音频 if voice_client.is_playing(): voice_client.stop() voice_client.play(player) await msg.channel.send(f"正在播放:{data['title']}") except Exception as err: await msg.channel.send(f"播放失败:{str(err)}") if msg.content.startswith(f"{prefix}pause"): try: if msg.guild.id in voice_clients and voice_clients[msg.guild.id].is_playing(): voice_clients[msg.guild.id].pause() except Exception as err: print(err) if msg.content.startswith(f"{prefix}resume"): try: if msg.guild.id in voice_clients and voice_clients[msg.guild.id].is_paused(): voice_clients[msg.guild.id].resume() except Exception as err: print(err) if msg.content.startswith(f"{prefix}stop"): try: if msg.guild.id in voice_clients: voice_clients[msg.guild.id].stop() await voice_clients[msg.guild.id].disconnect() del voice_clients[msg.guild.id] except Exception as err: print(err) client.run(token)
注意事项
- 确认Discord开发者后台已为机器人开启
Message Content Intent,邀请机器人进入服务器时勾选了语音频道的「连接」「说话」权限。 - 不需要硬编码FFmpeg的本地路径,只要将FFmpeg添加到系统PATH后重启VS Code即可正常识别,硬编码路径反而容易因目录变动失效。
- 不建议使用
from discord import *的通配符导入方式,容易出现命名冲突,按需导入需要的对象即可。 - 排查问题时不要用无参数的
except:直接吞掉所有异常,至少要打印异常内容,否则无法定位具体故障点。
内容的提问来源于stack exchange,提问作者Joksy
相关产品推荐
相关产品推荐

