暂未播放
0:00
0:00

博客音乐播放器:3D 频谱地形与 Meting 接入

749 字
4 分钟
博客音乐播放器:3D 频谱地形与 Meting 接入

一直想要个能看的音乐页,不是塞个 APlayer 列表就完事那种。后来看到 团子和蛋糕 的音乐页面,正是我想要的效果,就照着这个搬过来、接上自己的歌单。

三层分工#

MusicManager 是挂在 Layout 上的全局单例,负责拉歌单、管 <audio> 元素、做 Web Audio 频谱分析,是真正的”大脑”。MusicVisualizer 只管可视化:Three.js 那块 InstancedMesh 方块、着色器、OrbitControls,音频数据从 Manager 拿,驱动地形起伏。music.astro 就是个页面壳,把可视化挂上去。

为什么要做成全局单例、而不是只放在 /music/ 页里?因为 Firefly 的页面切换走的是 Swup 软导航——切页面不整页重载、不重跑脚本。播放器想跨页面连续播,就必须活在被保留的那一层(Layout)里。

数据源用 Meting API,配置只有一行 meting.api,歌单 id 写死在配置里。接口自己掌控的好处是灵活,代价是 cookie 和可用性得自己维护。

频谱怎么变成地形起伏#

链路本身不长:

const source = audioCtx.createMediaElementSource(audioEl); // 把 <audio> 接进 Web Audio
source.connect(analyser);
analyser.connect(audioCtx.destination); // 这根不能省
// 每帧
analyser.getByteFrequencyData(freqData); // 0–255 的实时频点能量

freqData 是一串 0–255 的频点能量,地形就是拿它去驱动 InstancedMesh 里每个方块的高度——方块数量固定,每帧只改矩阵、不重建几何体,所以铺几万个也不卡。

两个通用但容易忽略的点:

  • analyser.connect(destination) 这根线必须接。分析器不是”旁路监听”,它是串联在链路上的——接了分析器却没接输出,声音直接没了。
  • createMediaElementSource 对同一个 <audio> 元素只能调一次,重复调用会抛 InvalidStateError。所以初始化那里要有锁,否则 Swup 反复进出页面就会炸。

配置里两个要注意的细节#

meting.api 一定要写全 https:// 漏了协议头,浏览器把请求当成相对路径拼到当前页域名上,变成 x1anyu.cn/music/meting.yufish.cn/...,直接 404,歌单就拉不到。

<audio> 必须设 crossOrigin="anonymous" 否则跨域音频会被浏览器判定为”污染”,Web Audio 的 analyser 读到的永远是 0,地形一动不动。这个属性要在元素创建时就设上,等 src 赋值之后再补是无效的。

一点小问题#

地形不动。 MusicVisualizer 要靠 mgr.getAudio() 拿 audio 元素去做 createMediaElementSource,但 MusicManager 的公开 API 里压根没有 getAudio——拿到的是 null,代码就走进了”等一会儿再试”的 setTimeout 死循环,分析器从头到尾没连上。补上 getAudio: function () { return audio; } 之后地形立刻活了。

这个 bug 的表现很有迷惑性:歌照放、进度条照走、UI 一切正常,只有地形是死的。因为播放本身根本不经过 Web Audio,只有可视化依赖它。所以排查时很容易往”着色器写错了""数据没传到”那边想,实际是源头就没接上。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
博客音乐播放器:3D 频谱地形与 Meting 接入
https://x1anyu.cn/posts/11/
作者
羡鱼
发布于
2026-08-28
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
羡鱼
临渊空慕水中鱼, 不如携风自渡河.
分类
标签
最新动态
站点统计
文章
14
分类
7
标签
35
总字数
19,969
运行时长
0
最后活动
0 天前
文章目录