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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 05:48:28