谷歌浏览器 html5的声音和视频不能自动播放处理

摘要:
一开始,考虑到手机的带宽和电池消耗,手机浏览器完全禁止自动播放音频和视频。禁止使用Chrome更加人性化。它有一个MEI策略,这大致意味着用户只要在当前网页上主动播放音频和视频,就可以自动播放超过7秒的音频和视频。Chrome文档给出了一个最佳实践:首先,为音频和视频添加一个静音属性以自动播放它们,然后显示一个关闭声音的按钮,提示用户单击以打开声音。它还讨论了声音播放的技术实现。
AI模型开发就选MindSpore!新特性、新工具上线!>>>谷歌浏览器 html5的声音和视频不能自动播放处理第1张

声音无法自动播放这个在IOS/Android上面一直是个惯例,桌面版的Safari在2017年的11版本也宣布禁掉带有声音的多媒体自动播放功能,紧接着在2018年4月份发布的Chrome 66也正式关掉了声音自动播放,也就是说<audio autopaly></audio><video autoplay></video>在桌面版浏览器也将失效。

最开始移动端浏览器是完全禁止音视频自动播放的,考虑到了手机的带宽以及对电池的消耗。但是后来又改了,因为浏览器厂商发现网页开发人员可能会使用GIF动态图代替视频实现自动播放,正如IOS文档所说,使用GIF的带宽流量是Video(h264)格式的12倍,而播放性能消耗是2倍,所以这样对用户反而是不利的。又或者是使用Canvas进行hack,如Android Chrome文档提到。因此浏览器厂商放开了对多媒体自动播放的限制,只要具备以下条件就能自动播放:

(1)没音频轨道,或者设置了muted属性

(2)在视图里面是可见的,要插入到DOM里面并且不是display: none或者visibility: hidden的,没有滑出可视区域。

换句话说,只要你不开声音扰民,且对用户可见,就让你自动播放,不需要你去使用GIF的方法进行hack.

桌面版的浏览器在近期也使用了这个策略,如升级后的Safari 11的说明:

谷歌浏览器 html5的声音和视频不能自动播放处理第2张

以及Chrome文档的说明

谷歌浏览器 html5的声音和视频不能自动播放处理第3张

这个策略无疑对视频网站的冲击最大,如在Safari打开tudou的提示:

谷歌浏览器 html5的声音和视频不能自动播放处理第4张

添加了一个设置向导。Chrome的禁止更加人性化,它有一个MEI的策略,这个策略大概是说只要用户在当前网页主动播放过超过7s的音视频(视频窗口不能小于200 x 140),就允许自动播放。

对于网页开发人员来说,应当如何有效地规避这个风险呢?

Chrome的文档给了一个最佳实践:先把音视频加一个muted的属性就可以自动播放,然后再显示一个声音被关掉的按钮,提示用户点一下打开声音。对于视频来说,确实可以这样处理,而对于音频来说,很多人是监听页面点击事件,只要点一次了就开始播放声音,一般就是播放个背景音乐。但是如果对于有多个声音资源的页面来说如何自动播放多个声音呢?

首先,如果用户还没进行交互就调用播放声音的API,Chrome会这么提示:

DOMException: play() failed because the user didn't interact with the document first.

Safari会这么提示:

NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.

Chrome报错提示最为友善,意思是说,用户还没有交互,不能调play。用户的交互包括哪些呢?包括用户触发的touchend, click, doubleclick或者是 keydown事件,在这些事件里面就能调play.

所以上面提到很多人是监听整个页面的点击事件进行播放,不管点的哪里,只要点了就行,包括触摸下滑。这种方法只适用于一个声音资源,不适用多个声音,多个声音应该怎么破呢?这里并不是说要和浏览器对着干,“逆天而行”,我们的目的还是为了提升用户体验,因为有些场景如果能自动播放确实比较好,如一些答题的场景,需要听声音进行答题,如果用户在答题的过程中能依次自动播放相应题目的声音,确实比较方便。同时也是讨论声音播放的技术实现。

原生播放视频应该就只能使用video标签,而原生播放音频除了使用audio标签之外,还有另外一个API叫AudioContext,它是能够用来控制声音播放并带了很多丰富的操控接口。调audio.play必须在点击事件里面响应,而使用AudioContext的区别在于只要用户点过页面任何一个地方之后就都能播放了。所以可以用AudioContext取代audio标签播放声音。

我们先用audio.play检测页面是否支持自动播放,以便决定我们播放的时机。

1. 页面自动播放检测

方法很简单,就是创建一个audio元素,给它赋一个src,append到dom里面,然后调用它的play,看是否会抛异常,如果捕获到异常则说明不支持,如下代码所示:

functiontestAutoPlay(){
//返回一个promise以告诉调用者检测结果
returnnewPromise(resolve=>{
letaudio=document.createElement('audio');
//require一个本地文件,会变成base64格式
audio.src=require('@/assets/empty-audio.mp3');
document.body.appendChild(audio);
letautoplay=true;
//play返回的是一个promise
audio.play().then(()=>{
//支持自动播放
autoplay=true;
}).catch(err=>{
//不支持自动播放
autoplay=false;
}).finally(()=>{
audio.remove();
//告诉调用者结果
resolve(autoplay);
});
});
}

