直播上线后收到的第一个需求,往往不是画质、不是延迟,而是那句"能往回拖一点吗"。切片其实早就躺在磁盘上了,看起来只是改个配置的事。确实是改配置——但要跨三个系统同时改对,外加六十行左右的播放器代码。
前提条件很明确:ffmpeg 7.0+、hls.js 1.6+、node 22.x。先确认版本,再动手。
![]()
窗口长度就是一个乘法
DVR 窗口有多长,等于 hls_time × hls_list_size。就这一个公式,剩下的都是记账工作。
想要一个十分钟的滑动窗口,就是 6 秒切片乘以 100 条记录。打包命令里,-hls_time 6 定切片时长,-hls_list_size 100 定清单里保留多少条,-hls_flags delete_segments+independent_segments 负责删掉滑出窗口的旧切片,-hls_delete_threshold 5 让删除稍微滞后一点,避免刚滑出就被立刻清掉。
这里有个容易被忽略的默认值:hls_list_size 默认是 5。很多第一次上直播的部署,其实已经自带了一个 DVR 窗口,只是短到没人设计过它——大约三十秒。没显式设置过,不代表没有。
另一个细节是 GOP。-g 120 在 60fps 下等于 2 秒一个关键帧组,正好整除 6 秒的切片时长。如果 GOP 除不尽切片时长,切片会逐渐偏离目标值,清单里的 EXT-X-TARGETDURATION 就会一路往上爬。
验证方法很直接:看清单头部。EXT-X-MEDIA-SEQUENCE 每次刷新都在增长,而条目数稳定在 100,说明滑动窗口在正常工作。
滑动窗口还是事件清单,这是设计选择
还有第二种形态:-hls_playlist_type event。它会写出 #EXT-X-PLAYLIST-TYPE:EVENT,并把 hls_list_size 强制为 0,于是清单里保留从开播以来的每一个切片,只增不删。
代价是兼容性。部分播放器在 EVENT 清单下会拒绝拖动,直到 #EXT-X-ENDLIST 出现——而那个标记只在直播结束时才写出来。video.js 仓库里有一个长期未解决的 issue(videojs/video.js#8856)正是这个问题,同样的模式在别处也出现过。选 EVENT 之前,要在真实的目标播放器上测,不能只测桌面 Chrome。
本文后续的构建,走的是滑动窗口这条路。
没人跑的那项检查:存储跟得上吗
清单对外承诺了一个可拖动范围,但背后要有三个系统同时兜住它,而它们往往由不同的人在不同的时间配置:
- 清单:hls_time × hls_list_size = 600 秒
- 源站磁盘:(hls_list_size + hls_delete_threshold) × hls_time = 630 秒
- 对象存储生命周期:取决于桶策略怎么写
如果第三项比第一项短,进度条就会给出一个拖过去必然 404 的范围。这个断言应该放进 CI:
脚本里读入 HLS_TIME、HLS_LIST_SIZE、HLS_DELETE_THRESHOLD,以及从基础设施配置里读出来的 STORAGE_TTL_SECONDS——注意不要硬编码。然后分别算出对外承诺的窗口和源站实际保留的时长,做两次比较:承诺值是否超过磁盘保留值,承诺值是否超过存储 TTL。任一不满足就打印问题并以非零码退出,全部通过则输出一行确认。
九行断言,挡住的正是客服工单的来源。
在 hls.js 里读出这个窗口
播放器这一侧,hls.js 会给出直播边缘和可拖动范围,剩下的要自己转成界面。
初始化时,liveSyncDurationCount 决定落后直播边缘多远,单位是 EXT-X-TARGETDURATION 的倍数,默认是 3;也可以用秒为单位的 liveSyncDuration 替代,它的优先级更高。backBufferLength 控制回退缓冲的长度。
关键在 LEVEL_LOADED 事件里读 details.totalduration。直播层级上的这个值,就是当前清单的长度,也就是 DVR 窗口的大小。它会随着窗口滑动而变化,所以每次事件触发都要重新读,不能只缓存一次。
进度条追的是一个移动靶
陷阱在于:直播流里 video.duration 是 Infinity,而 seekable.start(0) 会随时间不断增大。一个常规的、绑定 currentTime 除以 duration 的 range 输入框,在这里做不了任何有用的事。
正确做法是映射 seekable 范围本身。先取到范围的起点和终点,算出跨度;跨度小于等于零就直接返回。位置用当前播放时间减去起点,再除以跨度,得到 0 到 1 之间的比例。写入进度条之前,先判断它是否处于激活状态,避免用户正在拖动时被程序覆盖。
至于"回到直播"按钮,不要直接跳到可拖动范围的末尾——那样会卡住。应该跳到 liveSyncPosition。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.