作者:semlinker

转发链接:https://mp.weixin.qq.com/s/tgiHiint3iTfPnr318kuFg

序言

Web 开拓者们一贯以来想在 Web 中利用音频和视频,但早些时候,传统的 Web 技能不能够在 Web 中嵌入音频和视频,以是一些像 Flash、Silverlight 的专利技能在处理这些内容上变得很受欢迎。

html5videotrack玩转前端 Video 播放器 HTML

这些技能能够正常的事情,但是却有着一系列的问题,包括无法很好的支持 HTML/CSS 特性、安全问题,以及可行性问题。

幸运的是,当 HTML5 标准公布后,个中包含许多的新特性,包括 <video>和 <audio> 标签,以及一些 JavaScript APIs 用于对其进行掌握。
随着通信技能和网络技能的不断发展,目前音视频已经成为大家生活中不可或缺的一部分。
此外,伴随着 5G 技能的逐步遍及,实时音视频领域还会有更大的想象空间。

接下来本文将从八个方面入手,全方位带你一起探索前端 Video 播放器和主流的流媒体技能。
阅读完本文后,你将理解以下内容:

为什么一些网页中的 Video 元素,其视频源地址是采取 Blob URL 的形式;什么是 HTTP Range 要求及流媒体技能干系观点;理解 HLS、DASH 的观点、自适应比特率流技能及流媒体加密技能;理解 FLV 文件构造、flv.js 的功能特性与利用限定及内部的事情事理;理解 MSE(Media Source Extensions)API 及干系的利用;理解视频播放器的事理、多媒体封装格式及 MP4 与 Fragmented MP4 封装格式的差异;

在末了的 「

<videoid=&#34;mse"autoplay=trueplaysinlinecontrols="controls"><sourcesrc="https://h5player.bytedance.com/video/mp4/xgplayer-demo-720p.mp4"type="video/mp4">你的浏览器不支持Video标签</video>

上述代码在浏览器渲染之后,在页面中会显示一个 Video 视频播放器,详细如下图所示:

