暮色太阳花の宝藏之地
首页项目归档照片墙灵境关于
封面

从播放器到音频终端:音乐页这一轮返工记录

写作时间:2026-10-27 02:55:00
# 开发日志
# Next.js
# 音乐播放器
# 音频处理
# 字体优化
# 开源

从播放器到音频终端:音乐页这一轮返工记录

本来只是想给音乐页换一个“更像 Rhine 的样子”,结果一路修到了字体、导航栏、音量弹窗、FLAC 转码、歌词标签和 LRC 匹配。

这篇文章记录今天实际做了什么、踩了哪些坑,以及为什么“看起来像”和“动起来像”是两件完全不同的事。


一、起因:只换配色是不够的

最开始,音乐页已经能从管理员后台上传本地音频,也有一套播放器 UI。

后来参考了 Rhine Music Database 的视觉风格,把页面改成了黑金/纸张橄榄色的“音频终端”:曲目表、编号、歌词面板、播放队列、底部播放条。

刚改完时,静态截图看着还行,但很快发现几个问题:

  • 切页面时字体会“弹”一下
  • 点导航后导航项会整体跳位
  • 音量弹窗鼠标一移动就消失
  • 上传 FLAC 后歌词抓不到
  • 原版那种“唱片墙”的动态感完全没有

于是今天基本就在做一件事:把“看起来像”往“用起来也对”推进。


二、音乐页现在是什么样

现在 /music 是一个只播放本站后台上传音频的本地音频终端:

  • 曲目 / 歌手 / 专辑三种索引
  • 播放队列、顺序 / 单曲 / 随机
  • 同步歌词、点击歌词跳转
  • 音量、静音、进度拖动
  • 系统媒体控制
  • 切到其他页面音频不中断
  • 首页的小播放器和全站悬浮播放器共享播放状态

公开的网易云播放接口已经移除,前台不会再去请求第三方曲库。
现在播放源只有一处:data/music.json + data/music/。


三、字体:从“分片加载”到自托管

现象

从首页切到项目、归档等页面时,文字会突然变一下,像是字体“弹”了一下。

原因

原来用的是 Google Fonts 版 Noto Serif SC。中文字体会被拆成很多个 unicode-range 分片,只有页面用到某些字时才加载对应的分片。

于是切换页面时:

  1. 新页面出现新的汉字
  2. 浏览器请求新的字体分片
  3. 文字从 fallback 字体切成 Noto Serif SC
  4. 视觉上就是“字体弹了一下”

处理

改成自托管单文件:

NotoSerifSC-400.woff2
NotoSerifSC-700.woff2
NotoSerifSC-900.woff2

三个字重合计约 6.1MB,首次加载后全站缓存。切换页面不再加载新的字体分片。

同时把页面切换动画从“上移 20px + 0.8 秒渐入”改成了 0.35 秒纯淡入,减少整页“弹一下”的感觉。

代价是首屏字体体积变大,但换来稳定的字体渲染。算是一次明确的取舍。


四、导航栏:不是动画问题,是每一页都在重新挂载

现象

点击导航后,导航项会突然缩一下,整体位置跳变;手机端圆盘也会重新分布。

原因

Navbar 之前在每个页面里单独挂载。每次切页:

  1. 新页面重新创建一个 Navbar
  2. 重新请求 /api/nav
  3. 接口返回前先渲染全部 10 个导航项
  4. 返回后隐藏空的「说说 / 杂谈 / 友链」
  5. 导航项从 10 个缩成 7 个,整体跳位

处理

  • 把 Navbar 提升到根布局,全站只挂载一次
  • 删除 12 个页面里重复的 <Navbar />
  • 把 /api/nav 的结果缓存到 sessionStorage
  • 首屏默认按当前已启用板块渲染,避免 10 → 7
  • 路由变化时关闭手机圆盘、复位拖拽球和旋转角度
  • /admin 自动隐藏站点导航

