2026-04-06-independent-player-page-phase1-design.md 8.1 KB

独立播放页第一阶段设计

背景

当前 entry/src/main/ets/pages/NewIndex.ets 打开的是独立播放页 entry/src/main/ets/view/player/MusiPlayerView.ets,但该页面目前只保留了封面、标题和三个基础按钮,缺少原播放页中的进度条、当前时间、总时长、歌词主体等核心内容。

现象上表现为:

  • 播放/暂停图标与实际播放状态可能不同步。
  • 点击上一首/下一首后,封面、歌名、歌手等信息更新不完整。
  • 页面缺少进度条、时间展示、歌词主体,用户感知上和原播放页差距过大。

仓库中原完整播放页仍存在于 entry/src/main/ets/view/LocalMusic.etsIjkMusicPlayerView() 链路中,但当前需求不是回退到原页面,而是继续保留独立播放页方向,并把原播放页的核心体验逐步迁移到 MusiPlayerView.ets

本阶段目标

第一阶段只恢复“核心可用”体验:

  • 封面、歌名、歌手在切歌后正确更新。
  • 播放/暂停按钮图标跟随真实播放状态刷新。
  • 进度条、当前时间、总时长恢复展示并持续更新。
  • 歌词主体恢复展示,并随播放进度滚动。
  • 整体页面布局尽量接近原播放页的主骨架。

非目标

本阶段不纳入以下功能:

  • 更多弹层、播放页更多菜单。
  • 歌词设置面板。
  • 复制歌词、分享歌词。
  • 单行歌词模式。
  • 频谱、长按倍速、复杂手势分支。
  • PIP/蓝牙歌词/投播特化 UI。

这些能力后续如果继续推进独立播放页,可在第二阶段单独迁移。

现状与问题归因

MusiPlayerView.ets 当前直接通过少量 @StorageLink 消费状态:

  • currentSong
  • CONTROL_PlayStatus
  • progressValue
  • cover

这只能支撑最基础的静态展示,无法完整复用原播放页能力。原播放页中的关键信息还依赖以下链路:

  • playbackPositionMsplaybackDurationMs 驱动当前时间和总时长。
  • currentSong.lyricContentLyricController 驱动歌词 UI。
  • 播放页内部还维护了当前显示页、歌词滚动位置、封面/歌词切换等展示状态。

因此当前问题不是单个按钮实现问题,而是独立播放页缺少完整的播放态输入和歌词渲染链路。

设计概览

保持 entry/src/main/ets/view/player/MusiPlayerView.ets 作为独立播放页根组件,不回退到 LocalMusic.ets 直接渲染原页面,但补齐独立页自身需要的状态消费和展示层。

页面第一阶段按四层组织:

  1. 顶部封面信息层:封面、歌名、歌手。
  2. 中部主体切换层:Swiper 两页,第一页展示封面主体,第二页展示歌词主体。
  3. 底部播放控制层:进度条、当前时间、总时长、上一首、播放/暂停、下一首。
  4. 页面内部状态层:当前 Swiper 页、点光按钮状态、歌词控制器、派生时间文本。

状态输入设计

独立播放页继续以宿主同步到 AppStorage 的状态为唯一数据来源,不直接依赖 LocalMusic.ets 页面实例字段。

直接消费的宿主状态

  • currentSong
  • cover
  • CONTROL_PlayStatus
  • progressValue
  • playbackPositionMs
  • playbackDurationMs

页面内部派生状态

  • currentTime
  • totalTime
  • 当前 Swiper 页索引
  • LyricController
  • 点光按钮的 PointLightOptions

派生规则

  • currentTimeplaybackPositionMs 转换为 mm:sshh:mm:ss 文本。
  • totalTimeplaybackDurationMs 转换为时间文本。
  • currentSongcurrentSong.lyricContent 变化时,重新解析歌词并更新 LyricController
  • playbackPositionMs 变化时,同步刷新 currentTime,并推动歌词滚动位置。

页面结构设计

1. 顶部封面信息层

保留当前独立页的封面背景模糊与主体封面展示,但视觉结构尽量靠拢原播放页:

  • 中心展示大封面。
  • 封面下方展示歌名。
  • 歌名下方展示歌手。

封面、歌名、歌手全部直接绑定 currentSongcover,不保留临时静态文案。

