1654 字
8 分钟
给 Fuwari 加一个网易云音乐播放器(不用后端)

侧边栏多了个播放器:

┌──────────────────────────┐
│ ▍音乐 │
│ ⬤ 曲名 │
│ 歌手 │
│ 0:00 / 4:45 🔊 ▬▬ │
│ ────────────────────── │
│ ⇄ ⏮ ▶ ⏭ ☰ │
└──────────────────────────┘

配色跟随你的主题色,自动适配明暗模式。

先说结论

不需要部署任何后端服务。

歌单在构建时抓取,音频由访客浏览器直接向网易云 CDN 取流——不占你的服务器带宽,也不多一个要维护的服务。

一、原理#

先说清为什么能不用后端,这决定了后面所有的限制。

传统做法(比如 Meting)要搭一个代理,是因为:

  • 网易云歌单接口没有 CORS 响应头,浏览器里的 fetch 会被拦
  • 所以要有个服务端帮忙转发

但有两个事实让代理变得没必要:

① <audio> 标签不受 CORS 限制。

网易云有个公开的外链接口:

https://music.163.com/song/media/outer/url?id=<歌曲ID>.mp3

访问它会 302 跳到真正的 CDN 地址。<audio src="..."> 加载它不需要任何跨域许可——HTML 媒体元素本来就允许加载跨域资源(只有用 Web Audio API 处理音频数据时才需要 CORS)。

而且它不校验 Referer,带不带都能跳转。

② 歌单接口可以在构建时抓。

浏览器不能直接调,但 Node 里没有跨域限制。构建的时候抓一次,把曲目列表直接烤进 HTML 就行了。

于是整个链路变成:

构建时(你的服务器)
fetch 歌单 → 曲目列表写进页面
↓
运行时(访客浏览器)
<audio src="music.163.com/song/media/outer/url?id=X.mp3">
直接向网易云 CDN 取流

代价是:往歌单加歌之后,得重新构建一次博客才会同步。做不到实时。如果非要实时,那就得回去搭后端了。

二、前置条件#

  • 一个 Fuwari 主题的博客(Astro 5 + Svelte 5)
  • @iconify/svelte 和 @iconify-json/material-symbols(Fuwari 自带,不用装)
  • 一个公开的网易云歌单

三、部署步骤#

第 1 步:拿到歌单 ID#

打开你的网易云歌单,分享链接长这样:

https://music.163.com/#/playlist?id=1234567890
^^^^^^^^^^

最后那串数字就是歌单 ID。歌单必须是公开的,否则接口拿不到。

第 2 步:放入两个文件#

在 src/components/widget/ 下新建两个文件:

src/components/widget/
├── MusicPlayer.astro # 构建期抓歌单 + 外层卡片
└── MusicPlayerClient.svelte # 客户端播放器

文件内容见仓库(文末有链接)。

第 3 步:改歌单 ID#

打开 MusicPlayer.astro,顶部有一行:

// 网易云歌单 ID(改成你自己的即可)
const PLAYLIST_ID = "你的歌单ID";

把 你的歌单ID 换成第 1 步拿到的那串数字。

第 4 步:挂进侧边栏#

打开 src/components/widget/SideBar.astro,导入组件:

import MusicPlayer from "./MusicPlayer.astro";

然后放到你想放的位置。位置就是数组里的顺序,比如放在「分类」和「标签」中间:

<Activity class="onload-animation" style="animation-delay: 120ms"></Activity>
<Categories class="onload-animation" style="animation-delay: 150ms"></Categories>
<MusicPlayer />
<Tag class="onload-animation" style="animation-delay: 200ms"></Tag>

第 5 步:本地构建测试#

Terminal window
pnpm build

构建日志里不会专门提这个组件。想确认歌单抓到了没有,构建完查一下产物:

Terminal window
grep -c "music.163.com/song/media/outer/url" dist/index.html

返回大于 0 就说明抓到了。

然后起个预览看看效果:

