我做了一个本地音乐播放器,致敬小时候的千千静听:技术、UI 与情怀实录

举报
yd_294377644 发表于 2026/10/21 22:08:46 2026/10/21
【摘要】 我电脑里躺着几千首歌,每首还配着 `.lrc` 歌词和封面图。可市面上的播放器要么花里胡哨、要么强推在线音乐、要么 UI 我不喜欢。于是我干脆自己写了一个**纯本地、能读歌词、能显封面、UI 全自定义**的音乐播放器——像小时候的千千静听那样,打开就能放,安静又听话。这篇博客记录从需求到能用的全过程:技术选型、歌词同步、ID3 标签解析以及那些"自己写一遍才知道"的细节。

目录

  1. 缘起:为什么我不想要任何在线音乐软件
  2. 目标拆解:本地播放器到底要做什么
  3. 技术选型:浏览器够用,不必上 Electron
  4. 核心实现一:扫描音乐库与 ID3 标签
  5. 核心实现二:.lrc 歌词逐行同步
  6. UI 定制:本地播放器最大的快乐
  7. 三个让我卡了一阵的坑
  8. 结尾:本地软件的"安静"是一种奢侈

1. 缘起:为什么我不想要任何在线音乐软件

我有几千首歌躺在硬盘里,每首基本都配好了 .lrc 歌词文件和封面图。这是多年攒下来的"音乐资产"。

但我打开任何一款主流播放器,体验都让我皱眉:

  • 启动先弹广告/推荐位,我要找的"播放本地音乐"入口藏得老深;
  • 强推在线曲库、登录账号、VIP 提示,每次都要点"跳过";
  • UI 风格统一是统一了,但不是我喜欢的样子——我想让封面占满半屏,它偏要列表占大头;
  • 隐私顾虑:我的本地文件路径、播放习惯,谁知道有没有被上报。

我怀念小时候的千千静听——打开就能放,安静又听话。既然现在的播放器都做不到,那就自己做一个。

2. 目标拆解:本地播放器到底要做什么

和写任何工具一样,我先把"做一个播放器"拆成可验收清单:

诉求(人话) 拆解后(可验收)
能放歌 选择文件夹 → 列出所有音频文件 → 点击播放
显示信息 解析 ID3 标签:标题、艺术家、专辑、封面图
显示歌词 解析同名 .lrc 文件,按时间戳逐行高亮、自动滚动
顺序/随机/循环 三种播放模式可切换,列表结束有明确行为
进度可控 拖动进度条、上一首/下一首、音量调节
UI 我说了算 整体视觉风格、封面尺寸、列表布局可自定义

这张表决定了播放器的骨架。功能不多,但每一项都做到位——这是我对所有"自己用的工具"的一贯要求。

3. 技术选型:浏览器够用,不必上 Electron

一开始我犹豫过要不要上 Electron,但仔细想需求——我不需要系统级文件监听、不需要托盘、不需要原生解码,浏览器的能力完全够用:

能力 浏览器方案
选文件夹 <input type="file" webkitdirectory>
读音频 URL.createObjectURL(file) + <audio>
读 ID3 标签 jsmediatags 库
解析 .lrc 自己写一个 30 行的解析器
播放控制 原生 HTMLAudioElement API

整个播放器就是一个 HTML 文件,双击就能开。没有安装包、没有更新提示、没有后台进程——这本身就是对"千千静听精神"的一种继承。

心得:选型时先问"我到底需要什么",而不是"什么技术更高级"。本地小工具的最高境界是双击即用、关掉即走。

4. 核心实现一:扫描音乐库与 ID3 标签

选完文件夹后,第一步是把音频文件筛出来,并读取每首歌的元数据:

const audioFiles = [...files].filter(f =>
    /\.(mp3|flac|wav|m4a|ogg)$/i.test(f.name)
);

for (const file of audioFiles) {
    const tags = await readTags(file);  // 用 jsmediatags 解析
    tracks.push({
        file,
        title:  tags.title  || file.name.replace(/\.\w+$/, ''),
        artist: tags.artist || '未知艺术家',
        album:  tags.album  || '未知专辑',
        cover:  tags.picture ? blobFromTag(tags.picture) : null,
    });
}

