客户端的每一个请求都不可信任,必须假定是恶意请求。
一个网页版个人音乐播放器:
music_ice
线上地址:
https://yuqianglianshou.com/music_ice/
运行界面
桌面端:
移动端:
它不是一个音乐平台,也不是一个要接入账号、会员、推荐算法的产品。
它更像是一个很小的私人播放器:
把自己喜欢的歌整理好,
把歌词、封面、分类放进项目里,
然后用一个网页打开它。
有时候我越来越觉得,工具不一定要复杂。
真正能长期使用的东西,往往是边界很清楚的东西。
一、这个项目解决什么问题
我最初想要的东西很简单:
- 有一个干净的网页播放器。
- 能按分类管理歌曲。
- 能展示封面、歌词、播放进度、音量、播放模式。
- 能适配桌面端和移动端。
- 添加歌曲不要太痛苦。
- 不依赖后端服务,静态部署即可访问。
所以 music_ice 的定位很明确:
一个可以部署在 GitHub Pages 或任意静态服务器上的个人音乐播放器。
它的数据不在数据库里,而是在项目文件里。
歌曲文件、歌词文件、封面文件放在 media/ 目录;
列表缩略图放在 assets/covers/music-thumbs/;
歌单数据按分类拆分在 catalog/;
页面逻辑集中在 app/。
整个项目的结构大概是这样:
music_ice
├── index.html
├── app/
│ ├── app.js
│ ├── config.js
│ ├── lyrics-manager.js
│ ├── music-store.js
│ ├── song-importer.js
│ ├── theme-manager.js
│ └── utils.js
├── catalog/
│ ├── index.js
│ ├── qingyinyue.js
│ ├── shangganzhiyu.js
│ ├── liuxingjingdian.js
│ └── ...
├── assets/
├── css/
├── media/
├── scripts/
└── tools/
└── import-song.html
这个结构的好处是直观。
播放器是播放器,数据是数据,资源是资源,导入工具是导入工具。
以后想加歌,主要就是维护 media/ 和 catalog/。
二、怎么使用
最简单的使用方式是直接打开线上地址:
https://yuqianglianshou.com/music_ice/
进入页面后,可以做这些事情:
- 切换不同分类的歌单。
- 点击歌曲播放。
- 切换上一首、下一首。
- 调整音量。
- 切换播放模式:列表循环、随机播放、单曲循环。
- 查看歌词同步滚动。
- 双击歌词跳转到对应播放时间。
- 在移动端打开播放面板,像一个小型音乐 App 一样使用。
如果只是听歌,到这里就够了。
如果要维护自己的歌单,就需要看项目内部的数据组织。
三、歌曲数据是怎么组织的
catalog/ 目录按分类拆分歌单。
比如:
catalog/qingyinyue.js 轻音乐
catalog/shangganzhiyu.js 伤感治愈
catalog/liuxingjingdian.js 流行经典
catalog/shishipeiyue.js 史诗配乐
catalog/jiezoulvdong.js 节奏律动
catalog/dongmanyingshi.js 动漫影视
catalog/huaijiujinqv.js 怀旧金曲
catalog/minyao.js 民谣
然后由 catalog/index.js 统一导出。
播放器启动时,app/music-store.js 会把这些分类数据汇总起来,并提供几个能力:
- 获取全部歌曲。
- 按分类获取歌曲。
- 搜索歌曲。
- 获取随机歌曲。
- 校验歌曲数据是否合法。
- 把歌单同步到
localStorage缓存。
这里有一个比较实用的小设计:
项目不是手动维护一个固定版本号,而是根据歌单内容生成签名。
当 catalog/ 里的歌曲内容发生变化时,签名就会变化,播放器会自动刷新本地缓存。
这解决了一个静态站点常见的问题:
页面部署后,用户浏览器里可能还留着旧数据。
如果没有缓存刷新机制,明明已经加了新歌,用户打开页面却还是旧歌单,就会很困惑。
四、播放逻辑
播放器核心在 app/app.js。
它没有引入复杂框架,而是直接使用浏览器原生的 Audio 对象。
核心状态大概包括:
currentMusicIndex; // 当前歌曲索引
playMode; // 播放模式
isPlaying; // 是否正在播放
currentMusicList; // 当前分类歌单
displayMusicList; // 当前展示歌单
当选择一首歌时,播放器会根据歌曲数据拼出音频路径:
./media/分类目录/歌曲文件
然后更新 audio.src。
播放过程里监听这些事件:
loadedmetadata:拿到歌曲总时长。durationchange:更新进度条范围。timeupdate:更新当前播放时间和歌词。ended:根据播放模式决定下一首。error:展示播放错误。
这套逻辑很朴素,但足够稳定。
浏览器已经提供了成熟的音频能力,项目要做的事情主要是:
把状态维护清楚,
把 UI 更新清楚,
把各种边界处理好。
五、歌词系统
歌词管理在 app/lyrics-manager.js。
它负责四件事:
- 加载歌词文件。
- 解析 LRC 时间轴。
- 渲染歌词 DOM。
- 根据播放进度滚动和高亮当前行。
歌词文件支持类似下面的时间格式:
[00:29.299]歌词内容
[00:15:50]歌词内容
解析之后,会得到两个数组:
timerArray 歌词时间点
currentLyrics 歌词文本
播放时,根据当前 currentTime 找到应该高亮的歌词行。
这里用了一个小优化:
不是每次从头到尾扫描歌词,而是用二分查找定位当前行。
歌词短的时候看不出差异,
但这个思路是对的。
因为播放器这类页面会频繁触发 timeupdate,任何可以减少重复 DOM 查询和重复计算的地方,都值得处理一下。
另外,歌词渲染时也做了 HTML 转义。
这点很重要。
因为歌词文件虽然看起来是“自己的文件”,但只要内容最终进入 DOM,就不能默认它安全。
所以文章开头那句话,在这里也成立:
客户端的每一个请求都不可信任,必须假定是恶意请求。
更准确地说:
不仅请求不可信,输入文件、歌词内容、歌单字段,也都不应该被无脑塞进页面。
六、本地导入工具
这个项目里我比较喜欢的一部分,是 tools/import-song.html。
它是一个本地导入工具。
打开方式:
tools/import-song.html
不过要注意,它需要浏览器的 File System Access API,所以最好通过本地 localhost 打开。
使用流程:
- 选择
music_ice项目根目录。 - 选择歌曲分类。
- 选择歌曲文件。
- 选择歌词文件,可选。
- 选择封面文件,可选。
- 填写歌曲名、作者、时长、描述。
- 点击“添加歌曲”。
工具会自动完成这些事情:
- 把歌曲写入
media/对应分类/。 - 把歌词写入
media/对应分类/。 - 把封面写入
media/对应分类/。 - 生成列表缩略图到
assets/covers/music-thumbs/。 - 把歌曲条目追加到对应的
catalog/*.js。 - 写入后再校验文件和歌单条目。
- 通过
BroadcastChannel或localStorage通知播放器刷新歌单。
这解决了手动维护静态歌单时最麻烦的问题。
以前加一首歌,要自己复制文件、改路径、写对象、处理封面缩略图,很容易写错。
现在导入工具把这些动作收束成一个表单。
这就是小工具的价值:
不是为了炫技,
而是减少重复劳动,减少出错机会。
七、封面与缩略图
播放器有两类封面:
- 默认封面。
- 歌曲自己的封面。
如果歌曲没有封面,导入工具会使用默认封面。
如果歌曲有封面,导入工具会生成列表缩略图。
项目里还提供了脚本:
node scripts/generate-thumbnails.mjs
这个脚本会读取 catalog/*.js 中的 img_file 字段,自动生成缺失或过期的缩略图。
为什么要单独做缩略图?
因为列表里不应该直接加载大图。
音乐播放器的列表会出现很多歌曲,如果每一项都加载原始封面,会影响首屏速度和滚动性能。
大图留给播放详情,
小图服务于列表展示。
这是很普通但很有效的性能优化。
八、这个项目适合怎么部署
因为它是纯前端项目,所以部署很简单。
可以放在:
- GitHub Pages。
- 自己的静态服务器。
- Nginx 静态目录。
- 任意支持静态文件托管的平台。
部署时需要注意两点:
第一,资源路径要保持稳定。
项目默认使用相对路径,比如:
./media/
./assets/
./catalog/
所以部署到子目录时也能工作,比如:
/music_ice/
第二,音频文件通常比较大。
如果歌曲很多,仓库体积会迅速变大。
这也是这个项目目前最明显的边界:
它适合个人收藏,不适合做公开音乐平台。
九、技术上的优点
我觉得这个项目目前有几个地方是比较舒服的。
第一,边界清楚。
播放、歌词、主题、存储、导入工具都有独立文件,不是全部堆在一个脚本里。
第二,数据组织直观。
歌单按分类拆分,后期维护比一个超大数组更轻松。
第三,纯静态部署。
没有数据库、没有接口服务、没有登录系统,复杂度被控制住了。
第四,导入工具补齐了维护体验。
一个静态项目如果只能手改数据,时间久了就会变成负担。
导入工具让它更像一个可以长期使用的个人工具。
第五,歌词体验做得比较完整。
同步滚动、当前行高亮、双击跳转、滚轮临时暂停自动滚动,这些细节会让播放器更像“真正能用”的东西。
十、还可以继续优化什么
当然,它也还有很多可以继续做的地方。
- 增加播放历史。
- 增加收藏列表。
- 增加最近播放。
- 增加关键词搜索入口。
- 增加键盘快捷键说明。
- 增加 PWA,让它更像一个本地 App。
- 增加音频文件缺失检测。
- 增加 catalog 数据格式校验脚本。
- 增加更明确的版权和私有使用说明。
这里我最想强调的是版权问题。
这个项目更适合管理自己的本地音乐收藏。
如果公开部署并放入大量受版权保护的歌曲,就会有明显风险。
技术上能做,不代表现实中就应该随便做。
结语
music_ice 不是一个大项目。
它没有复杂架构,也没有宏大的产品设想。
但它很实用。
它把一个个人需求拆成了几个清楚的问题:
怎么播放?
怎么展示?
怎么分类?
怎么维护?
怎么部署?
然后分别解决掉。
我现在越来越喜欢这种项目。
它不一定惊艳,
但它真的能用。
一个能长期留在自己生活里的工具,
有时候比一个看起来很厉害、但很快就不会再打开的项目,更有意义。