# 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:房间创建、加入、房主判定、房间快照、过期清理。 - SyncGateway:WebSocket 连接、播放控制广播、心跳、重连。 - 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:创建房间、加入房间、房间状态。 - SocketClient:WebSocket 连接、重连、事件收发。 - PlayerController:播放、暂停、seek、倍速、直播控制。 - SyncEngine:进度计算、误差校正、直播延迟同步。 - SourceManager:播放源初始化、凭据处理、URL 刷新。 - LocalDeviceStore:本地 deviceId 和 lastRoomCode。 ## 10. 后端架构 后端第一版建议保持单体服务,不拆微服务。 最小结构: - HTTP API:创建房间、加入房间、配置播放源、获取房间快照。 - WebSocket Gateway:播放、暂停、seek、直播同步、心跳、断线重连。 - Redis Store:房间状态、连接状态、播放源配置、过期时间。 - Stream Proxy:可选代理入口,只有房主开启代理模式时使用。 代码模块: - RoomService:房间创建、房间加入、房主判定、快照维护。 - PlaybackService:播放状态写入和广播。 - SourceService:播放源保存、直连信息下发、代理 URL 生成。 - StreamProxyService:WebDAV/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 后流式转发给客户端,不下载、不落盘。