用 Electron 把 B 站变成音乐播放器:Neko Music 的架构设计与性能优化实战

举报
yd_252634773 发表于 2026/11/01 23:44:14 2026/11/01
【摘要】 用 Electron 把 B 站变成音乐播放器:Neko Music 的架构设计与性能优化实战 一、缘起:为什么又要造一个播放器日常写代码的时候总想放点歌,但云音乐客户端的推荐越来越吵,本地曲库又要自己维护。而 B 站上其实沉淀了大量高质量的音乐内容——翻唱、纯音乐、OST、Lo-fi 合集,音质不差,还能直接按关键词搜。于是就动手做了 Neko Music:一个把 B 站当作曲库的桌面音...

用 Electron 把 B 站变成音乐播放器:Neko Music 的架构设计与性能优化实战

一、缘起:为什么又要造一个播放器

日常写代码的时候总想放点歌,但云音乐客户端的推荐越来越吵,本地曲库又要自己维护。而 B 站上其实沉淀了大量高质量的音乐内容——翻唱、纯音乐、OST、Lo-fi 合集,音质不差,还能直接按关键词搜。

于是就动手做了 Neko Music:一个把 B 站当作曲库的桌面音乐播放器。粘贴链接就能听,输入关键词就能搜,歌单、收藏夹、歌词、动态背景都有。技术上它并不复杂,但过程中踩的几个坑挺典型,尤其是在 Electron 里做视频背景的性能优化,值得单独写一写。

二、技术选型

层面 选择 理由
外壳 Electron 一套代码出 Windows 安装包,桌面端能力齐全
渲染 原生 HTML/CSS/JS 界面不复杂,上框架反而增加心智负担
打包 electron-builder 一键生成 NSIS 安装包,产物约 82 MB
音频 HTML5 Audio + 网络流 直接播放 B 站公开视频的音频流

没有用 React 或 Vue 是一个刻意的决定。这个应用的状态量不大——当前播放、播放列表、收藏、设置,用一个集中的状态对象加事件订阅就够管了,引入框架的收益不明显。

三、核心功能是怎么串起来的

整个应用围绕一条主线:拿到音频流地址 → 交给 Audio 元素 → 维护播放状态。

功能上分四块:

  1. 链接听歌:粘贴 B 站视频链接,解析出 BV 号,换取音频流地址直接播放。
  2. 关键词搜索:调用搜索接口,结果列表支持直接播放或加入歌单。
  3. 本地歌单:用户自建的播放列表,持久化到本地存档。
  4. 收藏夹导入:把自己 B 站收藏夹里的视频批量导入成歌单。

歌词做的是滚动高亮,跟着 timeupdate 事件推进。动态背景则是把用户选的视频或图片铺在底层,面板用毛玻璃盖在上面——视觉上好看,但也正是性能问题的发源地。

四、性能实战:动态背景把 GPU 吃到 60%

动态背景上线后收到反馈:笔记本风扇狂转,GPU 占用能到 60% 左右。我逐个排查,最后定位到四个独立的根因,它们叠加在一起。

根因 A:filter: blur() 每帧重模糊

最初的做法是给承载背景的 .bg-layer 加 filter: blur()。问题是视频每秒 30 帧,模糊滤镜就会每秒重算 30 次,每一次都是对整块区域的卷积运算,GPU 直接被打满。

对策:把模糊只留给静态图片用的 .bg-image——图片模糊一次就能缓存住;视频本身不加模糊,改用半透明遮罩层来降低存在感。视觉差异很小,开销天差地别。

根因 B:常驻面板的 backdrop-filter

四块常驻面板都用了 backdrop-filter(毛玻璃)。这个属性的代价在于它需要实时采样并模糊背景,只要背景在动(视频背景),面板每一帧都得重算。

对策:新增「省显卡模式」body.perf-lite,默认开启。开启时把这四块面板的 backdrop-filter 直接设为 none,再用 --glass-boost 变量把底色抬高一点,靠半透明色块维持观感。

这里有个细节值得记:不能写成 blur(0px),因为那仍然会触发背景拷贝,等于没优化。

根因 C:视频分辨率过高

用户可能选一个 4K 视频当背景,解码开销远超播放器本身的需求。

对策:用 probeVideoSize() 读取视频元数据里的分辨率,超过 1920 宽就给出提示,引导用户换个更轻的来源。

根因 D:失焦、最小化、被遮挡时仍在播

这个最隐蔽。窗口最小化了,视频背景还在解码,纯属浪费。

对策:把所有播放控制收敛到一个总闸 updateBgVideoPlayback(),统一判断「该不该播」。触发条件包括窗口失焦、最小化、以及被四块遮罩遮挡的情况——后者用 MutationObserver 监听遮罩的显示状态。

附带的坑:<video> 不支持 inset: 0 撑满

这个问题排查了很久。给 <video> 写:

#bgVideo {
  position: absolute;
  inset: 0;
}

理论上应该铺满父容器,实际上只占了左上角。

根因是 <video> 属于替换元素(replaced element),浏览器会优先使用它自己的固有分辨率来布局,right 和 bottom 在这种情况下被忽略,只有 left / top 生效。

正确写法是显式给尺寸:

#bgVideo {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

改完之后做了离屏实测,在 1230×810、848×648、1548×948 三种窗口尺寸下,#bgVideo 与 .bg-layer 的盒子误差都是 0px。

五、质量保障:206 条断言的自检体系

一个人写、一个人维护的项目,最怕改 A 坏 B。所以给仓库配了一套自检:

  • verify.js:41 类、206 条断言,覆盖存档结构、状态机、边界条件;
  • 8 套离屏实测脚本,把界面在无头环境里渲染出来逐项比对;
  • 每轮改动跑全量回归,要求 0 条 FAIL 才算通过。

这套东西在动态背景那轮改动里救了我两次——第一次是改名(「视频背景」→「动态背景」)后有断言扫出残留的旧文案,第二次是 body.perf-lite 加入后存档缺少默认值,被存档断言当场拦下。

另外要诚实标注的是:沙箱里验证不了真实 GPU 占用百分比(纯软件渲染环境),也验证不了视频解码出画(离线没有流)。这两项在报告里都写明了「未验证」,而不是含糊过去。

六、几个工程上的思考

1. 性能优化要先做归因,不要先做动作。 「降 GPU 占用」这句话没有可执行性,把它拆成「模糊重算、背景采样、解码分辨率、无效播放」四个具体原因之后,每条对策才有落点。

2. 默认值是产品决策。 省显卡模式默认开启,意味着默认牺牲一点视觉换安静的风扇。这个取舍要明确说出来,而不是藏在设置里。

3. 自检脚本是给未来的自己写的。 隔两周回来看代码,能跑一遍断言就知道有没有坏,比读代码快得多。

4. 边界要说清楚。 这个项目调用的是 B 站公开视频的音频流,仅供个人学习与试听;不内置账号、不绕过任何限制,音频版权归平台与 UP 主所有。技术上能做到什么和应该做什么,是两件事。

七、下一步

目前想到的方向:全局快捷键、音频可视化、以及把渲染层从纯 DOM 换成更细粒度的增量更新。不过在那之前,先把动态背景在低端显卡上的表现再测一轮——毕竟一个音乐播放器,安静运行比好看更重要。

项目地址:https://github.com/xingkinglb/neko-music

技术栈是 Electron + 原生 JS,如果你也在做类似的桌面工具,欢迎交流。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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