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

Discord音乐Bot无法播放音频且行为异常(Python+yt-dlp)

问题排查与解决方法

核心问题分析

从日志和代码来看,问题集中在三个关键点:

  • 同步的yt-dlp操作阻塞了Discord.py的异步事件循环,导致首次调用时语音频道连接流程未完成就中断。
  • 搜索关键词处理错误:*url是元组类型,直接拼接成搜索字符串时会保留元组的括号和逗号格式,导致yt-dlp搜索异常。
  • 语音客户端连接/移动后未等待就绪就执行播放操作,触发"Not connected to voice"错误。

分步修复方案

1. 用异步方式执行yt-dlp操作

yt-dlp的extract_info是同步方法,会阻塞Discord.py的事件循环(异步框架的核心),导致语音连接的异步回调无法处理。需要把yt-dlp的操作放到线程池中执行,避免阻塞事件循环。

2. 修正搜索关键词的格式

把*url元组转换成空格分隔的字符串,避免元组格式被带入搜索请求。

3. 等待语音客户端就绪后再播放

连接或移动语音频道后,需要确认ctx.voice_client.is_connected()为True,或者等待语音客户端的就绪状态,再执行播放逻辑。

修改后的完整代码

import discord
import asyncio
from discord.ext import commands
import yt_dlp
from concurrent.futures import ThreadPoolExecutor

# 创建线程池,用于执行同步的yt-dlp操作
executor = ThreadPoolExecutor(max_workers=1)

class musicTest(commands.Cog):
    def __init__(self, client):
        self.client = client
        self.ydl_options = {"format": "bestaudio"}
        self.ffmpeg_options = {
            'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
            'options': '-vn',
            'executable': r"C:\Users\XXXX\ffmpeg.exe"  # 统一FFmpeg路径
        }

    @commands.command()
    async def play(self, ctx, *url_parts):
        print('@@ Command \'play\' was called upon!')

        # 检查用户是否在语音频道
        if not ctx.author.voice:
            print('@@ Author is not in a voice channel. Warning them!')
            await ctx.send("你不在语音频道里!")
            return

        print('@@ Author is in a channel, proceeding!')
        voice_channel = ctx.author.voice.channel
        print('@@ Channel name has been identified!')

        # 处理语音连接/移动
        if not ctx.voice_client:
            print('@@ Bot is not in a voice channel!')
            voice_client = await voice_channel.connect()
            print('@@ Bot has entered the voice channel!')
        else:
            if ctx.voice_client.channel != voice_channel:
                print('@@ Bot is switching to the correct voice channel!')
                await ctx.voice_client.move_to(voice_channel)
            voice_client = ctx.voice_client

        # 等待语音客户端就绪
        while not voice_client.is_connected():
            await asyncio.sleep(0.1)

        # 处理搜索/链接请求
        query = ' '.join(url_parts)
        is_link = ".com" in query

        # 用线程池执行yt-dlp的同步操作
        def extract_info_sync():
            with yt_dlp.YoutubeDL(self.ydl_options) as ydl:
                if is_link:
                    return ydl.extract_info(query, download=False)
                else:
                    return ydl.extract_info(f"ytsearch:{query}", download=False)

        try:
            info = await self.client.loop.run_in_executor(executor, extract_info_sync)
        except Exception as e:
            await ctx.send(f"获取音频信息失败:{str(e)}")
            return

        # 提取音频URL和标题
        if not is_link:
            info = info['entries'][0]
        audio_url = info['url']
        title = info.get('title', '未知歌曲')

        # 创建音频源
        try:
            source = await discord.FFmpegOpusAudio.from_probe(audio_url, **self.ffmpeg_options)
        except Exception as e:
            await ctx.send(f"创建音频源失败:{str(e)}")
            return

        # 播放音频
        try:
            await ctx.send(f"正在播放:{title}")
            voice_client.play(source)
            print('@@ 音乐播放成功!')
        except Exception as e:
            await ctx.send(f"播放失败:{str(e)}")
            print(f'@@ 播放失败:{e}')

def setup(client):
    client.add_cog(musicTest(client))

关键修改说明

  • 使用ThreadPoolExecutor将yt-dlp的同步操作放到线程池中执行,避免阻塞Discord.py的异步事件循环,解决首次调用流程中断的问题。
  • 将*url_parts元组用' '.join()转换成正常的搜索字符串,修正搜索格式错误。
  • 添加了等待语音客户端就绪的逻辑,确保连接/移动完成后再执行播放操作,解决"Not connected to voice"报错。
  • 统一了FFmpeg路径,避免代码重复,同时减少出错概率。
  • 优化了错误捕获逻辑,更清晰地反馈不同环节的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 10:25:05