2. 中部主体切换层

恢复与原播放页相同的双页结构:

  • 第 1 页:封面主体视图。
  • 第 2 页:歌词主体视图。

本阶段只保留最基础的左右切换,不引入更多操作按钮和歌词设置入口。

3. 底部播放控制层

底部控制区复用已经独立出的 entry/src/main/ets/view/player/PlayerControls.ets,由 MusiPlayerView.ets 负责传入:

  • progressValue
  • currentTime
  • totalTime
  • controlPlayStatus
  • 上一首/播放暂停/下一首回调
  • onSeek

这样可以避免在 MusiPlayerView.ets 里再次手写一份完整底部控制 UI,也让独立页后续扩展更稳定。

歌词设计

本阶段仅恢复“歌词主体显示 + 随进度滚动”。

数据来源

优先使用 currentSong?.lyricContent

如果当前歌曲没有内嵌歌词,则歌词区域展示空态提示,不在第一阶段继续接本地 .lrc 自动读取、复制歌词、歌词设置等历史能力。

控制器

MusiPlayerView.ets 内创建独立的 LyricController,不复用 LocalMusic.ets 持有的控制器实例。

更新时机

  • currentSong 切换时:清空旧歌词,解析新歌词并喂给 LyricController
  • playbackPositionMs 更新时:调用歌词位置更新逻辑,推动高亮行滚动。
  • 切换到歌词页时:立即同步一次当前位置,避免页面首次切到歌词页时高亮延迟。

交互设计

本阶段保留以下基础交互:

  • 点击上一首。
  • 点击播放/暂停。
  • 点击下一首。
  • 拖动进度条 seek。
  • 左右切换封面页和歌词页。

本阶段不迁移以下交互:

  • 更多菜单。
  • 歌词长按复制。
  • 歌词设置弹层。
  • 长按倍速。

UI 对齐策略

目标不是逐像素复刻 LocalMusic.ets,而是优先恢复用户可感知的主结构一致性。

第一阶段要求:

  • 背景继续使用当前歌曲封面模糊。
  • 中部保持“封面/歌词”二页切换结构。
  • 底部恢复时间与进度条。
  • 控制按钮保留当前已验证可用的独立页按钮实现方式。

允许与原页存在的差异:

  • 暂不引入更多弹层按钮与歌词设置按钮。
  • 暂不接入频谱和单行歌词。
  • 暂不迁移原页中与特定设备场景绑定的复杂分支。

实现步骤

  1. MusiPlayerView.ets 中补充 playbackPositionMsplaybackDurationMs@StorageLink
  2. 在页面内部新增时间文本派生逻辑,统一生成 currentTimetotalTime
  3. 将当前简化中部区域改为 Swiper,分别承载封面页与歌词页。
  4. MusiPlayerView.ets 中接入 LyricController、歌词解析与歌词位置更新逻辑。
  5. 使用 PlayerControls.ets 替换当前简化版底部控制区,接入进度与时间。
  6. 复查上一首、播放/暂停、下一首在切歌后是否带动封面、标题、艺术家、歌词与进度一起刷新。

验收标准

满足以下条件即可认为第一阶段完成:

  • 点击播放/暂停后,按钮图标立即与真实播放状态一致。
  • 点击上一首/下一首后,封面、歌名、歌手同步刷新。
  • 进度条能随播放推进更新。
  • 当前时间与总时长正确显示。
  • 有歌词的歌曲能展示歌词并随进度滚动。
  • 无歌词的歌曲展示空态,页面无异常报错。

风险与约束

风险 1:歌词链路从原页面剥离时依赖过深

LocalMusic.ets 中歌词逻辑与设置项、复制歌词、蓝牙歌词、PIP 文本节点等深度耦合。第一阶段必须明确截断边界,只迁移播放页主歌词显示所需的最小链路。

风险 2:独立页继续直接堆代码会再次失控

本阶段虽然继续使用 MusiPlayerView.ets 作为根组件,但要优先复用已有的独立子组件,例如 PlayerControls.ets,避免把原 LocalMusic.ets 中的大段 UI 直接整块复制过来。

风险 3:状态来源混乱

独立页必须只消费宿主同步后的播放状态,不能一部分走 AppStorage,另一部分再去借 LocalMusic.ets 实例变量,否则后续仍会出现状态不同步问题。