anzhiyu 音乐播放器深度魔改系列(二):歌词功能——URL 歌词、自绘滚动与竞态根治

anzhiyu 音乐播放器深度魔改系列(二):歌词功能——URL 歌词、自绘滚动与竞态根治
Yoki开头:歌词不是「没有」,是「没对上」
左下角播放器从 APlayer 官方示例抄的歌词配置,跑起来永远是「暂无歌词」——明明歌单里每首歌都有 lrc 字段。后来换成 lrcType: 3(异步 fetch 歌词),歌词出来了,但永远慢半拍:歌已经唱到副歌,高亮还停在第一句。
根因:APlayer 的异步歌词加载与 timeupdate 播放进度不同步,且它只比对「播放列表索引」不比对「歌词内容」——切歌后旧歌单晚到的歌词 XHR 还能覆盖新歌词。本文给出三件套解法:统一解析器 + 自绘滚动 + 竞态对比根治。
目标声明 + 前置条件
本文带你完成:左下角播放器与音乐馆页歌词逐行精准同步、切歌不串歌词、深浅色模式可读,并把「暂无歌词」彻底消灭。
前置条件:
- 已完成本系列第一篇(音频保活架构)
- 歌单数据含
lrc/lyric字段(URL 或内嵌) - 可本地起
hexo s验证
你将学会:
- 统一歌词解析器
window.__parseLrc(多时间戳、毫秒、排序) - 为什么自绘比 APlayer lrcType 可靠
- 音乐馆歌词竞态的「期望 vs 实际」对比根治法
- 深浅色下歌词可读性细节
Step 1:统一歌词解析器
目的:一套解析逻辑全站复用(左下播放器 + 首页 + 音乐馆),消除三处重复实现。做完这步解析结果可用。
操作: 在 utils.js 顶部定义全局解析器:
1 | window.__parseLrc = function (text) { |
预期输出: window.__parseLrc("[00:12.500]第一句\n[00:15]第二句") 返回按时间排序的行数组与时间数组。
⚠️ 报错排查:歌词文件可能用
\r\n(Windows)或\r,split(/\r?\n/)必须兼容;同一行多个[mm:ss]标签(间奏复用行)要分别展开。
Step 2:自绘歌词 + timeupdate 同步
目的:放弃 APlayer lrcType,自己渲染歌词 DOM,用
timeupdate事件逐行滚动。做完这步歌词精确跟唱。
操作: 创建歌词容器并实现同步逻辑(utils.js:994-1099):
1 | const lrcEl = document.createElement("div"); |
预期输出: 播放时歌词逐行高亮滚动,与歌声同步;无歌词显示「暂无歌词」占位。
⚠️ 报错排查:行高与偏移是配套的(
28px行高、translateY基准56=第 3 行),改行距必须同步改偏移(本系列曾把当前行从第 2 行调到第 3 行);timeupdate事件频率够用,不要用requestAnimationFrame轮询。
Step 3:URL 歌词 fetch + 缓存去重
目的:歌词可能是外链 URL,需要异步拉取;同曲不重复请求。做完这步 URL 歌词可用且不刷接口。
操作: utils.js:1065-1099:
1 | const loadNavLrc = () => { |
预期输出: 切歌/播放触发加载;同曲(切回来)不重复请求,直接按当前进度刷新;fetch 失败兜底空歌词。
⚠️ 报错排查:
_url缓存字段必须存在(首次设_url后同 URL 直接复用),否则每切一次歌就重复请求一次;失败时也要清_times,否则残留上一首歌的时间轴会错位高亮。
Step 4:音乐馆歌词竞态根治
目的:切歌单时旧歌单晚到的歌词 XHR 不再覆盖新歌词。做完这步音乐馆歌词不串台。
操作: utils.js:1558-1589——对比「期望歌词」与「实际 DOM 歌词」,不一致则清缓存强制重载:
1 | // 歌词竞态修复:旧歌单的异步歌词 XHR 晚到仍会覆盖新歌词(APlayer 只比对索引不比对歌曲), |
预期输出: 音乐馆快速切换歌单后,歌词始终是当前歌曲的;旧 XHR 晚到也不覆盖。
⚠️ 报错排查:对比串用
join("|")拼整页歌词,避免逐行比对性能问题;两次延时检查覆盖不同竞态窗口,别只留一次。
Step 5:歌词样式与可读性
目的:大字歌词居中 + 渐隐遮罩 + 深浅色可读。做完这步视觉达标。
操作要点(music.styl + fix/aplayer.css):
- 音乐馆大字歌词 + mask 渐隐:
.aplayer-lrcheight 800%、mask-image渐变、当前行高亮 - 当前行对齐第 3 行:
#anMusic-page .aplayer.aplayer-withlrc .aplayer-lrc-contents { margin-top: 80px; } - 左下角歌词块宽度自适应歌词长度(最长 420px)
- 播放中金黄底 → 歌词当前行深色字
#18171d;浅色模式白底 → 深色文字([data-theme="light"]特例覆盖)
预期输出: 两种主题模式下歌词都清晰可读,不出现「黄底黄字」「白底白字」。
⚠️ 报错排查:金黄底(
--anzhiyu-main)上的文字必须深色#18171d,默认白字对比度极差(本系列实测过aplayer.css默认规则白字 + 金黄底不可读);浅色模式要单独覆盖金黄规则。
检查点:验证整体效果
全部勾选通过 = 歌词功能达标。
- 有 URL 歌词的歌逐行跟唱、无错位
- 无歌词的歌显示「暂无歌词」而非乱码/空
- 快速切歌单 10 次,歌词始终是当前歌曲
- 深浅色两模式歌词均可读
- 音乐馆歌词当前行在第 3 行(margin-top 80px,可用
tools/edge-verify/verify-music-lrc.js断言)
常见问题(FAQ)
| 问题 | 原因 | 解决 |
|---|---|---|
| 一直显示暂无歌词 | 歌单没有 lrc/lyric 字段 |
歌单数据补 URL 歌词字段 |
| 歌词慢半拍 | 用 APlayer lrcType 3 | 自绘 + timeupdate |
| 切歌后歌词串台 | 旧 XHR 晚到覆盖 | 期望 vs 实际对比 + 清缓存重载 |
| 黄底黄字看不清 | 金黄底默认白字 | 金黄底强制深字 #18171d |
| 歌词滚动行不对齐 | 行距与偏移不配套 | 行高与 translateY 基准同步改 |
总结
歌词问题的根因是「APlayer 异步歌词机制不可靠」,解法是绕开它自绘。核心要点:
- 解析统一:
window.__parseLrc全站共用,支持多标签/毫秒/排序 - 同步自绘:timeupdate 事件驱动,精准跟唱
- 竞态对比:用「期望 vs 实际」内容对比根治串台
- 可读性优先:深浅色两套覆盖,金黄底必深字
延伸阅读:











