在我之前的文章 《微信小程序原生开发的音频痛点》 中,我曾提到过微信小程序开发白噪音、冥想类应用时面临的“死局”:
“想要混音+连贯播放,最好的选择是 InnerAudioContext,但一旦退出或锁屏就会停止;而选择系统层的 BackgroundAudioManager,虽然允许后台播放,但不能混音,且每次循环都会有明显的加载停顿。”
为了实现产品最核心的“锁屏播放”功能,我们往往不得不向 BackgroundAudioManager 妥协。那么今天这篇,我们就来深究一下这个让人抓狂的技术细节:为什么它连最基本的“无缝循环播放”都做不到?
很多开发者以为是自己的代码没写好,但实际上,这种卡顿是由小程序的底层架构和 API 设计共同造成的“物理级”硬伤,主要有以下三大元凶:
1. API 的缺失
在普通的音频开发中,实现无缝循环只需要一个简单的 loop = true 属性,但操蛋的是,微信根本没有给 BackgroundAudioManager 提供 loop 参数。这就导致开发者只能被迫走一条弯路:监听音频的 onEnded(自然播放结束)事件,然后在回调函数里,手动给 src 重新赋值来触发下一次播放。正是这条弯路,引爆了后面的灾难。
2. JSBridge 跨进程通信延迟
微信小程序采用的是逻辑层(JS)和渲染层/原生层(Native)分离的双线程架构。
当你被迫用 onEnded 手动循环时,指令的流转是这样的:
- 手机底层播放完毕,发信号给微信客户端。
- 微信通过底层 JSBridge 跨进程通知你的小程序 JS 触发 onEnded。
- 你在 JS 里执行 audio.src = 'xxx' 。
- 指令再次跨进程发回给微信客户端,最终调用系统 API 重播。
在单线程环境只需几毫秒的操作,在小程序的跨进程通信中,一来一回通常会消耗 100~500毫秒。在这段通信时间里,扬声器就是一片死寂。
3. 系统级媒体中心的销毁与重建
BackgroundAudioManager 之所以能后台播放,是因为它直接接管了手机操作系统(iOS/Android)的锁屏媒体控制中心。
当一首歌彻底结束(触发 onEnded),系统会认为当前的音频任务已经终结。当你在零点几秒后再次给 src 赋值时,底层并不只是简单地“重新播放声音”,而是经历了一次重建:
- 销毁旧的音频会话(Audio Session)。
- 重新读取音频资源、title、封面图等元数据。
- 在系统层级构建一个新的播放任务,并刷新锁屏UI。
这种系统级资源的“销毁再重建”非常缓慢,将原本跨进程通信带来的停顿感又放大了数倍。
总结
BackgroundAudioManager 的循环卡顿,是 API 缺失 + 跨进程通信延迟 + 操作系统级媒体重置 共同压迫的结果。这不是靠前端代码的奇技淫巧就能抹平的逻辑问题,在官方开放更底层的 API 之前,这依然是微信小程序生态里一个无解的痛。