播放设置与缓存
播放设置页按节点调优播放代理(play-agent)的缓存与网络行为,面向部署了播放节点、需要控制磁盘占用与启播速度的运维者和站长。
播放设置
统一配置播放节点的缓存、网络优化、元数据探测与运行策略。
- 配置是节点级的,没有全局下发
- 四个 Tab:VFS 文件缓存 / 图片缓存 / CDN 优选 / 元数据探测
- 一台调好可以同步到其他节点

「播放设置」就是「缓存管理」
后台里的播放设置与缓存管理是同一个页面,不是两个功能——侧边栏叫「播放设置」,两个路由指向同一处。两个入口看到的是完全相同的界面,配置也只有一份。
能做什么
- 按节点开关 VFS 视频缓存,在「启播加速」与「传统预读」两种用途之间选择
- 设定缓存盘容量、缓存目录、保留时间与清理频率,把磁盘占用控制在预算内
- 逐条查看已缓存文件,看每个文件缓存了哪些字节区间,单独删或整体清空
- 缓存海报与缩略图,减少图片回源
- 为 Google Drive 播放节点优选延迟更低的上游 CDN 访问地址
- 让节点在播放时用 ffprobe 异步补全技术元数据
- 把一台节点调好的配置同步到其他播放节点
先选节点:配置是节点级的
单机版只有一个内置节点
单机版固定内置一个 Play Agent(node-local-play-agent),左侧节点列表里只有它一项, 没有节点切换菜单,右侧直接显示它的配置与状态。你不需要也无法切换到别的节点。
「同步到其他节点」按钮在单机版下没有可同步的目标;相关能力只在分布式版 (有多个外部 Play Agent 节点)时才有意义。内置节点随主应用整镜像升级, 不提供独立的节点级远程升级。
页面顶部先选节点,只列出 play-agent 节点,每项带在线/离线状态;选择记忆在浏览器 localStorage,默认优先在线节点。移动端是「先选节点、再选 Tab」的两步式布局。
没有全局下发
缓存配置保存在单个节点上,不存在「配一次、全部节点生效」的全局开关。多台节点要逐台配置,或用「同步到其他节点」把当前节点的配置推过去——推送结果按节点逐个返回成败。
四个 Tab
| Tab | 头部摘要 | 管什么 |
|---|---|---|
| VFS 文件缓存 | 已用空间 · 条目数 | 视频文件的本地磁盘缓存,配置项最多 |
| 图片缓存 | 已用空间 · 条目数 | 海报与缩略图的本地缓存 |
| CDN 优选 | Google Drive 优选地址 | 节点访问上游云盘 CDN 的 IP 优选 |
| 元数据探测 | FFprobe · 最多 N MB | 播放时异步补全容器、编码、轨道等技术元数据 |
VFS 文件缓存
两种缓存用途
mode | 界面文案 | 行为 |
|---|---|---|
SMART_STARTUP | 启播加速(推荐) | 只保存开场所需数据,不追求整片落盘 |
READ_AHEAD | 传统预读 | 边播边持续预读,会占用更多硬盘 |
智能启播缓存怎么工作
目标是让更多影片更快出画面,而不是把影片缓存完整。
| 机制 | 说明 |
|---|---|
| ffprobe 受控访问记录 | 探测容器元数据的真实位置,缓存位置不限于头尾(为什么,见核心概念) |
| 只存必要字节 | 持久化「基础头部 + 必要索引 + 少量开场字节」;头部目标按码率自适应,默认约 8 秒开场数据 |
| 启播回放验证 | 探测后回放验证,成功即停。单文件上限是保护线,不是缓存目标 |
| 混合读取 | 单个响应内可混合「本地缓存段 + 上游有界直传」,并保持原文件偏移 |
| 降级路径 | 关闭智能探测、或节点上 ffprobe 不可用时,退回只缓存基础头尾 |
全部可配置项
| 配置项 | 说明 |
|---|---|
| 启用 VFS 缓存 | 将视频文件缓存到本地磁盘,减少云盘请求 |
| 缓存用途 | SMART_STARTUP / READ_AHEAD |
| 空间与稳定性 | 三档预设:SPACE_SAVER 节省空间(最多 16 MB)、BALANCED 智能平衡(最多 32 MB)、STABLE 更稳启播(最多 64 MB)。改过下面的专家参数后,方案显示为「自定义参数」 |
| 启用 ffprobe 智能探测 | 探测容器元数据实际位置并缓存对应字节 |
| VFS 磁盘容量 | 所有视频缓存合计达到该容量后,自动淘汰最久未使用的内容 |
| 缓存目录 | 缓存文件落盘位置。默认:外部节点 ./vfs_cache(相对节点工作目录);单机版内置节点 /data/play-agent/vfs-cache(容器内挂载卷路径,容器重建与升级后仍保留)。想让缓存落在更快的盘(如 SSD),单机版请在部署目录的 .env 里设 YIYI_PLAY_AGENT_VFS_CACHE_DIR |
| 最长保留时间 | 按文件最后一次访问时间计算超期 |
| 清理轮询间隔 | 淘汰检查的轮询周期 |
| 预读缓冲区 | 每路播放的后台预读窗口,0 = 全量下载。机械盘建议 128~256 MB,固态盘建议 ≤512 MB |
| 后台下载并发 | 限制全节点同时进行的 bgFull 后台下载数,-1 = 不限制,默认 4 |
| 按发布时间跳过缓存 | 0 = 不启用;大于 0 时,TMDB 发布时间超过该天数的电影/剧集播放时不做 VFS 缓存 |
| 热门内容强制缓存 | 0 = 不启用;最近 1 天观看人数达到该值时仍做 VFS 缓存,优先级高于发布时间跳过策略 |
| 最小头部 / 尾部 | 低码率或缺少元数据时使用的保底范围 |
| 单文件硬上限 | 自动计算、元数据区间和 ffprobe 新增数据的合计上限 |
| ffprobe 流量预算 | 单次探测允许新增的云盘读取量,仍受单文件硬上限约束 |
| 多线程参数 | 每个已启用且非本地磁盘的网盘,可单独设「下载并发线程」(1–32)与分片大小 |
页面上没有这些配置项
播放设置页不存在「默认码率」「缓冲秒数」「转码开关」这类项。play-agent 不做转码与转封装,以直连播放(direct stream)为主,因此没有码率档位可配。
淘汰与机械盘告警
淘汰策略是 LRU + MaxAge:使用中的文件不淘汰;写盘前预留「已写入 + 在途」的空间;磁盘写满(ENOSPC)时降级为直传,不中断播放。传统预读模式下,预读达 90% 停止、低于 10% 继续。缓存文件是稀疏文件,按 Range 写盘,跨进程用目录锁协调。
页面会自动识别缓存盘介质,并按介质给出不同的预读告警阈值:
| 缓存盘类型 | 预读窗口告警阈值 |
|---|---|
| 机械盘(HDD) | 大于 256 MB |
| 固态盘(SSD) | 大于 512 MB |
| 无法识别 | 大于 384 MB |
超过阈值时页面提示:预读窗口是每路播放的后台预读量,并发播放时写盘量近似按路数叠加;机械盘在多路并发下「随机读 + 多路预读写」会瓜分磁头,可能饿死播放读,建议收紧预读。
查看缓存内容
点「查看缓存内容」打开 VFS 条目弹窗:
| 能力 | 说明 |
|---|---|
| 浏览 | 分页、关键词搜索、按剧集分组 |
| 排序 | 已缓存 / 总大小 / 最后访问 |
| 彩色区间条 | 每条一行,蓝色是头部、绿色是中部、紫色是尾部,直观显示这个文件缓存了哪些字节段 |
| 状态徽标 | 智能就绪 / 缓存失败 / 基础头尾 / 播放中 |
| 删除 | 播放中的条目禁止删除;其余可单条删除或全部删除 |
图片缓存
| 配置项 | 说明 |
|---|---|
| 启用图片缓存 | 海报/缩略图缓存到本地,加速图片响应 |
| 缓存目录 | 图片缓存落盘位置 |
| 最大容量 | 超出后自动 LRU 淘汰 |
| 最长保留时间 | 按文件最后一次访问时间计算超期,默认 7 天 |
默认值:外部节点缓存目录 ./image_cache(相对节点工作目录);单机版内置节点为 /data/play-agent/image-cache(容器内挂载卷路径,容器重建与升级后仍保留)。 最大容量 500 MB、最长保留 7 天、清理轮询 10 分钟。 每张图旁会存一个 JSON 边车文件,记录内容类型、ETag、最后访问时间与过期时间。
换了海报但页面还是旧图?
图片缓存没有按条目失效的能力——只有「清空全部缓存」这一个入口。 所以你在元数据覆盖里改了封面、或重新刮削拿到了新海报之后, 播放节点上的旧图会继续命中,直到它 7 天保留期到期,或你手动清空缓存。
想立刻生效:在播放设置页对该节点点 清空全部缓存(会同时清掉图片与 VFS 缓存, 但不会改动缓存配置),然后让用户刷新。
CDN 优选
| 配置项 | 说明 |
|---|---|
| 启用 Google Drive CDN 优选 | 为播放节点选择延迟更低的访问地址 |
| 查看和管理优选 IP | 打开状态弹窗:当前主 IP、优选池(最多 5 个达标 IP)、手动优选 IP、手动候选 IP |
优选由 config 服务的调度任务 gdrive-cdn.optimize 驱动,每 60 秒用 DoH 解析 Google CDN 候选 IP、测延迟并结合实时吞吐,对各 play-agent 无感切换最优 IP。
这是节点内的 IP 优选,不是节点间调度
CDN 优选解决的是「同一个节点访问上游云盘时走哪个 CDN IP」。YiYi Media 没有基于负载或延迟的自动播放节点调度:用户端选节点的方式是并发请求各入口的 /health(超时 2400 ms)取第一个可达的,兜底取第一个;管理端在这个页面也只是选节点配缓存。
元数据探测
| 配置项 | 可选值 | 说明 |
|---|---|---|
| 播放时异步补全技术元数据 | 开 / 关 | 不会阻塞播放;节点必须报告 FFprobe 可用 |
| 最大并发 | 1 / 2 | 同时进行的探测数 |
| 新增云盘流量上限 | 8 / 16 / 32 / 64 / 256 MB | 单次探测允许新增的云盘读取量 |
| 启动延迟 | 0 / 2 / 5 / 10 秒 | 播放开始后多久启动探测 |
| 失败冷却 | 1 / 6 / 12 / 24 / 48 小时 | 探测失败后多久再试 |
节点内部另有默认值与固定约束:默认并发 1、云盘读取上限 32 MB、失败冷却 24 小时(这三项都可在页面上改);探测队列上限固定为 32,页面上不可调。
节点操作
| 按钮 | 作用 |
|---|---|
| 保存配置 | 把当前修改写到这个节点 |
| 同步到其他节点 | 把当前节点配置推给其他 play-agent,逐节点返回成败 |
| 刷新状态 | 重新拉取该节点的用量与运行状态 |
| 清空全部缓存 | 二次确认后清除该节点的图片缓存与 VFS 文件缓存;缓存内容不可恢复,后续图片访问和视频播放会重新下载并生成缓存 |
| 查看缓存内容 | 打开 VFS 条目弹窗 |
115 网盘 302 直连
播放设置页顶部还有一张**「115 网盘 302 直连」配置卡,它是这页里唯一的全局开关**,不随左侧选中的节点变化。
和缓存配置的本质区别
| 缓存 / CDN / 元数据探测 | 115 网盘 302 直连 | |
|---|---|---|
| 生效范围 | 单个节点 | 全部 play-agent |
| 下发方式 | 保存到该节点 | 经策略刷新热更新(默认最多约 120 秒) |
| 是否依赖选中节点 | 是 | 否,没有选中任何节点时依然可见可改 |
生效条件
开关本身只是必要条件之一。要让某个账号真正走 302,必须同时满足:
全局开关开启
AND 该用户的「115 播放模式」= 302 直连
AND 媒体源是 115 网盘
AND 是视频播放,不是下载任一条件不满足都继续由 play-agent 代理播放。因此:
- 默认全关:升级后不改变任何用户的播放方式。
- 全局开关关闭时,就算用户已经配好「302 直连」,也会临时回退成代理播放;重新打开后自动恢复,用户配置不会丢。
- Google Drive、本地盘与其它媒体源完全不受这个开关影响。
- 下载(
/Items/{id}/Download)始终走代理,不受 302 影响。
开启前必须知道的代价
302 的原理是:play-agent 完成鉴权与直链解析后返回 HTTP 302,播放器直接连 115 CDN 取字节。视频数据从此不再经过 play-agent,因此下面这些能力全部失效:
| 能力 | 代理播放 | 302 直连 |
|---|---|---|
| 用户 / 入口实际播放流量统计 | 支持 | 不支持 |
| 流量配额持续扣减、耗尽后断流 | 支持 | 不支持 |
| 用户带宽限速 | 支持 | 不支持 |
| 最大并发播放数精确占用 | 支持 | 不支持 |
| VFS 文件缓存与智能启播缓存 | 支持 | 绕过 |
| CDN 重试与 401/403 自动恢复 | 支持 | 只能在发出 302 前处理 |
| CDN IP 优选 | 适用 | 不适用,连接由用户终端建立 |
还有两条固有行为:
- 直链可被复制:302 会把一条临时 CDN 签名直链交给播放器,用户在它过期前可以复制使用。这是 302 模式本身的性质,服务端无法规避。
- 失败不能透明降级:发出 302 之后,play-agent 无法观察客户端对 CDN 的访问结果。若该次访问失败,用户需要重新发起播放(重试会重新解析直链),不存在「服务端自动改回代理」的透明降级。
报表里的 302 流量记为「不可观测」
开启 302 后,后台流量报表中这部分播放不会产生字节计量。看到 0 字节不等于用户没有播放,不要把两者混为一谈。
302 发出前仍会做一次配额预检查
如果用户或入口的流量配额在本次请求之前就已经耗尽,play-agent 会直接拒绝,不会下发直链。但这次 302 播放本身不产生可计量字节,也就是「能拦住已经欠费的,但拦不住这次播放把配额用超」。
排查要点
- 直链解析失败、目标不是 HTTPS、或目标 host 不在允许清单内时,play-agent 不会下发
Location,而是返回502(固定脱敏文案)。这是刻意设计:302 用户应当看到明确失败并重试,而不是被静默降级成代理播放。 - play-agent 日志只记录
userId、itemId、providerCode、目标 host 与结果,不记录直链的签名参数。 - 目标 host 允许清单由节点环境变量
PLAY_AGENT_115_REDIRECT_ALLOWED_HOSTS(逗号分隔的域名后缀)配置,默认为空。空清单表示只校验「HTTPS + 可解析」,不限定域名——115 CDN 的真实域名形态需先用真实账号验证,确认后再固定后缀以收紧校验。 - 紧急停止只需要关掉全局开关,所有用户立刻回到代理播放,不必逐个改用户配置。
一个必须知道的限制:直链与 User-Agent 绑定
115 的直链与申请它时使用的 User-Agent 绑定。而 HTTP 302 有一个协议层面的硬限制: 服务端无法要求播放器用哪个 User-Agent——播放器跟随跳转时会发送它自己的 UA。
因此 302 采用「固定 UA + 无条件 302」策略:
- play-agent 始终用同一个固定 UA 去申请直链,绝不把客户端的 UA 转发给 115(避免触发风控);
- 只要全局开关与用户模式满足就返回 302,不比对客户端 UA。
这带来一个必然结果,也是启用前必须接受的:
播放器的 UA 与被绑定的固定 UA 不一致时,可能无法播放
CDN 在取字节阶段看到的是播放器自己的 UA,这个值服务端干预不了。若它与申请直链时 所用的固定 UA 不一致,CDN 可能拒绝该请求,表现为客户端侧无法播放。
这种情况下 play-agent 不会收到任何错误(302 之后字节不经过它),后台也不会有失败日志—— 排查时请直接从客户端侧观察。
固定 UA 的取值优先级:
| 来源 | 说明 |
|---|---|
节点环境变量 PLAY_AGENT_115_REDIRECT_USER_AGENT | 优先级最高,用于把 UA 钉成一个已知低风险的值 |
| 该 115 账号的存储配置 UA | 管理端「文件管理 → 115 网盘」里配置的 User-Agent(默认浏览器 UA) |
| 内置默认值 | 与 Java 侧 OneOneFiveStorageDriver.DEFAULT_USER_AGENT 保持一致 |
固定 UA 必须对所有 115 取链请求取同一个值
play-agent 的直链缓存键不含 UA。如果代理播放与 302 用两个不同的 UA 取链,两者会共享 同一条缓存记录,把绑定到 A 的直链发给以 B 取字节的请求。代码里已把 UA 解析收敛为唯一入口 (oneOneFiveUserAgent)并用测试锁住「两条链路解析结果一致」,运维侧不需要额外注意; 但若日后要调整 UA,不要只改其中一条路径。
缓存分几层
| 层级 | 位置 | 内容 |
|---|---|---|
| ① 进程内内存缓存 | play-agent | 带 TTL 的元数据与响应缓存 |
| ② VFS 视频缓存 | 节点本地磁盘 | 视频文件的字节区间 |
| ③ 图片缓存 | 节点本地磁盘 | 海报与缩略图 |
| ④ 播放入口列表缓存 | control-user 的 Redis | 下发给用户的播放入口列表 |
播放数据不经 Redis:视频字节只走节点本地磁盘与上游直传。
页面没有缓存命中率
播放设置页没有命中率看板。想判断缓存效果,只能通过「查看缓存内容」里的条目列表与彩色区间条间接观察——区间条会显示每个文件缓存了哪些字节段。
play-agent 侧的两件事
这两项由 play-agent 自己执行,页面上不单独配置,但直接决定缓存与限速的实际效果。
| 机制 | 行为 |
|---|---|
| 签名直链缓存 | 复用签名直链、避免逐个分片换链触发风控(原理见加载与播放速度)。play-agent 以 providerCode:itemId(多账号网盘再加 accountID)为键缓存:TTL 从直链 URL 自带的过期参数推算,剩余超过 2 分钟就取「过期时刻减 1 分钟」,否则兜底 30 分钟;本地拼装的直链(如 Google Drive)没有过期参数,则不设 TTL 过期,只在 CDN 返回 401/403 时重新获取一次。令牌失效这类错误还会做 5 分钟负向缓存,避免反复打云盘 API;并发的同一请求会合并成一次远端调用 |
| 令牌桶限速 | 输出带宽由令牌桶控制:rate=0 表示不限速;burst 约为 20 毫秒的配额,并夹在 4 KB ~ 4 MB 之间,以适配 10 Mbps ~ 1 Gbps 的限速区间。配置热更新时清零桶内余量,避免旧配额残留 |
相关接口
GET /api/config/nodes/{nodeId}/cache-config
PATCH /api/config/nodes/{nodeId}/cache-config
GET /api/config/nodes/{nodeId}/proxy/cache/disk-usage
GET /api/config/nodes/{nodeId}/proxy/cache/vfs-items
POST /api/config/nodes/{nodeId}/proxy/cache/vfs-delete
POST /api/config/nodes/{nodeId}/proxy/cache/flush
GET /api/config/nodes/{nodeId}/proxy/cache/startup-probe/status
POST /api/config/nodes/{nodeId}/proxy/cache/startup-probe/recheck
GET /api/config/nodes/{nodeId}/proxy/cdn/status
POST /api/config/nodes/{nodeId}/proxy/cdn/probe
POST /api/config/nodes/{nodeId}/proxy/cdn/measure-ip
POST /api/config/nodes/{nodeId}/proxy/cdn/manual