WebAudio使用小结🧭

背景

最近在负责游戏项目的开发,不免需要接触到音频相关能力的开发。在移动端实践后才了解到,iOS下的AudioDOM并没有被完全实现,比如iOS下就无法直接通过JS控制AudioDOM的音量大小,只能控制其是否被静音,另外静音拨杆的优先级比音量键的优先级要高。如果用户通过拨杆打开了静音模式,AudioDOM默认是没办法控制音量的。

尝试解决问题

由于需要实现通用能力中规划的音频的响度控制,搜索一圈后发现可以引入WebAudio来解决问题。(https://stackoverflow.com/questions/27296391/is-there-any-possibility-to-control-html5-audio-volume-on-ios

改造前音频架构

初始化阶段的时候,通用组件会检查DOM树是否存在AudioDOM,如果不存在就会尝试创建一个新的AudioDOM并挂载到DOM树上。状态操作也是直接对DOM树上的AudioDOM操作。

改造后音频架构

WebAudio支持多种方式创建音频输入,即可以直接通过AudioBuffer获得音频输入,也可以通过createMediaElementSource从已有的AudioDOM上创建音频输入。此处我使用的是后者,通过在AudioDOM与扬声器之间创建了一个“代理层”,将音频输出通过GainNode(增益器节点)处理了一下。通过GainNode我们可以设置音频的响度,间接的实现了iOS的音量控制。

错误上报

不幸的是,测试的Testcase还不够完善,业务被发布后,错误量激增,告警拉满😂。

经过梳理后主要是以下三种问题:

  1. Failed to construct ‘AudioContext’: The number of hardware contexts provided (6) is greater than or equal to the maximum bound (6).
  2. The provided value is non-finite
  3. undefined is not a constructor (evaluating ‘new(window.AudioContext||window.webkitAudioContext)’)

1.AudioContext只能创建6个实例

Webview版本小于66中只能创建小于等于6个的AudioContext,会导致页面无法继续播放音频。(https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)

解决办法:

1. 增加全局AudioContext池,通过一个实例来管理最大音频对象数量。

2. 不使用的AudioContext需要被及时释放,避免占用音频对象资源。

2.音频节点提供的不是有限值

在某些环境中,JS的除运算计算出来的值不是一个有限值,直接赋值给WebAudio的节点会报该错误。

解决办法:

赋值前通过isFinite函数检查是否是有限值,如果不是则使用一个固定的值作为兜底即可。

3.window.AudioContext不存在

按道理来说AudioContext在执行JS代码之前就应该被挂载到window上了。但遗憾的是,iOS15之后的WKWebview会经常出现,执行JS代码的时候AudioContext仍然未被挂载到window上,导致执行构造函数的时候报错。

解决办法:

捕获错误并且定时重试,一般第一次失败的2-3秒后就能创建成功了。