本文主要从架构边界角度梳理一类自托管媒体库(以 Octans 播放子系统为例):播放为何从 Catalog / 任务系统里拆出来,服务端与客户端各管什么,以及 PlaybackSession、Planner、DirectPlay / HLS 交付如何构成当前 Web 主链。重点是职责与名词,而不是接口字段大全。
细节决策见 播放能力 Planner,运行时排障见 播放排障指南,转码二进制与能力探测见 FFmpeg 工具链。本文只回答:模块怎么切、主链怎么走、什么算正式范围。
播放子系统为什么要独立
「点播放」看起来像详情页的一个按钮,实际会同时牵涉:
| 关切 | 为什么不能塞进纯查询或批处理 |
|---|---|
| 能否播 / 如何播 | 依赖客户端能力、媒体事实与策略的交叉决策 |
| 受控下发 | 短期 token、Range、HLS window,不能暴露裸静态路径 |
| 运行时 | 转封装 / 转码进程、Runtime Slot、缓存与清理 |
| 状态回收 | 进度、续播、逻辑会话生命周期、异常退出兜底 |
因此播放应形成独立业务域(概念上即 Modules/Playback 一类模块),读取 Catalog 已沉淀的媒体事实,但不把协商与流交付塞进海报墙 / 详情查询;不把会话与 HLS 运行时塞进扫描 / 刮削类 Operations;不把播放实时心跳直接复用任务 TaskHub。
一条已经能闭环的 Web 主链可以概括为:
MediaFile
→ 权限校验
→ PlaybackPlanner
→ PlaybackSession
→ 受控 HTTP Range 流 / HLS window
→ Web <video> / hls.js
→ Progress 上报
→ ResumePosition
核心原则
服务端决策,客户端执行
| 侧 | 负责 |
|---|---|
| 服务端 | 解析目标媒体文件、读权限、兼容性判断、选择播放与交付方式、生成受控流地址、进度与续播回收;后续转封装 / 转码 / 缓存 / 调度的最终决策也落在服务端 |
| 客户端 | 请求会话、按返回的播放计划初始化播放器、交互控制、展示不可播原因、上报进度;原生端再叠加端侧解码、字幕渲染与播放器切换 |
客户端不应本地重算应由服务端给出的 HLS window 生命周期、stream token 绑定关系或字幕 delay 等控制面字段;UI 只消费决策结果。
直放优先,最小服务端处理
播放方式(PlayMethod)优先级保持:
DirectPlay → DirectStream → Transcode → Unsupported
能由客户端直接播放时,服务端只做受控 I/O。只有容器、音视频、字幕或网络条件确实要求时,才逐步进入转封装、转码或明确 Unsupported。
播放方式 ≠ 交付方式
这两层必须拆开,否则「走了 HLS」会被误读成「一定转码了」。
| 概念 | 问的是什么 | 常见取值 |
|---|---|---|
| PlayMethod | 服务端是否改变媒体内容 | DirectPlay / DirectStream / Transcode / Unsupported |
| Delivery | 客户端如何拿到流 | HttpFile / Hls(未来还可扩展 Dash、NativePath 等) |
当前 Web 主链上的典型组合:
DirectPlay + HttpFile → 原始文件受控 Range,不启 FFmpeg
DirectStream + Hls → 视频 copy,容器转封装 / 必要音频 AAC
Transcode + Hls → 视频或完整主轨重新编码后输出 HLS
HLS 可以承载 DirectStream,也可以承载 Transcode;NativePath 只适合未来原生客户端本地路径场景,不适合 Web。
不把未落地方案写成事实
正式范围应限于代码与契约已接线的能力。ABR / 多 rendition、跨会话 Variant Cache、多端同步、服务端主动 force stop、Android / Apple 完整原生实现等,属于后续方向,进入实现后再写进「当前事实」。
决策模型(四档 PlayMethod)
DirectPlay
服务端不改变媒体内容,只把原始文件以受控方式交付。
适用直觉:用户有读权限、文件可访问,且客户端声明的容器 / 视频 / 音频能力覆盖源事实;色彩安全边界(如明确 HDR / DV / HLG / BT.2020 / 10-bit 风险)也允许直放。字幕路线由后端根据选轨事实决定(关闭、WebVTT 资源、native sidecar、ASS / bitmap overlay 等),不由前端指定「服务端画面合成」作为普通 Web 主线。
DirectStream
服务端只做容器转封装或必要音频转码,主视频保持 copy。
典型场景:视频编码浏览器可解但容器不兼容;视频可 copy 但选中音轨需转 AAC;需要 HLS 交付但不需要重编画面。
Transcode
服务端对视频或完整主轨重新编码。
典型场景:客户端不支持源编码;需要降码率;字幕必须烧录;设备能力不足需服务端降级。实现上通常还牵涉 FFmpeg capability graph、软件 / VAAPI / OpenCL 路径与 tone mapping——细节见工具链与硬转码专题,不在此展开命令构造。
Unsupported
当前无法为该客户端生成可执行计划,且必须带明确原因(容器 / 轨组合无路、工具链或硬件能力不足、能力上报与媒体事实不匹配、物理文件不可访问等)。
注意:用户无权限、资源不可见或媒体记录对当前身份不可达,一般按资源授权口径隐藏为不可见(如 404),不应伪装成「兼容性 Unsupported」。正式错误码以目标环境 API 契约为准。
PlaybackSession:控制面中枢
可以把一次用户点击播放理解为创建逻辑会话,而不是「直接塞一个文件 URL」。
会话层通常承接:
| 能力 | 说明 |
|---|---|
| 协商 | 带上客户端类型 / 能力与选轨意图,换回 method、delivery、流地址或 HLS 入口 |
| 生命周期 | pause / resume / seek / rebase / stop,以及异常退出后的 TTL 清理 |
| 进度 | session-scoped 进度上报与 ResumePosition |
| 与 HLS 解耦 | 逻辑会话 ID ≠ HLS window ID;rebase 或 grace miss 后会话可不变、window 可换新 |
实时侧若有 PlaybackHub 一类通道,当前更适合做 logical session heartbeat(页面隐藏、前台恢复、缓冲、reconnect 刷新 activity),而不是把多端同步或远程遥控一次做满。REST 进度仍负责持久观看位置;logical cleanup 仍是异常退出后的最终释放兜底。
排查时务必先分清「逻辑会话」与「HLS window」——见排障文的两个易混 ID。
Planner:如何从事实走到计划
Planner 不是「codec 白名单查表」,而是多维约束求解。产品视角可先收成三轴:
ClientCapabilities × MediaFacts × Policy
→ PlaybackPlan
(method + delivery + 代价与否决原因)
| 轴 | 问的是什么 |
|---|---|
| 客户端能力 | 端上声明能承接什么(平台 / 引擎、容器与 codec、MSE / native HLS、字幕路线、是否支持 HttpFile 等) |
| 媒体事实 | 源片实际是什么(容器、profile/level、动态范围、音轨、字幕形态等) |
| 策略 | 允许与偏好什么,以及服务端现在能不能做(质量 / 省电、LAN/WAN、FFmpeg / Slot / GPU / 磁盘、保守兜底) |
输出除 method / delivery 外,还应让控制面能解释代价:是否 tone mapping、是否 downmix、字幕路线、是否降档、是否等 Slot 等,避免用户只看到「转码中」而无归因。
更细的输入块拆分、DirectStream 边界与否决原则,见 播放能力 Planner;runtime 已接线的输入×输出真相表读法,见转码支持矩阵相关文。
交付:HttpFile 与 HLS
DirectPlay + HttpFile
- 受控文件流 + HTTP Range。
- 数据面常用短期签名 token(浏览器原生
<video>难以稳定带自定义 Authorization 头)。 - Token 应绑定用户、会话、认证版本、媒体文件与文件版本,并在 stream 端点复核会话 / 账号 / 文件存在性与读权限。
- 典型错误语义(公开口径):token 非法或会话失效 → 拒绝;过期 → 需刷新;资源不可见或文件不可访问 → 不可见。具体状态码以部署环境 API 为准。
DirectStream / Transcode + Hls
- 交付 playlist、segment,以及可选字幕分段。
- 运行时牵涉 HLS window、session cache、runtime pause、Runtime Slot 并发治理。
- 前端需处理 fatal、token refresh、window stale、segment missing 等恢复路径(rebase / 重建 window)。
- DirectPlay 不启 FFmpeg 是正常现象;HLS 路径则依赖专用工具链能力图是否完整——见 FFmpeg 工具链。
进度与续播
- Progress 上报绑定逻辑会话,写入持久 ResumePosition。
- 详情页 / 继续观看只消费位置,不负责重新决策 PlayMethod。
模块边界(与周边域的关系)
| 周边域 | 关系 |
|---|---|
| Catalog | 提供媒体事实与详情入口;播放协商与流地址不走详情查询接口 |
| Operations | 扫描 / 刮削 / 删除等后台动作;播放运行时即使内部有队列,也属于播放域内部,不塞进 actions |
| System / 任务 | 系统任务与 TaskHub 不做播放控制面;播放实时域独立演进 |
| Auth / 资源授权 | 会话与流地址都不能绕过库读权限;受控 token ≠ 裸静态路径 |
| Metadata | 可读技术轨信息;播放决策不写回 Metadata 模块 |
概念上的播放模块职责清单(公开层):播放协商与 options 预检、计划生成、HttpFile / HLS 输出、FFmpeg 能力图与转码调度、fallback replan、进度与续播、Runtime Slot 与 heartbeat,以及后续缓存与多端同步的扩展位。
客户端边界
当前 Web 正式范围(概念)
后端侧:会话协商、权限、Web 基线兼容、DirectPlay HttpFile、DirectStream / Transcode HLS、受控流与 HLS 输出、Range、pause/resume/seek/rebase/stop、进度与续播、heartbeat 与 TTL cleanup、明确失败原因。
前端侧:播放 API 调用、选轨 / options UI、独立播放页、原生 <video> + hls.js、HLS 恢复路径、错误提示、session 进度、heartbeat、续播提示。
播放入口可以从详情页「自然生长」,但应落到独立播放壳,而不是把会话生命周期散落在组件库里。
客户端类型与扩展
契约侧通常先承诺有限 clientKind(如 Web / Windows),能力模型再向多端协商演进。原生 Android / Apple / Desktop / TV 的双核播放器、端侧字幕、SMB/NFS、AFR、HDR passthrough 等,应在对应工程启动后再写入正式范围,而不是提前当作已交付事实。
跨端 UI 与 native 播放的分工,可另见系列中的跨端与 Windows / Android 专题。
当前正式范围 vs 后续
| 已在主链(概念已落地) | 后续方向(勿写成已交付) |
|---|---|
| DirectPlay + HttpFile | ABR / 多 rendition |
| DirectStream + Hls | 跨会话 Variant Cache |
| Transcode + Hls(含软硬解与 tone mapping 主线) | 多端同步 / 远程控制 / 服务端主动 stale 推送 |
| options、fallback、逻辑会话、进度与续播 | Android / Apple 完整原生实现 |
| Runtime Slot、PlaybackHub heartbeat | 外部播放器深度集成 |
推进 Variant Cache 或 ABR 前,需先想清:跨播放复用边界、cache key 与权限复核、源失效与失败 session 不入 cache、对 Slot / 磁盘的压力、用户可见错误与运维排查方式。
怎么读这一系列
建议阅读顺序(从总览到落地):
1. 本文:架构边界与主链名词
2. 播放能力 Planner:决策输入与四档边界
3. 转码支持矩阵读法 / 硬转码与 Tone Mapping:Transcode 内如何选型
4. FFmpeg 工具链:二进制与能力探测
5. 播放排障:会话 / window / token / 字幕 / 残留如何定位
一句话分工:
| 文档 | 回答 |
|---|---|
| 本文 | 模块怎么切、主链怎么串、正式范围在哪 |
| Planner | 为什么选这条 method / delivery |
| 工具链 | 服务端有没有能力把计划跑出来 |
| 排障 | 计划对了但播不了时落在哪一层 |
小结
- 播放是独立业务域:权限 + 决策 + 受控交付 + 状态回收,不是详情查询附件,也不是扫描任务变体。
- 服务端决策,客户端执行;直放优先;PlayMethod 与 Delivery 正交。
- 主链骨架:
MediaFile → Planner → Session → HttpFile / HLS → 客户端 → Progress / Resume。 - 逻辑会话控制面与 HLS window 数据面必须分开理解。
- 公开文只写已接线主链;ABR、跨会话缓存、多端同步与未启动的原生客户端保持后续位。
相关阅读
同系列还可对照:
- 媒体库播放能力 Planner:客户端能力 × 媒体事实 × 策略如何落到 DirectPlay / DirectStream / Transcode:决策三轴、四档边界与否决原则。
- 媒体库播放排障:从会话创建到 HLS / DirectPlay / 字幕链路:逻辑会话与 HLS window、token、字幕与残留的分场景定位。
- 媒体库 FFmpeg 工具链:从 Jellyfin 构建到自维护 runtime:专用 FFmpeg、能力探测与「二进制能跑」分层验证。
来源
本文改写自 Octans 项目文档中的播放架构说明(octans-docs arch 层)。正文收敛为公开架构总览与职责边界,不展开完整 API 字段、控制器方法、数据库表或前端组件实现;接口契约与运行时细节以目标环境当前 API / 实现文档为准。