当前位置: 首页 > 科技观察

Chrome66禁止自动播放声音,开发者该如何应对?

时间:2023-03-12 21:19:34 科技观察

无法自动播放声音。这一直是IOS/Android上的通行做法。桌面版Safari也在2017年11版宣布禁止自动播放带声音的多媒体,紧接着2018年4月发布的Chrome,66也正式关闭了自动播放声音,也就是说/audio在桌面浏览器中也将无效。一开始,手机浏览器完全禁止自动播放音视频,考虑到手机的带宽和电池的消耗。不过后来改了,因为浏览器厂商发现网页开发者可能会用GIF动态图片代替视频来实现自动播放。正如IOS文档所说,使用GIF的带宽流量是Video(h264)格式的12倍,播放性能消耗是2倍,所以这对用户来说是不利的。或者使用Canvas进行黑客攻击,如AndroidChrome文档中所述。因此,浏览器厂商发布了对多媒体自动播放的限制,只要满足以下条件:(1)没有音轨,或者设置了muted属性(2)在视图中可见,必须插入进入DOM并且它不是display:none或visibility:hidden,也不会滑出可见区域。也就是说,只要你不开声音扰民,而且是用户可见的,它就会让你自动播放,不需要用gif的方式破解。桌面版浏览器最近也使用了这种策略,比如升级后的Safari11的描述:以及Chrome文档的描述:这种策略无疑会对视频网站产生影响***,比如提示到在Safari中打开土豆网:添加了设置向导。Chrome的禁止更加人性化。它有一个MEI策略,大致意思是只要用户在当前网页主动播放音视频超过7s(视频窗口不能小于200x140),就允许自动播放。对于web开发者来说,如何有效规避这种风险呢?Chrome的文档给出了一个最佳实践:先给音视频添加静音属性自动播放,然后显示一个关闭声音的按钮,提示用户点击打开声音。对于视频,确实可以这样处理,但是对于音频,很多人都是监听页面点击事件,点击一次就开始播放声音,一般是播放背景音乐。但是如何为有多个声音资源的页面自动播放多个声音呢?首先,如果用户在交互前调用API播放声音,Chrome会提示:DOMException:play()failedbecausetheuserdidn'tfirstinteractwiththedocument。Safari会提示:NotAllowedError:TherequestisnotallowedBytheuseragentortheplatforminthecurrentcontext,可能是因为用户拒绝了权限。Chrome错误提示是最友好的,表示用户没有交互,无法调整播放。用户交互包括什么?包括用户触发的touchend、click、doubleclick或keydown事件,其中播放可以调整。所以上面说了很多人都是监听整个页面的点击事件来播放,不管点击哪里,只要点击,包括Touchdown。此方法只适用于一种声音资源,不适用于多种声音。多重音应该怎么破?这并不是说我们要跟浏览器斗,“逆天”,我们的目的是为了提升用户体验,因为有些场景如果能自动播放就更好了,比如一些接听的场景,你需要听语音回答问题,如果在回答问题的过程中,用户可以自动一一播放对应问题的声音,确实更方便。同时也讨论了声音播放的技术实现。视频的原生播放应该只使用video标签,而音频的原生播放除了使用audio标签外,还有一个API叫做AudioContext,可以用来控制声音的播放,有很多丰富的控制接口。调整audio.play必须在点击事件中响应,而使用AudioContext的区别在于只要用户点击页面任意位置就可以播放。所以可以使用AudioContext来代替audio标签来播放声音。我们首先通过audio.play检测页面是否支持自动播放,从而决定何时播放。1、自动页面播放的检测方法很简单。就是创建一个audio元素,给它赋一个src,追加到dom上,然后调用它的play,看有没有异常抛出。如果捕获到异常,则表示不支持。代码如下所示:functiontestAutoPlay(){//返回一个promise,告诉调用方检测结果returnnewPromise(resolve=>{letaudio=document.createElement('audio');//需要一个本地文件,会变成base64格式audio.src=require('@/assets/empty-audio.mp3');document.body.appendChild(audio);letautoplay=true;//播放返回一个promiseaudio.play().then(()=>{//支持自动播放autoplay=true;}).catch(err=>{//不支持自动播放autoplay=false;}).finally(()=>{audio.remove();//告诉调用者解决结果(autoplay);});});}这里使用了一个空的音频文件,是一个时长为0s的mp3文件,大小只有4kb,通过webpack打包成本地的base64格式,所以不需要在canplay事件之后才调用play,直接写成同步代码即可。如果src是一个远程url,那么你必须监听canplay事件,然后在其中播放。告诉调用者结果时,使用Promiseresolve,因为play的结果是异步的。而且不需要await,因为await不应该用在别人调用的库函数中。调用者决定是否等待,否则库函数将成为同步代码,必须强制其他人等待您的库函数。2.聆听页面上的交互式点击。如果当前页面可以自动播放,那你就可以毫无顾忌的让声音自动播放。否则,你必须等到用户开始与这个页面交互,即有点击操作,才能自动播放,如下代码所示:letaudioInfo={autoplay:false,testAutoPlay(){//代码是一样的,略...},//监听页面的点击事件,点击后可以自动播放setAutoPlayWhenClick(){functionsetAutoPlay(){//设置自动播放为trueaudioInfo.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;}});//用户点击交互后,设置为自动播放playaudioInfo.setAutoPlayWhenClick();}};audioInfo.init();exportdefaultaudioInfo;以上代码主要是监听文档的点击事件,并设置autop在点击事件中将值设置为true。也就是说,只要用户点击,我们就可以随时调用AudioContext播放API。即使不在点击事件响应函数中,虽然不能在异步回调中调用audio.play,但是AudioContext可以做到。代码***通过调用audioInfo.init将可以自动播放的信息保存在变量audioInfo.autoplay中。当需要播放声音时,比如切换到下一题时,需要自动播放当前题的几个音频资源。该变量用于判断是否可以自动播放,如果他点击一次,它会自动播放。那么如何用AudioContext来播放声音呢?3.播放声音,AudioContext首先请求音频文件,放入ArrayBuffer中,然后使用AudioContextAPI解码解码。解码后,让它去播放,就这样。我们先写一个音频文件的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,让它解码然后播放,如下代码所示://Safari使用webkit前缀letcontext=new(window.AudioContext||window.webkitAudioContext)();//请求音频数据letaudioMedia=awaitrequest(url);//解码和播放context.decodeAudioData(audioMedia,decode=>play(context,decode));play函数实现如下:functionplay(context,decodeBuffer){letsource=context.createBufferSource();source.buffer=decodeBuffer;source.connect(context.destination);//从0开始播放source.start(0);}这样就实现了AudioContext播放音频的基本功能。如果当前页面无法自动播放,那么在创建新的AudioContext时,Chrome控制台会报一个警告:这意味着你在用户与页面交互之前已经初始化了一个AudioContext,我不会让你播放。您需要在用户单击恢复后恢复上下文才能播放。假设我们忽略这个警告,直接调用play没有报错,但是没有声音。所以这时候就用到了上一步中的audioInfo.autoplay信息。如果是,则可以播放,否则无法播放,需要用户点击声音图标播放。于是,重新整理代码: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的自动播放功能。这样,基本目的就达到了。如果支持自动播放,直接在代码里播放,不支持就等点击即可。只要点击一次,无论点击哪里,都能自动播放下一首。可以达到每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控制声音属性。比如这个CSSTricks列举了几个例子,其中一个是使用AudioContext的振荡器oscillator来写一个电子木琴:这个例子没有使用任何音频资源,直接合成。喜欢这个演示:播放木琴(网络音频API)。还有一个这个混响均衡器的例子:看这个codepen:WebAudioAPI:parametricequalizer.***,只有手机浏览器禁用了音频视频的自动播放,现在桌面版浏览器也开始工作了.浏览器这样做的目的是创造一个纯净安静的环境,不允许用户打开一个有各种广告或播放其他杂乱声音的页面。但是浏览器并不是万能的,至少可以让音频和视频静默播放。所以对于视频,可以静音自动播放,然后添加一个关闭声音的图标供用户点击打开,然后添加设置向导或者其他方式引导用户设置允许当前网站自动播放。对于声音,您可以使用AudioContextAPI。只要点击一次页面,就会激活AudioContext,可以直接在代码中控制播放。以上可以作为目前网络多媒体播放的最佳实用参考。