状态:设计已落盘,P0/P1a 已实现并通过窄编译,后续阶段待实施。更新:2026-10-13。 用户授权:允许重构核心,先设计、精简上下文并开始编码;功能验收由用户完成。
- 分开优化点击到页面、点击到可见视频首帧、定位恢复、再次打开四项耗时;提前出现黑屏不算首帧改善。
- 当前文件优先,目录队列、预览、扫描不阻塞播放;主线程不执行磁盘/远程 I/O。
- 保留本地文件、content URI、网络连接、字幕/轨道、播放历史、手势、播放列表、PiP 与后台播放能力。
- 保留 Kotlin、Compose、Room 和 libmpv;不因 reex 使用 Flutter 就迁移框架。
- reex 只提供体验目标和静态对照线索;不能假定已证明其 MPV 复用或直连策略。
- 默认覆盖安装,保留原包名与签名;数据库迁移不破坏连接、收藏、历史。阶段提交可回退,不一次性替换所有模块。
浏览器 / 播放页面 / 通知与媒体按键
↓ commands / state
PlaybackCoordinator
├─ PlaybackEngine → NativeMpvAdapter → libmpv
├─ RenderTarget(Surface 租约)
├─ SourceResolver → 直连或协议适配/代理
├─ QueueRepository(按需解析项目)
└─ PlaybackHistoryRepository(有序快照保存)
MetadataRepository ← 当前播放元数据 / 后台探测
NetworkScheduler → 当前播放、下一集、缩略图、扫描
ConfigRepository → 带版本的不可变配置快照
| 模块 | 负责 | 禁止承担 |
|---|---|---|
| Coordinator | 会话、命令顺序、状态归约、资源移交 | View/Activity 引用、网络阻塞 |
| Engine | 唯一 native 播放实例、属性/事件适配 | UI 状态、Room 查询、连接凭据持久化 |
| RenderTarget | Surface 绑定与解绑,持有代次 | 创建/销毁整个播放会话 |
| Service | 后台播放时的系统生命周期、通知与音频焦点对接 | 独立读取/修改另一套 MPV 状态 |
| SourceResolver | 认证、地址与可读资源租约、传输模式选择 | 同步探测整个目录、持有 Activity |
| MetadataRepository | 文件版本对应的元数据与变更通知 | 为正在播放的文件重复发起低优先级探测 |
首批允许保留兼容层。只有所有直接入口迁移后,才能称为唯一控制者。
MediaId:本地规范 URI/媒体 ID;网络为 connectionId + 规范远端路径。不得用文件名或临时代理 URL 作历史主键。MediaVersion:强 ETag 优先,辅以大小/修改时间;弱验证或未知版本不拼接跨请求持久字节。PlaybackRequest:MediaId、队列引用、配置版本、恢复策略及用户传入的初始位置;不含可持久化明文认证头。SessionId:每次打开分配;LoadId:native 加载边界;RenderGeneration:每次 Surface 更替分配。ResolvedSource:可播放地址/文件描述符、临时请求头、元数据、资源租约。成功、失败、取消和切片都必须确定释放。PlaybackState:只读 StateFlow,携带 session、阶段、位置、nullable 时长、轨道、暂停/缓冲/定位标志及错误。- 控制接口:open、pause、resume、seek、selectTrack、setSpeed、stop、attachSurface、detachSurface。UI 不发送任意 MPV 命令;高级自定义命令走单独受控入口。
命令由单一执行通道排序;慢网络和数据库在其外部进行,完成时提交带 session/version 的结果。 连续 seek 合并为最新目标;打开/停止/配置重建不能丢弃。队列必须有界,不用无界协程隐藏积压。
主阶段:Idle → Preparing → Opening → Ready → Stopping → Idle;准备/打开可进入 Failed。 Playing/Paused、Buffering、Seeking 是 Ready 下的正交状态,不把所有组合扩成独立枚举。
- A→B→A 切换仍是三个会话,旧回调不能凭路径相同被接纳。
- native 回调携带真实可验证的加载身份;仅在收到事件时读取“当前 session”再贴标签不足以隔离旧事件。
- 若 AAR 事件缺少身份,先验证停止/加载序列及事件边界,在适配层建立映射;无法证明隔离时不启用热实例复用。
- FILE_LOADED、RESTART 和当前属性可参与就绪核对,但不能仅凭 time-pos 非空就接纳旧文件;初始化副作用保持幂等。
- 停止前读取快照,先退休会话,再停止 native;快照按 MediaId 有序落库,保存完成回调不再访问 native。
- 旋转/页面重建只替换 Surface。后台播放由 Service 对接系统生命周期;退出且不保留后台播放时释放文件和网络资源。
- 实例短暂保留只在应用前台考虑,建议初始空闲保留 10 秒,作为可调候选值;内存压力或配置不兼容时立即销毁。
- 不为预热启动常驻后台服务;不创建多个完整解码核心。进程死亡只恢复持久化状态,不能假设热实例仍存在。
- 当前 AAR 需先做实例/Surface 分离可行性验证;若需修改依赖,固定本地源码/版本和构建产物,不以反射访问私有字段替代正式接口。
- 点击生成请求并展示已有标题/封面,记录单调时钟起点。
- 并行取得配置快照、当前源、历史记录,并准备或复用引擎;只等待当前播放必需信息。
- 引擎与地址就绪后尽早读取;能否在 Surface 就绪前安全预读由适配层验证,不能以牺牲首帧/误播音频换提前读取。
- 恢复位置尽量在初次加载前确定;若历史迟到,超过准备期限或用户已操作则不迟到跳转。
- 当事件与会话匹配时进入 Ready,发布元数据;再安排可见预览和队列相邻项。
队列保存稳定 MediaId 快照和索引,Activity Intent 只传 queueId/currentId,不传整个目录 URL 数组。 仅当前项目及必要邻项持有源租约;扫描/排序更新不能改变正在播放项目的身份。随机、循环和下一集预读共用一个下一项解析器。 预读默认仅少量文件头/尾或协议允许的数据,不启动第二个解码器;真正播放改变时取消旧预读。
- WebDAV 目录与播放分离:目录使用 WebDAV;常规播放优先验证 HTTP(S) 直连 MPV。
- 直连必须覆盖认证、请求头、UTF-8/转义路径、重定向、Range/seek、TLS、超时和取消;跨域跳转不得携带原域凭据。
- 不为“探测模式”每次多发一轮网络请求。根据连接配置和已知能力选通路;只对已识别的兼容性失败回退一次,记录连接配置版本关联的结果。
- 认证失败、证书错误、用户取消不盲目切代理重试;不自动关闭 TLS 校验,不无限重试。
- SMB/FTP 首先沿用现有适配器与代理,统一资源租约和取消;是否改用 native 数据源另行用基准决定。
- 代理 GET 各自持有请求身份,不把“新 GET”必然认定为旧流已无用。明确停止/定位退休边界后再取消旧资源。
- 已知长度的 HEAD 不打开视频体。未知长度、Range 被忽略、短响应、文件更新和网络断开要有确定的错误处理。
- 连接池按认证/连接版本隔离;修改连接或凭据时退休旧会话;同步 native 读取的取消必须能中断底层 socket。
优先级:当前打开/定位/卡顿恢复 > 正常缓冲 > 下一集小量预读 > 可见预览 > 后台扫描。 应用自己的网络任务由共享调度器管理;直连 MPV 内部请求不声称可逐个调度,通过限制并暂停应用低优先级任务让出资源。 初始候选:单服务器后台任务最多 2 个;缓冲或定位期间暂停后台任务;native 缩略图单消费者。参数需基准验证。 取消中的请求立即退休,后来订阅者不加入已取消任务;清理按实例执行,不误删替代任务。
| 数据 | 来源/缓存策略 |
|---|---|
| 大小、修改时间、ETag | 目录结果优先,不为每个列表项目重复请求 |
| 当前时长、轨道、分辨率 | 播放引擎发布;其他项目才独立探测 |
| 元数据 | Room 按 MediaId + MediaVersion 保存,列表与播放器订阅同一来源 |
| 缩略图 | 版本化磁盘缓存+有界内存缓存;可见窗口优先 |
| 视频字节 | 默认依赖播放器缓冲;代理额外缓存仅在实测有收益时启用,有容量/失效上限 |
时长未知使用 null,不用 0 冒充已知;不同版本和迟到结果不能覆盖当前版本。页面 effect、在途任务和缓存使用同一版本身份。 缓存清理以明确待删集合执行,避免分块 NOT IN 误删仍有效项。保留当前播放所需资源,不为追求缓存命中扩大无界内存。
- ConfigRepository 后台读取并生成原子、带版本的快照;使用明确刷新入口/可靠变更通知及适当复核,不能假设所有 SAF 提供方修改时间可靠。
- 区分运行时可变参数和需要重建实例的参数;新会话使用完整同一版本,禁止半新半旧配置。
- 字体/资源按内容版本准备,失败沿用完整旧快照并显示可理解的错误。
- 历史恢复按进度、速度、轨道、延迟等分别开关;用户操作优先于迟到恢复。轨道选择保存稳定描述,不能跨版本盲信数值轨道 ID。
- 网络目录收藏保存 connectionId + path;不复制凭据,连接删除时联动处理。
- 页面显示准备、连接、缓冲、失败;只在数据可靠时展示缓存区间/时长,未知不伪造百分比。
- 凭据不进入日志、历史键或代理 URL;匿名会话 ID 用于诊断。
Room 采用增量迁移,不破坏升级前数据;旧历史 MediaTitle 主键先兼容读取,只有明确匹配时迁移到 MediaId,歧义同名项不自动串联。 不可逆 schema 变更与引擎替换分开。运行时兼容开关按会话选择,不能播放中途切换两套核心。 每阶段提交保持可构建;若新通路失败,保留明确旧通路回退,不长期维护两套独立历史状态。 APK 使用原包名/签名和递增版本码。回退交付使用更高版本码的兼容构建,不承诺直接安装降版本 APK。
| 阶段 | 交付内容 | 完成门槛 |
|---|---|---|
| P0 | 上下文与规则精简、设计落盘、保留现有基准日志 | 记录无重复/凭据,Git 可恢复 |
| P1a | MPV 访问网关接入 Activity/ViewModel/Service | 编译通过,调用参数与执行线程语义保持原状;不宣称唯一控制者已完成 |
| P1b | 其余入口迁移,Coordinator/状态/命令排序与适配器测试 | 无业务层直接 native 访问,旧会话/迟到事件隔离检查通过 |
| P2 | 当前文件优先、按需队列、配置快照与历史保存 | 主线程 I/O 移出;退出快照和 URI 租约路径可验证 |
| P3 | WebDAV 直连+兼容回退,代理请求隔离 | 认证/Range/取消/版本变化逻辑检查通过;设备网络验收另行授权 |
| P4 | 元数据统一、后台任务调度、版本缓存 | 无重复当前文件探测、无取消任务复用、队列与内存有界 |
| P5 | AAR 实例/Surface 分离、短期实例复用 | 重建/前后台/配置/停止边界验证完成,再默认启用 |
| P6 | 分项恢复、目录收藏、诊断体验、清理旧实现 | 数据迁移与兼容性验收后删除过渡代码 |
P5 的可行性分析可提前,但业务上线后置。每阶段完成记录明确已做与未做,不将设计列表当作已实现功能。
开发检查按改动运行窄编译/单测:命令顺序、A→B→A、停止前快照、迟到历史、Surface 代次、取消订阅/重入、弱版本缓存、数据库迁移边界。 外部 I/O 用可控替身;保留被测适配逻辑,不用重写整个实现的模拟器自证。无需为纯委托函数逐条写镜像测试。 功能/设备/网络测试列为后续验收,未获明确请求不执行。矩阵覆盖冷/热打开、小/大目录、长 RTT、低吞吐、无 Range、认证重定向、MP4/MKV、尾部索引、旋转/后台/快速切换。 比较同设备同源同缓存状态;阶段差值相加要避免并行耗时重复计数。记录 p50/p95、失败率、峰值内存、总字节和浪费字节。 点击→页面、打开→解码就绪与可见视频首帧分别测;FILE_LOADED/RESTART/首个 UI draw 均不能单独当成视频首帧。 目标先定方向:缩短界面前等待、减少重复打开成本、降低探测竞争且不增加失败率;数值门槛在有基线后确定,不预承诺“秒开”。
P0/P1a 已完成,P1b 核心加载/事件/退出链已收口:
- 应用 native 访问统一到 MpvGateway;加载/定位各一个待执行槽,初次加载也在 Surface 就绪后进入同一队列,Surface 重建不重播。
- PlaybackEventIdentity 将 START_FILE/END_FILE 的 playlist_entry_id 与 commandNode(loadfile) 返回 ID 关联;支持回调早于返回、替换/失败/退休释放等待者及重定向区间,不用当前 generation 冒充事件来源。FILE_LOADED/RESTART/EOF 还核对当前在播条目;普通高频属性不额外查询 native 条目。
- 退出先同步捕获播放快照,再退休会话/待执行命令;独立 IO 保存任务不随 Activity 生命周期取消。停止等待加载执行者退出,销毁等待所有应用 JNI 调用返回;JNI 调用不持有计数锁,关闭期间回调/迟到调用不能重新进入 native。移除固定 100ms 等待。
适配依据:本机 AAR mpv v0.41.0-224-gd54bad563,JNI event_thread 使用 mpv_event_to_node;对应 cmd_loadfile 返回 playlist_entry_id。mpv 属性约定中 playlist-playing-pos 与 playlist/N/id 用于校验实际在播条目。BaseMPVView 的文件槽保持为空,由原回调只负责 Surface 挂接/恢复 VO。
本批继续落地:
- 核心 PlaybackUiState 原子发布代次、时间线、暂停/缓冲和准备/失败状态;进度条及手势共用同一快照,失败事件按实际结束条目隔离。
- Intent 地址解析不再打开资源;当前文件名称/身份查询及资源打开在 IO 执行,旧代次结果丢弃并关闭。目录/数据库队列移到当前文件就绪之后;移除换片后固定 100ms 探测。
- content URI 使用可关闭资源租约;按 native START/END 及销毁释放,覆盖跳过条目和早于命令返回的回调。依据固定版本 stream_file.c 的 fd:// 借用语义及 loadfile.c 在 END_FILE 前关闭 demuxer 的顺序;附加字幕/音轨资源随引擎释放。
通知/PiP 核心控制与状态已接入统一通道;暂停命令按代次合并。P2 配置/字体/脚本在 IO 生成完整内容快照,原子发布、按引擎租约保留旧版本,失败回退完整旧快照;界面先绘制准备状态,再在主线程初始化 native。每次打开重新核对外部内容,不依赖 SAF 修改时间可靠性。
P2 队列已使用固定条目 ID 快照,窗口最多 100 项且目标项单独读取;目录用单次令牌交接 URI 快照,不再向 Intent 塞入全目录数组。代理条目只保存源描述,协议客户端首次读取才创建。本地历史采用 URI/规范路径身份,歧义旧文件名行不自动迁移。
P3 已将同视频 GET 隔离,只有明确换片/退休才取消全部旧请求;SMB/FTP 资源随请求关闭,部分建立失败确定清理。认证 WebDAV 直连/回退仍受 PROJECT_ISSUES.md 中的认证域和错误分类边界约束,暂不启用。
P4 本批修复目录版本失效与取消重入:实际单消费者调度器可独立检查,取消立即退休身份,旧 native 工作收尾后才调度新生产者;队列最多 64 个待执行项。内存图片/迟到结果均核对当前版本,本地缓存按明确失效集合分块删除。
P6 已补上分项恢复开关、迟到恢复失效与内置轨道描述匹配;数据库 11→12 增量迁移。网络目录收藏已通过 connectionId + path 表及 12→13 增量迁移落地,删除连接级联清理;外部字幕使用原 URI 描述恢复主次选择,附加加载串行于换片,未接收资源按队列生命周期关闭。
常用倍速、音轨、章节、解码器及延迟控件已接入 typed 控制与按属性合并的后台通道;轨道/章节/选择/倍速/解码器随当前代次快照发布,周期读取在 IO 并核对代次;字幕样式、滤镜、去色带与声道也已接入;附加音轨、逐帧及截图已串行化并核对代次,截图取消等待 native 结束后释放文件;统计/Anime4K 已接入实例级串行合并槽,换片保留、退休清除,视频宽高按代次发布;其余业务控制及完整元数据仍需迁移。
下一步:其余控制/元数据收口,以及 P5 实例/Surface 分离前提和 P6 恢复/收藏;整体重构仍未完成。native 初始化仍在主线程;不把本轮算作实例复用或启动耗时优化已验收。
验证和交付状态仅记于 PROJECT_CONTEXT.md;功能/性能与设备验收不由单元测试替代。