chrome浏览器下audio自动播放的hack

前言也许很多前端遇到过这个需求:消息提醒。一般来说,可以简单的实现绝不会用复杂的方式,audio标签提供了这个功能。但是,新版的chrome浏览器禁止了js自动播放音频的功能,见鬼了。音频播放<audio src="../audio.mp3" id=“myaudio” class=“hide”></audio><script>var audio = document.getElementById(‘myaudio’);audio.play();</script>这是最简单的音频播放脚本,但是在chrome下,抛出异常:Uncaught (in promise) DOMException,原因是这种操作必须由用户发起。当然,这不是特例,像F11全屏操作,浏览器也是禁止脚本操作的。但是我们有这个需求,怎么破?绝不屈服的脑洞能否不操作play呢?chrome不仅禁止了脚本自动调用play,还禁止了audio的autoplay属性。但是,如果音频是静音状态,autoplay属性还是可以生效的。意思是,你可以播放,但是不能干扰用户的视听。这就给我们提供了一个hack的方法:默认开启音频的静音播放,而且是循环播放,当我们需要提醒用户的时候,把声音打开,播放时间设置为0秒,播放完毕,关掉声音,继续循环。是的,音频一直在播放,但是用户听不见。只有我们想让用户听见的时候才能听见,客观上也能实现需求。具体实现<audio src="../audio.mp3" muted autoplay loop id=“myaudio” style=“display: none”></audio><script>/** muted 静音* autoplay 自动播放* loop 循环播放*/var audio = document.getElementById(‘myaudio’);var t1 = 3e3;//如果是轮询,这个时间必须大于音频的长度。如果是webscoket,应该设置一个状态play,避免重复播放,如下:var t2 = 2500;//音频的长度,确保能够完整的播放给用户var play = false;funcrion run(){ if(play){ return false; } audio.currentTime = 0;//设置播放的音频的起始时间 audio.volume = 0.5;//设置音频的声音大小 audio.muted = false;//关闭静音状态 play = true; setTimeout(function(){ play = false; audio.muted = true;//播放完毕,开启静音状态 },t2);}setInterval(function(){ run();//假装在轮询服务器,或者从websocket拉取数据},t1);</script>结语这个方法经过在chrome上的实测,可以使用。但是其他浏览器未做测试,据说有的浏览器,似乎是IE不支持muted属性,限于操作系统,没做测试,如果在IE运行有问题,可以给我提个醒。

February 28, 2019 · 1 min · jiezi

谈起音视频,前端能做些什么

