如何在discord.py音频源中实现x秒seek(跳转)功能?
实现discord.py音乐机器人的Seek跳转命令
要实现向前/向后跳转指定秒数的seek命令,核心是利用FFmpeg的-ss参数重新生成音频流,同时跟踪当前播放进度。下面结合你的YTDLSource类给出具体实现步骤:
1. 完善YTDLSource类,增加进度跟踪
首先确保你的YTDLSource能获取音频总时长,并记录播放起始时间,这样才能计算当前播放位置:
import time import asyncio import discord from discord.ext import commands # 你的YTDL和FFmpeg配置(假设已定义) ytdl_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' } ffmpeg_options = { 'options': '-vn', 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5' } ytdl = discord.FFmpegPCMAudio.ytdl # 或你自行初始化的youtube_dl.YoutubeDL实例 class YTDLSource(discord.PCMVolumeTransformer): def __init__(self, source, *, data, volume=0.5): super().__init__(source, volume) self.data = data self.title = data.get('title') self.url = data.get('url') self.duration = data.get('duration') # 从YTDL获取音频总时长 self.start_time = None # 记录播放器开始播放的时间戳 @classmethod async def from_url(cls, url, *, loop=None, stream=False): loop = loop or asyncio.get_event_loop() data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream)) if 'entries' in data: data = data['entries'][0] filename = data['url'] if stream else ytdl.prepare_filename(data) return cls(discord.FFmpegPCMAudio(filename, **ffmpeg_options), data=data)
2. 播放时记录起始时间
在你的play命令中,给YTDLSource实例设置start_time,用于后续计算播放进度:
@bot.command() async def play(ctx, url): if not ctx.voice_client: if ctx.author.voice: await ctx.author.voice.channel.connect() else: await ctx.send('你不在语音频道里!') return voice_client = ctx.voice_client source = await YTDLSource.from_url(url, loop=bot.loop, stream=True) source.start_time = time.time() # 记录开始播放的时间 voice_client.play(source) await ctx.send(f'正在播放:{source.title}')
3. 实现Seek命令
编写seek命令,计算新的播放位置,重新生成带跳转参数的FFmpeg流:
@bot.command() async def seek(ctx, offset: int): voice_client = ctx.voice_client # 检查是否在播放 if not voice_client or not voice_client.is_playing(): await ctx.send('当前没有播放音频!') return current_source = voice_client.source # 检查当前源是否是YTDLSource if not isinstance(current_source, YTDLSource): await ctx.send('当前播放的内容不支持跳转!') return # 计算当前已播放时长 elapsed = time.time() - current_source.start_time new_position = elapsed + offset # 边界校验,防止超出音频范围 if new_position < 0: new_position = 0 await ctx.send('已跳转到音频开头') elif new_position > current_source.duration: new_position = current_source.duration await ctx.send('已跳转到音频末尾') # 重新生成带seek参数的FFmpeg源 ffmpeg_seek_opts = ffmpeg_options.copy() # 给FFmpeg添加-ss参数指定起始位置 ffmpeg_seek_opts['before_options'] = f'-ss {new_position} ' + ffmpeg_seek_opts.get('before_options', '') new_ffmpeg_source = discord.FFmpegPCMAudio(current_source.url, **ffmpeg_seek_opts) # 包装成YTDLSource,保留原音量和元数据 new_source = YTDLSource(new_ffmpeg_source, data=current_source.data, volume=current_source.volume) # 修正start_time,让后续进度计算准确 new_source.start_time = time.time() - new_position # 替换音频源并继续播放 voice_client.stop() voice_client.play(new_source) await ctx.send(f'已跳转到 {int(new_position)} 秒位置') # 处理参数错误 @seek.error async def seek_error(ctx, error): if isinstance(error, commands.BadArgument): await ctx.send('请输入整数秒数,例如`seek 5`(向前跳5秒)或`seek -5`(向后跳5秒)')
关键注意事项
- 确保YTDL能正确获取
duration字段:如果是YouTube视频,YTDL默认会返回该字段;如果是本地文件,需要确保FFmpeg能识别时长(YTDL处理本地文件也会返回duration)。 - 部分流媒体(比如直播)没有固定时长,此时
current_source.duration会是None,可以在seek命令里加判断,提示用户无法跳转。 - 有些音频流可能不支持
-ss跳转(比如部分加密流),这种情况会导致FFmpeg报错,你可以加try-except块捕获异常并提示用户。
内容的提问来源于stack exchange,提问作者user17411100
相关产品推荐
相关产品推荐