这里使用一个空的音频文件,它是一个时间长度为0s的mp3文件,大小只有4kb,并且通过webpack打包成本地的base64格式,所以不用在canplay事件之后才调用play,直接写成同步代码,如果src是一个远程的url,那么就得监听canplay事件,然后在里面play.

在告诉调用者结果时,使用Promise resolve的方式,因为play的结果是异步的,并且不用await,是因为在给别人调用的库函数里面不应该使用await,由调用者自行决定是否要await,不然库函数就变成同步的代码,就得强制别人去await你这个库函数。

2. 监听页面交互点击

如果当前页面能够自动播放,那么可以毫无顾忌地让声音自动播放了,否则就得等到用户开始和这个页面交互了即有点击操作了之后才能自动播放,如下代码所示:

letaudioInfo={
autoplay:false,
testAutoPlay(){
//代码同,略...
},
//监听页面的点击事件,一旦点过了就能autoplay了
setAutoPlayWhenClick(){
functionsetAutoPlay(){
//设置自动播放为true
audioInfo.autoplay=true;
document.removeEventListener('click',setAutoPlay);
document.removeEventListener('touchend',setAutoPlay);
}
document.addEventListener('click',setCallback);
document.addEventListener('touchend',setCallback);
},
init(){
//检测是否能自动播放
audioInfo.testAutoPlay().then(autoplay=>{
if(!audioInfo.autoplay){
audioInfo.autoplay=autoplay;
}
});
//用户点击交互之后,设置成能自动播放
audioInfo.setAutoPlayWhenClick();
}
};
audioInfo.init();
exportdefaultaudioInfo;

上面代码主要监听document的click事件,在click事件里面把autoplay值置为true。换句话说,只要用户点过了,我们就能随时调AudioContext的播放API了,即使不是在点击事件响应函数里面,虽然无法在异步回调里面调用audio.play,但是AudioContext可以做到。

代码最后通过调用audioInfo.init,把能够自动播放的信息存储在了audioInfo.autoplay这个变量里面。当需要播放声音的时候,例如切到了下一题,需要自动播放当前题的几个音频资源,就取这个变量判断是否能自动播放,如果能就播,不能就等用户点声音图标自己去播,并且如果他点过了一次之后就都能自动播放了。

那么怎么用AudioContext播放声音呢?

3. AudioContext播放声音

先请求音频文件,放到ArrayBuffer里面,然后用AudioContext的API进行decode解码,解码完了再让它去play,就行了。

我们先写一个请求音频文件的ajax:

functionrequest(url){
returnnewPromise(resolve=>{
letxhr=newXMLHttpRequest();
xhr.open('GET',url);
//这里需要设置xhrresponse的格式为arraybuffer
//否则默认是二进制的文本格式
xhr.responseType='arraybuffer';
xhr.onreadystatechange=function(){
//请求完成,并且成功
if(xhr.readyState===4&&xhr.status===200){
resolve(xhr.response);
}
};
xhr.send();
});
}

这里需要注意的是要把xhr响应类型改成arraybuffer,因为decode需要使用这种存储格式,这样设置之后,xhr.response就是一个ArrayBuffer格式了。

接着实例化一个AudioContext,让它去解码然后play,如下代码所示:

//Safari是使用webkit前缀
letcontext=new(window.AudioContext||window.webkitAudioContext)();
//请求音频数据
letaudioMedia=awaitrequest(url);
//进行decode和play
context.decodeAudioData(audioMedia,decode=>play(context,decode));

play的函数实现如下:

functionplay(context,decodeBuffer){
letsource=context.createBufferSource();
source.buffer=decodeBuffer;
source.connect(context.destination);
//从0s开始播放
source.start(0);
}

这样就实现了AudioContext播放音频的基本功能。

如果当前页面是不能autoplay,那么在 new AudioContext的时候,Chrome控制台会报一个警告:

谷歌浏览器 html5的声音和视频不能自动播放处理第5张

这个的意思是说,用户还没有和页面交互你就初始化了一个AudioContext,我是不会让你play的,你需要在用户点击了之后resume恢复这个context才能够进行play.

假设我们不管这个警告,直接调用play没有报错,但是没有声音。所以这个时候就要用到上一步audioInfo.autoplay的信息,如果这个为true,那么可以play,否则不能play,需要让用户自己点声音图标进行播放。所以,把代码重新组织一下:

functionplay(context,decodeBuffer){
//调用resume恢复播放
context.resume();
letsource=context.createBufferSource();
source.buffer=decodeBuffer;
source.connect(context.destination);
source.start(0);
}

functionplayAudio(context,url){
letaudioMedia=awaitrequest(url);
context.decodeAudioData(audioMedia,decode=>play(context,decode));
}

