This commit is contained in:
2026-06-15 22:46:12 +08:00
commit f6508eccdb
38 changed files with 3133 additions and 0 deletions

717
docs/first-version-plan.md Normal file
View File

@@ -0,0 +1,717 @@
# 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 后流式转发给客户端,不下载、不落盘。