若是咱們想要理解 HTML5 視頻,首先須要知道,你應該知道,但你不知道的內容?那怎麼去判斷呢?
ok,很簡單,我提幾個問題便可,若是某些童鞋知道答案的話,能夠直接跳過。html
你知道 ogg,mp4,flv,webm(前面加個點 .
)這些叫作什麼嗎?前端
那 FLV,MPEG-4,VP8 是啥?git
若是,基友問你要片源,你會說我這是 mp4 的仍是 MPEG-4 的呢?github
固然,還有一些問題,我這裏就不廢話了。上面主要想說的其實就兩個概念:視頻文件格式(容器格式),視頻編解碼器(視頻編碼格式)。固然,還有另一種,叫作音頻編解碼器。簡而言之,就是這三個概念比較重要:web
視頻文件格式(容器格式)vim
視頻編解碼器(視頻編碼格式)跨域
音頻編解碼器(音頻編碼格式)數組
這裏,咱們主要講解一下前面兩個。視頻一開始會由兩個端採集,一個是視頻輸入口,是一個音頻輸入口。而後,採集的數據會分別進行相關處理,簡而言之就是,將視頻/音頻流,經過必定的手段轉換爲比特流。最終,將這裏比特流以必定順序放到一個盒子裏進行存放,從而生成咱們最終所看到的,好比,mp4/mp3/flv 等等音視頻格式。瀏覽器
視頻編碼格式就是咱們上面提到的第一步,將物理流轉換爲比特流,而且進行壓縮。一樣,它的壓縮編碼格式會決定它的視頻文件格式。因此,第一步很重要。針對於 HTML5 中的 video/audio,它其實是支持多種編碼格式的,但侷限於各瀏覽器廠家的普及度,目前視頻格式支持度最高的是 MPEG-4/H.264,音頻則是 MP3/AC3。(下面就主要說下視頻的,音頻就先不談了。)緩存
目前市面上,主流瀏覽器支持的幾個有:
H.264
MEPG-4 第 2 部分
VP8
Ogg
WebM(免費)
其它格式,咱們這裏就不過多贅述,來看一下前兩個比較有趣的。以下圖:
請問,上面箭頭所指的編碼格式是同一個嗎?
答案是:No~
由於,MPEG-4 其實是於 1999 年提出的一個標準。而 H.264 則是後臺做爲優化提出的新的標準。簡單來講就是,咱們一般說的 MPEG-4 其實就是MPEG-4 Part 2。而,H.264 則是MPEG-4(第十部分,也叫ISO/IEC 14496-10),又能夠理解爲 MPEG-4 AVC。而二者,不一樣的地方,能夠參考:latthias 的講解。簡單的區別是:H.264 壓縮率比之前的 MPEG-4(第 2 部分) 高不少。簡單能夠參考的就是:
詳細參考: 編碼格式詳解
視頻文件格式實際上,咱們經常稱做爲容器格式,也就是,咱們通常生活中最常常談到的格式,flv,mp4,ogg 格式等。它就能夠理解爲將比特流按照必定順序放進特定的盒子裏。那選用不一樣格式來裝視頻有什麼問題嗎?
答案是,沒有任何問題,可是你須要知道如何將該盒子解開,而且可以找到對應的解碼器進行解碼。那若是按照這樣看的話,對於這些 mp4,ogv,webm等等視頻格式,只要我有這些對應的解碼器以及播放器,那麼就沒有任何問題。那麼針對於,將視頻比特流放進一個盒子裏面,若是其中某一段出現問題,那麼最終生成的文件其實是不可用的,由於這個盒子自己就是有問題的。
不過,上面有一個誤解的地方在於,我只是將視頻理解爲一個靜態的流。試想一下,若是一個視頻須要持續不斷的播放,例如,直播,現場播報等。這裏,咱們就拿 TS/PS 流來進行講解。
PS(Program Stream): 靜態文件流
TS(Transport Stream): 動態文件流
針對於上面兩種容器格式,其實是對一個視頻比特流作了不同的處理。
PS: 將完成視頻比特流放到一個盒子裏,生成固定的文件
TS: 將接受到的視頻,分紅不一樣的盒子裏。最終生成帶有多個盒子的文件。
那麼結果就是,若是一個或多個盒子出現損壞,PS 格式沒法觀看,而 TS 只是會出現跳幀或者馬賽克效應。二者具體的區別就是:對於視頻的容錯率越高,則會選用 TS,對視頻容錯率越低,則會選用 PS。
經常使用爲:
AVI:MPEG-2,DIVX,XVID,AC-1,H.264;
WMV:WMV,AC-1;
RM、RMVB:RV, RM;
MOV:MPEG-2,XVID,H.264;
TS/PS:MPEG-2,H.264,MPEG-4;
MKV:能夠封裝全部的視頻編碼格式。
詳細參考:視頻文件格式
2016 年是直播元年,一是因爲各大寬帶提供商順應民意增寬降價
,二是大量資本流進了直播板塊,促進了技術的更新迭代。市面上,最經常使用的是 Apple 推出的 HLS 直播協議(原始支持 H5 播放),固然,還有 RTMP、HTTP-FLV、RTP等。
這裏,再問一個問題:
HLS 和 MPEG-4/H.264 以及容器格式 TS/PS 是啥關係?
簡單來講,不要緊。
HLS 根本就不會涉及到視頻自己的解碼問題。它的存在只是爲了確保你的視頻可以及時,快速,正確的播放。
如今,直播行業依舊很火,而 HTML5 直播,一直以來都是一個比較蛋疼的內容。一是,瀏覽器廠商更新速度比較慢,二是,這並非咱們前端專攻的一塊,因此,有時候的確很雞肋。固然,進了前端,你就別想着休息。接下來,咱們來詳細的看一下市面上主流的幾個協議。
HLS 全稱是 HTTP Live Streaming。這是 Apple 提出的直播流協議。目前,IOS 和 高版本 Android 都支持 HLS。那什麼是 HLS 呢?
HLS 主要的兩塊內容是 .m3u8
文件和 .ts
播放文件。接受服務器會將接受到的視頻流進行緩存,而後緩存到必定程度後,會將這些視頻流進行編碼格式化,同時會生成一份 .m3u8
文件和其它不少的 .ts
文件。根據 wiki 闡述,HLS 的基本架構爲:
服務器:後臺服務器接受視頻流,而後進行編碼和片斷化。
編碼:視頻格式編碼採用 H.264。音頻編碼爲 AAC, MP3, AC-3,EC-3。而後使用 MPEG-2 Transport Stream 做爲容器格式。
分片:將 TS 文件分紅若干個相等大小的 .ts
文件。而且生成一個 .m3u8
做爲索引文件(確保包的順序)
分發:因爲 HLS 是基於 HTTP 的,因此,做爲分發,最經常使用的就是 CDN 了。
客戶端:使用一個 URL 去下載 m3u8 文件,而後,開始下載 ts 文件,下載完成後,使用 playback software
(即時播放器) 進行播放。
這裏,咱們着重介紹一下客戶端的過程。首先,直播之因此是直播,在於它的內容是實時更新的。那 HLS 是怎麼完成呢?
咱們使用 HLS 直接就用一個 video 進行包括便可:
<video controls autoplay> <source src="http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8" type="application/vnd.apple.mpegurl" /> <p class="warning">Your browser does not support HTML5 video.</p> </video>
根據上面的描述,它實際上就是去請求一個 .m3u8
的索引文件。該文件包含了對 .ts
文件的相關描述,例如:
#EXT-X-VERSION:3 PlayList 的版本,可帶可不帶。下面有說明 #EXTM3U m3u文件頭 #EXT-X-TARGETDURATION:10 分片最大時長,單位爲 s #EXT-X-MEDIA-SEQUENCE:1 第一個TS分片的序列號,若是沒有,默認爲 0 #EXT-X-ALLOW-CACHE 是否容許cache #EXT-X-ENDLIST m3u8文件結束符 #EXTINF 指定每一個媒體段(ts)的持續時間(秒),僅對其後面的URI有效
不過,這只是一個很是簡單,不涉及任何功能的直播流。實際上,HLS 的整個架構,能夠分爲:
固然,若是你使用的是 masterplaylist
做爲連接,如:
<video controls autoplay> <source src="http://devimages.apple.com/iphone/samples/bipbop/masterplaylist.m3u8" type="application/vnd.apple.mpegurl" /> <p class="warning">Your browser does not support HTML5 video.</p> </video>
咱們看一下,masterplaylist 裏面具體的內容是啥:
#EXTM3U #EXT-X-VERSION:6 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2855600,CODECS="avc1.4d001f,mp4a.40.2",RESOLUTION=960x540 live/medium.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=5605600,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720 live/high.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1755600,CODECS="avc1.42001f,mp4a.40.2",RESOLUTION=640x360 live/low.m3u8 #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=545600,CODECS="avc1.42001e,mp4a.40.2",RESOLUTION=416x234 live/cellular.m3u8
EXT-X-STREAM-INF
這個標籤頭表明:當前用戶的播放環境。masterplaylist 主要乾的事就是根據, 當前用戶的帶寬,分辨率,解碼器等條件決定使用哪個流。因此,master playlist 是爲了更好的用戶體驗而存在的。不過,弊端就是後臺儲備流的量會成倍增長。
如今,咱們來主要看一下,若是你使用 master playlist,那麼整個流程是啥?
當填寫了 master playlist URL,那麼用戶只會下載一次該 master playlist。接着,播放器根據當前的環境決定使用哪個 media playlist(就是 子 m3u8 文件)。若是,在播放當中,用戶的播放條件發生變化時,播放器也會切換對應的 media playlist。關於 master playlist 內容,咱們就先介紹到這裏。
關於 HLS,感受主要內容還在 media playlist 上。固然,media playlist 還分爲三種 list:
live playlist: 動態列表。顧名思義,該列表是動態變化的,裏面的 ts 文件會實時更新,而且過時的 ts 索引會被刪除。默認,狀況下都是使用動態列表。
event playlist: 靜態列表。它和動態列表主要區別就是,原來的 ts 文件索引不會被刪除,該列表是不斷更新,並且文件大小會逐漸增大。它會在文件中,直接添加 #EXT-X-PLAYLIST-TYPE:EVENT
做爲標識。
VOD playlist: 全量列表。它就是將全部的 ts 文件都列在 list 當中。若是,使用該列表,就和播放一整個視頻沒有啥區別了。它是使用 #EXT-X-ENDLIST
表示文件結尾。
live playlist DEMO:
#EXTM3U #EXT-X-VERSION:6 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:26 #EXTINF:9.901, http://media.example.com/wifi/segment26.ts #EXTINF:9.901, http://media.example.com/wifi/segment27.ts #EXTINF:9.501, http://media.example.com/wifi/segment28.ts
evet playlist DEMO:
#EXTM3U #EXT-X-VERSION:6 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:EVENT #EXTINF:9.9001, http://media.example.com/wifi/segment0.ts #EXTINF:9.9001, http://media.example.com/wifi/segment1.ts #EXTINF:9.9001, http://media.example.com/wifi/segment2.ts
VOD playlist DEMO:
#EXTM3U #EXT-X-VERSION:6 #EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:9.9001, http://media.example.com/wifi/segment0.ts #EXTINF:9.9001, http://media.example.com/wifi/segment1.ts #EXTINF:9.9001, http://media.example.com/wifi/segment2.ts #EXT-X-ENDLIST
上面提到過一個 EXT-X-VERSION
這樣的標籤,這是用來表示當前 HLS 的版本。那 HLS 有哪些版本呢?
根據 apple 官方文檔 的說明,咱們能夠了解到,不一樣版本的區別:
固然,HLS 支持的功能,並不僅是分片播放(專門適用於直播),它還包括其餘應有的功能。
使用 HTTPS 加密 ts 文件
快/倒放
廣告插入
不一樣分辨率視頻切換
因爲 HLS 是基於 HTTP 的,因此,它關於 HTTP 的好處,咱們大部分都瞭解,好比,高兼容性,高可擴展性等。不過正因爲是 HTTP 協議,因此會在握手協議上形成必定的延遲性。HLS 首次鏈接時,總共的延時包括:
TCP 握手,2. m3u8 文件下載,3. m3u8 下的 ts 文件下載。
其中,每一個 ts 文件,大概會存放 5s~10s 的時長,而且每一個 m3u8 文件會存放 3~8 個 ts 文件。咱們折中算一下,5 個 ts 文件,每一個時長大約 8s 那麼,總的下來,一共延時 40s。固然,這還不算上 TCP 握手,m3u8 文件下載等問題。那優化辦法有嗎?有的,那就是減小每一個 m3u8 文件中的 ts 數量和 ts 文件時長,不過,這樣也會成倍的增長後臺承受流量請求的壓力。因此,這仍是須要到業務中去探索最優的配置(打個廣告:騰訊雲的直播視頻流業務,作的確實挺棒。)
關於 HLS 的詳細內容,能夠參考:HLS 詳解
關於 m3u8 文件的標籤內容,能夠參考:HLS 標籤頭詳解
總而言之,HLS 之因此能這麼流行,關鍵在於它的支持度是真的廣,因此,對於通常 H5 直播來講,應該是很是友好的。不過,既然是直播,關鍵在於它的實時性,而 HLS 天生就存在必定的延時,因此,就能夠考慮其餘低延時的方案,好比 RTMP,HTTP-FLV。下面,咱們來看一下 RTMP 內容。
RTMP 全稱爲:Real-Time Messaging Protocol 。它是專門應對實時交流場景而開發出來的一個協議。它爹是 Macromedia,後來賣身給了 Adobe。RTMP 根據不一樣的業務場景,有不少變種:
純 RTMP 使用 TCP 鏈接,默認端口爲 1935(有可能被封)。
RTMPS: 就是 RTMP + TLS/SSL
RTMPE: RTMP + encryption。在 RTMP 原始協議上使用,Adobe 自身的加密方法
RTMPT: RTMP + HTTP。使用 HTTP 的方式來包裹 RTMP 流,這樣能直接經過防火牆。
RTMFP: RMPT + UDP。該協議經常用於 P2P 的場景中,針對延時有變態的要求。
既然是 Adobe 公司開發的(算吧),那麼,該協議針對的就是 Flash Video,即,FLV。不過,在移動端上,Flash Player 已經被殺絕了,那爲啥還會出現這個呢?簡單來講,它主要是針對 PC 端的。RTMP 出現的時候,仍是 零幾 年的時候,IE 還在大行其道,Flash Player 也並未被各大瀏覽器所排斥。那時候 RTMP 毋庸置疑的能夠在視頻界有本身的一席之地。
RTMP 因爲藉由 TCP 長鏈接協議,因此,客戶端向服務端推流這些操做而言,延時性很低。它會將上傳的流分紅不一樣的分片,這些分片的大小,有時候變,有時候不會變。默認狀況下就是,64B 的音頻數據 + 128B 的視頻數據 + 其它數據(好比 頭,協議標籤等)。但 RTMP 具體傳輸的時候,會將分片進一步劃分爲包,即,視頻包,音頻包,協議包等。由於,RTMP 在進行傳輸的時候,會創建不一樣的通道,來進行數據的傳輸,這樣對於不一樣的資源,對不一樣的通道設置相關的帶寬上限。
RTMP 處理的格式是 MP3/ACC + FLV1。
不過,因爲支持性的緣由,RTMP 並未在 H5 直播中,展現出優點。下列是簡單的對比:
HTTP-FLV 和 RTMPT 相似,都是針對於 FLV 視頻格式作的直播分發流。但,二者有着很大的區別。
相同點
二者都是針對 FLV 格式
二者延時都很低
二者都走的 HTTP 通道
不一樣點
HTTP-FLv
直接發起長鏈接,下載對應的 FLV 文件
頭部信息簡單
RTMPT
握手協議過於複雜
分包,組包過程耗費精力大
經過上面來看,HTTP-FLV 和 RTMPT 確實不是一回事,但,若是瞭解 SRS(simple rtmp server),那麼 對 HTTP-FLV 應該清楚很多。SRS 本質上,就是 RTMP + FLV 進行傳輸。由於 RTMP 發的包很容易處理,一般 RTMP 協議會做爲視頻上傳端來處理,而後經由服務器轉換爲 FLV 文件,經過 HTTP-FLV 下發給用戶。
如今市面上,比較經常使用的就是 HTTP-FLV 進行播放。但,因爲手機端上不支持,因此,H5 的 HTTP-FLV 也是一個痛點。不過,如今 flv.js 能夠幫助高版本的瀏覽器,經過 mediaSource 來進行解析。HTTP-FLV 的使用方式也很簡單。和 HLS 同樣,只須要添加一個鏈接便可:
<object type="application/x-shockwave-flash" src="http://s6.pdim.gs/static/a2a36bc596148316.flv"></object>
不過,並非末尾是 .flv
的都是 HTTP-FLV 協議,由於,涉及 FLV 的流有三種,它們三種的使用方式都是如出一轍的。
FLV 文件:至關於就是一整個文件,官方稱爲 漸進 HTTP 流。它的特色是隻能漸進下載,不能進行點播。
FLV 僞流:該方式,能夠經過在末尾添加 ?start=xxx
的參數,指定返回的對應開始時間視頻數據。該方式比上面那種就多了一個點播的功能。本質上仍是 FLV 直播。
FLV 直播流:這就是 HTTP-FLV 真正所支持的流。SRS 在內部使用的是 RTMP 進行分發,而後在傳給用戶的使用,通過一層轉換,變爲 HTTP 流,最終傳遞給用戶。
上面說到,HTTP-FLV 就是長鏈接,簡而言之只須要加上一個 Connection:keep-alive
便可。關鍵是它的響應頭,因爲,HTTP-FLV 傳遞的是視頻格式,全部,它的 Content-Type
和 Transfer-Encoding
須要設置其它值。
Content-Type:video/x-flv Expires:Fri, 10 Feb 2017 05:24:03 GMT Pragma:no-cache Transfer-Encoding:chunked
不過,通常而言,直播服務器通常和業務服務是不會放在一塊的,因此這裏,可能會額外須要支持跨域直播的相關技術。在 XHR2 裏面,解決辦法也很簡單,直接使用 CORS 便可:
// 那麼整個響應頭,能夠爲: Access-Control-Allow-credentials:true Access-Control-Allow-max-age:86400 Access-Control-Allow-methods:GET,POST,OPTIONS Access-Control-Allow-Origin:* Cache-Control:no-cache Content-Type:video/x-flv Expires:Fri, 10 Feb 2017 05:24:03 GMT Pragma:no-cache Transfer-Encoding:chunked
對於 HTTP-FLV 來講,關鍵難點在於 RTMP 和 HTTP 協議的轉換,這裏我就很少說了。由於,咱們主要針對的是前端開發,講一下和前端相關的內容。
接下來,咱們在主要來介紹一下 FLV 格式的。由於,後面咱們須要經過 mediaSource 來解碼 FLV。
FLV 原始格式,Adobe 能夠直接看 flv格式詳解。我這裏就抽主要的內容講講。FLV 也是與時俱進,之前 FLV 的格式叫作 FLV,新版的能夠叫作 F4V。二者的區別,簡單的區分方法就是:
FLV 是專門針對 Flash 播放器的
F4V 是有點像 MEPG 格式的 Flash 播放,主要爲了兼容 H.264/ACC。F4V 不支持 FLV(二者原本都不是同一個格式)
這裏咱們主要針對 FLV 進行相關了解。由於,通常狀況下,後臺發送視頻流時,爲了簡潔快速,就是發送 FLV 視頻。FLV 因爲年限比較久,它所支持的內容是 H.263,VP6 codec。FLV 通常能夠嵌套在 .swf
文件當中,不過,對於 HTTP-FLV
等 FLV 直播流來講,通常直接使用 .flv
文件便可。在 07 年的時候,提出了 F4V 這個視頻格式,固然,FLV 等也會向前兼容。
這裏,咱們來正式介紹一下 FLV 的格式。一個完整的 FLV 流包括 FLV Header + FLV Packets。
FLV 格式頭不難,就幾個字段:
Field | Data Type | Default | Details |
---|---|---|---|
Signature | byte[3] | 「FLV」 | 有三個B的大小,算是一種身份的象徵 |
Version | uint8 | 1 | 只有 0x01 是有效的。其實就是默認值 |
Flags | uint8 bitmask | 0x05 | 表示該流的特徵。0x04 是 audio,0x01 是 video,0x05 是 audio+video |
Header Size | uint32_be | 9 | 用來跳過多餘的頭 |
在 FLV 的頭部以後,就正式開始發送 FLV 文件。文件會被拆解爲數個包(FLV tags)進行傳輸。每一個包都帶有 15B 的頭。前 4 個字節是用來表明前一個包的頭部內容,用來完成倒放的功能。整個包的結構爲:
具體解釋以下:
字段 | 字段大小 | 默認值 | 詳解 |
---|---|---|---|
Size of previous packet | uint32_be | 0 | 關於前一個包的信息,若是是第一個包,則該部分爲 NULL |
Packet Type | uint8 | 18 | 設置包的內容,若是是第一個包,則該部分爲 AMF 元數據 |
Payload Size | uint24_be | varies | 該包的大小 |
Timestamp Lower | uint24_be | 0 | 起始時間戳 |
Timestamp Upper | uint8 | 0 | 持續時間戳,一般加上 Lower 實際上戳,表明整個時間。 |
Stream ID | uint24_be | 0 | 流的類型,第一個流設爲 NULL |
Payload Data | freeform | varies | 傳輸數據 |
其中,因爲 Packet Type 的值能夠取多個, 須要額外說明一下。
Packet Type
1: RTMP 包的大小
3: RTMP 字節讀包反饋,RTMP ping,RTMP 服務器帶寬,RTMP 客戶端帶寬
8: 音頻和視頻的數據
15: RTMP flex 流
24: 通過封裝的 flash video。
上面是關於 FLV 簡單的介紹。不過,若是沒有 `
Media Source Extensions` 的幫助,那麼上面說的基本上全是廢話。因爲,Flash Player 已經被時代所遺棄,因此,咱們不能在瀏覽器上,順利的播放 FLV 視頻。接下來,咱們先來詳細瞭解一下 MSE 的相關內容。
在沒有 MSE 出現以前,前端對 video 的操做,僅僅侷限在對視頻文件的操做,而並不能對視頻流作任何相關的操做。如今 MSE 提供了一系列的接口,使開發者能夠直接提供 media stream。
那 MSE 是如何完成視頻流的加載和播放呢?
這能夠參考 google 的 MSE 簡介
var vidElement = document.querySelector('video'); if (window.MediaSource) { var mediaSource = new MediaSource(); vidElement.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', sourceOpen); } else { console.log("The Media Source Extensions API is not supported.") } function sourceOpen(e) { URL.revokeObjectURL(vidElement.src); var mime = 'video/webm; codecs="opus, vp9"'; var mediaSource = e.target; var sourceBuffer = mediaSource.addSourceBuffer(mime); var videoUrl = 'droid.webm'; fetch(videoUrl) .then(function(response) { return response.arrayBuffer(); }) .then(function(arrayBuffer) { sourceBuffer.addEventListener('updateend', function(e) { if (!sourceBuffer.updating && mediaSource.readyState === 'open') { mediaSource.endOfStream(); } }); sourceBuffer.appendBuffer(arrayBuffer); }); }
能夠從上面的代碼看出,一套完整的執行代碼,不只須要使用 MSE 並且,還有一下這些相關的 API。
HTMLVideoElement.getVideoPlaybackQuality()
SourceBuffer
SourceBufferList
TextTrack.sourceBuffer
TrackDefault
TrackDefaultList
URL.createObjectURL()
VideoPlaybackQuality
VideoTrack.sourceBuffer
咱們簡單講解一下上面的流程。根據 google 的闡述,整個過程能夠爲:
第一步,經過異步拉取數據。
第二步,經過 MediaSource 處理數據。
第三步,將數據流交給 audio/video 標籤進行播放。
而中間傳遞的數據都是經過 Buffer
的形式來進行傳遞的。
中間有個須要注意的點,MS 的實例經過 URL.createObjectURL()
建立的 url 並不會同步鏈接到 video.src。換句話說,URL.createObjectURL()
只是將底層的流(MS)和 video.src 鏈接中間者,一旦二者鏈接到一塊兒以後,該對象就沒用了。
那麼何時 MS 纔會和 video.src 鏈接到一塊兒呢?
建立實例都是同步的,可是底層流和 video.src 的鏈接時異步的。MS 提供了一個 sourceopen
事件給咱們進行這項異步處理。一旦鏈接到一塊兒以後,該 URL object 就沒用了,處於內存節省的目的,可使用 URL.revokeObjectURL(vidElement.src)
銷燬指定的 URL object。
mediaSource.addEventListener('sourceopen', sourceOpen); function sourceOpen(){ URL.revokeObjectURL(vidElement.src) }
MS 提供了咱們對底層音視頻流的處理,那一開始咱們怎麼決定以何種格式進行編解碼呢?
這裏,可使用 addSourceBuffer(mime)
來設置相關的編碼器:
var mime = 'video/webm; codecs="opus, vp9"'; var sourceBuffer = mediaSource.addSourceBuffer(mime);
而後經過,異步拉取相關的音視頻流:
fetch(url) .then(res=>{ return res.arrayBuffer(); }) .then(buffer=>{ sourceBuffer.appendBuffer(buffer); })
若是視頻已經傳完了,而相關的 Buffer 還在佔用內存,這時候,就須要咱們顯示的中斷當前的 Buffer 內容。那麼最終咱們的異步處理結果變爲:
fetch(url) .then(res=>{ return res.arrayBuffer(); }) .then(function(arrayBuffer) { sourceBuffer.addEventListener('updateend', function(e) { // 是否有持續更新的流 if (!sourceBuffer.updating && mediaSource.readyState === 'open') { // 沒有,則中斷鏈接 mediaSource.endOfStream(); } }); sourceBuffer.appendBuffer(arrayBuffer); });
上面咱們大體瞭解了一下關於 Media Source Extensions 的大體流程,但裏面的細節咱們尚未細講。接下來,咱們來具體看一下 MSE 一籃子的生態技術包含哪些內容。首先是,MediaSource
MS(MediaSource) 能夠理解爲多個視頻流的管理工具。之前,咱們只能下載一個清晰度的流,而且不能平滑切換低畫質或者高畫質的流,而如今咱們能夠利用 MS 實現這裏特性。咱們先來簡單瞭解一下他的 API。
建立一個 MS:
var mediaSource = new MediaSource();
該是用來返回一個具體的視頻流,接受一個 mimeType 表示該流的編碼格式。例如:
var mimeType = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"'; var sourceBuffer = mediaSource.addSourceBuffer(mimeType);
sourceBuffer 是直接和視頻流有交集的 API。例如:
function sourceOpen (_) { var mediaSource = this; var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec); fetchAB(assetURL, function (buf) { sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); video.play(); }); // 經過 fetch 添加視頻 Buffer sourceBuffer.appendBuffer(buf); }); };
它經過 appendBuffer
直接添加視頻流,實現播放。不過,在使用 addSourceBuffer
建立以前,還須要保證當前瀏覽器是否支持該編碼格式。
用來移除某個 sourceBuffer。移除也主要是考慮性能緣由,將不須要的流移除以節省相應的空間,格式爲:
mediaSource.removeSourceBuffer(sourceBuffer);
用來表示接受的視頻流的中止,注意,這裏並非斷開,至關於只是下好了一部分視頻,而後你能夠進行播放。此時,MS 的狀態變爲:ended
。例如:
var mediaSource = this; var sourceBuffer = mediaSource.addSourceBuffer(mimeCodec); fetchAB(assetURL, function (buf) { sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); // 結束當前的接受 video.play(); // 能夠播放當前得到的流 }); sourceBuffer.appendBuffer(buf); });
該是用來檢測當前瀏覽器是否支持指定視頻格式的解碼。格式爲:
var isItSupported = mediaSource.isTypeSupported(mimeType); // 返回值爲 Boolean
mimeType 能夠爲 type 或者 type + codec。
例如:
// 不一樣的瀏覽器支持不同,不過基本的類型都支持。 MediaSource.isTypeSupported('audio/mp3'); // false,這裏應該爲 audio/mpeg MediaSource.isTypeSupported('video/mp4'); // true MediaSource.isTypeSupported('video/mp4; codecs="avc1.4D4028, mp4a.40.2"'); // true
這裏有一份具體的 mimeType 參考列表。
當 MS 從建立開始,都會自帶一個 readyState
屬性,用來表示其當前打開的狀態。MS 有三個狀態:
closed: 當前 MS 沒有和 media element(好比:video.src) 相關聯。建立時,MS 就是該狀態。
open: source 打開,而且準備接受經過 sourceBuffer.appendBuffer 添加的數據。
ended: 當 endOfStream() 執行完成,會變爲該狀態,此時,source 依然和 media element 鏈接。
var mediaSource = new MediaSource; mediaSource.readyState; // 默認爲 closed
當由 closed 變爲 open 狀態時,須要監聽 sourceopen
事件。
video.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', sourceOpen);
MS 針對這幾個狀態變化,提供了相關的事件:sourceopen
,sourceended
,sourceclose
。
sourceopen: 當 "closed" to "open" 或者 "ended" to "open" 時觸發。
sourceended: 當 "open" to "ended" 時觸發。
sourceclose: 當 "open" to "closed" 或者 "ended" to "closed" 時觸發。
MS 還提供了其餘的監聽事件 sourceopen,sourceended,sourceclose,updatestart,update,updateend,error,abort,addsourcebuffer,removesourcebuffer. 這裏主要選了比較重要的,其餘的能夠參考官方文檔。
比較經常使用的屬性有: duration,readyState。
duration: 得到當前媒體播放的時間,既能夠設置(get),也能夠獲取(set)。單位爲 s(秒)
mediaSource.duration = 5.5; // 設置媒體流播放的時間 var myDuration = mediaSource.duration; // 得到媒體流開始播放的時間
在實際應用中爲:
sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); mediaSource.duration = 120; // 設置當前流播放的時間 video.play(); });
readyState: 得到當前 MS 的狀態。取值上面已經講過了: closed
,open
,ended
。
var mediaSource = new MediaSource; //此時的 mediaSource.readyState 狀態爲 closed
以及:
sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); // 調用該方法後結果爲:ended video.play(); });
除了上面兩個屬性外,還有 sourceBuffers
,activeSourceBuffers
這兩個屬性。用來返回經過 addSourceBuffer()
建立的 SourceBuffer 數組。這沒啥過多的難度。
接下來咱們就來看一下靠底層的 sourceBuffer
。
SourceBuffer 是由 mediaSource
建立,並直接和 HTMLMediaElement
接觸。簡單來講,它就是一個流的容器,裏面提供的 append()
,remove()
來進行流的操做,它能夠包含一個或者多個 media segments
。一樣,接下來,咱們再來看一下該構造函數上的基本屬性和內容。
前面說過 sourceBuffer 主要是一個用來存放流的容器,那麼,它是怎麼存放的,它存放的內容是啥,有沒有順序等等。這些都是 sourceBuffer 最最根本的問題。OK,接下來,咱們來看一下的它的基本架構有些啥。
參考 W3C,能夠基本瞭解到裏面的內容爲:
interface SourceBuffer : EventTarget { attribute AppendMode mode; readonly attribute boolean updating; readonly attribute TimeRanges buffered; attribute double timestampOffset; readonly attribute AudioTrackList audioTracks; readonly attribute VideoTrackList videoTracks; readonly attribute TextTrackList textTracks; attribute double appendWindowStart; attribute unrestricted double appendWindowEnd; attribute EventHandler onupdatestart; attribute EventHandler onupdate; attribute EventHandler onupdateend; attribute EventHandler onerror; attribute EventHandler onabort; void appendBuffer(BufferSource data); void abort(); void remove(double start, unrestricted double end); };
上面這些屬性決定了其 sourceBuffer 整個基礎。
首先是 mode
。上面說過,SB(SourceBuffer) 裏面存儲的是 media segments(就是你每次經過 append 添加進去的流片斷)。SB.mode 有兩種格式:
segments: 亂序排放。經過 timestamps
來標識其具體播放的順序。好比:20s的 buffer,30s 的 buffer 等。
sequence: 按序排放。經過 appendBuffer
的順序來決定每一個 mode 添加的順序。timestamps
根據 sequence 自動產生。
那麼上面兩個哪一個是默認值呢?
看狀況,講真,沒騙你。
當 media segments
天生自帶 timestamps
,那麼 mode
就爲 segments
,不然爲 sequence
。因此,通常狀況下,咱們是不用管它的值。不過,你能夠在後面,將 segments
設置爲 sequence
這個是沒毛病的。反之,將 sequence
設置爲 segments
就有問題了。
var bufferMode = sourceBuffer.mode; if (bufferMode == 'segments') { sourceBuffer.mode = 'sequence'; }
而後另外兩個就是 buffered
和 updating
。
buffered:返回一個 timeRange 對象。用來表示當前被存儲在 SB 中的 buffer。
updating: 返回 Boolean,表示當前 SB 是否正在被更新。例如: SourceBuffer.appendBuffer(), SourceBuffer.appendStream(), SourceBuffer.remove() 調用時。
另外還有一些其餘的相關屬性,好比 textTracks,timestampOffset,trackDefaults,這裏就很少說了。實際上,SB 是一個事件驅動的對象,一些常見的處理,都是在具體的事件中完成的。那麼它又有哪些事件呢?
在 SB 中,相關事件觸發包括:
updatestart: 當 updating 由 false 變爲 true。
update:當 append()/remove() 方法被成功調用完成時,updating 由 true 變爲 false。
updateend: append()/remove() 已經結束
error: 在 append() 過程當中發生錯誤,updating 由 true 變爲 false。
abort: 當 append()/remove() 過程當中,使用 abort()
方法廢棄時,會觸發。此時,updating 由 true 變爲 false。
注意上面有兩個事件比較相似:update
和 updateend
。都是表示處理的結束,不一樣的是,update 比 updateend 先觸發。
sourceBuffer.addEventListener('updateend', function (e) { // 當指定的 buffer 加載完後,就能夠開始播放 mediaSource.endOfStream(); video.play(); });
SB 處理流的方法就是 +/- : appendBuffer, remove。另外還有一箇中斷處理函數 abort()
。
appendBuffer(ArrayBuffer):用來添加 ArrayBuffer。該 ArrayBuffer 通常是經過 fetch 的 response.arrayBuffer();
來獲取的。
remove(start, end): 用來移除具體某段的 media segments。
@param start/end: 都是時間單位(s)。用來表示具體某段的 media segments 的範圍。
abort(): 用來放棄當前 append 流的操做。不過,該方法的業務場景也比較有限。它只能用在當 SB 正在更新流的時候。即,此時經過 fetch,已經接受到新流,而且使用 appendBuffer 添加,此爲開始的時間。而後到 updateend 事件觸發以前,這段時間以內調用 abort()
。有一個業務場景是,當用戶移動進度條,而,此時 fetch 已經獲取前一次的 media segments,那麼可使用 abort
放棄該操做,轉而請求新的 media segments。具體能夠參考:abort 使用
上面主要介紹了處理音視頻流須要用的 Web 技術,後面章節,咱們接入實戰,具體來說一下,如何作到使用 MSE 進行 remux 和 demux。