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

@(音视频)[Audio|Video|MSE]javascript

音视频随着互联网的发展,对音视频的需求愈来愈多,然而音视频无乱是播放仍是编解码,封装对性能要求都比较高,那现阶段的前端再音视频领域都能作些什么呢。html


[TOC]前端

音频或视频的播放

html5 audio

提起音视频的播放,我萌首先想到的是HTMLMediaElementvideo播放视频,audio播放音频。举个栗子:html5

<audio controls autoplay loop="true" preload="auto" src="audio.mp3"></audio> 复制代码
  • controls指定浏览器渲染成html5 audio.
  • autoplay属性告诉浏览器,当加载完的时候,自动播放.
  • loop属性循环播放.
  • preload当渲染到audio元素时,便加载音频文件.
  • 移动端的浏览器并不支持autoplaypreload 属性,即不会自动加载音频文件,只有经过一些事件触发,好比touchclick事件等触发加载而后播放.
  • 媒体元素还有一些改变音量,某段音频播放完成事件等,请阅读HTMLMediaElement.
  • 固然若是你的网页是跑在WebView中,可让客户端设置一些属性实现预加载和自动播放。

AudioContext

虽然使用html5的audio能够播放音频,可是正如你看到存在不少问题,同时我萌不能对音频的播放进行很好的控制,好比说从网络中获取到音频二进制数据,有的时候我萌想顺序播放多段音频,对于使用audio元素也是力不从心,处理起来并不优雅。 举个栗子:java

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 事件,顺序播放。c++

为了更好的控制音频播放,我萌须要AudioContext.git

AudioContext接口表示由音频模块链接而成的音频处理图,每一个模块对应一个AudioNode。AudioContext能够控制它所包含的节点的建立,以及音频处理、解码操做的执行。作任何事情以前都要先建立AudioContext对象,由于一切都发生在这个环境之中。github

可能理解起来比较晦涩,简单的来讲,AudioContext 像是一个工厂,对于一个音频的播放,从音源到声音控制,到连接播放硬件的实现播放,都是由各个模块负责处理,经过connect 实现流程的控制。 web

点击查看

如今我萌便能实现音频的播放控制,好比从网络中获取。利用AJAX中获取 arraybuffer类型数据,经过解码,而后把音频的二进制数据传给AudioContext建立的BufferSourceNode,最后经过连接 destination 模块实现音频的播放。api

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);
    })
}
复制代码

简单的来讲,就是fetchresponse返回一个readableStream接口,经过从中读取流,不断的喂给audioContext 实现播放,测试发现移动端不能顺利实现播放,pc端浏览器能够。

PCM audio

实现audioContext播放时,我萌须要解码,利用decodeAudioDataapi实现解码,我萌都知道,通常音频都要压缩成mp3,aac这样的编码格式,我萌须要先解码成PCM数据才能播放,那PCM 又是什么呢?我萌都知道,声音都是由物体振动产生,可是这样的声波没法被计算机存储计算,我萌须要使用某种方式去刻画声音,因而乎便有了PCM格式的数据,表示麦克风采集声音的频率,采集的位数以及声道数,立体声仍是单声道。

Media Source Extensions

Media Source Extensions能够动态的给AudioVideo建立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

相关文章
相关标签/搜索