|
@@ -0,0 +1,416 @@
|
|
|
|
|
+# 播放宿主完全独立设计
|
|
|
|
|
+
|
|
|
|
|
+## 背景
|
|
|
|
|
+
|
|
|
|
|
+当前项目虽然已经引入了 `MusicPlaybackController` 与 `PlaybackCoordinator`,但真实播放运行时仍然绑定在 [LocalMusic.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/view/LocalMusic.ets):
|
|
|
|
|
+
|
|
|
|
|
+- `IjkMediaPlayer` 由 `LocalMusic` 持有。
|
|
|
|
|
+- `PlaybackRuntime` 与 controller actions 由 `LocalMusic` 在页面生命周期里注册。
|
|
|
|
|
+- 当前歌曲、播放队列、索引、进度、歌词、AVSession、卡片同步、播放页显隐等关键状态仍以 `LocalMusic` 为主状态源。
|
|
|
|
|
+- `EntryAbility` 打开播放页时,仍然依赖 `PlaybackCoordinator.hasRuntime()`,本质上还是在等 `LocalMusic` 先成为宿主。
|
|
|
|
|
+
|
|
|
|
|
+这意味着当前播放能力只是“入口抽象了一层”,并没有真正脱离 `LocalMusic`。只要 `LocalMusic` 还是运行时宿主,播放就不算完全独立。
|
|
|
|
|
+
|
|
|
|
|
+## 本次目标
|
|
|
|
|
+
|
|
|
|
|
+本轮目标是把“真实播放 runtime + 播放状态源 + 播放器宿主 UI”从 `LocalMusic` 中完整迁出,建立独立播放宿主。
|
|
|
|
|
+
|
|
|
|
|
+完成后必须满足:
|
|
|
|
|
+
|
|
|
|
|
+- `LocalMusic` 只负责内容展示与发起播放请求,不再持有真实播放器。
|
|
|
|
|
+- `NewIndex` 挂载独立 `PlaybackHost`,成为唯一播放器宿主。
|
|
|
|
|
+- `PlaybackCoordinator` 的 runtime 来源是 `PlaybackHost`,不再是 `LocalMusic`。
|
|
|
|
|
+- 迷你播放条与全屏播放页的数据来源是独立宿主,不反向依赖 `LocalMusic`。
|
|
|
|
|
+- 强杀应用后,音乐卡片点击播放会先拉起应用,再由独立宿主恢复“上次整条播放队列 + 当前索引 + 当前进度”并开始播放。
|
|
|
|
|
+
|
|
|
|
|
+## 用户确认的约束
|
|
|
|
|
+
|
|
|
|
|
+- 首轮优先实现“完全独立”,不主动重做现有播放行为和 UI 视觉。
|
|
|
|
|
+- 强杀应用后不要求后台自动继续播放。
|
|
|
|
|
+- 只有用户点击音乐卡片播放时,才触发恢复播放。
|
|
|
|
|
+- 恢复范围不是单曲,而是“上次整条播放队列 + 当前索引 + 当前进度”。
|
|
|
|
|
+- 首轮不重写底层 Ijk 播放内核。
|
|
|
|
|
+
|
|
|
|
|
+## 非目标
|
|
|
|
|
+
|
|
|
|
|
+- 不重做现有播放器视觉设计。
|
|
|
|
|
+- 不重构所有远程源的 URL 解析实现,只调整归属和调用链。
|
|
|
|
|
+- 不顺手重构 `LocalMusic` 的本地列表结构、搜索结构、歌单结构。
|
|
|
|
|
+- 不实现“应用被系统杀死后自动后台恢复播放”。
|
|
|
|
|
+- 不追求一轮消灭所有 `AppStorage` 兼容字段,允许保留必要镜像。
|
|
|
|
|
+
|
|
|
|
|
+## 推荐方案
|
|
|
|
|
+
|
|
|
|
|
+采用“独立宿主 + 快照恢复”的轻量宿主方案。
|
|
|
|
|
+
|
|
|
|
|
+核心思想:
|
|
|
|
|
+
|
|
|
|
|
+- 新增独立 `PlaybackHost`,挂在 [NewIndex.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/pages/NewIndex.ets)。
|
|
|
|
|
+- `PlaybackHost` 接管真实播放运行时、核心播放状态、恢复链路、播放器页面开关和卡片联动。
|
|
|
|
|
+- `MusicPlaybackController` 继续作为统一播放入口。
|
|
|
|
|
+- `PlaybackCoordinator` 继续作为运行时协调层,但 runtime 改由 `PlaybackHost` 注册。
|
|
|
|
|
+- `LocalMusic`、`WebDavMainPage`、`FindView`、`PlaylistDetailPage`、`ChartsCount` 等页面全部退化成内容页,只负责组装队列并发起请求。
|
|
|
|
|
+
|
|
|
|
|
+不采用以下方案:
|
|
|
|
|
+
|
|
|
|
|
+- 继续让 `LocalMusic` 作为隐藏宿主常驻。这样只是“藏起来的耦合”,不是完全独立。
|
|
|
|
|
+- 直接把所有播放逻辑堆回 `MusicPlaybackController`。这样会把 controller 演化成新的超大类,结构上只是把问题平移。
|
|
|
|
|
+- 首轮同时做 store、runtime service、全量 UI 重建。改动面过大,验证成本太高,不符合“先完全独立,再保持行为稳定”的要求。
|
|
|
|
|
+
|
|
|
|
|
+## 目标架构
|
|
|
|
|
+
|
|
|
|
|
+### 1. MusicPlaybackController
|
|
|
|
|
+
|
|
|
|
|
+职责保持为统一入口层:
|
|
|
|
|
+
|
|
|
|
|
+- 对外暴露统一播放 API。
|
|
|
|
|
+- 负责少量纯逻辑解析。
|
|
|
|
|
+- 把动作转发给 `PlaybackCoordinator`。
|
|
|
|
|
+- 对外提供“宿主激活恢复播放”的统一入口。
|
|
|
|
|
+
|
|
|
|
|
+它不再承担:
|
|
|
|
|
+
|
|
|
|
|
+- 真实播放器生命周期。
|
|
|
|
|
+- 播放页显隐状态源。
|
|
|
|
|
+- 强杀恢复状态的主存储。
|
|
|
|
|
+
|
|
|
|
|
+### 2. PlaybackCoordinator
|
|
|
|
|
+
|
|
|
|
|
+继续作为协调层存在:
|
|
|
|
|
+
|
|
|
|
|
+- 持有当前唯一 runtime 引用。
|
|
|
|
|
+- 负责把 controller 请求转发给 runtime。
|
|
|
|
|
+- 负责在起播队列失败时回滚基础桥接状态。
|
|
|
|
|
+
|
|
|
|
|
+它不再依赖 `LocalMusic` 生命周期,而是只认 `PlaybackHost` 注册的 runtime。
|
|
|
|
|
+
|
|
|
|
|
+### 3. PlaybackHost
|
|
|
|
|
+
|
|
|
|
|
+新增独立播放宿主,挂在 `NewIndex`。
|
|
|
|
|
+
|
|
|
|
|
+它是本轮的核心新增组件,负责:
|
|
|
|
|
+
|
|
|
|
|
+- 持有 `IjkMediaPlayer`。
|
|
|
|
|
+- 持有当前歌曲、当前队列、当前索引。
|
|
|
|
|
+- 处理播放、暂停、上一首、下一首、seek。
|
|
|
|
|
+- 处理起播、切歌、恢复播放、记忆进度。
|
|
|
|
|
+- 处理歌词解析与同步。
|
|
|
|
|
+- 处理 AVSession、卡片同步、播放状态桥接。
|
|
|
|
|
+- 处理播放页显隐与迷你播放条数据来源。
|
|
|
|
|
+- 注册 `PlaybackRuntime` 与 controller actions。
|
|
|
|
|
+
|
|
|
|
|
+拆完后,`PlaybackHost` 是唯一真实播放宿主。
|
|
|
|
|
+
|
|
|
|
|
+### 4. 内容页
|
|
|
|
|
+
|
|
|
|
|
+包括但不限于:
|
|
|
|
|
+
|
|
|
|
|
+- [LocalMusic.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/view/LocalMusic.ets)
|
|
|
|
|
+- [WebDavMainPage.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/pages/WebDavMainPage.ets)
|
|
|
|
|
+- [FindView.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/view/FindView.ets)
|
|
|
|
|
+- [PlaylistDetailPage.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/pages/PlaylistDetailPage.ets)
|
|
|
|
|
+- [ChartsCount.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/pages/ChartsCount.ets)
|
|
|
|
|
+
|
|
|
|
|
+这些页面迁移后只负责:
|
|
|
|
|
+
|
|
|
|
|
+- 展示内容。
|
|
|
|
|
+- 组织队列。
|
|
|
|
|
+- 调用 `MusicPlaybackController.playQueue/playSong`。
|
|
|
|
|
+
|
|
|
|
|
+这些页面迁移后不再负责:
|
|
|
|
|
+
|
|
|
|
|
+- 持有播放器实例。
|
|
|
|
|
+- 维护当前播放状态源。
|
|
|
|
|
+- 决定播放页是否可打开。
|
|
|
|
|
+- 执行真实播放控制动作。
|
|
|
|
|
+
|
|
|
|
|
+## 硬边界
|
|
|
|
|
+
|
|
|
|
|
+本轮完成后必须满足以下硬边界:
|
|
|
|
|
+
|
|
|
|
|
+- `LocalMusic` 不能再持有 `IjkMediaPlayer`。
|
|
|
|
|
+- `LocalMusic` 不能再注册 `PlaybackRuntime` 或 `MusicPlaybackControllerActions`。
|
|
|
|
|
+- `LocalMusic` 不能再作为 `showPlayerView`、播放页开关、切歌逻辑、进度推进、当前歌曲、歌词、AVSession、卡片同步的主状态源。
|
|
|
|
|
+- 打开播放页的逻辑不能再依赖 `LocalMusic` 是否已挂载。
|
|
|
|
|
+- 迷你播放条和全屏播放页都必须从 `PlaybackHost` 读取状态。
|
|
|
|
|
+- 强杀恢复不能再依赖 `LocalMusic` 生命周期。
|
|
|
|
|
+
|
|
|
|
|
+## 强杀恢复设计
|
|
|
|
|
+
|
|
|
|
|
+### 1. 恢复原则
|
|
|
|
|
+
|
|
|
|
|
+强杀后的恢复语义明确如下:
|
|
|
|
|
+
|
|
|
|
|
+- 应用被强杀后不自动播放。
|
|
|
|
|
+- 只有用户点击音乐卡片播放时,才会拉起应用并恢复播放。
|
|
|
|
|
+- 恢复的是“上次整条播放队列 + 当前索引 + 当前进度”,不是单曲。
|
|
|
|
|
+- 恢复成功后直接开始播放,不严格还原“上次是暂停还是播放”的状态。
|
|
|
|
|
+
|
|
|
|
|
+最后一点的原因是:在应用已死场景下,用户从卡片点击播放的意图更接近“恢复并开始播放”,而不是“恢复到暂停态”。
|
|
|
|
|
+
|
|
|
|
|
+### 2. PlaybackSnapshot
|
|
|
|
|
+
|
|
|
|
|
+新增独立快照模型 [PlaybackSnapshot.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/playback/model/PlaybackSnapshot.ets)。
|
|
|
|
|
+
|
|
|
|
|
+快照至少包含:
|
|
|
|
|
+
|
|
|
|
|
+- `queue`: 当前播放队列的精简快照。
|
|
|
|
|
+- `currentIndex`: 当前播放索引。
|
|
|
|
|
+- `currentSongKey`: 当前歌曲稳定标识。
|
|
|
|
|
+- `positionMs`: 当前播放进度。
|
|
|
|
|
+- `playType`: 当前播放模式。
|
|
|
|
|
+- `playlistContext`: 队列来源上下文。
|
|
|
|
|
+- `updatedAt`: 最近更新时间。
|
|
|
|
|
+- `shouldResumeWhenActivated`: 是否存在待恢复播放意图。
|
|
|
|
|
+
|
|
|
|
|
+队列内每首歌的快照项只保留恢复所需字段,例如:
|
|
|
|
|
+
|
|
|
|
|
+- `filePath`
|
|
|
|
|
+- `id`
|
|
|
|
|
+- `type`
|
|
|
|
|
+- `name`
|
|
|
|
|
+- `artist`
|
|
|
|
|
+- `remote_rel_path`
|
|
|
|
|
+- `webdav_account_id`
|
|
|
|
|
+- 其他当前远程源重建播放地址所需的稳定字段
|
|
|
|
|
+
|
|
|
|
|
+明确不持久化:
|
|
|
|
|
+
|
|
|
|
|
+- 实时解析出来的播放 URL
|
|
|
|
|
+- `IjkMediaPlayer` 内部状态
|
|
|
|
|
+- UI 动画状态
|
|
|
|
|
+- 临时回调与运行时对象引用
|
|
|
|
|
+
|
|
|
|
|
+### 3. PlaybackSnapshotStore
|
|
|
|
|
+
|
|
|
|
|
+新增 [PlaybackSnapshotStore.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/playback/PlaybackSnapshotStore.ets),职责如下:
|
|
|
|
|
+
|
|
|
|
|
+- 写入当前快照
|
|
|
|
|
+- 读取最近快照
|
|
|
|
|
+- 清理无效快照
|
|
|
|
|
+- 兼容损坏数据回退
|
|
|
|
|
+
|
|
|
|
|
+写入时机由 `PlaybackHost` 统一控制:
|
|
|
|
|
+
|
|
|
|
|
+- 歌曲切换
|
|
|
|
|
+- 队列切换
|
|
|
|
|
+- 播放进度推进到节流点
|
|
|
|
|
+- 播放模式变化
|
|
|
|
|
+- 应用即将进入不可见状态
|
|
|
|
|
+
|
|
|
|
|
+### 4. 强杀后的恢复流程
|
|
|
|
|
+
|
|
|
|
|
+统一恢复流程如下:
|
|
|
|
|
+
|
|
|
|
|
+1. `PlaybackHost` 正常播放过程中持续写入 `PlaybackSnapshot`。
|
|
|
|
|
+2. 应用被强杀后,音乐卡片触发一个“播放/恢复播放”激活动作。
|
|
|
|
|
+3. [EntryAbility.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/entryability/EntryAbility.ets) 不直接播放,只写入一个待执行的激活动作。
|
|
|
|
|
+4. 应用启动后,`NewIndex` 先挂载 `PlaybackHost`。
|
|
|
|
|
+5. `PlaybackHost` 初始化时读取 `PlaybackSnapshot`,恢复队列、当前索引、当前歌曲和进度到内存,但先不自动播放。
|
|
|
|
|
+6. `PlaybackHost` 检测到待执行激活动作后,再开始恢复播放:
|
|
|
|
|
+ - 恢复整条队列
|
|
|
|
|
+ - 修正当前索引
|
|
|
|
|
+ - 为当前歌曲重新解析播放 URL
|
|
|
|
|
+ - 从 `positionMs` 开始播放
|
|
|
|
|
+7. 若当前歌曲恢复失败,则优先尝试队列中的下一首可播歌曲。
|
|
|
|
|
+8. 若整条队列失效,则清空快照并提示恢复失败。
|
|
|
|
|
+
|
|
|
|
|
+## 文件边界
|
|
|
|
|
+
|
|
|
|
|
+### 新增文件
|
|
|
|
|
+
|
|
|
|
|
+- [PlaybackHost.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/playback/PlaybackHost.ets)
|
|
|
|
|
+ 独立播放宿主组件,挂在 `NewIndex`,负责 runtime 注册和宿主生命周期。
|
|
|
|
|
+- [PlaybackHostState.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/playback/PlaybackHostState.ets)
|
|
|
|
|
+ 收口宿主共享状态定义。
|
|
|
|
|
+- [PlaybackSnapshotStore.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/playback/PlaybackSnapshotStore.ets)
|
|
|
|
|
+ 读写恢复快照。
|
|
|
|
|
+- [PlaybackRestoreCoordinator.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/playback/PlaybackRestoreCoordinator.ets)
|
|
|
|
|
+ 负责快照恢复与激活动作编排。
|
|
|
|
|
+- [PlaybackSnapshot.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/playback/model/PlaybackSnapshot.ets)
|
|
|
|
|
+ 定义恢复快照和精简队列项结构。
|
|
|
|
|
+
|
|
|
|
|
+### 主要修改文件
|
|
|
|
|
+
|
|
|
|
|
+- [NewIndex.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/pages/NewIndex.ets)
|
|
|
|
|
+ 挂载 `PlaybackHost`,让迷你播放条和全屏播放页从宿主取状态。
|
|
|
|
|
+- [LocalMusic.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/view/LocalMusic.ets)
|
|
|
|
|
+ 收缩为内容页,删除宿主职责。
|
|
|
|
|
+- [MusicPlaybackController.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/controller/MusicPlaybackController.ets)
|
|
|
|
|
+ 保持统一入口定位,补激活动作入口。
|
|
|
|
|
+- [PlaybackCoordinator.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/controller/PlaybackCoordinator.ets)
|
|
|
|
|
+ 保持协调层定位,runtime 改由宿主注册。
|
|
|
|
|
+- [EntryAbility.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/entryability/EntryAbility.ets)
|
|
|
|
|
+ 音乐卡片点击后改为写入待执行宿主激活动作,而不是等待 `LocalMusic`。
|
|
|
|
|
+- [PlayerPage.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/view/player/PlayerPage.ets)
|
|
|
|
|
+ 继续做 UI 壳层,但状态来源换成 `PlaybackHost`。
|
|
|
|
|
+- [PlayerControls.ets](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/entry/src/main/ets/view/player/PlayerControls.ets)
|
|
|
|
|
+ 保持纯组件定位,不新增业务状态。
|
|
|
|
|
+
|
|
|
|
|
+## 分阶段迁移顺序
|
|
|
|
|
+
|
|
|
|
|
+### 阶段 1:建立独立宿主
|
|
|
|
|
+
|
|
|
|
|
+目标:
|
|
|
|
|
+
|
|
|
|
|
+- 新增 `PlaybackHost`
|
|
|
|
|
+- 迁出 `IjkMediaPlayer`
|
|
|
|
|
+- 迁出 runtime 注册和 controller actions 注册
|
|
|
|
|
+- 迁出基础播放状态源
|
|
|
|
|
+- 让 `NewIndex` 挂载独立宿主
|
|
|
|
|
+
|
|
|
|
|
+验收:
|
|
|
|
|
+
|
|
|
|
|
+- 即使 `LocalMusic` 不可见,播放器也能存活
|
|
|
|
|
+- `PlaybackCoordinator` 的 runtime 来源是 `PlaybackHost`
|
|
|
|
|
+
|
|
|
|
|
+### 阶段 2:迁出播放器页面和迷你条状态源
|
|
|
|
|
+
|
|
|
|
|
+目标:
|
|
|
|
|
+
|
|
|
|
|
+- 迷你播放条和全屏播放页改为从宿主读状态
|
|
|
|
|
+- 打开播放页不再依赖 `LocalMusic`
|
|
|
|
|
+- `showPlayerView/openPlayerViewFromMiniBar/dismissPlayerView` 等入口统一收敛到宿主
|
|
|
|
|
+
|
|
|
|
|
+验收:
|
|
|
|
|
+
|
|
|
|
|
+- 不打开 `LocalMusic` 页面,也能打开播放页并正常播控
|
|
|
|
|
+
|
|
|
|
|
+### 阶段 3:迁出恢复链路
|
|
|
|
|
+
|
|
|
|
|
+目标:
|
|
|
|
|
+
|
|
|
|
|
+- 新增 `PlaybackSnapshotStore`
|
|
|
|
|
+- 新增 `PlaybackRestoreCoordinator`
|
|
|
|
|
+- 打通“强杀后卡片点击播放 -> 应用拉起 -> 恢复整条队列并播放”
|
|
|
|
|
+
|
|
|
|
|
+验收:
|
|
|
|
|
+
|
|
|
|
|
+- 强杀后点击卡片可以恢复整条队列、当前索引和当前进度
|
|
|
|
|
+
|
|
|
|
|
+### 阶段 4:内容页彻底退化
|
|
|
|
|
+
|
|
|
|
|
+目标:
|
|
|
|
|
+
|
|
|
|
|
+- `LocalMusic` 只保留内容页逻辑
|
|
|
|
|
+- 其他内容页统一只发请求
|
|
|
|
|
+- 删除 `LocalMusic` 中剩余宿主级播放状态写入
|
|
|
|
|
+
|
|
|
|
|
+验收:
|
|
|
|
|
+
|
|
|
|
|
+- `LocalMusic` 不再是播放状态源
|
|
|
|
|
+- 理论上删除 `LocalMusic` 不应影响播放器主链路,只会影响本地内容页功能
|
|
|
|
|
+
|
|
|
|
|
+## 错误处理策略
|
|
|
|
|
+
|
|
|
|
|
+错误处理统一收口在 `PlaybackHost` 与 `PlaybackRestoreCoordinator`,不再分散在 `LocalMusic`。
|
|
|
|
|
+
|
|
|
|
|
+### 1. 快照读取失败
|
|
|
|
|
+
|
|
|
|
|
+- 视为无可恢复状态
|
|
|
|
|
+- 不阻塞应用启动
|
|
|
|
|
+- 记录日志并清理损坏快照
|
|
|
|
|
+
|
|
|
|
|
+### 2. 队列为空或全部无效
|
|
|
|
|
+
|
|
|
|
|
+- 清空恢复状态
|
|
|
|
|
+- 给出“暂无可恢复播放内容”或“恢复播放失败”的提示
|
|
|
|
|
+
|
|
|
|
|
+### 3. 当前索引越界
|
|
|
|
|
+
|
|
|
|
|
+- 自动修正到安全范围
|
|
|
|
|
+- 修正后若无有效歌曲,则清空快照
|
|
|
|
|
+
|
|
|
|
|
+### 4. 当前歌曲恢复失败
|
|
|
|
|
+
|
|
|
|
|
+- 优先尝试队列中的下一首可播放歌曲
|
|
|
|
|
+- 全部失败后再提示恢复失败
|
|
|
|
|
+
|
|
|
|
|
+### 5. 远程播放 URL 失效
|
|
|
|
|
+
|
|
|
|
|
+- 绝不复用旧 URL
|
|
|
|
|
+- 恢复时统一重新解析
|
|
|
|
|
+- 解析失败则本次播放失败,但保留队列状态
|
|
|
|
|
+
|
|
|
|
|
+### 6. 本地文件不存在
|
|
|
|
|
+
|
|
|
|
|
+- 跳过当前项尝试下一首
|
|
|
|
|
+- 必要时提示文件已失效
|
|
|
|
|
+
|
|
|
|
|
+### 7. 宿主未就绪时收到动作
|
|
|
|
|
+
|
|
|
|
|
+- 动作进入待执行队列
|
|
|
|
|
+- `PlaybackHost` ready 后统一消费
|
|
|
|
|
+- 避免卡片动作、迷你条动作、页面动作打空
|
|
|
|
|
+
|
|
|
|
|
+## 日志与可观测性
|
|
|
|
|
+
|
|
|
|
|
+本轮必须新增统一日志前缀,便于排查恢复链路:
|
|
|
|
|
+
|
|
|
|
|
+- `[playback-host]`
|
|
|
|
|
+- `[playback-snapshot]`
|
|
|
|
|
+- `[playback-restore]`
|
|
|
|
|
+- `[playback-activation]`
|
|
|
|
|
+
|
|
|
|
|
+至少覆盖以下事件:
|
|
|
|
|
+
|
|
|
|
|
+- 宿主初始化完成
|
|
|
|
|
+- runtime 注册与注销
|
|
|
|
|
+- 快照保存与读取
|
|
|
|
|
+- 卡片激活动作入队与消费
|
|
|
|
|
+- 队列恢复成功与失败
|
|
|
|
|
+- 当前歌曲 URL 重建成功与失败
|
|
|
|
|
+- 从哪个进度开始恢复播放
|
|
|
|
|
+
|
|
|
|
|
+## 自动化测试建议
|
|
|
|
|
+
|
|
|
|
|
+优先覆盖纯逻辑与恢复决策,不强求首轮就完整自动化播放器真机链路。
|
|
|
|
|
+
|
|
|
|
|
+建议新增 Hypium 或纯逻辑测试:
|
|
|
|
|
+
|
|
|
|
|
+- `PlaybackSnapshotStore`
|
|
|
|
|
+ - 写入后可正确读回
|
|
|
|
|
+ - 损坏数据能安全回退
|
|
|
|
|
+ - 空快照返回空结果
|
|
|
|
|
+- `PlaybackRestoreCoordinator`
|
|
|
|
|
+ - 当前索引越界时能修正
|
|
|
|
|
+ - 当前歌曲失效时能尝试下一首
|
|
|
|
|
+ - 空队列与全失效队列能正确失败
|
|
|
|
|
+- `MusicPlaybackController`
|
|
|
|
|
+ - 宿主未就绪时动作不会丢失
|
|
|
|
|
+ - 激活动作可以被宿主消费
|
|
|
|
|
+
|
|
|
|
|
+若播放器行为难以自动化,至少把“恢复决策”“快照解析”“索引修正”拆成纯函数做单测。
|
|
|
|
|
+
|
|
|
|
|
+## 手工验收清单
|
|
|
|
|
+
|
|
|
|
|
+必须手工验证以下场景:
|
|
|
|
|
+
|
|
|
|
|
+- 本地歌曲播放后强杀,点击音乐卡片播放,能恢复整条队列与当前进度
|
|
|
|
|
+- WebDAV 歌曲播放后强杀,点击音乐卡片播放,能重新解析地址并恢复
|
|
|
|
|
+- 当前歌曲失效时,能自动跳到下一首可播歌曲
|
|
|
|
|
+- 不进入 `LocalMusic` 页面,也能从排行榜、发现页、歌单详情发起播放
|
|
|
|
|
+- 迷你播放条和全屏播放页在 `LocalMusic` 不可见时仍正常
|
|
|
|
|
+- `LocalMusic` 打开后不会重复注册 runtime,也不会重新抢占宿主状态
|
|
|
|
|
+- 无有效快照时,点击卡片只提示无可恢复内容,不崩溃
|
|
|
|
|
+
|
|
|
|
|
+## 完成定义
|
|
|
|
|
+
|
|
|
|
|
+只有以下条件全部成立,才算完成“播放完全独立”:
|
|
|
|
|
+
|
|
|
|
|
+- 播放 runtime 不再属于 `LocalMusic`
|
|
|
|
|
+- `LocalMusic` 不再是播放状态源
|
|
|
|
|
+- `NewIndex + PlaybackHost` 成为唯一播放器宿主
|
|
|
|
|
+- 强杀后通过音乐卡片可以恢复“整条队列 + 当前索引 + 当前进度”的播放
|
|
|
|
|
+- 播放主链路不依赖先进入 `LocalMusic`
|
|
|
|
|
+
|
|
|
|
|
+## 与既有设计的关系
|
|
|
|
|
+
|
|
|
|
|
+本设计是对已有两份设计文档的收束与推进:
|
|
|
|
|
+
|
|
|
|
|
+- [2026-03-29-localmusic2-player-refactor-design.md](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/docs/superpowers/specs/2026-03-29-localmusic2-player-refactor-design.md)
|
|
|
|
|
+ 已提出“播放器宿主应独立于内容页”的方向,但当时仍偏向新入口与大范围文件重组。
|
|
|
|
|
+- [2026-04-02-playback-controller-decouple-localmusic-design.md](/mnt/d/harmony/2025/qimeng/TTMusic_Card/TTMusic/docs/superpowers/specs/2026-04-02-playback-controller-decouple-localmusic-design.md)
|
|
|
|
|
+ 已把 controller/coordinator 入口抽出来,但 runtime 仍未真正脱离 `LocalMusic`。
|
|
|
|
|
+
|
|
|
|
|
+本次设计的定位是:在现有 controller/coordinator 基础上,补齐“独立宿主 + 强杀恢复 + 内容页彻底退化”这三个关键缺口,完成真正意义上的完全独立。
|