Files
synctv-weihang/docs/first-version-plan.md
2026-06-15 22:46:12 +08:00

718 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SyncTV 第一版方案
## 1. 产品定位
SyncTV 第一版是一个无注册、无用户体系的同步观影客户端,面向 App 和 Windows 应用。
核心目标:
- 输入房间 Code 即可进入房间。
- 不做账号、昵称、好友、聊天、历史用户等用户体系。
- 不支持本地视频。
- 只支持网络播放源普通链接、OSS、WebDAV、直播流。
- 客户端负责解码播放,避免网页播放器格式支持弱的问题。
- 后端负责房间状态、同步信令、播放源解析、临时凭据保存,以及必要时的流式转发。
- 支持断线重连,并恢复到房间当前播放进度。
第一版优先把三件事做好:
- 能播。
- 同步准。
- 断线能回来。
## 2. 第一版功能范围
### 2.1 创建和进入房间
用户打开应用后可以创建房间,或输入已有房间 Code 加入房间。
房间可以先没有播放源。第一个加入房间成功的设备自动成为房主。
房主可以配置:
- 播放源类型。
- 播放源地址或配置。
- 是否为直播流。
- 连接方式,默认客户端直连,可手动切换为服务端代理。
创建新房间时服务端生成房间 Code例如
```text
A7K29Q
```
### 2.2 加入房间
客户端只需要输入房间 Code。
加入后客户端自动获取:
- 播放源信息。
- 当前播放状态。
- 当前播放进度。
- 是否直播。
- 当前房主设备信息。
不需要用户登录,也不需要设置昵称。
只有房主可以配置或更换播放源。所有房间成员都可以执行播放、暂停、跳转进度、回到直播等播放控制。
播放源连接方式由房主在配置时选择。默认是客户端直连源站,代理模式必须由房主主动打开。
### 2.3 播放控制同步
点播场景支持:
- 播放。
- 暂停。
- 跳转进度。
- 当前进度同步。
- 缓冲后恢复。
直播场景支持:
- 播放。
- 暂停。
- 回到直播。
- 直播延迟同步。
### 2.4 断线重连
客户端 WebSocket 断开后自动重连。
重连时携带:
- 房间 Code。
- 本地设备 ID。
服务端返回最新房间快照,客户端重新计算应播放位置并恢复。
房间无人在线后保留一段时间,建议第一版保留 30 分钟。超过后销毁房间和临时播放源凭据。
## 3. 非目标
第一版不做:
- 注册。
- 登录。
- 昵称。
- 用户资料。
- 好友。
- 聊天。
- 弹幕。
- 房间列表。
- 历史记录。
- 本地视频同步。
- 内置影视内容。
- 完整资源站。
## 4. 技术选型建议
### 4.1 客户端
客户端改为分端开发。
Windows 客户端建议:
- Python。
- PySide6。
- python-vlc。
- PyInstaller 打包 Windows exe。
- 系统播放器逻辑和同步逻辑分离。
移动 App 建议:
- HBuilder/uni-app。
- UI 和房间同步逻辑使用 Vue/uni-app。
- 播放器通过 App 原生插件接 VLC。
- Android 原生插件封装 libVLC。
- iOS 原生插件封装 MobileVLCKit / VLCKit。
分端开发的原因:
- 避免重型桌面工具链问题。
- Windows 端优先把强播放能力跑通。
- App 端通过 HBuilder 快速开发,同时保留原生 VLC 播放能力。
- 客户端共享后端协议,不共享 UI 代码。
选择播放器时需要重点验证:
- mp4。
- mkv。
- flv。
- m3u8。
- dash。
- 常见编码格式。
- 直播流。
- HTTP Header 自定义。
- Cookie / Authorization Header。
- WebDAV URL 播放。
- 字幕和音轨扩展能力。
### 4.2 后端
第一版服务端原则:
- 尽量轻。
- 尽量少模块。
- 尽量少状态。
- 默认不接触视频流。
- 只有房主手动开启代理时才做流式转发。
- 不做用户系统。
- 不做数据库表设计。
- 不做文件存储。
推荐:
- Go。
- 标准库 net/http 或 Gin/Fiber/Echo。
- WebSocket。
- Redis。
- Docker + Nginx。
第一版不建议上 PostgreSQL。房间状态、连接状态、临时播放源和过期时间都放 Redis开发更快清理也简单。
Go 更适合服务端保持轻量:单二进制部署,内存占用低,并发连接和流式转发能力好。第一版可以优先使用标准库 net/http减少框架依赖。
### 4.3 服务模块
后端建议只拆最少模块:
- RoomService房间创建、加入、房主判定、房间快照、过期清理。
- SyncGatewayWebSocket 连接、播放控制广播、心跳、重连。
- SourceService播放源保存、直连信息下发、代理 URL 生成。
- StreamProxy房主手动开启代理时对 WebDAV/OSS/普通私有链接做流式转发,不落盘存储。
第一版可以先不单独拆 CredentialService、SourceResolver 等细模块,避免服务端过早变重。等代理来源和鉴权方式变多后再拆。
## 5. 播放源设计
第一版支持四类来源。
| 类型 | 示例 | 第一版处理方式 |
| --- | --- | --- |
| 普通链接 | mp4、m3u8、flv、dash | 默认客户端直连,可手动开启代理 |
| OSS | 阿里云 OSS、S3 兼容对象存储 | 默认客户端直连,可手动开启代理 |
| WebDAV | WebDAV 文件 URL | 默认客户端直连,可手动开启代理 |
| 直播流 | m3u8、flv、其他播放器支持协议 | 默认客户端直连,可手动开启代理 |
房主配置播放源时需要有一个明确的连接方式控件:
- 默认:客户端直连。
- 可选:服务端代理。
这个控件可以做成分段按钮或开关,不做自动切换。代理模式涉及服务器带宽成本和隐私边界,必须让房主显式选择。
### 5.1 普通链接
普通链接是最简单的来源。
后端只保存 URL 和基础元信息,客户端直接播放。
需要支持:
- 普通 HTTP/HTTPS URL。
- m3u8。
- flv。
- dash。
- 带必要 Header 的 URL。
### 5.2 OSS
OSS 有两种使用方式。
方式一:用户直接输入公开 URL 或签名 URL。
- 实现简单。
- 客户端直接播放。
- 后端只保存 URL。
- 适合第一版快速落地。
方式二:房主输入 OSS 配置,服务端保存临时凭据,并返回代理播放 URL。
- 更安全。
- 不把 Secret 直接广播给所有客户端。
- 服务端只做流式转发,不下载、不落盘。
- 房间销毁后清理配置。
- 适合稍微完整的第一版。
建议第一版支持方式一和方式二。方式一是默认模式;方式二需要房主手动开启,会增加服务器带宽压力,但不会增加硬盘存储压力。
### 5.3 WebDAV
WebDAV 是第一版需要重点设计的来源,因为它通常需要账号密码。
常见 WebDAV 鉴权方式:
- Basic Auth。
- Digest Auth。
- Cookie。
- Token。
播放器播放 WebDAV 文件时,客户端通常必须具备以下能力之一:
- URL 中包含凭据。
- 播放请求附带 Authorization Header。
- 访问服务端流式代理地址。
所以“WebDAV 的 key 不给客户端还能不能用”的答案是:
> 如果客户端要直接连接 WebDAV凭据或等价的临时授权信息必须给客户端不给就不能直连播放。
>
> 如果使用服务端流式代理,客户端不需要拿到原始 WebDAV 凭据;服务端拿到凭据后直接向 WebDAV 拉流,并边读边转发给客户端,不下载、不落盘。
#### 5.3.1 极简方案:凭据下发客户端
创建房间时,房主输入:
- WebDAV 文件 URL。
- 用户名。
- 密码或 Token。
服务端保存房间播放源,并在成员加入房间时下发给客户端。
客户端用这些凭据直接播放。
优点:
- 实现最简单。
- 后端不承担视频流量。
- 播放性能最好。
- 成本最低。
缺点:
- 所有加入房间的客户端都能拿到 WebDAV 凭据。
- 如果房间 Code 泄露,凭据也可能泄露。
- 对安全和隐私不友好。
适用场景:
- 早期内测。
- 用户之间互相信任。
- WebDAV 凭据本身是临时的。
- 产品明确提示风险。
#### 5.3.2 代理模式:服务端保存凭据并流式转发
创建房间时,房主提交 WebDAV 配置给后端。
后端:
- 加密保存 WebDAV 凭据。
- 验证文件是否可访问。
- 为房间成员生成代理播放 URL。
- 使用 WebDAV 凭据向源站发起请求。
- 将源站响应以流式方式转发给客户端。
- 不把视频文件下载到服务器硬盘。
- 不做长期缓存。
- 房间过期后删除凭据。
客户端播放:
```text
https://api.example.com/rooms/A7K29Q/source/stream?token=short-lived-token
```
服务端收到请求后使用 WebDAV 凭据去拉取源文件,再把内容边读边转发给客户端。
优点:
- 客户端拿不到原始 WebDAV 密码。
- 可以做房间权限、过期、限速、防盗链。
- 房间销毁后授权自然失效。
- 不承担硬盘存储压力。
缺点:
- 后端要承担视频流量,带宽成本高。
- 需要支持 Range 请求,否则拖动进度会很差。
- 大文件和高并发时压力明显。
- 直播和大码率视频对服务端要求更高。
- 需要处理连接中断、源站限速、响应头透传等转发细节。
这个方案安全性更好,不占用硬盘,但基础设施带宽成本更高。
#### 5.3.3 第一版建议
第一版建议做两个模式,但默认始终是直连模式:
1. 直连模式:客户端直连 WebDAV。
- 房主输入 WebDAV URL 和凭据。
- 后端临时保存并下发给房间客户端。
- 明确这是共享房间凭据,适合可信房间。
- 这是默认模式。
2. 代理模式:服务端流式转发 WebDAV。
- 房主输入 WebDAV URL 和凭据。
- 房主必须手动打开代理模式。
- 后端保存凭据。
- 客户端只拿代理播放 URL。
- 后端支持 Range 请求。
- 后端不下载、不落盘,只做流式转发。
- 后端承担带宽和连接压力。
普通 URL 和 OSS 也可以使用同样的代理模式。只要服务端能拿到访问源站所需的 URL、Header、Cookie 或 Secret就可以向源站发起请求并流式转发给客户端。
## 6. 房间状态模型
Redis 中建议保存房间快照。
点播房间示例:
```json
{
"roomCode": "A7K29Q",
"sourceType": "webdav",
"sourceId": "src_123",
"isLive": false,
"playbackState": "playing",
"positionMs": 128400,
"serverUpdatedAt": 1780000000000,
"ownerDeviceId": "dev_abc",
"onlineCount": 2,
"expireAt": 1780001800000
}
```
直播房间示例:
```json
{
"roomCode": "A7K29Q",
"sourceType": "url",
"sourceId": "src_456",
"isLive": true,
"playbackState": "playing",
"liveSyncMode": "latency",
"targetLatencyMs": 3000,
"serverUpdatedAt": 1780000000000,
"ownerDeviceId": "dev_abc",
"onlineCount": 2,
"expireAt": 1780001800000
}
```
## 7. 同步协议
### 7.1 WebSocket 事件
客户端到服务端:
- createRoom
- joinRoom
- leaveRoom
- play
- pause
- seek
- syncProgress
- syncToLive
- heartbeat
- reconnectRoom
服务端到客户端:
- roomCreated
- roomJoined
- roomSnapshot
- playbackChanged
- progressSynced
- liveSynced
- controllerChanged
- roomExpired
- error
### 7.2 点播同步
控制端执行播放控制时发送事件。
播放事件示例:
```json
{
"type": "play",
"roomCode": "A7K29Q",
"positionMs": 128400,
"clientSentAt": 1780000000000
}
```
服务端写入:
```json
{
"playbackState": "playing",
"positionMs": 128400,
"serverUpdatedAt": 1780000000120
}
```
其他客户端收到后计算目标进度:
```text
targetPositionMs = positionMs + (clientNowServerTime - serverUpdatedAt)
```
误差处理建议:
- 小于 500ms不处理。
- 500ms 到 2000ms短时间微调倍速。
- 大于 2000ms直接 seek。
控制端每 3 秒上报一次当前状态,用于校准。
### 7.3 直播同步
直播不以 positionMs 为核心,而以直播延迟为核心。
建议同步字段:
```json
{
"isLive": true,
"targetLatencyMs": 3000,
"playbackState": "playing"
}
```
客户端尽量保持距离直播边缘 3 秒左右。
直播控制:
- play开始拉流。
- pause暂停本地播放。
- syncToLive回到直播边缘或目标延迟。
直播流由于源站、CDN、客户端缓冲策略不同很难做到毫秒级同步。第一版目标应是让多个客户端处于接近的直播延迟。
## 8. 断线重连恢复
### 8.1 客户端本地保存
客户端本地保存:
```json
{
"deviceId": "dev_abc",
"lastRoomCode": "A7K29Q",
"lastConnectedAt": 1780000000000
}
```
deviceId 是本地随机生成的设备标识。
它不是账号,也不是用户 ID只用于
- 断线重连。
- 控制权恢复。
- 同一设备重复连接识别。
### 8.2 重连流程
1. WebSocket 断开。
2. 客户端进入重连状态。
3. 客户端带 roomCode 和 deviceId 重连。
4. 服务端检查房间是否还存在。
5. 服务端返回 roomSnapshot。
6. 客户端重新解析播放源。
7. 点播房间根据 positionMs 和 serverUpdatedAt 恢复。
8. 直播房间恢复到目标直播延迟。
### 8.3 房主和控制权处理
建议第一版规则:
- 第一个加入房间成功的设备成为房主。
- 只有房主可以配置或更换播放源。
- 所有房间成员都可以播放、暂停、跳转进度、回到直播。
- 房主短暂断线 30 秒内,房主身份保留。
- 房主超过 30 秒未恢复,允许当前房间内最早在线的设备成为新房主。
- 房间无人后进入保留期,保留期间房主身份和房间状态仍然存在。
这个规则避免了用户体系,同时保留了“谁能换片”的边界。
## 9. 客户端架构
客户端建议拆成:
- RoomController创建房间、加入房间、房间状态。
- SocketClientWebSocket 连接、重连、事件收发。
- PlayerController播放、暂停、seek、倍速、直播控制。
- SyncEngine进度计算、误差校正、直播延迟同步。
- SourceManager播放源初始化、凭据处理、URL 刷新。
- LocalDeviceStore本地 deviceId 和 lastRoomCode。
## 10. 后端架构
后端第一版建议保持单体服务,不拆微服务。
最小结构:
- HTTP API创建房间、加入房间、配置播放源、获取房间快照。
- WebSocket Gateway播放、暂停、seek、直播同步、心跳、断线重连。
- Redis Store房间状态、连接状态、播放源配置、过期时间。
- Stream Proxy可选代理入口只有房主开启代理模式时使用。
代码模块:
- RoomService房间创建、房间加入、房主判定、快照维护。
- PlaybackService播放状态写入和广播。
- SourceService播放源保存、直连信息下发、代理 URL 生成。
- StreamProxyServiceWebDAV/OSS/私有链接流式转发,不落盘存储。
暂时不做:
- 用户数据库。
- PostgreSQL。
- 微服务拆分。
- 消息队列。
- 文件存储。
- 复杂权限系统。
Redis Key 示例:
```text
room:A7K29Q
room:A7K29Q:connections
source:src_123
device:dev_abc:last
```
## 11. WebDAV 凭据结论
WebDAV 凭据问题可以概括为一句话:
> 客户端直连 WebDAV 时,客户端必须拿到凭据或等价授权;代理模式下客户端只拿我们服务端的播放 URL原始凭据由服务端持有。
两种可选做法:
| 方案 | 客户端拿到什么 | 后端流量成本 | 安全性 | 实现难度 |
| --- | --- | --- | --- | --- |
| 直连模式 | 原始凭据或 Basic Auth Header | 低 | 低 | 低 |
| 代理模式 | 服务端代理 URL | 高 | 高 | 中高 |
代理模式的关键点:
- 服务端拿到用户的 key、Cookie、Header 或账号密码。
- 服务端向 WebDAV/OSS/私有源站发起请求。
- 服务端把响应流转发给客户端。
- 服务端不下载完整文件。
- 服务端不做硬盘存储。
- 服务端必须正确支持 Range、Content-Type、Content-Length、Accept-Ranges 等响应头。
- 服务端需要承担带宽、并发连接和转发稳定性压力。
第一版建议:
- 同时支持直连模式和代理模式。
- 房主配置播放源时通过按钮或开关选择连接方式。
- 默认连接方式是客户端直连。
- 代理模式必须由房主主动开启。
- 直连模式适合可信房间和低成本场景。
- 代理模式适合不想把 key 暴露给房间成员的场景。
## 12. 第一版里程碑
### Milestone 1最小可跑通
- Windows 客户端。
- 轻量 Node.js 服务端。
- Redis 房间状态。
- 房间创建和加入。
- 普通 URL 播放。
- 播放、暂停、seek 同步。
- WebSocket 断线重连。
### Milestone 2播放源扩展
- m3u8 直播流。
- OSS 签名 URL。
- WebDAV 直连模式。
- HTTP Header 支持。
### Milestone 3同步体验打磨
- 周期性进度校准。
- 小误差倍速追赶。
- 大误差 seek。
- 直播回到实时。
### Milestone 4移动端
- App 客户端。
- 复用房间和同步协议。
- 验证播放器内核能力。
### Milestone 5代理能力增强
- WebDAV/OSS 最小流式代理。
- 凭据加密存储。
- 代理 URL 短期 Token。
- Range 请求和拖动进度优化。
- 源站 Header 透传和错误处理。
- 房间过期自动清理。
## 13. 关键风险
### 13.1 播放器能力风险
不同平台播放器能力不一致,尤其是:
- iOS 对部分格式限制更多。
- Android 机型和系统版本差异大。
- Windows mpv/libVLC 能力较强,但集成和打包需要验证。
第一阶段应优先做播放内核 PoC。
### 13.2 WebDAV 兼容风险
不同 WebDAV 服务差异很大:
- 鉴权方式不同。
- 是否支持 Range 请求不同。
- 是否支持文件直链不同。
- 是否限制 User-Agent 或 Header。
第一版应先支持最常见的 Basic Auth + Range。
### 13.3 服务端流式代理成本风险
如果服务端代理 WebDAV 或 OSS 视频流,所有视频流量都会走后端。
这会带来:
- 高带宽成本。
- 高并发压力。
- 大文件拖动复杂度。
- 直播流转发压力。
- 但不会带来硬盘存储压力,因为代理模式只做流式转发,不下载、不落盘。
因此代理模式适合安全要求更高的房间但需要配套限流、超时、Range 支持和错误恢复。
### 13.4 直播同步风险
直播流本身缓冲和 CDN 延迟不稳定。
第一版不要承诺毫秒级同步直播,目标是保持相近的直播延迟。
## 14. 推荐第一版决策
建议第一版采用:
- Windows 和 App 分端开发。
- Windows 优先跑通。
- Windows 客户端使用 Python + PySide6 + python-vlc并用 PyInstaller 打包。
- App 使用 HBuilder/uni-app + 原生 VLC 插件。
- 后端 Go + WebSocket + Redis。
- 服务端保持单体和轻量,不上 PostgreSQL不拆微服务。
- 普通 URL、OSS、WebDAV、m3u8 直播优先。
- 播放源支持直连和服务端流式代理。
- 默认客户端直连,代理模式由房主手动开启。
- 第一个加入房间的设备为房主。
- 只有房主能配置或更换播放源。
- 所有人都可以播放、暂停、跳转进度、回到直播。
- 房间无人后 30 分钟销毁。
最终第一版交付目标:
> 用户创建或加入房间,第一个加入者成为房主并配置网络播放源;默认由客户端直接获取源站内容,房主也可以手动开启服务端代理;其他设备输入 Code 加入后自动播放同一来源并同步播放、暂停、跳转断线后自动回到房间当前进度支持普通链接、OSS、WebDAV 和直播流;代理模式下服务端拿到 key 后流式转发给客户端,不下载、不落盘。