LinPlayer 架构说明(Architecture)
面向二次开发与排障的开发者文档。本文以当前
main分支代码结构为准。
1. 文档目标
- 快速理解项目的“分层边界”和“关键调用链”。
- 明确每个目录/模块该放什么,不该放什么。
- 为后续新增服务器类型、播放能力、TV 能力提供落点参考。
2. 架构总览
LinPlayer 采用“页面层 + 状态层 + 适配层 + API 层 + 播放层 + 平台服务层”的结构:
Flutter 页面(lib/*.dart)
-> AppState (packages/lin_player_state)
-> Server Adapter (packages/lin_player_server_adapters)
-> API Client (packages/lin_player_server_api)
-> Emby/Jellyfin/WebDAV/Plex 等服务
播放页(lib/player_screen*.dart, lib/play_network_page*.dart)
-> lin_player_player (PlayerService / Danmaku / 播放控制)
-> media_kit(mpv) 或 video_player_android(Exo)补充:仓库内还保留一条独立的兼容线 legacy/tv-legacy/,用于 Android 4.4 / API 19 电视盒子。它不是 Flutter 壳的一个 flavor,而是单独的 Android Application:
Legacy TV Activity(XML/View)
-> AppPrefs / ServerStore
-> EmbyClient(首页/收藏/搜索/Item 详情)
或 Backends.media(MediaBackend)
-> EmbyLike / Plex / WebDAV / Demo Backend
-> NetworkClients / ProxyService / RemoteHttpServer
-> PlayerActivity
-> PlayerCores / VlcPlayerCore
-> libVLC3. 顶层目录职责
.github/- CI/CD、Nightly/Latest 发布流程、安装包脚本。
assets/- 图标、Anime4K shader、TV 代理资源、TV 远程网页静态资源。
lib/- Flutter 业务页面与应用编排(入口、路由、页面、平台服务 glue)。
packages/- 模块化后的核心包(state/ui/player/server/api/core/prefs)与 patched 依赖。
docs/- 用户与开发文档。
legacy/- 独立兼容工程与历史实现;当前主要是
legacy/tv-legacy/(Java + XML/View 的 Android 4.4 TV 端)。
- 独立兼容工程与历史实现;当前主要是
tool/- 构建辅助脚本(如 TV 代理资源拉取)。
workers/- 辅助 Worker(当前有
workers/dandanplay-proxy)。
- 辅助 Worker(当前有
4. 分层说明
4.1 页面与编排层(lib/)
入口:lib/main.dart
职责:
- 初始化媒体后端(
MediaKit.ensureInitialized())。 - 初始化设备信息与 User-Agent(
ServerApiBootstrap.configure(...))。 - 加载全局状态(
AppState.loadFromStorage())。 - 根据设备类型与当前服务类型,选择 Home 壳:
- TV:
lib/tv/tv_shell.dart - Desktop:
lib/desktop_ui/desktop_shell.dart - Mobile/Tablet:
lib/home_page.dart或lib/webdav_home_page.dart
- TV:
关键页面:
- 服务管理:
lib/server_page.dart - 首页与库:
lib/home_page.dart、lib/library_page.dart、lib/library_items_page.dart - 详情页:
lib/show_detail_page.dart - 播放页:
- MPV:
lib/player_screen.dart、lib/play_network_page.dart - Exo:
lib/player_screen_exo.dart、lib/play_network_page_exo.dart
- MPV:
- WebDAV:首页壳与浏览器:
lib/webdav_home_page.dart、lib/webdav_browser_page.dart - 聚合检索:
lib/aggregate_service_page.dart
4.2 状态层(packages/lin_player_state)
核心:packages/lin_player_state/lib/app_state.dart
职责:
- 全局状态中心(
ChangeNotifier)。 - 持久化(SharedPreferences)与缓存(库缓存、首页缓存、播放偏好、弹幕偏好、TV 偏好等)。
- 服务管理流程:
addServer(...)(Emby/Jellyfin)addWebDavServer(...)addPlexServer(...)enterServer(...)/leaveServer()
- 数据加载流程:
loadItems(...)、loadHome(...)、loadMediaStats(...)
相关模型:
packages/lin_player_state/lib/server_profile.dartpackages/lin_player_state/lib/local_playback_handoff.dartpackages/lin_player_state/lib/route_entries.dart
4.3 适配层(packages/lin_player_server_adapters)
核心接口:packages/lin_player_server_adapters/lib/server_adapters/server_adapter.dart
职责:
- 定义统一服务能力接口
MediaServerAdapter,对页面层屏蔽服务端差异。 - 在工厂
server_adapter_factory.dart中按产品线选择具体适配器实现。
现有实现:
lin/lin_emby_adapter.dart(Emby/Jellyfin 主实现)uhd/uhd_adapter.dart
页面侧统一接入点:
lib/server_adapters/server_access.dart(resolveServerAccess(...))
4.4 API 层(packages/lin_player_server_api)
主要文件:
services/emby_api.dartservices/plex_api.dartservices/webdav_api.dartservices/webdav_proxy.dartservices/server_share_text_parser.dartnetwork/lin_http_client.dart
职责:
- 原始 HTTP 能力与协议细节(header、鉴权、重试、fallback)。
- 业务接口封装:鉴权、列表、详情、播放信息、播放上报、章节、相似推荐等。
说明:
- Emby/Jellyfin 共用
EmbyApi(通过MediaServerType区分 header 与 prefix 策略)。 - WebDAV 负责目录列举与鉴权处理(含 Digest/Basic)。
- Plex 负责 PIN 登录和服务器资源发现(当前项目中主要是“登录与保存服务器信息”)。
4.5 播放层(packages/lin_player_player)
主要文件:
player_service.dart:MPV 播放能力封装。src/player/playback_controls.dart:播放控制 UI 复用组件。src/player/danmaku_stage.dart+danmaku*.dart:弹幕管线。src/player/anime4k.dart:Anime4K shader 管线。dandanplay_api.dart:在线弹幕匹配与下载。
说明:
- MPV 路径使用
media_kit。 - Exo 路径使用
video_player_android(并通过 patched 包增强轨道能力)。
4.5.1 预加载(Preload)
开关:AppState.preloadEnabled(设置 → 预加载,默认关闭)
实现:
PlaybackSourceBuilder:统一构建真实播放与预加载共享的ResolvedPlaybackSourcePlaybackPreloadCoordinator:收口详情页 / 播放页的 current / next / resume 预加载入口StreamPreloadService(packages/lin_player_player/lib/src/preload/stream_preload_service.dart):执行实际预热请求与去重 / 熔断
补充说明:
- 共享 source builder 内部已复用 STRM / redirect / body-link 解析逻辑,并把
proxyUrl等元数据保留在ResolvedPlaybackSource中,供播放页与预加载共同消费。 PlaybackPreloadCoordinator会把targetKind编进去重命名空间,因此currentItem/nextItem分开判重,但不会因为triggerSource不同而重复预热同一语义目标。- 预加载固定命中共享
ResolvedPlaybackSource的远端 URL;若真实播放需要自定义 HTTP 代理,则通过httpProxyUrl继承代理语义,而不是改为命中本地回环代理 URL。 - direct file / redirect file / body-link file 的预热已收口到
HttpStreamProxyServer.warmRangeToCache(...):按 Range 直接写入磁盘缓存,续播场景会复用同一CacheKey追加热续播附近区间,并把 redirect-final URL 写回缓存元数据供后续播放补尾复用。 StreamCacheDownloadService已删除;StreamCacheDownloadRequest是当前保留的缓存语义载体,用来把共享CacheKey、代理语义和 redirect-final URL 绑定到同一份缓存对象。
行为:
- 集详情页(
EpisodeDetailPage)加载完成后,使用 UApreload-linplayer预取:- 当前集前 3 秒
- 下一集前 3 秒
- 播放过程中,当观看进度达到用户设置的观看阈值时,预取下一集前 3 秒(兜底:非从详情页进入的场景)。
失败策略:
- 单次预取最多尝试 3 次;若同一 source / proxy scope 在短时间内连续失败,会进入带 TTL 的短时熔断,并在恢复窗口后重新允许尝试。
- 预取实现为 best-effort:优先使用直链流的 Range 拉取;若为 HLS(
.m3u8)则解析并请求初始化段与前若干分片。 - HLS 当前仍采用“最高带宽 variant + 最多 3 段”的保守预热策略,相关常量已收口在 preload service。
4.6 UI 基建层(packages/lin_player_ui)
主要文件:
src/ui/app_theme.dart、theme_sheet.dart、ui_scale.dartsrc/ui/glass_background.dart、frosted_card.dartsrc/ui/app_components.dart、rating_badge.dart等
职责:
- 统一主题、风格模板、缩放与基础组件。
- 保证页面层不重复实现公共视觉逻辑。
4.7 配置与偏好层(packages/lin_player_prefs)
主要文件:
preferences.dartinteraction_preferences.dartdanmaku_preferences.dartanime4k_preferences.dart
职责:
- 偏好枚举、配置模型定义(供
AppState与 UI 使用)。
4.8 核心配置层(packages/lin_player_core)
主要文件:
app_config/app_config.dartapp_config/app_feature_flags.dartapp_config/app_product.dartstate/media_server_type.dart
职责:
- 产品线差异(
lin/uhd) - 功能开关(允许的 server type)
- 基础枚举与全局配置上下文
4.9 Legacy TV 兼容工程(legacy/tv-legacy/)
定位:
- 独立 Android 应用,不参与 Flutter 运行时。
- 技术栈:Java + XML/View,
minSdk = 19,兼容 Android 4.4 TV/盒子设备。 - 主要依赖:AppCompat 1.6.1、RecyclerView、OkHttp 3.12、libVLC 3.6、ZXing。
入口与页面:
LinPlayerApp- 首页:
MainActivity - 服务器管理:
ServersActivity、ServerEditActivity - 搜索/媒体库:
SearchActivity、LibraryDetailActivity - 详情页:
ItemDetailActivity、ShowDetailActivity、EpisodeListActivity、EpisodeDetailActivity - 播放页:
PlayerActivity - 设置页:
SettingsActivity
数据与状态:
- 持久化入口:
AppPrefs - 服务器配置:
servers/ServerStore+servers/ServerConfig - 当前实现同时存在两条数据访问路径:
- Emby 专用路径:
MainActivity、SearchActivity、LibraryDetailActivity、ItemDetailActivity直接使用emby/EmbyClient - 统一后端路径:
backend/Backends.media(...)根据 active server 选择EmbyLikeMediaBackend、PlexMediaBackend、WebDavMediaBackend或DemoMediaBackend
- Emby 专用路径:
- 这意味着
tv-legacy仍处在“专用实现 + 统一抽象并存”的过渡状态。
播放与平台服务:
- 播放核心:
player/PlayerCores->player/VlcPlayerCore-> libVLC PlayerActivity负责播放 HUD、选集、音轨/字幕切换、进度控制,以及 STRM / Emby-like URL 的解析与重定向处理。- 网络层:
NetworkClients统一注入User-Agent,并按设置决定直连或 per-app 代理。 - 内置代理:
ProxyService+MihomoConfig+MihomoProcess+ProxyEnv - 遥控/配网:
remote/RemoteControl+remote/RemoteHttpServer+remote/PlaybackSessionServersActivity会启动局域网 HTTP 服务并展示 QR,手机网页可批量添加服务器、调整代理设置、控制播放。
UI 组织:
- 以
activity_*.xml、item_*.xml为主的 XML 布局 +RecyclerView.Adapter - 共享 TV 视觉参数通过
TvStyle+AppPrefs管理(面板透明度、背景模糊等) - 背景图和毛玻璃视觉由
BitmapFetcher/BitmapBlur/ImageLoader等工具类完成
5. patched 依赖说明
packages/media_kit_patched- 为 MPV 路径提供更细粒度参数传递能力。
packages/video_player_android_patched- 增强 Exo 轨道相关能力(音轨/字幕轨选择等)。
pubspec.yaml 中通过 dependency_overrides 强制覆盖到项目内 patched 包。
6. 关键业务链路
6.1 启动链路
main.dart初始化媒体内核、设备信息、AppConfig。AppState.loadFromStorage()恢复服务、主题、偏好、缓存。- 根据设备类型进入
TvShell/DesktopShell/ 普通 Home。 - 启动可选服务:TV Remote、Built-in Proxy、自动更新检查。
6.2 添加服务器链路
页面:lib/server_page.dart
- Emby/Jellyfin:
AppState.addServer(...)- 内部通过 adapter/API 完成鉴权与基础信息拉取。
- WebDAV:
AppState.addWebDavServer(...) - Plex:
- 账号授权流(PIN)+ 资源选择,或手动填 token。
- 最终进入
AppState.addPlexServer(...)。
6.3 首页/列表加载链路
- 页面触发
AppState.loadHome()/loadItems(...)。 AppState使用当前 active server 构建访问上下文。- 调 adapter 拉取数据并写入本地缓存。
notifyListeners()驱动 UI 更新。
6.4 播放链路(网络媒体)
入口:show_detail_page.dart / 列表页 -> play_network_page*.dart
- 通过
resolveServerAccess(...)获取adapter + auth。 fetchPlaybackInfo(...)获取PlaySessionId/MediaSources。- (可选)若开启「预加载」,会在集详情页/播放达到观看阈值后触发
StreamPreloadService,用 UApreload-linplayer预取当前集与下一集的前 3 秒数据。 - 组装流 URL 与 header,交给 MPV 或 Exo 播放。
- 播放期间上报:
reportPlaybackStartreportPlaybackProgressreportPlaybackStoppedupdatePlaybackPosition
6.5 播放链路(本地文件/WebDAV)
- 本地:
player_screen*.dart直接从本地路径建立播放列表。 - WebDAV:
webdav_browser_page.dart- 先通过
webdav_proxy.dart注册本地代理 URL。 - 再把 URL 作为本地播放队列交给播放器。
- 先通过
6.6 弹幕链路
- 播放页叠加
DanmakuStage。 - 数据来源:本地 XML 或在线(
dandanplay_api.dart)。 - 渲染参数来自
AppState(透明度、字号、速度、去重、防遮挡等)。
6.7 TV 专项链路
- TV 遥控网页:
tv_remote_service.dart- 内置 HTTP + WebSocket 服务,提供移动端控制入口。
- TV 内置代理:
built_in_proxy_service.dart- 管理 mihomo 进程、配置生成、代理规则及 UI 面板资源。
6.8 Legacy TV 关键链路
MainActivity启动时先检查ServerStore.hasAny(...);若没有服务器则跳转ServersActivity。ServersActivity/ServerEditActivity负责登录 Emby/Jellyfin、保存ServerConfig,并可通过 QR Remote 做批量导入与远程配网。- 首页/搜索/媒体库当前直接使用
EmbyClient拉取 Emby/Jellyfin 数据;剧集详情与播放器侧则更多通过Backends.media(...)访问 Emby-like / Plex / WebDAV。 PlayerActivity根据 URL、showId、episodeIndex 等上下文调用VlcPlayerCore打开流,并接管选集、音轨、字幕、seek 与远程控制状态同步。- 若启用代理,则
ProxyService启动本地 mihomo,NetworkClients与ProxyEnv让 App 内网络请求经127.0.0.1走 per-app 代理。
7. 平台与壳层
7.1 Desktop 壳层
目录:lib/desktop_ui/
职责:
- 提供桌面端独立壳与页面组织。
- 与移动端共享状态层、适配层、API 层。
- 详细设计与组件拆分:
docs/dev/DESKTOP_UI_ARCHITECTURE.md
7.2 TV 壳层
目录:lib/tv/
职责:
- TV 首页、背景模式、首启向导、遥控操作体验。
7.3 Legacy TV 兼容壳层
目录:legacy/tv-legacy/
职责:
- 为 Android 4.4 / API 19 设备提供独立 TV 壳与播放器。
- 与主 Flutter 工程共存,但构建、运行、依赖链完全独立。
- 作为低版本设备兼容线保留,便于继续维护传统 Java/XML TV 端能力。
8. 数据与持久化策略
持久化入口:AppState(SharedPreferences)
典型内容:
- 当前服务与服务器列表。
- 主题、缩放、交互手势、播放器偏好。
- 弹幕偏好、TV 偏好、自动更新设置。
- 轻量缓存:库列表、首页区块、部分统计信息。
9. 扩展指南
9.1 新增一个服务端类型
建议步骤:
- 在
lin_player_core扩展MediaServerType与 FeatureFlag。 - 在
lin_player_server_api新增 API 客户端。 - 在
lin_player_server_adapters实现新的 Adapter。 - 在
ServerAdapterFactory与server_page.dart接入。 - 在
AppState补充服务保存/切换逻辑。
9.2 新增播放能力
建议步骤:
- 优先落在
lin_player_player(而不是页面直接写)。 - 页面层只做参数编排与状态展示。
- 涉及平台特性时,通过 patched 包或平台通道落地。
9.3 新增 UI 模板或全局视觉能力
建议步骤:
- 在
lin_player_ui扩展主题/样式模型。 - 由
AppState持久化模板/参数。 - 页面层仅消费,不重复定义 token。
10. 调试与维护建议
- 静态检查:
flutter analyze - 测试:
flutter test - 平台构建前先确认:
flutter doctor -v - 低版本 TV 工程单独构建:
cd legacy/tv-legacy; .\gradlew.bat :app:assembleDebug - 对外接口异常优先查:
- 当前 active server 信息
- Adapter 选型是否正确
- API prefix / token / userId 是否一致
- 播放异常优先查:
fetchPlaybackInfo返回的MediaSources- 选中的音轨/字幕索引
- MPV/Exo 内核是否匹配当前片源
11. 相关文档
docs/dev/README.mddocs/dev/ANDROID_SIGNING.mddocs/dev/TV_PROXY_ROADMAP.mddocs/SERVER_IMPORT.mdlegacy/tv-legacy/README.mdlegacy/tv-legacy/TV_GUIDE.mdlegacy/tv-legacy/API.md
如果你准备继续模块化重构,建议下一步先统一:
- 页面层对
AppState的直接读写边界 - 播放页的共享 ViewModel/Controller 抽象
- 详情页(剧/集)的公共组件拆分