跳转到内容

Provider 开发指南

本指南用于新增 Provider 或扩展现有 Provider。目标是让每个 Provider 保持独立的协议模型、业务能力和产品流程,同时共享稳定的 SyncTV 核心契约。

  1. 一个 Provider 一个模型边界。 DTO、URL 解析、签名、分页、登录、播放格式和错误映射放在该 Provider 模块内。
  2. 按 Provider 实际能力建模。 视频网站、直播平台、媒体服务器和 NAS 使用各自的 source、target、pagination 和 playback metadata。
  3. 复用稳定核心契约。 MediaProviderDynamicPlaylistProviderPlaybackResultSourceConfigProviderTarget、凭据仓库和 transport action 是跨 Provider 共享边界。
  4. typed config 贯穿发现与创建。 Parse、resolve、list、search 和 preview 返回可直接提交的 media 或 playlist source config。
  5. 生成 URL 必须可解析。 Provider 生成的 stream、manifest、segment、subtitle、danmaku、thumbnail 和 cover URL 都需要对应 resolver 与测试。
  6. 本地与远程行为一致。 内嵌 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 Providersynctv-core/src/provider/<provider>.rs凭据、source config、target、动态浏览、自动播放、PlaybackResult、proxy resolver
持久化模型synctv-core/src/models/source_config.rsprovider_target.rs数据库存储使用的强类型来源与 target
公开 protobufsynctv-proto/proto/source_config.protoproviders/playback_provider/App、CLI、HTTP/gRPC 使用的公开契约
API 实现synctv-api/src/impls/providers/impls/playback_provider/用户上下文、Provider 调用和公开响应映射
传输注册synctv-api/src/http/providers/grpc/providers/openapi.rsHTTP、公开 gRPC、OpenAPI 路由和 schema
管理与 CLIsynctv-management/synctv/src/cli/远程 instance、管理 RPC、命令行创建和调试
Flutter Appproto/lib/models/lib/services/lib/widgets/add_media/typed codec、API facade、绑定、预览、选择和创建流程
  1. 定义 Provider 的实际能力和产品流程。
  2. 实现独立上游 client、DTO 与 WireMock 契约测试。
  3. 定义持久化 source config、target 和凭据模型。
  4. 实现 Core MediaProvider,需要动态播放列表能力时实现 DynamicPlaylistProvider
  5. 添加内部 remote-provider protobuf、server 和 client transport。
  6. 添加公开 source config、Provider API 和 playback-provider protobuf。
  7. 接入 adapter、management、HTTP、gRPC、OpenAPI 和 CLI。
  8. 同步 Flutter protobuf,添加 codec、domain service、绑定和添加媒体 UI。
  9. 完成单元、契约、Core、API、CLI、Testcontainers 和 Flutter 测试。
  10. 运行完整验证并请求每个生成的播放资源。

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 和响应映射。

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 中。动态来源前缀使用领域名称,例如 ChannelHistoryFavoritesSearch

ProviderTarget 保存动态列表中条目的稳定身份。Target 应包含重新定位当前条目所需的最小字段,并支持 JSON/protobuf round trip。媒体 source config 负责生成播放结果;target 负责列表定位、自动切换和播放状态身份。

公开 protobuf 在 source_config.protoclient.proto 中使用对应 oneof。新项目可以直接采用当前最佳字段号和排列,声明顺序、字段号和生成代码位置应保持一致。

凭据 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

公开发现接口应返回足够的信息让 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 条之后仍可推进。

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长期保存在数据库。

Provider 在 playback info 中创建上游 session、stream 或 transcode 时,实现 playback_lifecycle_session_id 并返回稳定 ID。生命周期状态按房间保存,播放租约为 45 秒,暂停租约为 10 分钟;成功的进度上报会续租。

on_playback_stop 负责最终进度和上游资源释放,并返回真实错误。停止失败的会话会保留在全局房间索引中,由后台回收器重试;租约过期与 graceful shutdown 也会触发同一清理路径。清理调用需要支持重复执行,部分成功时只持久化仍需清理的上游资源。

缓存中包含上游 session ID 时,cache key 需要包含房间 ID、凭据版本和客户端能力指纹。资源创建后的本地持久化失败需要立即执行补偿清理。

需要远程运行的 Provider 在 synctv-media-providers/proto/ 定义独立 service,并完成:

  • build.rs protobuf 注册;
  • Provider gRPC server;
  • remote_transport/clients.rs client;
  • remote connector 注册与认证 secret;
  • local 和 remote 路径的相同错误语义;
  • protobuf contract test。

内部 remote protocol 与公开 SyncTV API 分属不同 package。公开 API 位于 synctv-proto/proto/providers/,由主 SyncTV server 暴露。

Provider 公开接口完成以下注册:

  1. synctv-api/src/impls/providers/<provider>.rs 共享实现。
  2. http/providers/<provider>.rs 路由与 utoipa::path
  3. grpc/providers/<provider>.rs service implementation。
  4. http/providers/mod.rsgrpc/mod.rs service 注册。
  5. openapi.rs schema 和 path 注册。
  6. rate-limit category、认证和 instance name 提取。

HTTP 与 gRPC 调用同一 impl。OpenAPI response 使用公开 protobuf 对应结构,避免手写第二套 DTO。

同步公开 protobuf 后执行:

Terminal window
cd /Volumes/workspace/flutter/synctv-app
dart pub global activate protoc_plugin
bash tool/generate_proto.sh

App 接入顺序:

  1. lib/models/ 添加 Provider 独立 source-config helper 与 domain model。
  2. SourceConfigCodec 实现 protobuf 和 App map 的双向 round trip。
  3. synctv_api_facades.dart 添加 HTTP facade。
  4. synctv_provider_service.dart 映射 protobuf 到 domain model。
  5. synctv_service.dart 暴露稳定调用入口。
  6. platform_binding_dialog.dart 实现绑定、能力显示和解绑。
  7. widgets/add_media/<provider>_add_media_form.dart 实现解析、预览、选择与创建。
  8. add_media_dialog.dart 注册入口,并按绑定 capability 控制需要 Cookie/scope 的来源。

Provider UI 使用自己的模式 enum 和状态。资源预览采用服务端 typed config。列表支持 page/cursor load more,按钮使用 App 组件封装,并在窄屏和桌面尺寸下验证布局。

必测内容
ClientWireMock 请求契约、签名、URL 规范化、响应 variant、错误 envelope
Internal protoservice/method/message/field oneof 契约,local/remote transport
Coreconfig 校验、凭据 instance scope、target round trip、分页、封面、自动播放
Playbackdirect/proxy modes、Range、manifest/segment、subtitle、danmaku、expiry、refresh
Public APIHTTP、OpenAPI、公开 gRPC 注册和响应 typed config
CLIJSON 到 protobuf oneof、发现/绑定/创建命令
DatabaseTestcontainers 凭据加密、持久化和 provider-instance 绑定
Fluttercodec、domain service、绑定 Widget、预览/创建 Widget、UI guard

Provider 修改完成后运行:

Terminal window
cd /Volumes/workspace/rust/synctv
cargo +nightly fmt --all
cargo +nightly check --workspace --all-targets
make nextest
cd /Volumes/workspace/flutter/synctv-app
dart format lib test
dart analyze
flutter test

make 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 使用手册