@(音视频)[Audio|Video|MSE]音视频随着互联网的发展,对音视频的需求越来越多,然而音视频无乱是播放还是编解码,封装对性能要求都比较高,那现阶段的前端再音视频领域都能做些什么呢。[TOC]音频或视频的播放html5 audio提起音视频的播放,我萌首先想到的是HTMLMediaElement,video播放视频,audio播放音频。举个栗子:<audio controls autoplay loop=“true” preload=“auto” src=“audio.mp3”></audio>controls指定浏览器渲染成html5 audio.autoplay属性告诉浏览器,当加载完的时候,自动播放.loop属性循环播放.preload当渲染到audio元素时,便加载音频文件.移动端的浏览器并不支持autoplay和preload 属性,即不会自动加载音频文件,只有通过一些事件触发,比如touch、click事件等触发加载然后播放.媒体元素还有一些改变音量,某段音频播放完成事件等,请阅读HTMLMediaElement.当然如果你的网页是跑在WebView中,可以让客户端设置一些属性实现预加载和自动播放。AudioContext虽然使用html5的audio可以播放音频,但是正如你看到存在很多问题,同时我萌不能对音频的播放进行很好的控制,比如说从网络中获取到音频二进制数据,有的时候我萌想顺序播放多段音频,对于使用audio元素也是力不从心,处理起来并不优雅。举个栗子:function queuePlayAudio(sounds) { let index = 0; function recursivePlay(sounds, index) { if(sounds.length == index) return; sounds[index].play(); sounds[index].onended = recursivePlay.bind(this, sounds, ++index); }}监听audio元素的 onended 事件,顺序播放。为了更好的控制音频播放,我萌需要AudioContext.AudioContext接口表示由音频模块连接而成的音频处理图,每个模块对应一个AudioNode。AudioContext可以控制它所包含的节点的创建,以及音频处理、解码操作的执行。做任何事情之前都要先创建AudioContext对象,因为一切都发生在这个环境之中。可能理解起来比较晦涩,简单的来说,AudioContext 像是一个工厂,对于一个音频的播放,从音源到声音控制,到链接播放硬件的实现播放,都是由各个模块负责处理,通过connect 实现流程的控制。现在我萌便能实现音频的播放控制,比如从网络中获取。利用AJAX中获取 arraybuffer类型数据,通过解码,然后把音频的二进制数据传给AudioContext创建的BufferSourceNode,最后通过链接 destination 模块实现音频的播放。 export default class PlaySoundWithAudioContext { constructor() { if(PlaySoundWithAudioContext.isSupportAudioContext()) { this.duration = 0; this.currentTime = 0; this.nextTime = 0; this.pending = []; this.mutex = false; this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); } } static isSupportAudioContext() { return window.AudioContext || window.webkitAudioContext; } play(buffer) { var source = this.audioContext.createBufferSource(); source.buffer = buffer; source.connect(this.audioContext.destination); source.start(this.nextTime); this.nextTime += source.buffer.duration; } addChunks(buffer) { this.pending.push(buffer); let customer = () => { if(!this.pending.length) return; let buffer = this.pending.shift(); this.audioContext.decodeAudioData(buffer, buffer => { this.play(buffer); console.log(buffer) if(this.pending.length) { customer() } }, (err) => { console.log(‘decode audio data error’, err); }); } if(!this.mutex) { this.mutex = true; customer() } } clearAll() { this.duration = 0; this.currentTime = 0; this.nextTime = 0; }}AJAX调用function xhr() { var XHR = new XMLHttpRequest(); XHR.open(‘GET’, ‘//example.com/audio.mp3’); XHR.responseType = ‘arraybuffer’; XHR.onreadystatechange = function(e) { if(XHR.readyState == 4) { if(XHR.status == 200) { playSoundWithAudioContext.addChunks(XHR.response); } } } XHR.send();}使用Ajax播放对于小段的音频文件还行,但是一大段音频文件来说,等到下载完成才播放,不太现实,能否一边下载一边播放呢。这里就要利用 fetch 实现加载stream流。fetch(url).then((res) => { if(res.ok && (res.status >= 200 && res.status <= 299)) { readData(res.body.getReader()) } else { that.postMessage({type: constants.LOAD_ERROR}) }})function readData(reader) { reader.read().then((result) => { if(result.done) { return; } console.log(result); playSoundWithAudioContext.addChunks(result.value.buffer); })}简单的来说,就是fetch的response返回一个readableStream接口,通过从中读取流,不断的喂给audioContext 实现播放,测试发现移动端不能顺利实现播放,pc端浏览器可以。PCM audio实现audioContext播放时,我萌需要解码,利用decodeAudioDataapi实现解码,我萌都知道,一般音频都要压缩成mp3,aac这样的编码格式,我萌需要先解码成PCM数据才能播放,那PCM 又是什么呢?我萌都知道,声音都是由物体振动产生,但是这样的声波无法被计算机存储计算,我萌需要使用某种方式去刻画声音,于是乎便有了PCM格式的数据,表示麦克风采集声音的频率,采集的位数以及声道数,立体声还是单声道。Media Source ExtensionsMedia Source Extensions可以动态的给Audio和Video创建stream流,实现播放,简单的来说,可以很好的播放进行控制,比如再播放的时候实现 seek 功能什么的,也可以在前端对某种格式进行转换进行播放,并不是支持所有的格式的。通过将数据append进SourceBuffer中,MSE把这些数据存进缓冲区,解码实现播放。这里简单的举个使用MSE播放 audio的栗子:export default class PlaySoundWithMSE{ constructor(audio) { this.audio = audio; if(PlaySoundWithMSE.isSupportMSE()) { this.pendingBuffer = []; this._mediaSource = new MediaSource(); this.audio.src = URL.createObjectURL(this._mediaSource); this._mediaSource.addEventListener(‘sourceopen’, () => { this.sourcebuffer = this._mediaSource.addSourceBuffer(‘audio/mpeg’); this.sourcebuffer.addEventListener(‘updateend’, this.handleSourceBufferUpdateEnd.bind(this)); }) } } addBuffer(buffer) { this.pendingBuffer.push(buffer); } handleSourceBufferUpdateEnd() { if(this.pendingBuffer.length) { this.sourcebuffer.appendBuffer(this.pendingBuffer.shift()); } else { this._mediaSource.endOfStream(); } } static isSupportMSE() { return !!window.MediaSource; }}HTML5 播放器谈起html5播放器,你可能知道bilibili的flv.js,它便是依赖Media Source Extensions将flv编码格式的视频转包装成mp4格式,然后实现播放。从流程图中可以看到,IOController实现对视频流的加载,这里支持fetch的 stream能力,WebSocket等,将得到的视频流,这里指的是flv格式的视频流,将其转封装成MP4格式,最后将MP4格式的数据通过appendBuffer将数据喂给MSE,实现播放。未来上面谈到的都是视频的播放,你也看到,即使播放都存在很多限制,MSE的浏览器支持还不多,那在视频的编码解码这些要求性能很高的领域,前端能否做一些事情呢? 前端性能不高有很多原因,在浏览器这样的沙盒环境下,同时js这种动态语言,性能不高,所以有大佬提出把c++编译成js ,然后提高性能,或许你已经知道我要说的是什么了,它就是ASM.js,它是js的一种严格子集。我萌可以考虑将一些视频编码库编译成js去运行提高性能,其中就不得不提到的FFmpeg,可以考虑到将其编译成asm,然后对视频进行编解码。写在最后我萌可以看到,前端对音视频的处理上由于诸多原因,可谓如履薄冰,但是在视频播放上,随着浏览器的支持,还是可以有所作为的。招纳贤士今日头条长期大量招聘前端工程师,可选北京、深圳、上海、厦门等城市。欢迎投递简历到 tcscyl@gmail.com / yanglei.yl@bytedance.com ...

November 17, 2018 · 2 min · jiezi