# 独立播放页第一阶段设计 ## 背景 当前 `entry/src/main/ets/pages/NewIndex.ets` 打开的是独立播放页 `entry/src/main/ets/view/player/MusiPlayerView.ets`,但该页面目前只保留了封面、标题和三个基础按钮,缺少原播放页中的进度条、当前时间、总时长、歌词主体等核心内容。 现象上表现为: - 播放/暂停图标与实际播放状态可能不同步。 - 点击上一首/下一首后,封面、歌名、歌手等信息更新不完整。 - 页面缺少进度条、时间展示、歌词主体,用户感知上和原播放页差距过大。 仓库中原完整播放页仍存在于 `entry/src/main/ets/view/LocalMusic.ets` 的 `IjkMusicPlayerView()` 链路中,但当前需求不是回退到原页面,而是继续保留独立播放页方向,并把原播放页的核心体验逐步迁移到 `MusiPlayerView.ets`。 ## 本阶段目标 第一阶段只恢复“核心可用”体验: - 封面、歌名、歌手在切歌后正确更新。 - 播放/暂停按钮图标跟随真实播放状态刷新。 - 进度条、当前时间、总时长恢复展示并持续更新。 - 歌词主体恢复展示,并随播放进度滚动。 - 整体页面布局尽量接近原播放页的主骨架。 ## 非目标 本阶段不纳入以下功能: - 更多弹层、播放页更多菜单。 - 歌词设置面板。 - 复制歌词、分享歌词。 - 单行歌词模式。 - 频谱、长按倍速、复杂手势分支。 - PIP/蓝牙歌词/投播特化 UI。 这些能力后续如果继续推进独立播放页,可在第二阶段单独迁移。 ## 现状与问题归因 `MusiPlayerView.ets` 当前直接通过少量 `@StorageLink` 消费状态: - `currentSong` - `CONTROL_PlayStatus` - `progressValue` - `cover` 这只能支撑最基础的静态展示,无法完整复用原播放页能力。原播放页中的关键信息还依赖以下链路: - `playbackPositionMs`、`playbackDurationMs` 驱动当前时间和总时长。 - `currentSong.lyricContent` 与 `LyricController` 驱动歌词 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` ### 派生规则 - `currentTime` 由 `playbackPositionMs` 转换为 `mm:ss` 或 `hh:mm:ss` 文本。 - `totalTime` 由 `playbackDurationMs` 转换为时间文本。 - 当 `currentSong` 或 `currentSong.lyricContent` 变化时,重新解析歌词并更新 `LyricController`。 - 当 `playbackPositionMs` 变化时,同步刷新 `currentTime`,并推动歌词滚动位置。 ## 页面结构设计 ### 1. 顶部封面信息层 保留当前独立页的封面背景模糊与主体封面展示,但视觉结构尽量靠拢原播放页: - 中心展示大封面。 - 封面下方展示歌名。 - 歌名下方展示歌手。 封面、歌名、歌手全部直接绑定 `currentSong` 和 `cover`,不保留临时静态文案。 ### 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` 中补充 `playbackPositionMs`、`playbackDurationMs` 的 `@StorageLink`。 2. 在页面内部新增时间文本派生逻辑,统一生成 `currentTime` 与 `totalTime`。 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` 实例变量,否则后续仍会出现状态不同步问题。