Terminal window
pnpm preview

打开 http://127.0.0.1:4321/,侧边栏应该就有播放器了。

图标可能会晚几秒才出现

@iconify/svelte 默认在运行时去 api.iconify.design 请求图标数据(实测要 3 秒左右)。所以首次打开时,控制栏那几个按钮会先显示成空白,几秒后图标才蹦出来。

之后有浏览器缓存就快了。想彻底消除的话,可以在构建期把这 11 个图标内联进页面(见「进一步优化」)。

第 6 步:部署#

跟平时一样构建部署就行,没有任何额外步骤。

如果你的部署是 git push 自动构建(比如用钩子/CI),那就更简单——推一次代码,服务器构建时会自动抓最新歌单。

四、日常使用#

加歌的流程:

网易云里收藏到歌单
↓
重新构建 + 部署(推送一次)
↓
博客上的歌单更新

记住那个限制:歌单是构建时抓的。光在网易云加歌不会自动生效。

五、几个实现细节#

如果你要改样式或者复用这套思路,这几处值得看一眼。

歌单展开的动画#

用 grid-template-rows: 0fr → 1fr 做高度过渡,不需要预先知道内容高度:

.mp-drawer {
display: grid;
grid-template-rows: 0fr;
opacity: 0;
transition:
grid-template-rows 0.3s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.mp-drawer.open {
grid-template-rows: 1fr;
opacity: 1;
}
.mp-drawer-inner {
overflow: hidden;
min-height: 0; /* 必须,否则 grid 子项不会收缩 */
}

比 max-height: 0 → 999px 好:后者要么动画速度忽快忽慢,要么得猜一个足够大的值。

封面旋转#

播放时转,暂停时停在当前角度:

.mp-cover img {
animation: mp-spin 12s linear infinite;
animation-play-state: paused; /* 动画常驻,只是暂停 */
transform-origin: center;
}
.mp-cover.spinning img {
animation-play-state: running;
}

要点是动画一直挂着,只切换 play-state。如果直接增删类名,animation 会被移除,旋转角度立刻重置回 0°。

播放状态由事件驱动#

playing 只由 <audio> 的 onplay / onpause 事件驱动,不做乐观赋值——否则 play() 被拒(自动播放策略、网络失败)时,界面会卡在”正在播放”但一点声音都没有。

拖动进度条#

用 Pointer 事件 + setPointerCapture:

el.setPointerCapture(e.pointerId); // 指针移出元素后仍能继续拖

没有这一句,鼠标一旦拖出那个细条,拖动就断了。触屏上还要给滑块加 touch-action: none,否则会误触发页面滚动。

错误处理#

网易云有些歌因为版权、地区限制或下架,外链接口会拿不到地址。<audio> 的 onerror 里要跳到下一首,并累计失败次数——全部失败时停下,否则会无限循环。

六、已知限制#

限制说明
加歌要重新构建歌单构建时抓取,无法实时同步
图标首次加载慢见上面那个警告
不预检歌曲可用性构建时不会逐首探测,失效的歌由前端运行时跳过
只在 Fuwari 上测过其他 Astro 主题需要自己调整样式变量

七、进一步优化#

如果想让首次加载的图标立刻出现,可以在构建期把用到的图标内联进页面:从 @iconify-json/material-symbols/icons.json 里取出用到的十几个图标,通过 addCollection() 喂给 @iconify/svelte,页面里就不用再向 Iconify API 发请求了。

八、源码#

两个文件,加起来不到 500 行:

https://github.com/BH2XOL/fuwari-netease-player

MIT 协议,随便拿走用。


关于本文

本文由 AI 完成,方案和代码都在我的博客上真实运行。

给 Fuwari 加一个网易云音乐播放器(不用后端)
https://blog.bh2xol.com/posts/fuwari侧边栏音乐播放器/
作者
BH2XOL
发布于
2026-09-15
许可协议
CC BY-NC-SA 4.0