在 /api/nav 人为延迟 1.5 秒的情况下,切页前后导航项数量和位置都保持一致。


五、音量弹窗:8px 的空隙

音量弹窗原来用 onMouseLeave 关闭,判断区域只有音量按钮本身。弹窗在按钮上方,两者之间有大约 8px 的空隙。

鼠标从按钮移向弹窗时,会先穿过这段空隙,mouseleave 立刻触发,弹窗就消失了,根本来不及调音量。

现在改成:

  • 点击弹窗/按钮外部关闭
  • 再点一次音量按钮关闭
  • 按 Esc 关闭
  • 点击、拖动滑块不会误关

不再依赖鼠标离开判定,空隙也就不再是问题。


六、音频上传:批量 + 自动标签

后台的「音乐」页现在是批量上传:

  • 一次可选多个音频
  • 逐个上传,避免单请求体积过大
  • 上传后自动读取内嵌标签:
    • 标题
    • 歌手
    • 专辑
    • 年份
    • 时长
    • 内嵌封面
    • 内嵌歌词
  • 没有标签时用文件名推断
  • 内嵌封面保存到本地,通过自己的封面接口提供
  • 删除曲目时同步删除本地音频和封面

单个文件上限 150MB,Nginx 请求体上限调到 160MB。


七、FLAC 太大:后台自动转 MP3 320

上传 FLAC 后,后台会用 ffmpeg 自动转成 MP3:

  • 默认 320kbps CBR
  • 双声道
  • 超过 48kHz 时降到 48kHz
  • 转码前先读取标签、封面、歌词
  • 转码成功后默认删除原始 FLAC

实测:

36MB FLAC → 7.06MB MP3,约 2.8 秒
71MB WAV  → 16.8MB MP3,约 4.4 秒

为什么用 MP3 而不是 AAC?

  • AAC 320 与 MP3 320 体积基本相同
  • 测试发现无头 Chromium 不带 AAC 解码器
  • MP3 兼容所有主流浏览器

320kbps MP3 对个人日常聆听已经足够接近透明。如果需要保留母带,可以设置环境变量保留原始文件,但会继续占用空间。


八、歌词:为什么 FLAC 的 LYRICS 抓不到

这是今天最意外的坑。

旧代码只读:

metadata.common.lyrics[0].text

但实际测试发现:

  1. FLAC 的 LYRICS 纯文本会被 music-metadata 解析成空的 syncText,原文丢失
  2. UNSYNCEDLYRICS、UNSYNCED LYRICS、LYRIC 这些标签根本不会进入 common.lyrics

新逻辑改成优先读 native 原始标签:

  • FLAC / Vorbis:LYRICS、UNSYNCEDLYRICS、UNSYNCED LYRICS、UNSYNCED_LYRICS、LYRIC
  • MP3 ID3:USLT、ULT、SYLT、TXXX:LYRICS
  • M4A:©lyr
  • APE:Lyrics

然后再读 common.lyrics,保留 LRC 时间轴,纯文本歌词也会保留并在页面上显示成 TXT / 纯文本歌词,不再被丢掉。


九、LRC 批量匹配:按歌名补歌词

后台新增了「批量匹配歌词」:

  • 可一次选择多个 .lrc / .txt
  • 支持 UTF-8、UTF-8 BOM、GBK、UTF-16
  • 文件名匹配:
    • 歌名.lrc
    • 歌手 - 歌名.lrc
    • 01. 歌名.lrc
  • 先按完整「歌名 - 歌手」匹配
  • 再按纯歌名匹配
  • 最后做唯一的包含匹配
  • 多歌手分隔符 /、,、&、、、+ 会统一处理
  • 匹配成功写入曲库,列表显示「歌词已匹配」
  • 匹配不到会提示先上传音频,或把 LRC 重命名为歌名

有一点很重要:

LRC 只给已上传的曲目补歌词,不会根据歌词文件创建新歌。

