最近在 web 手机页面和 webview 上遇到了不少关于声音的问题,深入研究下egret用的两种播放声音的方式

HTML Audio

简单使用:

const audioObj = new Audio('./resource/assets/sounds/home_bg.mp3');
            audioObj.addEventListener('canplaythrough', (event) => {
                console.log('loaded');
                // audioObj.play();
            });

Audio()构造器创建并返回一个 HTMLAudioElement,通过标签的形式加载声音,创建的这个标签可以不用append到html中播放。

HTML Audio 在 canplaythrough 事件中声音资源已经被加载成功如果直接调用play方法,会报错并且不播web标准中认为只有在用户主动操作页面之后才允许播放,否则会惊吓到用户。所以上面的代码要改成这样才可以正常播放。

const audioObj = new Audio('./resource/assets/sounds/home_bg.mp3');
            let loaded = false;
            audioObj.addEventListener('canplaythrough', (event) => {
                loaded = true;
                console.log('loaded');
                // 不添加点击事件直接播放报错
                // test.html:13 Uncaught (in promise) DOMException: play() failed because the user didn't interact with the document first. https://goo.gl/xX8pDD
                // /* the audio is now playable; play it if permissions allow */
                // audioObj.play();
            });
            window.onload = () => {
                document.body.addEventListener('click', () => {
                    if (loaded) {
                        audioObj.play();
                    }
                });
            };

Web Audio Api

web audio api的使用和理解稍微复杂一点

一个简单而典型的web audio流程如下:
1. 创建音频上下文
2. 在音频上下文里创建源 — 例如 <audio>, 振荡器, 流
3. 创建效果节点,例如混响、双二阶滤波器、平移、压缩
4. 为音频选择一个目的地,例如你的系统扬声器
5. 连接源到效果器,对目的地进行效果输出
使用这个API,时间可以被非常精确地控制,几乎没有延迟,这样开发人员可以准确地响应事件,并且可以针对采样数据进行编程,甚至是较高的采样率。这样,鼓点和节拍是准确无误的。

1.创建AudioContext

window.AudioContext = window.AudioContext||window.webkitAudioContext;
const context = new AudioContext();

2.通过ajax请求资源,注意将 responseType 设置为 arraybuffer

function loadDogSound(url) {
  var request = new XMLHttpRequest();
  request.open('GET', url, true);
  request.responseType = 'arraybuffer';

  // Decode asynchronously
  request.onload = function() {
    context.decodeAudioData(request.response, function(buffer) {
      dogBarkingBuffer = buffer;
    }, onError);
  }
  request.send();
}

3.播放声音,创建声音源并将它们连接到AudioContext实例提供的声音目标

function playSound(buffer) {
  var source = context.createBufferSource(); // creates a sound source
  source.buffer = buffer;                    // tell the source which sound to play
  source.connect(context.destination);       // connect the source to the context's destination (the speakers)
  source.start(0);                           // play the source now
                                             // note: on older systems, may have to use deprecated noteOn(time);
}

HTML Audio 与 Web Audio Api的不同

Web Audio API并不会取代<audio>音频元素,倒不如说它是<audio>的补充更好,就好比如<canvas>与<img>共存的关系。你使用来实现音频的方式取决于你的使用情况。如果你只是想控制一个简单的音轨的播放,<audio>或许是一个更好更快的选择。如果你想实现更多复杂的音频处理,以及播放,Web Audio API提供了更多的优势以及控制。例如:立体声声像

总结:

  1. HTML Audio通过标签请求,web Audio api通过ajax请求
  2. web Audio api是更高级的用法可以处理更复杂的声音场景。
  3. 在egret中默认是用web Audio api

关于自动播放

通常,您可以假定仅当以下至少一项为真时才允许媒体自动播放:
* 音频被静音或其音量设置为0
* 用户已经与站点进行了交互(通过单击,点击,按下键等)。
* 如果站点已被列入白名单;如果浏览器确定用户经常与媒体互动,这可能会自动发生,也可能通过首选项或其他用户界面功能手动发生
* 如果自动播放功能策略用于向<iframe>和及其文档授予自动播放支持。
否则,播放可能会被阻止。导致阻塞的确切情况以及将网站列入白名单的具体方法因浏览器而异,但以上是遵循的良好指南。
注意:换句话说,如果在尚未与用户进行任何交互的选项卡中以编程方式启动播放,则通常会阻止任何包含音频的媒体的播放。浏览器还可以选择在其他情况下进行阻止。

Web Audio Api 如果直接调用了play方法在实际测试中,安卓手机无论是webview还是web页面只要支持Audio特性的浏览器几乎都可以不经过用户操作直接播放,但是ios会有很多不一样的表现。用户没有操作过直接播放,会导致 ctx.state 状态挂起,如果不手动 resume 即使用户后续操作了也没有办法正常播放。
image.png

总的建议是:
1.尽量不要使用自动播放,否则会有各种各样的问题。
2.如果需要自动播放除了做好兼容以外,也要考虑到用户体验,选择一个声音从小到大渐进的音乐。

兼容性问题

在低版本的安卓机中(如安卓4.2)AudioContext 为 undefined,不可用
Audio 虽然存在,但是资源加载可能会有问题,不会走 canplay,complete 等事件,如果资源加载是一个前置条件(例如egret的资源组加载,就会监听不了complete事件导致一直await),需要尝试在 stalled 等事件中做错误处理。https://developer.mozilla.org...

离线包

上面提到,html audio 通过标签的方式加载,请求类型是 media,在离线包的请求中是可以被拦截缓存的。
但是 web Audio api 通过 ajax 请求类型是 xhr,在 ios 中不能被拦截。
虽然mdn上也有示例 web audio api 也可以通过标签形式加载,但是也用到了html audio。

image.png

参考链接

https://www.html5rocks.com/en...
https://www.zhangxinxu.com/wo...


Obeing
665 声望108 粉丝

努力地成为一只小牛