一段“无声”网页音频,为什么能让蓝牙耳机无法切换设备?

摘要:网页把GainNode设为零并不等于释放音频设备。本文从Web Audio实时图、浏览器音频会话、设备指纹与蓝牙Multipoint仲裁解释“听不见却占线”的机制、证据边界和复现实验。

静默WebAudio与蓝牙多点连接仲裁示意图 你在手机上听歌,同时让支持 multipoint(多点连接)的耳机连着电脑。打开某购物网站后,手机音乐突然停了;关闭网页,又立刻恢复。标签页没有视频,扬声器图标也没亮,甚至把标签页、浏览器或 Windows 静音都无效——这并不一定是蓝牙坏了,而可能是网页启动了一条**听不见、却仍在运行的音频链路**。

2026 年 8 月,Laserphile 对 AliExpress 首页的一次调查记录了这个现象。作者注入代码监视 AudioContext 构造和 AudioNode.connect(),发现两个混淆过的安全脚本在页面闲置数秒后创建实时 Web Audio 图:

1
2
3
4
5
6
7
8
锯齿波 Oscillator
→ AnalyserNode
→ ScriptProcessorNode
→ GainNode(增益为 0)
→ AudioContext.destination
→ 浏览器 / 操作系统音频会话
→ 蓝牙 A2DP 输出
→ 耳机的 multipoint 仲裁
静默WebAudio与蓝牙多点连接仲裁示意图
静默WebAudio与蓝牙多点连接仲裁示意图

“没有声音”不等于“没有播放链路”

AudioContext 是 Web Audio API 的实时处理容器,内部由振荡器、滤波器、分析器、增益等节点组成。节点接到 destination 后,浏览器通常会按声卡时钟持续拉取、计算音频块。把末端 GainNode 设为 0,只会把样本乘成零,并不等于调用 suspend()disconnect()close();MDN 也明确说明,suspend() 会暂停时间推进和音频硬件访问,close() 则释放其系统音频资源。

这解释了为什么“静音”可能无效:标签页或系统音量静音往往发生在更下游,只改变最终混音结果,不必然撤销浏览器已经建立的输出流。标签页不显示扬声器图标也不能反证“没有音频活动”:浏览器可能依据最终是否产生可听样本来显示图标,而操作系统关心的是应用是否仍持有并向输出端提交音频缓冲。两者观察的是不同层级。

至于零样本是否仍会让蓝牙链路保持活跃,取决于浏览器、操作系统音频栈、驱动和耳机固件,不能由 Web Audio 规范单独推出。原调查证明的是其特定 Firefox/Chrome、Windows 和耳机组合上的可重复关联,不是所有设备必现的通则。

它为什么像指纹,而不是播放器?

调查中的脚本没有 <audio><video>play() 或 Media Session;它生成已知波形,再读取分析结果。相同信号经过不同浏览器版本、数学实现和 CPU 指令路径后,输出可能有细小差异,哈希后可成为一个“半标识符”,再与 Canvas、WebGL、屏幕尺寸、硬件并发数、性能时序和交互数据组合,用于反欺诈、机器人识别,也可能用于跟踪。

但用途与效果要分开说。客户端代码能证明它采集并上报了多类指纹式特征,却不能证明服务端如何留存、是否跨站关联。Firefox 开发者 Tom Ritter 的后续测试还指出:Firefox 118 起已把多数 Web Audio 差异归一化;其遥测样本中 99.24% 用户只落入三个主要结果,剩余差异主要与 CPU 的 FMA/NEON 路径有关。因此,这段检测“在做 Web Audio 特征采集”较有依据,“足以唯一识别某台设备”则并不成立。Brave 采用的另一思路是对 Canvas 与 Web Audio 输出做按站点、按会话的轻微随机化(farbling)。

蓝牙多点为何会被“占线”

Multipoint 的核心是耳机同时维持两个源设备连接,但多数产品并不能同时播放两路媒体;耳机必须按厂商规则选择当前 A2DP 媒体源,并通常让来电等 HFP 事件获得更高优先级。不同产品依据“是否有活动媒体流”、播放控制状态、超时或私有策略切换,标准和实现并不统一。

于是可能出现这样的副作用:电脑端浏览器持续保有实时输出会话,操作系统继续把电脑视作活动音频源;耳机也迟迟不把媒体通道让给手机。这里最关键的表述是“可能”:网页并没有直接控制耳机,更没有调用所谓“蓝牙抢占 API”,而是通过浏览器音频输出,间接触发了整条链路的仲裁行为。

可复现实验思路

准备一副支持 multipoint 的耳机,连接电脑和手机,先确认电脑无声时手机能正常播放。随后在本地测试页中,由一次按钮点击创建四组对照:

  1. 只创建 AudioContext,不接 destination
  2. Oscillator → Gain(0) → destination,保持 state === "running"
  3. 对第 2 组调用 suspend()
  4. 再调用 close(),或断开节点。

每组记录手机是否中断、恢复耗时、浏览器状态,以及系统音量混音器/浏览器媒体诊断页是否出现活动会话;再交叉测试有线输出、另一浏览器和另一副耳机。另加 OfflineAudioContext 对照:它渲染到内存缓冲区,不需要实时扬声器目的地,更适合一次性指纹测量。实验不要用“网页能否听见”代替“系统是否保持音频会话”,也不要把单一硬件结果外推到所有平台。

用户、网站与浏览器可以做什么

  • 用户侧:关闭可疑标签页;按站点限制自动播放;使用可信内容拦截器阻止已确认的脚本;必要时在耳机应用中手动指定音源。拦截反欺诈脚本可能增加验证码,甚至影响登录或支付。
  • 网站侧:一次性特征计算优先使用 OfflineAudioContext;实时上下文用完立即 suspend()/close();页面隐藏或任务结束时释放资源;不要仅靠零增益“假装停止”。
  • 浏览器侧:把“占用输出设备”与“可听见声音”分别可视化;对长时间零输出的后台上下文尝试释放硬件、改用空输出,同时保持必要的图时钟;进一步归一化或随机化可指纹化结果;在不破坏网页乐器、会议、游戏和无障碍应用的前提下,收紧无用户意图的实时音频激活。

优化并不简单:Web Audio 图可能有自动化参数、反馈连接或 AudioWorklet 副作用,浏览器不能看到增益为零就武断停算。但至少,这次案例揭示了一个产品边界:“静音”是声学结果,“不占用音频设备”才是资源状态。浏览器若只展示前者,用户就看不见后者造成的真实硬件副作用。

参考资料

  1. Laserphile, AliExpress webpage keeping multipoint Bluetooth headphones active with WebAudio fingerprinting, 2026-08。
  2. Tom Ritter, WebAudio fingerprinting on Alibaba, 2026-08-20。
  3. MDN, AudioContextBaseAudioContext.state
  4. Chrome for Developers, Autoplay policy in Chrome(Web Audio 自 Chrome 71 起纳入自动播放策略)。
  5. Brave, Fingerprinting defenses 2.0
  6. SoundGuys, What is Bluetooth multipoint?
分享到