几个细节值得说:

  1. ID3 标签不一定准。很多老歌的标签是空的或者乱码,所以文件名永远作为兜底,不能完全依赖标签;
  2. 封面图从 ID3 里取。jsmediatags 给的是二进制数据,要转成 Blob 再 URL.createObjectURL 才能给 <img> 用;
  3. 没有封面时给占位图。统一视觉的前提是"无封面也有样子",不要让某首歌塌成空白。

5. 核心实现二:.lrc 歌词逐行同步

这是整个播放器我最喜欢的部分。.lrc 文件长这样:

[ti:歌曲名]
[ar:歌手]
[00:12.50]第一行歌词
[00:18.20]第二行歌词
[00:24.80]第三行歌词

解析器其实非常短:

function parseLrc(text) {
    return text.split('\n')
        .map(line => {
            const m = line.match(/\[(\d+):(\d+\.\d+)\](.*)/);
            if (!m) return null;
            return { time: +m[1] * 60 + +m[2], text: m[3].trim() };
        })
        .filter(Boolean)
        .sort((a, b) => a.time - b.time);
}

同步显示的逻辑:监听 audio.timeupdate,找到当前时间对应的最后一行,高亮它并滚动到视图中央。

audio.addEventListener('timeupdate', () => {
    const t = audio.currentTime;
    const idx = lrc.findIndex((line, i) =>
        line.time <= t && (i === lrc.length - 1 || lrc[i + 1].time > t)
    );
    highlightLine(idx);
    scrollToLine(idx);
});

心得:歌词同步的关键不是"解析",而是"滚动得舒服"——用 scrollIntoView({ behavior: 'smooth', block: 'center' }) 让当前行缓缓滑到中间,比硬跳转观感好十倍。这种细节,是本地播放器才愿意花心思打磨的地方。

6. UI 定制:本地播放器最大的快乐

本地播放器最大的特权,是 UI 完全我说了算。我给自己定了几个商业播放器不会这么做的设计选择:

  • 封面占左半屏,满铺。商业播放器为了塞推荐位不敢这么干,我没有这个包袱;
  • 歌词占右半屏,逐行渐变高亮。当前行最亮,前后两行半亮,其余暗淡——像聚光灯打在舞台上;
  • 列表是抽屉,不是主界面。想切歌时滑出来,平时隐去,让封面和歌词独占视野;
  • 配色随封面变化。用 Canvas 提取封面主色,动态调整背景渐变——每张专辑都有了自己的氛围。

这些设计在商业产品里几乎不可能出现——它们太"个人化"了。但本地播放器的意义,恰恰就是让软件贴合你的审美,而不是让你迁就软件的审美。

7. 三个让我卡了一阵的坑

坑一:<audio> 跨格式支持差异。 Chrome 不支持 FLAC?实际上新版 Chrome 已经支持,但某些 .m4a 编码仍会失败。我的处理是播放失败时降级提示,并在扫描阶段标注"可能不兼容"的格式,不让用户点了没反应才报错。

坑二:.lrc 时间戳精度不一。 有的歌词文件是 [00:12.50](两位小数),有的是 [00:12.500](三位),还有的是 [00:12](没小数)。正则要写成 \d+\.\d+ 兼容小数位不固定的情况,否则部分歌词行解析不出来。

坑三:大音乐库的卡顿。 几千首歌一次性读 ID3 标签会卡死界面。改成惰性加载——列表先显示文件名,标签在滚动到可视区域时再读。这个优化让几千首歌的库也丝滑。

8. 结尾:本地软件的"安静"是一种奢侈

做完这个播放器,我对"本地软件"有了一种新的理解:

  1. 它不抢你注意力。没有推荐位、没有红点、没有推送,打开就是为了放歌;
  2. 它尊重你的数据。文件、歌词、封面都在你的硬盘里,软件只是"播放它们",不是"管理它们";
  3. 它尊重你的审美。UI 是你自己的,不是产品经理 A/B 测出来的。

在这个所有软件都在抢用户时长的时代,一个"打开就做事、关掉就消失"的本地工具,反而成了一种奢侈。

如果你也有一堆本地音乐、也怀念千千静听那种安静,我强烈建议你自己写一个。技术门槛比想象中低,一个 HTML 文件就够;真正难的是克制住加功能的冲动——不做推荐、不做社交、不做"发现",就做一个老老实实的播放器。

工具小而静,价值往往不在多,而在"它永远在那里,永远不打扰你"。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。