Provider 开发指南
本指南用于新增 Provider 或扩展现有 Provider。目标是让每个 Provider 保持独立的协议模型、业务能力和产品流程,同时共享稳定的 SyncTV 核心契约。
- 一个 Provider 一个模型边界。 DTO、URL 解析、签名、分页、登录、播放格式和错误映射放在该 Provider 模块内。
- 按 Provider 实际能力建模。 视频网站、直播平台、媒体服务器和 NAS 使用各自的 source、target、pagination 和 playback metadata。
- 复用稳定核心契约。
MediaProvider、DynamicPlaylistProvider、PlaybackResult、SourceConfig、ProviderTarget、凭据仓库和 transport action 是跨 Provider 共享边界。 - typed config 贯穿发现与创建。 Parse、resolve、list、search 和 preview 返回可直接提交的 media 或 playlist source config。
- 生成 URL 必须可解析。 Provider 生成的 stream、manifest、segment、subtitle、danmaku、thumbnail 和 cover URL 都需要对应 resolver 与测试。
- 本地与远程行为一致。 内嵌 client、remote provider gRPC 和公开 SyncTV HTTP/gRPC 暴露相同业务语义。
| 层 | 主要位置 | 职责 |
|---|---|---|
| 上游客户端 | synctv-media-providers/src/<provider>/ | HTTP/WebSocket 协议、签名、独立 DTO、上游分页、播放资源解析 |
| 内部远程协议 | synctv-media-providers/proto/、src/grpc/、src/remote_transport/ | local client 与 remote provider service 的 wire contract |
| Core Provider | synctv-core/src/provider/<provider>.rs | 凭据、source config、target、动态浏览、自动播放、PlaybackResult、proxy resolver |
| 持久化模型 | synctv-core/src/models/source_config.rs、provider_target.rs | 数据库存储使用的强类型来源与 target |
| 公开 protobuf | synctv-proto/proto/source_config.proto、providers/、playback_provider/ | App、CLI、HTTP/gRPC 使用的公开契约 |
| API 实现 | synctv-api/src/impls/providers/、impls/playback_provider/ | 用户上下文、Provider 调用和公开响应映射 |
| 传输注册 | synctv-api/src/http/providers/、grpc/providers/、openapi.rs | HTTP、公开 gRPC、OpenAPI 路由和 schema |
| 管理与 CLI | synctv-management/、synctv/src/cli/ | 远程 instance、管理 RPC、命令行创建和调试 |
| Flutter App | proto/、lib/models/、lib/services/、lib/widgets/add_media/ | typed codec、API facade、绑定、预览、选择和创建流程 |
- 定义 Provider 的实际能力和产品流程。
- 实现独立上游 client、DTO 与 WireMock 契约测试。
- 定义持久化 source config、target 和凭据模型。
- 实现 Core
MediaProvider,需要动态播放列表能力时实现DynamicPlaylistProvider。 - 添加内部 remote-provider protobuf、server 和 client transport。
- 添加公开 source config、Provider API 和 playback-provider protobuf。
- 接入 adapter、management、HTTP、gRPC、OpenAPI 和 CLI。
- 同步 Flutter protobuf,添加 codec、domain service、绑定和添加媒体 UI。
- 完成单元、契约、Core、API、CLI、Testcontainers 和 Flutter 测试。
- 运行完整验证并请求每个生成的播放资源。
1. 上游客户端与 DTO
Section titled “1. 上游客户端与 DTO”Provider 目录建议保持以下结构:
synctv-media-providers/src/example/├── mod.rs├── client.rs├── types.rs├── client_tests.rs├── sign.rs # 仅签名协议需要└── chat.rs # 仅直播聊天需要types.rs 表达上游 JSON、XML、WebSocket 或二进制协议。字段名、可选性和数字宽度应对应真实响应。平台之间语义相似的字段仍保留各自 DTO;映射到 SyncTV 核心结构时再收敛。
Client 负责:
- 输入 URL/ID 规范化和 SSRF 边界;
- 登录、刷新、签名和请求 header;
- 上游 page/cursor/offset/continuation;
- metadata、cover、thumbnail、subtitle、chapter、danmaku/chat;
- 所有原生清晰度、CDN、codec、transcode 或 remux 资源;
- 平台错误到
ProviderClientError的映射。
每个公开 client 方法都应有 WireMock 或协议级单元测试。测试同时验证请求 method、path、query、header/body 和响应映射。
2. Source Config 与 Target
Section titled “2. Source Config 与 Target”Source config 是可持久化的播放来源。Media 与 playlist 分开定义:
pub struct ExampleMediaSourceConfig { pub resource_id: String, pub shared: bool,}
pub enum ExamplePlaylistSourceConfig { Channel { channel_id: String, shared: bool }, Search { query: String, shared: bool },}每个资源类型使用独立 variant。Provider 的配置差异应直接体现在 enum 中。动态来源前缀使用领域名称,例如 Channel、History、Favorites、Search。
ProviderTarget 保存动态列表中条目的稳定身份。Target 应包含重新定位当前条目所需的最小字段,并支持 JSON/protobuf round trip。媒体 source config 负责生成播放结果;target 负责列表定位、自动切换和播放状态身份。
公开 protobuf 在 source_config.proto 和 client.proto 中使用对应 oneof。新项目可以直接采用当前最佳字段号和排列,声明顺序、字段号和生成代码位置应保持一致。
3. 凭据与 Provider Instance
Section titled “3. 凭据与 Provider Instance”凭据 key 包含:
(user_id, provider, server_id, provider_instance_name)同一主机通过多个 instance 绑定时,server_id 需要包含 instance 作用域,或使用能够稳定区分 instance 的标识。登录、列表、封面和播放路径都从 ProviderContext 获取 credential repository fallback:
let repo = self.credential_repo.as_deref().or(ctx.credential_repo);保存 media 或 playlist 前,validate_source_config 应验证创建者拥有所引用的凭据。credential_dependencies 返回缓存失效和凭据删除保护所需依赖。
shared 表示房主凭据模式。解析来源本身保持中立,App 在创建前根据用户选择设置 shared。
4. Parse、Resolve、List 与 Preview
Section titled “4. Parse、Resolve、List 与 Preview”公开发现接口应返回足够的信息让 App 完成预览与创建:
message Candidate { string title = 1; string cover = 2; oneof source_config { MediaSourceConfig media = 3; PlaylistSourceConfig playlist = 4; }}Parse 适合一个 URL 可能表达多个资源的 Provider,例如多 P 视频、季度、直播间、频道和播放列表。Resolve 适合 URL 对应单个媒体且需要返回原生格式详情的 Provider。List/Search 适合平台发现和 NAS 浏览。
预览响应中的每个可添加条目都应携带 typed source config。目录条目可以携带 playlist source config,用于继续浏览和直接创建动态播放列表。App 只做选择与 shared scope 应用。
Core 使用显式 enum:
pub enum DynamicPagination { Page { page: u64 }, Cursor { cursor: Option<String> },}公开 protobuf 使用 pagination oneof。响应始终返回当前上游采用的方式:
message ListResponse { repeated Item items = 1; oneof pagination { PagePagination page = 2; CursorPagination cursor = 3; }}Cursor 是 opaque token。App 和 Core 保存并原样回传。上游使用 offset 时可以把 page 映射成稳定 offset;上游原生 cursor 直接透传或用版本化 envelope 包装。
顺序自动播放需要跨页扫描到当前 target 和下一条媒体。固定采样上限适合 shuffle。大型文件夹的 sequential/repeat-all 流程应继续分页,保证第 200 条之后仍可推进。
6. PlaybackResult 与高级资源
Section titled “6. PlaybackResult 与高级资源”generate_playback 负责构造完整播放决策:
- 原生 direct/progressive/DASH/HLS/FLV mode;
- Provider 原生 transcode、remux、CDN、quality 和 codec 选项;
proxy_*sibling 与default_mode;- 必需的 headers 和 Range;
- subtitles、chapters、danmaku/chat、storyboard;
- cover、thumbnail 和 playback metadata;
- URL expiry、缓存 key、刷新上下文;
- 播放开始、进度、暂停和停止回写。
Provider 生成的 proxy URL 需要在 playback_provider 中实现对应 action:redirect、upstream proxy、manifest rewrite 或本地 stream。Manifest 中的 segment 与 key URL也要回到同一个 Provider resolver。
Source cover 使用当前请求的 ProviderContext 解析凭据。签名缩略图和短期 URL在请求时刷新,避免把过期 URL长期保存在数据库。
播放生命周期契约
Section titled “播放生命周期契约”Provider 在 playback info 中创建上游 session、stream 或 transcode 时,实现 playback_lifecycle_session_id 并返回稳定 ID。生命周期状态按房间保存,播放租约为 45 秒,暂停租约为 10 分钟;成功的进度上报会续租。
on_playback_stop 负责最终进度和上游资源释放,并返回真实错误。停止失败的会话会保留在全局房间索引中,由后台回收器重试;租约过期与 graceful shutdown 也会触发同一清理路径。清理调用需要支持重复执行,部分成功时只持久化仍需清理的上游资源。
缓存中包含上游 session ID 时,cache key 需要包含房间 ID、凭据版本和客户端能力指纹。资源创建后的本地持久化失败需要立即执行补偿清理。
7. 内部远程协议
Section titled “7. 内部远程协议”需要远程运行的 Provider 在 synctv-media-providers/proto/ 定义独立 service,并完成:
build.rsprotobuf 注册;- Provider gRPC server;
remote_transport/clients.rsclient;- remote connector 注册与认证 secret;
- local 和 remote 路径的相同错误语义;
- protobuf contract test。
内部 remote protocol 与公开 SyncTV API 分属不同 package。公开 API 位于 synctv-proto/proto/providers/,由主 SyncTV server 暴露。
8. HTTP、gRPC 与 OpenAPI
Section titled “8. HTTP、gRPC 与 OpenAPI”Provider 公开接口完成以下注册:
synctv-api/src/impls/providers/<provider>.rs共享实现。http/providers/<provider>.rs路由与utoipa::path。grpc/providers/<provider>.rsservice implementation。http/providers/mod.rs与grpc/mod.rsservice 注册。openapi.rsschema 和 path 注册。- rate-limit category、认证和 instance name 提取。
HTTP 与 gRPC 调用同一 impl。OpenAPI response 使用公开 protobuf 对应结构,避免手写第二套 DTO。
9. Flutter App
Section titled “9. Flutter App”同步公开 protobuf 后执行:
cd /Volumes/workspace/flutter/synctv-appdart pub global activate protoc_pluginbash tool/generate_proto.shApp 接入顺序:
- 在
lib/models/添加 Provider 独立 source-config helper 与 domain model。 - 在
SourceConfigCodec实现 protobuf 和 App map 的双向 round trip。 - 在
synctv_api_facades.dart添加 HTTP facade。 - 在
synctv_provider_service.dart映射 protobuf 到 domain model。 - 在
synctv_service.dart暴露稳定调用入口。 - 在
platform_binding_dialog.dart实现绑定、能力显示和解绑。 - 在
widgets/add_media/<provider>_add_media_form.dart实现解析、预览、选择与创建。 - 在
add_media_dialog.dart注册入口,并按绑定 capability 控制需要 Cookie/scope 的来源。
Provider UI 使用自己的模式 enum 和状态。资源预览采用服务端 typed config。列表支持 page/cursor load more,按钮使用 App 组件封装,并在窄屏和桌面尺寸下验证布局。
10. 测试矩阵
Section titled “10. 测试矩阵”| 层 | 必测内容 |
|---|---|
| Client | WireMock 请求契约、签名、URL 规范化、响应 variant、错误 envelope |
| Internal proto | service/method/message/field oneof 契约,local/remote transport |
| Core | config 校验、凭据 instance scope、target round trip、分页、封面、自动播放 |
| Playback | direct/proxy modes、Range、manifest/segment、subtitle、danmaku、expiry、refresh |
| Public API | HTTP、OpenAPI、公开 gRPC 注册和响应 typed config |
| CLI | JSON 到 protobuf oneof、发现/绑定/创建命令 |
| Database | Testcontainers 凭据加密、持久化和 provider-instance 绑定 |
| Flutter | codec、domain service、绑定 Widget、预览/创建 Widget、UI guard |
Provider 修改完成后运行:
cd /Volumes/workspace/rust/synctvcargo +nightly fmt --allcargo +nightly check --workspace --all-targetsmake nextest
cd /Volumes/workspace/flutter/synctv-appdart format lib testdart analyzeflutter testmake nextest 使用项目定义的 nightly/Testcontainers 流程并运行 ignored integration tests。Provider 网络契约测试使用 mock upstream;自托管服务的真实兼容性测试可以使用 Testcontainers 固定镜像版本。
- Provider DTO、client、签名和错误映射保持独立。
- 所有实际可用的上游功能都映射成 typed source 或 playback capability。
- Parse/List/Preview 返回可直接提交的 typed source config。
- Media 与 playlist source config、target 完成 JSON/protobuf round trip。
- Page、cursor、offset 或 continuation 的响应模式明确。
- 顺序和循环播放可以跨越大型目录边界。
- 凭据使用 user/provider/server/instance 作用域并由
ProviderContext解析。 - Cover、thumbnail、subtitle、danmaku、manifest 和 segment 都有 resolver。
- local 与 remote provider 行为一致。
- HTTP、gRPC、OpenAPI、management 和 CLI 完成注册。
- App 完成绑定、能力控制、URL 解析、预览、部分选择和动态播放列表创建。
- WireMock、Core、API、CLI、Testcontainers 和 Flutter 测试通过。
继续阅读 实现契约、客户端集成 和 Provider 使用手册。