(图片来源:https://h5player.bytedance.com/examples/)

通过 Chrome 开拓者工具,我们可以知道当播放 「xgplayer-demo-720p.mp4」 视频文件时,发了 3 个 HTTP 要求:

此外,从图中可以清楚地看到,头两个 HTTP 要求相应的状态码是 「206」。
这里我们来剖析第一个 HTTP 要求的要求头和相应头:

在上面的要求头中,有一个 range: bytes=0- 首部信息,该信息用于检测做事端是否支持 Range 要求。
如果在相应中存在 Accept-Ranges 首部(并且它的值不为 “none”),那么表示该做事器支持范围要求。

在上面的相应头中, Accept-Ranges: bytes 表示界定例模的单位是 bytes 。
这里 Content-Length 也是有效信息,由于它供应了要下载的视频的完整年夜小。

1.1 从做事器端要求特定的范围

如果做事器支持范围要求的话,你可以利用 Range 首部来天生该类要求。
该首部指示做事器该当返回文件的哪一或哪几部分。

1.1.1 单一范围

我们可以要求资源的某一部分。
这里我们利用 Visual Studio Code 中的 REST Client 扩展来进行测试,在这个例子中,我们利用 Range 首部来要求 www.example.com 首页的前 1024 个字节。

对付利用 REST Client 发起的 「单一范围要求」,做事器端会返回状态码为「206 Partial Content」 的相应。
而相应头中的 「Content-Length」首部现在用来表示先前要求范围的大小(而不是全体文件的大小)。
「Content-Range」 相应首部则表示这一部分内容在全体资源中所处的位置。

1.1.2 多重范围

Range 头部也支持一次要求文档的多个部分。
要求范围用一个逗号分别隔。
比如:

$curlhttp://www.example.com-i-H"Range:bytes=0-50,100-150"

对付该要求会返回以下相应信息:

由于我们是要求文档的多个部分,以是每个部分都会拥有独立的 「Content-Type」 和 「Content-Range」 信息,并且利用 boundary 参数对相应体进行划分。

1.1.3 条件式范围要求

当重新开始要求更多资源片段的时候,必须确保自从上一个片段被吸收之后该资源没有进行过修正。

「If-Range」 要求首部可以用来天生条件式范围要求:如果条件知足的话,条件要求就会生效,做事器会返回状态码为 206 Partial 的相应,以及相应的主体。
如果条件未能得到知足,那么就会返回状态码为 「200 OK」 的相应,同时返回全体资源。
该首部可以与 「Last-Modified」 验证器或者「ETag」 一起利用,但是二者不能同时利用。

1.1.4 范围要求的相应

与范围要求干系的有三种状态:

在要求成功的情形下,做事器会返回 「206 Partial Content」 状态码。
在要求的范围越界的情形下(范围值超过了资源的大小),做事器会返回「416 Requested Range Not Satisfiable」 (要求的范围无法知足) 状态码。
在不支持范围要求的情形下,做事器会返回 「200 OK」 状态码。

剩余的两个要求,阿宝哥就不再详细剖析了。
感兴趣的小伙伴,可以利用 Chrome 开拓者工具查看一下详细的要求报文。

通过第 3 个要求,我们可以知道全体视频的大小大约为 7.9 MB。
若播放的视频文件太大或涌现网络不稳定,则会导致播放时,须要等待较长的韶光,这严重降落了用户体验。

那么如何办理这个问题呢?要办理该问题我们可以利用流媒体技能,接下来我们来先容流媒体。

二、流媒体

流媒体是指将持续串的媒体数据压缩后,经由网上分段发送数据,在网上即时传输影音以供不雅观赏的一种技能与过程,此技能使得数据包得以像流水一样发送;如果不该用此技能,就必须在利用前下载全体媒体文件。

流媒体实际指的是一种新的媒体传送办法,有声音流、视频流、文本流、图像流、动画流等,而非一种新的媒体。
流媒体最紧张的技能特色便是流式传输,它使得数据可以像流水一样传输。
流式传输是指通过网络传送媒体技能的总称。
实现流式传输紧张有两种办法:顺序流式传输(Progressive Streaming)和实时流式传输(Real Time Streaming)。

目前网络上常见的流媒体协议:

通过上表可知,不同的协议有着不同的优缺陷。
在实际利用过程中,我们常日会在平台兼容的条件下选用最优的流媒体传输协议。
比如,在浏览器里做直播,选用 HTTP-FLV 协议是不错的,性能优于 RTMP+Flash,延迟可以做到和 RTMP+Flash 一样乃至更好。

而有于 HLS 延迟较大,一样平常只适宜视频点播的场景,但由于它在移动端拥有较好的兼容性,以是在接管高延迟的条件下,也是可以运用在直播场景。

讲到这里相信有些小伙伴会好奇,对付 Video 元向来说利用流媒体技能之后与传统的播放模式有什么直不雅观的差异。
下面阿宝哥以常见的 HLS 流媒体协议为例,来大略比拟一下它们之间的差异。

通过不雅观察上图,我们可以很明显地看到,当利用 HLS 流媒体网络传输协议时,<video> 元素 src 属性利用的是 blob:// 协议。
讲到该协议,我们就不得不聊一下 Blob 与 Blob URL。

2.1 Blob

Blob(Binary Large Object)表示二进制类型的大工具。
在数据库管理系统中,将二进制数据存储为一个单一个体的凑集。
Blob 常日是影像、声音或多媒体文件。
「在 JavaScript 中 Blob 类型的工具表示不可变的类似文件工具的原始数据。

Blob 由一个可选的字符串 type(常日是 MIME 类型)和 blobParts组成:

MIME(Multipurpose Internet Mail Extensions)多用场互联网邮件扩展类型,是设定某种扩展名的文件用一种运用程序来打开的办法类型,当该扩展名文件被访问的时候,浏览器会自动利用指定运用程序来打开。
多用于指定一些客户端自定义的文件名,以及一些媒体文件打开办法。

常见的 MIME 类型有:超文本标记措辞文本 .html text/html、PNG图像 .png image/png、普通文本 .txt text/plain 等。

为了更直不雅观的感想熏染 Blob 工具,我们先来利用 Blob 布局函数,创建一个 myBlob 工具,详细如下图所示:

如你所见,myBlob 工具含有两个属性:size 和 type。
个中 size 属性用于表示数据的大小(以字节为单位),type 是 MIME 类型的字符串。
Blob 表示的不一定是 JavaScript 原生格式的数据。
比如 File 接口基于 Blob,继续了 blob 的功能并将其扩展使其支持用户系统上的文件。

2.2 Blob URL/Object URL

Blob URL/Object URL 是一种伪协议,许可 Blob 和 File 工具用作图像,下载二进制数据链接等的 URL 源。
在浏览器中,我们利用 URL.createObjectURL 方法来创建 Blob URL,该方法吸收一个 Blob工具,并为其创建一个唯一的 URL,其形式为 blob:<origin>/<uuid>,对应的示例如下:

blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f641

浏览器内部为每个通过 URL.createObjectURL 天生的 URL 存储了一个 URL → Blob 映射。
因此,此类 URL 较短,但可以访问 Blob。
天生的 URL 仅在当前文档打开的状态下才有效。
但如果你访问的 Blob URL 不再存在,则会从浏览器中收到 404 缺点。

上述的 Blob URL 看似很不错,但实际上它也有副浸染。
虽然存储了 URL → Blob 的映射,但 Blob 本身仍驻留在内存中,浏览器无法开释它。
映射在文档卸载时自动打消,因此 Blob 工具随后被开释。
但是,如果运用程序寿命很长,那不会很快发生。
因此,如果我们创建一个 Blob URL,纵然不再须要该 Blob,它也会存在内存中。

针对这个问题,我们可以调用 URL.revokeObjectURL(url) 方法,从内部映命中删除引用,从而许可删除 Blob(如果没有其他引用),并开释内存。

2.3 Blob vs ArrayBuffer

其实在前端除了 「Blob 工具」 之外,你还可能会碰着 「ArrayBuffer 工具」。
它用于表示通用的,固定长度的原始二进制数据缓冲区。
你不能直接操纵 ArrayBuffer 的内容,而是须要创建一个 TypedArray 工具或 DataView 工具,该工具以特定格式表示缓冲区,并利用该工具读取和写入缓冲区的内容。

Blob 工具与 ArrayBuffer 工具拥有各自的特点,它们之间的差异如下:

除非你须要利用 ArrayBuffer 供应的写入/编辑的能力,否则 Blob 格式可能是最好的。
Blob 工具是不可变的,而 ArrayBuffer 是可以通过 TypedArrays 或 DataView 来操作。
ArrayBuffer 是存在内存中的,可以直接操作。
而 Blob 可以位于磁盘、高速缓存内存和其他不可用的位置。
虽然 Blob 可以直接作为参数通报给其他函数,比如 window.URL.createObjectURL()。
但是,你可能仍须要 FileReader 之类的 File API 才能与 Blob 一起利用。
Blob 与 ArrayBuffer 工具之间是可以相互转化的:利用 FileReader 的 readAsArrayBuffer() 方法,可以把 Blob 工具转换为 ArrayBuffer 工具;利用 Blob 布局函数,如 new Blob([new Uint8Array(data]);,可以把 ArrayBuffer 工具转换为 Blob 工具。

在前端 AJAX 场景下,除了常见的 JSON 格式之外,我们也可能会用到 Blob 或 ArrayBuffer 工具:

functionGET(url,callback){letxhr=newXMLHttpRequest();xhr.open('GET',url,true);xhr.responseType='arraybuffer';//orxhr.responseType="blob";xhr.send();xhr.onload=function(e){if(xhr.status!=200){alert("Unexpectedstatuscode"+xhr.status+"for"+url);returnfalse;}callback(newUint8Array(xhr.response));//ornewBlob([xhr.response]);};}

在以上示例中,通过为 xhr.responseType 设置不同的数据类型,我们就可以根据实际须要获取对应类型的数据了。
先容完上述内容,下面我们先来先容目前运用比较广泛的 HLS 流媒体传输协议。

三、HLS3.1 HLS 简介

HTTP Live Streaming(缩写是 HLS)是由苹果公司提出基于 HTTP 的流媒体网络传输协议,它是苹果公司 QuickTime X 和 iPhone 软件系统的一部分。
它的事情事理是把全体流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。
当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,许可流媒体会话适应不同的数据速率。

此外,当用户的旗子暗记强度发生抖动时,视频流会动态调度以供应出色的再现效果。

(图片来源:https://www.wowza.com/blog/hls-streaming-protocol)

最初, 金 iOS 支持 HLS。
但现在 HLS 已成为专有格式,险些所有设备都支持它。
顾名思义,HLS(HTTP Live Streaming)协议通过标准的 HTTP Web 做事器传送视频内容。
这意味着你无需集成任何分外的根本架构即可分发 HLS 内容。

HLS 拥有以下特性:

HLS 将播放利用 H.264 或 HEVC / H.265 编解码器编码的视频。
HLS 将播放利用 AAC 或 MP3 编解码器编码的音频。
HLS 视频流一样平常被切成 10 秒的片段。
HLS 的传输/封装格式是 MPEG-2 TS。
HLS 支持 DRM(数字版权管理)。
HLS 支持各种广告标准,例如 VAST 和 VPAID。

为什么苹果要提出 HLS 这个协议,实在它的紧张是为理解决 RTMP 协议存在的一些问题。
比如 RTMP 协议不该用标准的 HTTP 接口传输数据,以是在一些分外的网络环境下可能被防火墙屏蔽掉。
但是 HLS 由于利用的 HTTP 协议传输数据,常日情形下不会碰着被防火墙屏蔽的情形。
除此之外,它也很随意马虎通过 CDN(内容分发网络)来传输媒体流。

3.2 HLS 自适应比特流

HLS 是一种自适应比特率流协议。
因此,HLS 流可以动态地使视频分辨率自适应每个人的网络状况。
如果你正在利用高速 WiFi,则可以在手机上流式传输高清视频。
但是,如果你在有限数据连接的公共汽车或地铁上,则可以以较低的分辨率不雅观看相同的视频。

在开始一个流媒体会话时,客户端会下载一个包含元数据的 Extended M3U(m3u8)Playlist 文件,用于探求可用的媒体流。

(图片来源:https://www.wowza.com/blog/hls-streaming-protocol)

为了便于大家的理解,我们利用 hls.js 这个 JavaScript 实现的 HLS 客户端,所供应的 在线示例,来看一下详细的 m3u8 文件。

「x36xhzz.m3u8」

#EXTM3U#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2149280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=1280x720,NAME="720"url_0/193039199_mp4_h264_aac_hd_7.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=246440,CODECS="mp4a.40.5,avc1.42000d",RESOLUTION=320x184,NAME="240"url_2/193039199_mp4_h264_aac_ld_7.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=460560,CODECS="mp4a.40.5,avc1.420016",RESOLUTION=512x288,NAME="380"url_4/193039199_mp4_h264_aac_7.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=836280,CODECS="mp4a.40.2,avc1.64001f",RESOLUTION=848x480,NAME="480"url_6/193039199_mp4_h264_aac_hq_7.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=6221600,CODECS="mp4a.40.2,avc1.640028",RESOLUTION=1920x1080,NAME="1080"url_8/193039199_mp4_h264_aac_fhd_7.m3u8

通过不雅观察 Master Playlist 对应的 m3u8 文件,我们可以知道该视频支持以下 5 种不同清晰度的视频:

1920x1080(1080P)1280x720(720P)848x480(480P)512x288320x184

而不同清晰度视频对应的媒体播放列表,会定义在各自的 m3u8 文件中。
这里我们以 720P 的视频为例,来查看其对应的 m3u8 文件:

#EXTM3U#EXT-X-VERSION:3#EXT-X-PLAYLIST-TYPE:VOD#EXT-X-TARGETDURATION:11#EXTINF:10.000,url_462/193039199_mp4_h264_aac_hd_7.ts#EXTINF:10.000,url_463/193039199_mp4_h264_aac_hd_7.ts#EXTINF:10.000,url_464/193039199_mp4_h264_aac_hd_7.ts#EXTINF:10.000,...url_525/193039199_mp4_h264_aac_hd_7.ts#EXT-X-ENDLIST

当用户选定某种清晰度的视频之后,将会下载该清晰度对应的媒体播放列表(m3u8 文件),该列表中就会列出每个片段的信息。
HLS 的传输/封装格式是 MPEG-2 TS(MPEG-2 Transport Stream),是一种传输和存储包含视频、音频与通信协议各种数据的标准格式,用于数字电视广播系统,如 DVB、ATSC、IPTV 等等。

「须要把稳的是利用一些现成的工具,我们是可以把多个 TS 文件合并为 mp4 格式的视频文件。
」 如果要做视频版权保护,那我们可以考虑利用对称加密算法,比如 AES-128 对切片进行对称加密。
当客户端进行播放时,先根据 m3u8 文件中配置的密钥做事器地址,获取对称加密的密钥,然后再下载分片,当分片下载完成后再利用匹配的对称加密算法进行解密播放。

对上述过程感兴趣的小伙伴可以参考 Github 上 video-hls-encrypt 这个项目,该项目深入浅出先容了基于 HLS 流媒体协议视频加密的办理方案并供应了完全的示例代码。

(图片来源:https://github.com/hauk0101/video-hls-encrypt)

先容完苹果公司推出的 HLS (HTTP Live Streaming)技能,接下来我们来先容另一种基于 HTTP 的动态自适应流 —— DASH。

四、DASH4.1 DASH 简介

「基于 HTTP 的动态自适应流(英语:Dynamic Adaptive Streaming over HTTP,缩写 DASH,也称 MPEG-DASH)是一种自适应比特率流技能,使高质量流媒体可以通过传统的 HTTP 网络做事器以互联网通报。
」 类似苹果公司的 HTTP Live Streaming(HLS)方案,MPEG-DASH 会将内容分解成一系列小型的基于 HTTP 的文件片段,每个片段包含很短长度的可播放内容,而内容总长度可能长达数小时。

内容将被制成多种比特率的备选片段,以供应多种比特率的版本供选用。
当内容被 MPEG-DASH 客户端回放时,客户端将根据当前网络条件自动选择下载和播放哪一个备选方案。
客户端将选择可及时下载的最高比特率片段进行播放,从而避免播放卡顿或重新缓冲事宜。
也因如此,MPEG-DASH 客户端可以无缝适应不断变革的网络条件并供应高质量的播放体验,拥有更少的卡顿与重新缓冲发生率。

MPEG-DASH 是首个基于 HTTP 的自适应比特率流办理方案,它也是一项国际标准。
MPEG-DASH 不应该与传输协议稠浊 —— MPEG-DASH 利用 TCP 传输协议。
「不同于 HLS、HDS 和 Smooth Streaming,DASH 不关心编解码器,因此它可以接管任何编码格式编码的内容,如 H.265、H.264、VP9 等。

虽然 HTML5 不直接支持 MPEG-DASH,但是已有一些 MPEG-DASH 的 JavaScript 实现许可在网页浏览器中通过 HTML5 Media Source Extensions(MSE)利用 MPEG-DASH。
另有其他 JavaScript 实现,如 bitdash 播放器支持利用 HTML5 加密媒体扩展播放有 DRM 的MPEG-DASH。
当与 WebGL 结合利用,MPEG-DASH 基于 HTML5 的自适应比特率流还可实现 360° 视频的实时和按需的高效流式传输。

4.2 DASH 主要观点MPD:媒体文件的描述文件(manifest),浸染类似 HLS 的 m3u8 文件。
Representation:对应一个可选择的输出(alternative)。
如 480p 视频,720p 视频,44100 采样音频等都利用 Representation 描述。
Segment(分片):每个 Representation 会划分为多个 Segment。
Segment 分为 4 类,个中,最主要的是:Initialization Segment(每个 Representation 都包含 1 个 Init Segment),Media Segment(每个 Representation 的媒体内容包含多少 Media Segment)。

(图片来源:https://blog.csdn.net/yue_huang/article/details/78466537)

在海内 Bilibili 于 2018 年开始利用 DASH 技能,至于为什么选择 DASH 技能。
感兴趣的小伙伴可以阅读 我们为什么利用DASH 这篇文章。

讲了那么多,相信有些小伙伴会好奇 MPD 文件长什么样?这里我们来看一下西瓜视频播放器 DASH 示例中的 MPD 文件:

<?xmlversion="1.0"?><!--MPDfileGeneratedwithGPACversion0.7.2-DEV-rev559-g61a50f45-masterat2018-06-11T11:40:23.972Z--><MPDxmlns="urn:mpeg:dash:schema:mpd:2011"minBufferTime="PT1.500S"type="static"mediaPresentationDuration="PT0H1M30.080S"maxSegmentDuration="PT0H0M1.000S"profiles="urn:mpeg:dash:profile:full:2011"><ProgramInformationmoreInformationURL="http://gpac.io"><Title>xgplayer-demo_dash.mpdgeneratedbyGPAC</Title></ProgramInformation><Periodduration="PT0H1M30.080S"><AdaptationSetsegmentAlignment="true"maxWidth="1280"maxHeight="720"maxFrameRate="25"par="16:9"lang="eng"><ContentComponentid="1"contentType="audio"/><ContentComponentid="2"contentType="video"/><Representationid="1"mimeType="video/mp4"codecs="mp4a.40.2,avc3.4D4020"width="1280"height="720"frameRate="25"sar="1:1"startWithSAP="0"bandwidth="6046495"><AudioChannelConfigurationschemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011"value="2"/><BaseURL>xgplayer-demo_dashinit.mp4</BaseURL><SegmentListtimescale="1000"duration="1000"><Initializationrange="0-1256"/><SegmentURLmediaRange="1257-1006330"indexRange="1257-1300"/><SegmentURLmediaRange="1006331-1909476"indexRange="1006331-1006374"/>...<SegmentURLmediaRange="68082016-68083543"indexRange="68082016-68082059"/></SegmentList></Representation></AdaptationSet></Period></MPD>

(文件来源:https://h5player.bytedance.com/examples/)

在播放视频时,西瓜视频播放器会根据 MPD 文件,自动要求对应的分片进行播放。

前面我们已经提到了 Bilibili,接下来不得不提其开源的一个著名的开源项目 —— flv.js,不过在先容它之前我们须要来理解一下 FLV 流媒体格式。

五、FLV5.1 FLV 文件构造

FLV 是 FLASH Video 的简称,FLV 流媒体格式是随着 Flash MX 的推进身展而来的视频格式。
由于它形成的文件极小、加载速率极快,使得网络不雅观看视频文件成为可能,它的涌现有效地办理了视频文件导入 Flash 后,使导出的 SWF 文件体积弘大,不能在网络上很好的利用等问题。

FLV 文件由 FLV Header 和 FLV Body 两部分构成,而 FLV Body 由一系列的 Tag 构成:

5.1.1 FLV 头文件

FLV 头文件:(9 字节)

1-3:前 3 个字节是文件格式标识(FLV 0x46 0x4C 0x56)。
4-4:第 4 个字节是版本(0x01)。
5-5:第 5 个字节的前 5 个 bit 是保留的必须是 0。
第 5 个字节的第 6 个 bit 音频类型标志(TypeFlagsAudio)。
第 5 个字节的第 7 个 bit 也是保留的必须是 0。
第5个字节的第8个bit视频类型标志(TypeFlagsVideo)。
6-9: 第 6-9 的四个字节还是保留的,其数据为 00000009。
全体文件头的长度,一样平常是 9(3+1+1+4)。
5.1.2 tag 基本格式

tag 类型信息,固定长度为 15 字节:

1-4:前一个 tag 长度(4字节),第一个 tag 便是 0。
5-5:tag 类型(1 字节);0x8 音频;0x9 视频;0x12 脚本数据。
6-8:tag 内容大小(3 字节)。
9-11:韶光戳(3 字节,毫秒)(第 1 个 tag 的时候总是为 0,如果是脚本 tag 便是 0)。
12-12:韶光戳扩展(1 字节)让韶光戳变成 4 字节(以存储更永劫光的 flv 韶光信息),本字节作为韶光戳的最高位。

在 flv 回放过程中,播放顺序是按照 tag 的韶光戳顺序播放。
任何加入到文件中韶光设置数据格式都将被忽略。

13-15:streamID(3 字节)总是 0。

FLV 格式详细的构造图如下图所示:

在浏览器中 HTML5 的 <video> 是不支持直接播放 FLV 视频格式,须要借助 flv.js 这个开源库来实现播放 FLV 视频格式的功能。

5.2 flv.js 简介

flv.js 是用纯 JavaScript 编写的 HTML5 Flash Video(FLV)播放器,它底层依赖于 Media Source Extensions。
在实际运行过程中,它会自动解析 FLV 格式文件并喂给原生 HTML5 Video 标签播放音视频数据,使浏览器在不借助 Flash 的情形下播放 FLV 成为可能。

5.2.1 flv.js 的特性支持播放 H.264 + AAC / MP3 编码的 FLV 文件;支持播放多段分段视频;支持播放 HTTP FLV 低延迟实时流;支持播放基于 WebSocket 传输的 FLV 实时流;兼容 Chrome,FireFox,Safari 10,IE11 和 Edge;极低的开销,支持浏览器的硬件加速。
5.2.2 flv.js 的限定MP3 音频编解码器无法在 IE11/Edge 上运行;HTTP FLV 直播流不支持所有的浏览器。
5.2.3 flv.js 的利用

<scriptsrc="flv.min.js"></script><videoid="videoElement"></video><script>if(flvjs.isSupported()){varvideoElement=document.getElementById('videoElement');varflvPlayer=flvjs.createPlayer({type:'flv',url:'http://example.com/flv/video.flv'});flvPlayer.attachMediaElement(videoElement);flvPlayer.load();flvPlayer.play();}</script>5.3 flv.js 事情事理

flv.js 的事情事理是将 FLV 文件流转换为 ISO BMFF(Fragmented MP4)片段,然后通过 Media Source Extensions API 将 mp4 段位给 HTML5 <video> 元素。
flv.js 的设计架构图如下图所示:

(图片来源:https://github.com/bilibili/flv.js/blob/master/docs/design.md)

有关 flv.js 事情事理更详细的先容,感兴趣的小伙们可以阅读 花椒开源项目实时互动流媒体播放器 这篇文章。
现在我们已经先容了 hls.js 和 flv.js 这两个主流的流媒体办理方案,实在它们的成功离不开 Media Source Extensions 这个幕后英雄默默地支持。
因此,接下来阿宝哥将带大家一起认识一下 MSE(Media Source Extensions)。

六、MSE6.1 MSE API

媒体源扩展 API(Media Source Extensions) 供应了实现无插件且基于 Web 的流媒体的功能。
利用 MSE,媒体串流能够通过 JavaScript 创建,并且能通过利用 audio 和 video 元素进行播放。

近几年来,我们已经可以在 Web 运用程序上无插件地播放视频和音频了。
但是,现有架构过于大略,只能知足一次播放全体曲目的须要,无法实现拆分/合并数个缓冲文件。
早期的流媒体紧张利用 Flash 进行做事,以及通过 RTMP 协议进行视频串流的 Flash 媒体做事器。

媒体源扩展(MSE)实现后,情形就不一样了。
MSE 使我们可以把常日的单个媒体文件的 src 值更换成引用 MediaSource 工具(一个包含即将播放的媒体文件的准备状态等信息的容器),以及引用多个 SourceBuffer 工具(代表多个组成全体串流的不同媒体块)的元素。

为了便于大家理解,我们来看一下根本的 MSE 数据流:

MSE 让我们能够根据内容获取的大小和频率,或是内存占用详情(例如什么时候缓存被回收),进行更加精准地掌握。
它是基于它可扩展的 API 建立自适应比特率流客户端(例如 DASH 或 HLS 的客户端)的根本。

在当代浏览器中创造能兼容 MSE 的媒体非常费时费力,还要花费大量打算机资源和能源。
此外,还须利用外部运用程序将内容转换成得当的格式。
虽然浏览器支持兼容 MSE 的各种媒体容器,但采取 H.264 视频编码、AAC 音频编码和 MP4 容器的格式是非常常见的,以是 MSE 须要兼容这些主流的格式。
此外 MSE 还为开拓者供应了一个 API,用于运行时检测容器和编解码是否受支持。

6.2 MediaSource 接口

MediaSource 是 Media Source Extensions API 表示媒体资源 HTMLMediaElement 工具的接口。
MediaSource 工具可以附着在 HTMLMediaElement 在客户端进行播放。
在先容 MediaSource 接口前,我们先来看一下它的构造图:

(图片来源 —— https://www.w3.org/TR/media-source/)

要理解 MediaSource 的构造图,我们得先来先容一下客户端音视频播放器播放一个视频流的紧张流程:

获取流媒体 -> 解协议 -> 解封装 -> 音、视频解码 -> 音频播放及视频渲染(需处理音视频同步)。

由于采集的原始音视频数据比较大,为了方便网络传输,我们常日会利用编码器,如常见的 H.264 或 AAC 来压缩原始媒体旗子暗记。
最常见的媒体旗子暗记是视频,音频和字幕。
比如,日常生活中的电影,便是由不同的媒体旗子暗记组成,除运动图片外,大多数电影还含有音频和字幕。

常见的视频编解码器有:H.264,HEVC,VP9 和 AV1。
而音频编解码器有:AAC,MP3 或 Opus。
每个媒体旗子暗记都有许多不同的编解码器。
下面我们以西瓜视频播放器的 Demo 为例,来直不雅观感想熏染一下音频轨、视频轨和字幕轨:

现在我们来开始先容 MediaSource 接口的干系内容。

6.2.1 状态

enumReadyState{"closed",//指示当前源未附加到媒体元素。
"open",//源已经被媒体元素打开,数据即将被添加到SourceBuffer工具中"ended"//源仍附加到媒体元素,但endOfStream()已被调用。
};
6.2.2 流终止非常

enumEndOfStreamError{"network",//终止播放并发出网络缺点旗子暗记。
"decode"//终止播放并发出解码缺点旗子暗记。
};
6.2.3 布局器

[Constructor]interfaceMediaSource:EventTarget{readonlyattributeSourceBufferListsourceBuffers;readonlyattributeSourceBufferListactiveSourceBuffers;readonlyattributeReadyStatereadyState;attributeunrestricteddoubleduration;attributeEventHandleronsourceopen;attributeEventHandleronsourceended;attributeEventHandleronsourceclose;SourceBufferaddSourceBuffer(DOMStringtype);voidremoveSourceBuffer(SourceBuffersourceBuffer);voidendOfStream(optionalEndOfStreamErrorerror);voidsetLiveSeekableRange(doublestart,doubleend);voidclearLiveSeekableRange();staticbooleanisTypeSupported(DOMStringtype);};6.2.4 属性MediaSource.sourceBuffers —— 只读:返回一个 SourceBufferList 工具,包含了这个 MediaSource 的SourceBuffer 的工具列表。
MediaSource.activeSourceBuffers —— 只读:返回一个 SourceBufferList 工具,包含了这个MediaSource.sourceBuffers 中的 SourceBuffer 子集的工具—即供应当前当选中的视频轨(video track),启用的音频轨(audio tracks)以及显示/隐蔽的字幕轨(text tracks)的工具列表MediaSource.readyState —— 只读:返回一个包含当前 MediaSource 状态的凑集,纵然它当前没有附着到一个 media 元素(closed),或者已附着并准备吸收 SourceBuffer 工具(open),亦或者已附着但这个流已被 MediaSource.endOfStream() 关闭。
MediaSource.duration:获取和设置当前正在推流媒体的持续韶光。
onsourceopen:设置 sourceopen 事宜对应的事宜处理程序。
onsourceended:设置 sourceended 事宜对应的事宜处理程序。
onsourceclose:设置 sourceclose 事宜对应的事宜处理程序。
6.2.5 方法MediaSource.addSourceBuffer():创建一个带有给定 MIME 类型的新的 SourceBuffer 并添加到 MediaSource 的 SourceBuffers 列表。
MediaSource.removeSourceBuffer():删除指定的 SourceBuffer 从这个 MediaSource 工具中的 SourceBuffers 列表。
MediaSource.endOfStream():表示流的结束。
6.2.6 静态方法MediaSource.isTypeSupported():返回一个 Boolean 值表明给定的 MIME 类型是否被当前的浏览器支持—— 这意味着是否可以成功的创建这个 MIME 类型的 SourceBuffer 工具。
6.2.7 利用示例

varvidElement=document.querySelector('video');if(window.MediaSource){//(1)varmediaSource=newMediaSource();vidElement.src=URL.createObjectURL(mediaSource);mediaSource.addEventListener('sourceopen',sourceOpen);}else{console.log("TheMediaSourceExtensionsAPIisnotsupported.")}functionsourceOpen(e){URL.revokeObjectURL(vidElement.src);varmime='video/mp4;codecs="avc1.42E01E,mp4a.40.2"';varmediaSource=e.target;varsourceBuffer=mediaSource.addSourceBuffer(mime);//(2)varvideoUrl='hello-mse.mp4';fetch(videoUrl)//(3).then(function(response){returnresponse.arrayBuffer();}).then(function(arrayBuffer){sourceBuffer.addEventListener('updateend',function(e){(4)if(!sourceBuffer.updating&&mediaSource.readyState==='open'){mediaSource.endOfStream();}});sourceBuffer.appendBuffer(arrayBuffer);//(5)});}

以上示例先容了如何利用 MSE API,接下来我们来剖析一下紧张的事情流程:

(1) 判断当前平台是否支持 Media Source Extensions API,若支持的话,则创建 MediaSource 工具,且绑定 sourceopen 事宜处理函数。
(2) 创建一个带有给定 MIME 类型的新的 SourceBuffer 并添加到 MediaSource 的 SourceBuffers 列表。
(3) 从远程流做事器下载视频流,并转换成 ArrayBuffer 工具。
(4) 为 sourceBuffer 工具添加 updateend 事宜处理函数,在视频流传输完成后关闭流。
(5) 往 sourceBuffer 工具中添加已转换的 ArrayBuffer 格式的视频流数据。

上面阿宝哥只是大略先容了一下 MSE API,想深入理解它实际运用的小伙伴,可以进一步理解一下 「hls.js」 或 「flv.js」 项目。
接下来阿宝哥将先容音视频根本之多媒体容器格式。

七、多媒体封装格式

一样平常情形下,一个完全的视频文件是由音频和视频两部分组成的。
常见的 AVI、RMVB、MKV、ASF、WMV、MP4、3GP、FLV 等文件只能算是一种封装格式。
H.264,HEVC,VP9 和 AV1 等便是视频编码格式,MP3、AAC 和 Opus 等便是音频编码格式。
「比如:将一个 H.264 视频编码文件和一个 AAC 音频编码文件按 MP4 封装标准封装往后,就得到一个 MP4 后缀的视频文件,也便是我们常见的 MP4 视频文件了。

音视频编码的紧张目的是压缩原始数据的体积,而封装格式(也称为多媒体容器),比如 MP4,MKV,是用来存储/传输编码数据,并按一定规则把音视频、字幕等数据组织起来,同时还会包含一些元信息,比如当前流中包含哪些编码类型、韶光戳等,播放器可以按照这些信息来匹配解码器、同步音视频。

为了能更好地理解多媒体封装格式,我们再来回顾一下视频播放器的事理。

7.1 视频播放器事理

视频播放器是指能播放以数字旗子暗记形式存储的视频的软件,也指具有播放视频功能的电子器件产品。
大多数视频播放器(除了少数波形文件外)携带解码器以还原经由压缩的媒体文件,视频播放器还要内置一整套转换频率以及缓冲的算法。
大多数的视频播放器还能支持播放音频文件。

视频播放基本处理流程大致包括以下几个阶段:

(1)解协议

从原始的流媒体协议数据中删除信令数据,只保留音视频数据,如采取 RTMP 协议传输的数据,经由解协议后输出 flv 格式的数据。

(2)解封装

分离音频和视频压缩编码数据,常见的封装格式 MP4,MKV,RMVB,FLV,AVI 这些格式。
从而将已经压缩编码的视频、音频数据放到一起。
例如 FLV 格式的数据经由解封装后输出 H.264 编码的视频码流和 AAC 编码的音频码流。

(3)解码

视频,音频压缩编码数据,还原成非压缩的视频,音频原始数据,音频的压缩编码标准包括 AAC,MP3,AC-3 等,视频压缩编码标准包含 H.264,MPEG2,VC-1 等经由解码得到非压缩的视频颜色数据如 YUV420P,RGB 和非压缩的音频数据如 PCM 等。

(4)音视频同步

将同步解码出来的音频和视频数据分别送至系统声卡和显卡播放。

理解完视频播放器的事理,下一步我们来先容多媒体封装格式。

7.2 多媒体封装格式

对付数字媒体数据来说,容器便是一个可以将多媒体数据混在一起存放的东西,就像是一个包装箱,它可以对音、视频数据进行打包装箱,将原来的两块独立的媒体数据整合到一起,当然也可以单单只存放一种类型的媒体数据。

「有时候,多媒体容器也称封装格式,它只是为编码后的多媒体数据供应了一个 “外壳”,也便是将所有的处理好的音频、视频或字幕都包装到一个文件容器内呈现给不雅观众,这个包装的过程就叫封装。
」 常用的封装格式有:MP4,MOV,TS,FLV,MKV 等。
这里我们来先容大家比较熟习的 MP4 封装格式。

7.2.1 MP4 封装格式

MPEG-4 Part 14(MP4)是最常用的容器格式之一,常日以 .mp4 文件结尾。
它用于 HTTP(DASH)上的动态自适应流,也可以用于 Apple 的 HLS 流。
MP4 基于 ISO 基本媒体文件格式(MPEG-4 Part 12),该格式基于 QuickTime 文件格式。
MPEG 代表动态图像专家组,是国际标准化组织(ISO)和国际电工委员会(IEC)的互助。
MPEG 的成立是为了设置音频和视频压缩与传输的标准。

MP4 支持多种编解码器,常用的视频编解码器是 H.264 和 HEVC,而常用的音频编解码器是 AAC,AAC 是著名的 MP3 音频编解码器的后继产品。

MP4 是由一些列的 box 组成,它的最小组成单元是 box。
MP4 文件中的所有数据都装在 box 中,即 MP4 文件由多少个 box 组成,每个 box 有类型和长度,可以将 box 理解为一个数据工具块。
box 中可以包含另一个 box,这种 box 称为 container box。

一个 MP4 文件首先会有且仅有 一个 ftype 类型的 box,作为 MP4 格式的标志并包含关于文件的一些信息,之后会有且只有一个 moov 类型的 box(movie box),它是一种 container box,可以有多个,也可以没有,媒体数据的构造由 metadata 进行描述。

相信有些读者会有疑问 —— 实际的 MP4 文件构造是怎么样的?通过利用 mp4box.js 供应的在线做事,我们可以方便的查看本地或在线 MP4 文件内部的构造:

mp4box.js 在线地址:https://gpac.github.io/mp4box.js/test/filereader.html

由于 MP4 文件构造比较繁芜(不信请看下图),这里我们就不连续展开,有兴趣的读者,可以自行阅读干系文章。

接下来,我们来先容 Fragmented MP4 容器格式。

7.2.2 Fragmented MP4 封装格式

MP4 ISO Base Media 文件格式标准许可以 fragmented 办法组织 box,这也就意味着 MP4 文件可以组织成这样的构造,由一系列的短的 metadata/data box 对组成,而不是一个长的 metadata/data 对。
Fragmented MP4 文件构造如下图所示,图中只包含了两个 fragments:

(图片来源 —— https://alexzambelli.com/blog/2009/02/10/smooth-streaming-architecture/)

在 Fragmented MP4 文件中含有三个非常关键的 boxes:moov、moof 和mdat。

moov(movie metadata box):用于存放多媒体 file-level 的元信息。
mdat(media data box):和普通 MP4 文件的 mdat 一样,用于存放媒体数据,不同的是普通 MP4 文件只有一个 mdat box,而 Fragmented MP4 文件中,每个 fragment 都会有一个 mdat 类型的 box。
moof(movie fragment box):用于存放 fragment-level 的元信息。
该类型的 box 在普通的 MP4 文件中是不存在的,而在 Fragmented MP4 文件中,每个 fragment 都会有一个 moof 类型的 box。

Fragmented MP4 文件中的 fragment 由 moof 和 mdat 两部分组成,每个 fragment 可以包含一个音频轨或视频轨,并且也会包含足够的元信息,以担保这部分数据可以单独解码。
Fragment 的构造如下图所示:

(图片来源 —— https://alexzambelli.com/blog/2009/02/10/smooth-streaming-architecture/)

同样,利用 mp4box.js 供应的在线做事,我们也可以清晰的查看 Fragmented MP4 文件的内部构造:

我们已经先容了 MP4 和 Fragmented MP4 这两种容器格式,我们用一张图来总结一下它们之间的紧张差异:

八、阿宝哥有话说8.1 如何实现视频本地预览

视频本地预览的功能紧张利用 URL.createObjectURL() 方法来实现。
URL.createObjectURL() 静态方法会创建一个 DOMString,个中包含一个表示参数中给出的工具的 URL。
这个 URL 的生命周期和创建它的窗口中的 document 绑定。
这个新的 URL 工具表示指定的 File 工具或 Blob 工具。

<!DOCTYPEhtml><html><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width,initial-scale=1.0"/><title>视频本地预览示例</title></head><body><h3>阿宝哥:视频本地预览示例</h3><inputtype="file"accept="video/"onchange="loadFile(event)"/><videoid="previewContainer"controlswidth="480"height="270"style="display:none;"></video><script>constloadFile=function(event){constreader=newFileReader();reader.onload=function(){constoutput=document.querySelector("#previewContainer");output.style.display="block";output.src=URL.createObjectURL(newBlob([reader.result]));};reader.readAsArrayBuffer(event.target.files[0]);};</script></body></html>8.2 如何实现播放器截图

播放器截图功能紧张利用 CanvasRenderingContext2D.drawImage()API 来实现。
Canvas 2D API 中的 CanvasRenderingContext2D.drawImage() 方法供应了多种办法在 Canvas 上绘制图像。

drawImage API 的语法如下:

void ctx.drawImage(image, dx, dy);

void ctx.drawImage(image, dx, dy, dWidth, dHeight);

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

个中 image 参数表示绘制到高下文的元素。
许可任何的 canvas 图像源(CanvasImageSource),例如:CSSImageValue,HTMLImageElement,SVGImageElement,HTMLVideoElement,HTMLCanvasElement,ImageBitmap 或者 OffscreenCanvas。

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width,initial-scale=1.0"/><title>播放器截图示例</title></head><body><h3>阿宝哥:播放器截图示例</h3><videoid="video"controls="controls"width="460"height="270"crossorigin="anonymous"><!--请更换为实际视频地址--><sourcesrc="https://xxx.com/vid_159411468092581"/></video><buttononclick="captureVideo()">截图</button><script>letvideo=document.querySelector("#video");letcanvas=document.createElement("canvas");letimg=document.createElement("img");img.crossOrigin="";letctx=canvas.getContext("2d");functioncaptureVideo(){canvas.width=video.videoWidth;canvas.height=video.videoHeight;ctx.drawImage(video,0,0,canvas.width,canvas.height);img.src=canvas.toDataURL();document.body.append(img);}</script></body></html>

现在我们已经知道如何获取视频的每一帧,其实在结合 gif.js 这个库供应的 GIF 编码功能,我们就可以快速地实现截取视频帧天生 GIF 动画的功能。
这里阿宝哥不连续展开先容,有兴趣的小伙伴可以阅读 ”利用 JS 直接截取 视频片段 天生 gif 动画“ 这篇文章。

8.3 如何实现 Canvas 播放视频

利用 Canvas 播放视频紧张是利用 ctx.drawImage(video, x, y, width, height) 来对视频当前帧的图像进行绘制,个中 video 参数便是页面中的 video 工具。
以是如果我们按照特定的频率不断获取 video 当前画面,并渲染到 Canvas 画布上,就可以实现利用 Canvas 播放视频的功能。

<!DOCTYPEhtml><html><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width,initial-scale=1.0"/><title>利用Canvas播放视频</title></head><body><h3>阿宝哥:利用 Canvas 播放视频</h3><videoid="video"controls="controls"style="display:none;"><!--请更换为实际视频地址--><sourcesrc="https://xxx.com/vid_159411468092581"/></video><canvasid="myCanvas"width="460"height="270"style="border:1pxsolidblue;"></canvas><div><buttonid="playBtn">播放</button><buttonid="pauseBtn">停息</button></div><script>constvideo=document.querySelector("#video");constcanvas=document.querySelector("#myCanvas");constplayBtn=document.querySelector("#playBtn");constpauseBtn=document.querySelector("#pauseBtn");constcontext=canvas.getContext("2d");lettimerId=null;functiondraw(){if(video.paused||video.ended)return;context.clearRect(0,0,canvas.width,canvas.height);context.drawImage(video,0,0,canvas.width,canvas.height);timerId=setTimeout(draw,0);}playBtn.addEventListener("click",()=>{if(!video.paused)return;video.play();draw();});pauseBtn.addEventListener("click",()=>{if(video.paused)return;video.pause();clearTimeout(timerId);});</script></body></html>8.4 如何实现色度键控(绿屏效果)

上一个示例我们先容了利用 Canvas 播放视频,那么可能有一些小伙伴会有疑问,为什么要通过 Canvas 绘制视频,Video 标签不 “喷鼻香” 么?这是由于 Canvas 供应了 getImageData 和 putImageData 方法使得开拓者可以动态地变动每一帧图像的显示内容。
这样的话,我们就可以实时地操纵视频数据来合成各种视觉殊效到正在呈现的视频画面中。

比如 MDN 上的 ”利用 canvas 处理视频“ 的教程中就演示了如何利用 JavaScript 代码实行色度键控(绿屏或蓝屏效果)。

所谓的色度键控,又称色彩嵌空,是一种去背合成技能。
Chroma 为纯色之意,Key 则是抽离颜色之意。
把被拍摄的人物或物体放置于绿幕的前面,并进行去背后,将其更换成其他的背景。
此技能在电影、电视剧及游戏制作中被大量利用,色键也是虚拟拍照棚(Virtual studio)与视觉效果(Visual effects)当中的一个主要环节。

下面我们来看一下关键代码:

processor.computeFrame=functioncomputeFrame(){this.ctx1.drawImage(this.video,0,0,this.width,this.height);letframe=this.ctx1.getImageData(0,0,this.width,this.height);letl=frame.data.length/4;for(leti=0;i<l;i++){letr=frame.data[i4+0];letg=frame.data[i4+1];letb=frame.data[i4+2];if(g>100&&r>100&&b<43)frame.data[i4+3]=0;}this.ctx2.putImageData(frame,0,0);return;}

以上的 computeFrame() 方法卖力获取一帧数据并实行色度键控效果。
利用色度键控技能,我们还可以实现纯客户端实时蒙版弹幕。
这里阿宝哥就不详细先容了,感兴趣的小伙伴可以阅读一下创宇前端 ”弹幕不挡人!
基于色键技能的纯客户端实时蒙版弹幕“ 这篇文章。

作者:semlinker

转发链接:https://mp.weixin.qq.com/s/tgiHiint3iTfPnr318kuFg