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