letcontext=new(window.AudioContext||window.webkitAudioContext)();
//如果能够自动播放
if(audioInfo.autoplay){
playAudio(url);
}
//支持用户点击声音图标自行播放
$('.audio-icon').on('click',function(){
playAudio($(this).data('url'));
});

调了resume之后,如果之前有被禁止播放的音频就会开始播放,如果没有则直接恢复context的自动播放功能。这样就达到基本目的,如果支持自动播放就在代码里面直接play,不支持就等点击。只要点了一次,不管点的哪里接下来的都能够自动播放了。就能实现类似于每隔3s自动播下一题的音频的目的:

//每隔3秒自动播放一个声音
playAudio('question-1.mp3');
setTimeout(()=>playAudio(context,'question-2.mp3'),3000);
setTimeout(()=>playAudio(context,'question-3.mp3'),3000);

这里还有一个问题,怎么知道每个声音播完了,然后再隔个3s播放下一个声音呢?可以通过两个参数,一个是解码后的decodeBuffer有当前音频的时长duration属性,而通过context.currentTime可以知道当前播放时间精度,然后就可以弄一个计时器,每隔100ms比较一下context.currentTime是否大于docode.duration,如果是的话说明播完了。soundjs这个库就是这么实现的,我们可以利用这个库以方便对声音的操作。

这样就实现了利用AudioContext自动播放多个音频的目的,限制是用户首次打开页面是不能自动播放的,但是一旦用户点过页面的任何一个地方就可以了。

AudioContext还有其它的一些操作。

4. AudioContext控制声音属性

例如这个CSS Tricks列了几个例子,其中一个是利用AudioContext的振荡器oscillator写了一个电子木琴:

谷歌浏览器 html5的声音和视频不能自动播放处理第6张

这个例子没有用到任何一个音频资源,都是直接合成的,感受如这个Demo:Play the Xylophone (Web Audio API).

还有这种混响均衡器的例子:

谷歌浏览器 html5的声音和视频不能自动播放处理第7张

见这个codepen:Web Audio API: parametric equalizer.

最后,一直以来都是只有移动端的浏览器禁掉了音视频的自动播放,现在桌面版的浏览器也开始下手了。浏览器这样做的目的在于,不想让用户打开一个页面就各种广告或者其它乱七八糟的声音在播,营造一个纯静的环境。但是浏览器也不是一刀切,至少允许音视频静音的播放。所以对于视频来说,可以静音自动播放,然后加个声音被关掉的图标让用户点击打开,再加添加设置向导之类的方法引导用户设置允许当前网站自动播放。而对于声音可以用AudioContext的API,只要页面被点过一次AudioContext就被激活了,就能直接在代码里面控制播放了。

免责声明:文章转载自《谷歌浏览器 html5的声音和视频不能自动播放处理》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Fiddler (四) 实现手机的抓包Linux下如何生成core dump 文件(解决segment fault段错误的问题)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

5个Android开发中比较常见的内存泄漏问题及解决办法

在Android开发中,内存泄漏是比较常见的问题,有过一些Android编程经历的童鞋应该都遇到过,但为什么会出现内存泄漏呢?内存泄漏又有什么影响呢? 在Android程序开发中,当一个对象已经不需要再使用了,本该被回收时,而另外一个正在使用的对象持有它的引用从而导致它不能被回收,这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了。 内存...

egg内置对象

http://eggjs.org/zh-cn/basics/objects.html 目录 egg内置对象 1.1 Application 1.2 Context 1.3 Request and Response 1.4 Helper(扩展) 1.5 Config 1.6 Logger egg内置对象 框架内置基础对象:从 Koa 继承而来的...

Django相关问题

遇到models模型变动后无法用migrations生成改动后的表通过以下几个方面实现1python manage.py makemigrations yourapp(你改变的app) 2python manage.py makemigrations 3python manage.py migrate pip没办法安装: 删除C:Python36Libsi...

彻底修改Google Chrome浏览器的安装目录

谷歌浏览器以其简洁的界面和快速的Javascript解析速度v8引擎,很快在浏览器市场中占有了一席之地,我们公司的绝大多数系统就建议用户选 择使用谷歌浏览器。但是说起他的安装绝对是个杯具:一是默认下载的是在线安装版的;另外一个就是默认的安装目录在系统盘,而且不能选择!! 本文就针对这两点分别给出一个解决方案。 一、下载谷歌的离线安装包谷歌浏览器的默认下载...

Android基础——广播(静态注册)

安卓版本高了就会有点问题,不能静态注册  令活动Main用来发广播,另一个接收器(不是Activity而是receiver)用来接收广播 注册文件 <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/r...

SharedPreferences 详解(多进程,存取数组解决方案)

一、SharedPreferences基本概念 文件保存路径:/data/data/<包名>/shared_prefs目录下目录下生成了一个SP.xml文件 SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过Editor对象实现。 实现SharedPreferences存储的步骤如下: 根据Context...