所以如果提示「没有找到同名歌曲」,通常不是文件坏了,而是对应音频还没上传,或者曲库里的歌名和文件名不一致。


十、网易云接口:只给管理员补元数据

保留了网易云接口,但只放在管理员后台:

  • 需要管理员登录
  • 只返回标题、歌手、专辑、年份、封面、时长
  • 不返回音频播放地址
  • 不对访客开放
  • 前台公开的网易云播放路由已删除

后台编辑曲目时可以搜索并补全元数据。补全的封面会在保存时自动下载到本地,不会让前台直接引用网易云外链。


十一、来源声明

在关于页底部新增了「音乐页来源与音频版权声明」:

  • 视觉与交互参考 Rhine Music Database(MIT License)
  • 未直接复制其源代码或美术素材
  • 前台不接入第三方曲库播放
  • 全部曲目由站长后台上传
  • 管理员后台仅使用网易云公开接口查询元数据
  • 音频/封面/歌词版权归各自权利人

直达链接:

/about#music-credit

十二、还没做完:原版的动态效果

这一点必须承认:

现在的 /music 复刻了配色、排版和一部分播放交互,但没有复刻原版的 3D 录音带/CD 唱片盒墙。

原版的动态效果不是 CSS 斜纹,也不是粒子或音频频谱,而是:

  • 5 条纵深排列、互相重叠的 CD 唱片盒墙
  • 约 3° 的超长焦镜头 + 移轴投影
  • 距离雾化 + 纸张感
  • 选中唱片盒抬升、相机推进
  • 光盘 20 秒一圈旋转
  • 悬停抬升、拖拽惯性、吸附、涟漪、主题波

它没有 bloom、没有粒子、没有音频频谱。
“高级感”来自 3D 空间、镜头和弹簧,而不是堆特效。

今天已经把可行性、架构、源码复用方式、性能预算和工期写成了技术报告。下一步先做静态 3D 骨架验证:

160 个简化唱片盒实例
+ 3° 长焦相机
+ 移轴
+ 雾
+ 右侧 300px 渐变遮罩

先看一张截图是否已经“像原版”,再决定是否投入完整的选中卡片、光盘和拖拽交互。


十三、今天的状态

  • 生产服务已重新构建并重启
  • 音乐页、后台、关于页正常
  • 曲库中的 10 首曲目都保留着
  • 音频已压缩为 MP3
  • 原有曲目的歌词需要补 .lrc,或重新上传带歌词的原始 FLAC
  • 字体、导航、音量弹窗问题已修复
  • 3D 动态效果还没开始实现,技术报告已完成

十四、明天

明天优先做音乐页的 3D 动态骨架,不再继续在 CSS 背景上加纹理。

如果这一步验证通过,再继续做:

  • 选中唱片盒
  • 相机推进
  • 光盘旋转
  • 拖拽与惯性
  • 主题波和涟漪
  • 移动端降级

希望下一次更新的时候,音乐页不只是“像 Rhine”,而是真的能在里面拖动那面唱片墙。


本文由暮色太阳花整理。音乐页视觉参考 Rhine Music Database,相关来源与版权说明见 关于页。

avatar

涂涂小弟

暮色太阳花的开发日志与日常分享 —— 涂涂小弟(t_yh) 的个人站,记录代码、生活与那些发光的小事。

RECOMMENDED

《卫戍协议:狩猎模拟》V0.0.4 发布——事件期加了一格 boss,狩猎轮改成 7 类抽 4 类

2026-10-21 23:54:54

《卫戍协议:狩猎模拟》V0.0.3 发布——2V2 机变改成「双方 4 选 2」,还有一批手机端体验修复

2026-10-18 19:00:00

《卫戍协议:狩猎模拟》V0.0.2 玩法介绍——从建房到终局,一局到底怎么打

2026-10-18 16:00:00

Table of Contents

© 2026 暮色太阳花的开发日志与日常分享 · 涂涂小弟

蜀ICP备2026066824号

联系·版权与免责·隐私政策