内容简介:@(音视频)[Audio|Video|MSE]音视频随着互联网的发展,对音视频的需求越来越多,然而音视频无乱是播放还是编解码,封装对性能要求都比较高,那现阶段的前端再音视频领域都能做些什么呢。[TOC]
@(音视频)[Audio|Video|MSE]
音视频随着互联网的发展,对音视频的需求越来越多,然而音视频无乱是播放还是编解码,封装对性能要求都比较高,那现阶段的前端再音视频领域都能做些什么呢。
[TOC]
音频或视频的播放
html5 audio
提起音视频的播放,我萌首先想到的是HTMLMediaElement, video
播放视频, audio
播放音频。举个栗子:
<audio controls autoplay loop="true" preload="auto" src="audio.mp3"></audio> 复制代码
-
controls
指定浏览器渲染成html5audio
. -
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播放时,我萌需要解码,利用 decodeAudioData
api实现解码,我萌都知道,一般音频都要压缩成mp3,aac这样的编码格式,我萌需要先解码成PCM数据才能播放,那PCM 又是什么呢?我萌都知道,声音都是由物体振动产生,但是这样的声波无法被计算机存储计算,我萌需要使用某种方式去刻画声音,于是乎便有了PCM格式的数据,表示麦克风采集声音的频率,采集的位数以及声道数,立体声还是单声道。
Media Source Extensions
Media 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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 从 Kubernetes 中的对象谈起
- 那些年曾谈起的跨域
- 2020 年,从架构谈起,到 Mesh 结束
- 华为云到底用AI做了什么?从少坐一趟机场摆渡车谈起
- 前端科普系列(三):CommonJS 不是前端却革命了前端
- 前端科普系列(三):CommonJS 不是前端却革命了前端
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python for Data Analysis
Wes McKinney / O'Reilly Media / 2012-11-1 / USD 39.99
Finding great data analysts is difficult. Despite the explosive growth of data in industries ranging from manufacturing and retail to high technology, finance, and healthcare, learning and accessing d......一起来看看 《Python for Data Analysis》 这本书的介绍吧!