|
@@ -0,0 +1,269 @@
|
|
|
|
|
+# 音乐卡片后台控播改造设计
|
|
|
|
|
+
|
|
|
|
|
+## 背景
|
|
|
|
|
+
|
|
|
|
|
+当前 `TTMusic` 的音乐卡片播放控制没有完全独立:
|
|
|
|
|
+
|
|
|
|
|
+- 卡片 `play_pause/prev/next/seek_to` 当前优先走 `call` 到 `EntryAbility`
|
|
|
|
|
+- 当应用进程已被强杀时,`call` 的接收方不存在,按钮无响应
|
|
|
|
|
+- `FormExtensionAbility` 当前只能把事件转发给 `eventHub`,或兜底拉起 `EntryAbility`
|
|
|
|
|
+- 一旦走 `EntryAbility` 冷启动兜底,就会把应用前台界面拉起,不符合目标
|
|
|
|
|
+
|
|
|
|
|
+用户目标已经明确:
|
|
|
|
|
+
|
|
|
|
|
+- 强杀后,点击卡片 `播放/暂停/上一首/下一首` 仍然可用
|
|
|
|
|
+- 这些控制动作不能把应用前台界面拉起
|
|
|
|
|
+- 只有明确点击“打开播放器”的动作,才允许进入前台页面
|
|
|
|
|
+
|
|
|
|
|
+## 平台依据
|
|
|
|
|
+
|
|
|
|
|
+根据用户提供的鸿蒙官方文档《卡片拉起应用UIAbility到后台(call事件)》:
|
|
|
|
|
+
|
|
|
|
|
+- 卡片 `postCardAction(... action: 'call')` 可以将指定 `UIAbility` 拉到后台
|
|
|
|
|
+- `call` 事件支持指定方法名和参数
|
|
|
|
|
+- 该能力依赖 `ohos.permission.KEEP_BACKGROUND_RUNNING`
|
|
|
|
|
+- 该路径不要求 `ohos.permission.START_INVISIBLE_ABILITY`
|
|
|
|
|
+
|
|
|
|
|
+这意味着本项目不必走 `AppServiceExtensionAbility` 或 invisible ability 路线,可以直接采用:
|
|
|
|
|
+
|
|
|
|
|
+`卡片 -> call -> 后台 UIAbility -> 独立播放控制入口`
|
|
|
|
|
+
|
|
|
|
|
+## 现状问题
|
|
|
|
|
+
|
|
|
|
|
+### 1. 控制入口仍然绑在 `EntryAbility`
|
|
|
|
|
+
|
|
|
|
|
+`TTMusic` 当前卡片控制动作的接收方是 `EntryAbility.callee`。这在应用存活时可用,但在进程被强杀后没有稳定的后台控制入口。
|
|
|
|
|
+
|
|
|
|
|
+### 2. `FormAbility` 不能直接控播
|
|
|
|
|
+
|
|
|
|
|
+`MusicCardFormAbility` 当前只负责:
|
|
|
|
|
+
|
|
|
|
|
+- 更新卡片数据
|
|
|
|
|
+- 将消息转发到 `eventHub`
|
|
|
|
|
+- 兜底拉起 `EntryAbility`
|
|
|
|
|
+
|
|
|
|
|
+它不是实际的冷启动控播执行者。
|
|
|
|
|
+
|
|
|
|
|
+### 3. 播放控制仍依赖页面运行态
|
|
|
|
|
+
|
|
|
|
|
+当前播放能力虽然已经在做拆分,但仍然与页面侧运行态、页面事件和 `LocalMusic` 生命周期耦合较深。结果是:
|
|
|
|
|
+
|
|
|
|
|
+- 热态控播能工作
|
|
|
|
|
+- 冷态后台控播缺少一个真正独立的宿主
|
|
|
|
|
+
|
|
|
|
|
+## 设计目标
|
|
|
|
|
+
|
|
|
|
|
+本次改造只解决音乐卡片后台控播,不做无关重构。
|
|
|
|
|
+
|
|
|
|
|
+成功标准:
|
|
|
|
|
+
|
|
|
|
|
+1. 应用未强杀时,卡片 `播放/暂停/上一首/下一首/进度跳转` 可正常工作
|
|
|
|
|
+2. 应用被强杀后,卡片 `播放/暂停/上一首/下一首` 可直接生效
|
|
|
|
|
+3. 上述控制动作不会拉起前台页面
|
|
|
|
|
+4. 卡片“打开播放器”动作仍然可以进入前台播放器页面
|
|
|
|
|
+5. 卡片状态在控播后能继续刷新
|
|
|
|
|
+
|
|
|
|
|
+## 方案对比
|
|
|
|
|
+
|
|
|
|
|
+### 方案 A:卡片控制统一走后台 `UIAbility`
|
|
|
|
|
+
|
|
|
|
|
+路径:
|
|
|
|
|
+
|
|
|
|
|
+`卡片 -> call -> 后台控播 UIAbility -> 独立播放控制器`
|
|
|
|
|
+
|
|
|
|
|
+优点:
|
|
|
|
|
+
|
|
|
|
|
+- 与官方文档能力完全一致
|
|
|
|
|
+- 不需要 `START_INVISIBLE_ABILITY`
|
|
|
|
|
+- 能天然满足“强杀后可控播但不拉前台”
|
|
|
|
|
+- 冷启动路径和热启动路径可以统一到同一个控制接口
|
|
|
|
|
+
|
|
|
|
|
+缺点:
|
|
|
|
|
+
|
|
|
|
|
+- 需要把现有控播逻辑进一步从页面侧拆出来
|
|
|
|
|
+
|
|
|
|
|
+### 方案 B:卡片走 `FormAbility.message`
|
|
|
|
|
+
|
|
|
|
|
+路径:
|
|
|
|
|
+
|
|
|
|
|
+`卡片 -> message -> FormAbility -> 独立播放控制器`
|
|
|
|
|
+
|
|
|
|
|
+优点:
|
|
|
|
|
+
|
|
|
|
|
+- 保持卡片提供方逻辑集中在 `FormAbility`
|
|
|
|
|
+
|
|
|
|
|
+缺点:
|
|
|
|
|
+
|
|
|
|
|
+- 与官方推荐的“后台拉起 `UIAbility` 执行 call”路径不一致
|
|
|
|
|
+- 当前项目里 `FormAbility` 已经承担卡片数据职责,再承担后台播放器宿主会混杂两类生命周期
|
|
|
|
|
+
|
|
|
|
|
+### 方案 C:继续复用 `EntryAbility`
|
|
|
|
|
+
|
|
|
|
|
+路径:
|
|
|
|
|
+
|
|
|
|
|
+`卡片 -> call/router -> EntryAbility -> 播放控制器`
|
|
|
|
|
+
|
|
|
|
|
+优点:
|
|
|
|
|
+
|
|
|
|
|
+- 表面改动最少
|
|
|
|
|
+
|
|
|
|
|
+缺点:
|
|
|
|
|
+
|
|
|
|
|
+- 很难稳定保证“不拉前台”
|
|
|
|
|
+- 会继续把卡片后台控播和主界面生命周期绑在一起
|
|
|
|
|
+
|
|
|
|
|
+## 推荐方案
|
|
|
|
|
+
|
|
|
|
|
+采用方案 A。
|
|
|
|
|
+
|
|
|
|
|
+具体做法:
|
|
|
|
|
+
|
|
|
|
|
+- 新增一个专门承接音乐卡片后台 `call` 的 `UIAbility`
|
|
|
|
|
+- 该 `UIAbility` 不负责页面展示,只负责注册 `callee.on(...)`
|
|
|
|
|
+- 卡片上的 `播放/暂停/上一首/下一首/进度跳转` 全部发送到这个后台控播 `UIAbility`
|
|
|
|
|
+- 卡片上的“打开播放器”仍然走 `router` 到 `EntryAbility`
|
|
|
|
|
+- 新增一个真正独立的“卡片后台播放宿主”,它不依赖 `LocalMusic` 页面实例
|
|
|
|
|
+
|
|
|
|
|
+## 目标架构
|
|
|
|
|
+
|
|
|
|
|
+### 卡片事件分流
|
|
|
|
|
+
|
|
|
|
|
+- `play_pause` -> `call` -> `MusicCardControlAbility`
|
|
|
|
|
+- `prev_song` -> `call` -> `MusicCardControlAbility`
|
|
|
|
|
+- `next_song` -> `call` -> `MusicCardControlAbility`
|
|
|
|
|
+- `seek_to` -> `call` -> `MusicCardControlAbility`
|
|
|
|
|
+- `open_player` -> `router` -> `EntryAbility`
|
|
|
|
|
+
|
|
|
|
|
+### 后台控播宿主职责
|
|
|
|
|
+
|
|
|
|
|
+新增后台控播宿主后,它需要负责:
|
|
|
|
|
+
|
|
|
|
|
+- 接收卡片 `call` 方法和参数
|
|
|
|
|
+- 解析动作、表单 ID、跳转位置
|
|
|
|
|
+- 恢复已持久化的播放快照和播放队列
|
|
|
|
|
+- 在没有页面实例时独立执行 `play/pause/prev/next/seek`
|
|
|
|
|
+- 执行后刷新卡片快照和卡片 UI
|
|
|
|
|
+
|
|
|
|
|
+### 页面侧职责
|
|
|
|
|
+
|
|
|
|
|
+`LocalMusic` 和页面运行态继续负责:
|
|
|
|
|
+
|
|
|
|
|
+- 前台播放器 UI
|
|
|
|
|
+- 正常播放中的实时状态上报
|
|
|
|
|
+- 将最新播放状态、队列和当前歌曲写入持久化存储
|
|
|
|
|
+
|
|
|
|
|
+页面侧不再承担“卡片强杀后冷启动时的唯一执行入口”。
|
|
|
|
|
+
|
|
|
|
|
+## 关键设计细节
|
|
|
|
|
+
|
|
|
|
|
+### 1. 新增后台控播 `UIAbility`
|
|
|
|
|
+
|
|
|
|
|
+建议新增单独能力,例如:
|
|
|
|
|
+
|
|
|
|
|
+- `MusicCardControlAbility`
|
|
|
|
|
+
|
|
|
|
|
+职责:
|
|
|
|
|
+
|
|
|
|
|
+- 在 `onCreate` 中注册 `callee.on(MusicCardActionConstants.CALL_METHOD_HANDLE_ACTION, ...)`
|
|
|
|
|
+- 接收卡片参数后分发给后台控播宿主
|
|
|
|
|
+- 不做页面路由,不创建播放器页面
|
|
|
|
|
+- 生命周期结束时执行 `callee.off(...)`
|
|
|
|
|
+
|
|
|
|
|
+该能力在 `module.json5` 中新增 `abilities` 配置。
|
|
|
|
|
+
|
|
|
|
|
+### 2. 卡片控制动作不再发往 `EntryAbility`
|
|
|
|
|
+
|
|
|
|
|
+所有卡片页面中的控制按钮都改成:
|
|
|
|
|
+
|
|
|
|
|
+- `abilityName: 'MusicCardControlAbility'`
|
|
|
|
|
+- `action: 'call'`
|
|
|
|
|
+
|
|
|
|
|
+只有“打开播放器”保留:
|
|
|
|
|
+
|
|
|
|
|
+- `abilityName: 'EntryAbility'`
|
|
|
|
|
+- `action: 'router'`
|
|
|
|
|
+
|
|
|
|
|
+### 3. 新增后台独立播放控制入口
|
|
|
|
|
+
|
|
|
|
|
+建议新增一个面向后台卡片控播的控制器,例如:
|
|
|
|
|
+
|
|
|
|
|
+- `MusicCardBackgroundPlaybackController`
|
|
|
|
|
+
|
|
|
|
|
+职责:
|
|
|
|
|
+
|
|
|
|
|
+- 从现有持久化快照中恢复当前歌曲
|
|
|
|
|
+- 从现有持久化队列中恢复播放队列和当前下标
|
|
|
|
|
+- 执行 `toggle/previous/next/seekTo`
|
|
|
|
|
+- 将执行结果写回快照存储
|
|
|
|
|
+- 触发 `MusicCardManager.updateAllForms(...)`
|
|
|
|
|
+
|
|
|
|
|
+该控制器的目标不是替代现有前台控制器,而是提供“无页面实例时也能运行”的最小控播闭环。
|
|
|
|
|
+
|
|
|
|
|
+### 4. 播放恢复源
|
|
|
|
|
+
|
|
|
|
|
+后台控播所需的数据优先来自现有持久化层:
|
|
|
|
|
+
|
|
|
|
|
+- 当前播放快照
|
|
|
|
|
+- 当前播放队列
|
|
|
|
|
+- 当前下标
|
|
|
|
|
+- 当前文件路径
|
|
|
|
|
+- 当前播放进度
|
|
|
|
|
+
|
|
|
|
|
+如果快照存在但队列缺失:
|
|
|
|
|
+
|
|
|
|
|
+- `play_pause` 至少应支持恢复当前歌曲
|
|
|
|
|
+- `prev/next` 在没有有效队列时退化为保持当前歌曲不切换,并记录日志
|
|
|
|
|
+
|
|
|
|
|
+### 5. 热态与冷态统一接口
|
|
|
|
|
+
|
|
|
|
|
+最终控制接口应统一为:
|
|
|
|
|
+
|
|
|
|
|
+- 热态:页面/前台宿主调用同一组控制方法
|
|
|
|
|
+- 冷态:后台控播 `UIAbility` 也调用同一组控制方法
|
|
|
|
|
+
|
|
|
|
|
+这样可以避免出现两套播放切换逻辑不一致的问题。
|
|
|
|
|
+
|
|
|
|
|
+## 错误处理
|
|
|
|
|
+
|
|
|
|
|
+- 没有任何可恢复的快照时,`play_pause` 不做前台拉起,只记录日志
|
|
|
|
|
+- 队列不可恢复时,`prev/next` 不做前台拉起,只记录日志
|
|
|
|
|
+- `seek_to` 参数非法时忽略
|
|
|
|
|
+- 后台控播失败时,必须确保不会误触发页面路由
|
|
|
|
|
+
|
|
|
|
|
+## 测试策略
|
|
|
|
|
+
|
|
|
|
|
+### 自动化
|
|
|
|
|
+
|
|
|
|
|
+新增或补充以下测试:
|
|
|
|
|
+
|
|
|
|
|
+- 卡片动作参数解析测试
|
|
|
|
|
+- 后台控播宿主在只有快照时的恢复测试
|
|
|
|
|
+- 后台控播宿主在有队列时的上一首/下一首切换测试
|
|
|
|
|
+- 控制动作不会落到 `EntryAbility` 的路由测试
|
|
|
|
|
+
|
|
|
|
|
+### 手工验证
|
|
|
|
|
+
|
|
|
|
|
+1. 应用前台运行时,卡片 `播放/暂停/上一首/下一首` 正常
|
|
|
|
|
+2. 应用切后台但未强杀时,卡片控制正常且不拉前台
|
|
|
|
|
+3. 强杀应用后,点击卡片播放可直接播放,不拉前台
|
|
|
|
|
+4. 强杀应用后,点击卡片上一首/下一首可切歌,不拉前台
|
|
|
|
|
+5. 点击卡片封面或“打开播放器”时,前台页面正常打开
|
|
|
|
|
+
|
|
|
|
|
+## 实施顺序
|
|
|
|
|
+
|
|
|
|
|
+1. 新增后台控播 `UIAbility` 配置与空实现
|
|
|
|
|
+2. 将卡片控制按钮的 `call` 目标切换到新能力
|
|
|
|
|
+3. 提炼后台独立播放控制入口
|
|
|
|
|
+4. 接入快照/队列恢复逻辑
|
|
|
|
|
+5. 接入卡片刷新
|
|
|
|
|
+6. 补测试并验证强杀场景
|
|
|
|
|
+
|
|
|
|
|
+## 范围外
|
|
|
|
|
+
|
|
|
|
|
+本次不处理:
|
|
|
|
|
+
|
|
|
|
|
+- 全量重构现有前台播放器架构
|
|
|
|
|
+- 远程歌曲缓存策略重做
|
|
|
|
|
+- 卡片 UI 视觉调整
|
|
|
|
|
+- 非音乐卡片场景的后台唤起能力复用
|
|
|
|
|
+
|