如何修复Discord音乐Bot在歌曲未播放完成前随机停止播放的问题
Discord音乐Bot播放随机中断故障修复方案
故障根因
- 音频源对象被垃圾回收随机销毁:
play方法内创建的player为局部变量,方法执行后无外部引用持有,Python垃圾回收机制会在不确定的时间回收该对象,直接导致音频流中断,表现为播放大半后无报错随机停止。 - FFmpeg无拉流容错能力:当前FFmpeg配置仅添加
-vn参数,未配置网络重连、超时逻辑,Heroku出口网络波动、YouTube临时限流时,FFmpeg收到流EOF会直接终止播放,无重试机制。 - 临时流URL无缓冲机制:当前
stream=True模式下直接将YouTube返回的带签名临时URL喂给FFmpeg,未做预缓冲,加上youtube_dl已停止维护,对YouTube最新的流校验规则兼容性不足,拉流到中段容易出现授权失效断连。 - 异常回调无兜底处理:当前播放结束的回调仅打印错误日志,未对非用户主动操作触发的中断做恢复处理,只要出现网络波动断连就直接停止,无自动续播逻辑。
修复实现步骤
1. 全局持有当前音频源,避免被垃圾回收
在Music cog的初始化方法中新增实例属性,保存当前播放的播放器对象:
class Music(commands.Cog): def __init__(self, bot): self.bot = bot # 新增:全局持有当前播放器,避免局部变量被GC回收 self.current_player = None
修改play方法中的赋值逻辑,将创建的播放器实例存入全局属性:
@commands.command(description="streams music") async def play(self, ctx, *, url): async with ctx.typing(): player = await YTDLSource.from_url(url, loop=self.bot.loop, stream=True) # 新增:将播放器赋值给全局属性 self.current_player = player ctx.voice_client.play( player, after=lambda e: self.bot.loop.create_task(self._after_play(ctx, e)) ) embed = discord.Embed(title="Now playing", description=f"[{player.title}]({player.url}) [{ctx.author.mention}]") await ctx.send(embed=embed)
2. 为FFmpeg配置拉流容错参数
修改ffmpeg_options,补充网络重连、超时相关参数,适配Heroku的网络环境:
ffmpeg_options = { 'options': '-vn -reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 -rw_timeout 10000000' }
参数作用:
-reconnect 1:开启网络断连自动重连-reconnect_streamed 1:针对音频流场景开启重连-reconnect_delay_max 5:单次重连最大等待时长5秒-rw_timeout 10000000:设置10秒网络读写超时,避免无限挂起
3. 优化youtube_dl配置,提升流拉取稳定性
补充ytdl_format_options的相关配置,降低YouTube侧的断连概率:
ytdl_format_options = { 'format': 'bestaudio/best', 'outtmpl': '%(extractor)s-%(id)s-%(title)s.%(ext)s', 'restrictfilenames': True, 'noplaylist': True, 'nocheckcertificate': True, 'ignoreerrors': False, 'logtostderr': False, 'quiet': True, 'no_warnings': True, 'default_search': 'auto', 'source_address': '0.0.0.0', # 新增流稳定配置 'geo_bypass': True, 'extractor_retries': 3, 'fragment_retries': 10, 'buffersize': 16777216 }
4. 增加播放异常自动续播逻辑
在Music cog中新增异常处理方法,当出现非用户主动停止的播放中断时,自动尝试重新拉取资源续播:
async def _after_play(self, ctx, error): if not error: return print(f"播放中断错误: {error}") # 等待2秒规避临时网络波动 await asyncio.sleep(2) # 校验Bot仍在语音频道、未处于播放状态时,自动重拉流续播 if ctx.voice_client and ctx.voice_client.is_connected() and not ctx.voice_client.is_playing(): try: new_player = await YTDLSource.from_url( self.current_player.data['webpage_url'], loop=self.bot.loop, stream=True ) self.current_player = new_player ctx.voice_client.play( new_player, after=lambda e: self.bot.loop.create_task(self._after_play(ctx, e)) ) except Exception as e: print(f"自动续播失败: {e}")
可选优化
将已停止维护的youtube_dl替换为yt-dlp,仅需修改导入语句即可无缝适配现有代码,可大幅提升对YouTube最新流规则的兼容性,从根源减少拉流中断概率。
内容的提问来源于stack exchange,提问作者Aditya Tomar
相关产品推荐
相关产品推荐

