本文作者:ivweb villainthr
接 《 全面進階 H5 直播(上)》
在沒有 MSE 出現之前,前端對 video 的操作,僅僅局限在對視訊檔案的操作,而并不能對視訊流做任何相關的操作。現在 MSE 提供了一系列的接口,使開發者可以直接提供 media stream。
那 MSE 是如何完成視訊流的加載和播放呢?
這可以參考 google 的 MSE 簡介
可以從上面的代碼看出,一套完整的執行代碼,不僅需要使用 MSE 而且,還有一下這些相關的 API。
HTMLVideoElement.getVideoPlaybackQuality()
SourceBuffer
SourceBufferList
TextTrack.sourceBuffer
TrackDefault
TrackDefaultList
URL.createObjectURL()
VideoPlaybackQuality
VideoTrack.sourceBuffer
我們簡單講解一下上面的流程。根據 google 的闡述,整個過程可以為:

第一步,通過異步拉取資料。
第二步,通過 MediaSource 處理資料。
第三步,将資料流交給 audio/video 标簽進行播放。
而中間傳遞的資料都是通過 <code>Buffer</code>的形式來進行傳遞的。
中間有個需要注意的點,MS 的執行個體通過 <code>URL.createObjectURL()</code> 建立的 url 并不會同步連接配接到 video.src。換句話說,<code>URL.createObjectURL()</code> 隻是将底層的流(MS)和 video.src 連接配接中間者,一旦兩者連接配接到一起之後,該對象就沒用了。
那麼什麼時候 MS 才會和 video.src 連接配接到一起呢?
建立執行個體都是同步的,但是底層流和 video.src 的連接配接時異步的。MS 提供了一個<code>sourceopen</code> 事件給我們進行這項異步處理。一旦連接配接到一起之後,該 URL object 就沒用了,處于記憶體節省的目的,可以使用 <code>URL.revokeObjectURL(vidElement.src)</code> 銷毀指定的
MS 提供了我們對底層音視訊流的處理,那一開始我們怎麼決定以何種格式進行編解碼呢?
這裡,可以使用<code>addSourceBuffer(mime)</code>來設定相關的編碼器:
然後通過,異步拉取相關的音視訊流:
如果視訊已經傳完了,而相關的 Buffer 還在占用記憶體,這時候,就需要我們顯示的中斷目前的 Buffer 内容。那麼最終我們的異步處理結果變為:
上面我們大緻了解了一下關于 Media Source Extensions 的大緻流程,但裡面的細節我們還沒有細講。接下來,我們來具體看一下 MSE 一籃子的生态技術包含哪些内容。首先是,MediaSource
MS(MediaSource) 可以了解為多個視訊流的管理工具。以前,我們隻能下載下傳一個清晰度的流,并且不能平滑切換低畫質或者高畫質的流,而現在我們可以利用 MS 實作這裡特性。我們先來簡單了解一下他的 API。
建立一個 MS:
<code>var mediaSource = new MediaSource();</code>
該是用來傳回一個具體的視訊流,接受一個 mimeType 表示該流的編碼格式。例如:
sourceBuffer 是直接和視訊流有交集的 API。例如:
它通過<code>appendBuffer</code>直接添加視訊流,實作播放。不過,在使用 <code>addSourceBuffer</code> 建立之前,還需要保證目前浏覽器是否支援該編碼格式。
用來移除某個 sourceBuffer。移除也主要是考慮性能原因,将不需要的流移除以節省相應的空間,格式為:
<code>mediaSource.removeSourceBuffer(sourceBuffer);</code>
用來表示接受的視訊流的停止,注意,這裡并不是斷開,相當于隻是下好了一部分視訊,然後你可以進行播放。此時,MS 的狀态變為:<code>ended</code>。例如:
該是用來檢測目前浏覽器是否支援指定視訊格式的解碼。格式為:
<code>var isItSupported = mediaSource.isTypeSupported(mimeType); // 傳回值為 Boolean</code>
mimeType 可以為 type 或者 type + codec。
例如:
這裡有一份具體的 mimeType 參考清單。
當 MS 從建立開始,都會自帶一個<code>readyState</code> 屬性,用來表示其目前打開的狀态。MS 有三個狀态:
closed: 目前 MS 沒有和 media element(比如:video.src) 相關聯。建立時,MS 就是該狀态。
open: source 打開,并且準備接受通過 sourceBuffer.appendBuffer 添加的資料。
ended: 當 endOfStream() 執行完成,會變為該狀态,此時,source 依然和 media element 連接配接。
<code>var mediaSource = new MediaSource; mediaSource.readyState; // 預設為 closed</code>
當由 closed 變為 open 狀态時,需要監聽 sourceopen 事件。
<code>video.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', sourceOpen);</code>
MS 針對這幾個狀态變化,提供了相關的事件:<code>sourceopen</code>,<code>sourceended</code>,<code>sourceclose</code>。
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(秒)
<code>mediaSource.duration = 5.5; // 設定媒體流播放的時間</code> <code>var myDuration = mediaSource.duration; // 獲得媒體流開始播放的時間</code>
在實際應用中為:
<code>sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); mediaSource.duration = 120; // 設定目前流播放的時間 video.play(); });</code>
readyState: 獲得目前 MS 的狀态。取值上面已經講過了:<code>closed</code>,<code>open</code>,<code>ended</code>。
<code>var mediaSource = new MediaSource; //此時的 mediaSource.readyState 狀态為 closed</code>
以及:
<code>sourceBuffer.addEventListener('updateend', function (_) { mediaSource.endOfStream(); // 調用該方法後結果為:ended video.play(); });</code>
除了上面兩個屬性外,還有 <code>sourceBuffers</code>,<code>activeSourceBuffers</code>這兩個屬性。用來傳回通過 <code>addSourceBuffer()</code> 建立的 SourceBuffer 數組。這沒啥過多的難度。
接下來我們就來看一下靠底層的<code>sourceBuffer</code>。
SourceBuffer 是由<code>mediaSource</code> 建立,并直接和 <code>HTMLMediaElement</code>接觸。簡單來說,它就是一個流的容器,裡面提供的 <code>append()</code>,<code>remove()</code>來進行流的操作,它可以包含一個或者多個 <code>media segments</code>。同樣,接下來,我們再來看一下該構造函數上的基本屬性和内容。
前面說過 sourceBuffer 主要是一個用來存放流的容器,那麼,它是怎麼存放的,它存放的内容是啥,有沒有順序等等。這些都是 sourceBuffer 最最根本的問題。OK,接下來,我們來看一下的它的基本架構有些啥。
參考 W3C,可以基本了解到裡面的内容為:
上面這些屬性決定了其 sourceBuffer 整個基礎。
首先是 <code>mode</code>。上面說過,SB(SourceBuffer) 裡面存儲的是 media segments(就是你每次通過 append 添加進去的流片段)。SB.mode 有兩種格式:
segments: 亂序排放。通過 <code>timestamps</code> 來辨別其具體播放的順序。比如:20s的 buffer,30s 的 buffer 等。
sequence: 按序排放。通過 <code>appendBuffer</code> 的順序來決定每個 mode 添加的順序。<code>timestamps</code> 根據 sequence 自動産生。
那麼上面兩個哪個是預設值呢?
看情況,講真,沒騙你。
當 <code>media segments</code> 天生自帶<code>timestamps</code>,那麼 <code>mode</code> 就為 <code>segments</code> ,否則為 <code>sequence</code>。是以,一般情況下,我們是不用管它的值。不過,你可以在後面,将 <code>segments</code> 設定為 <code>sequence</code> 這個是沒毛病的。反之,将 <code>sequence</code> 設定為 <code>segments</code> 就有問題了。
然後另外兩個就是 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() 過程中,使用 <code>abort()</code> 方法廢棄時,會觸發。此時,updating 由 true 變為 false。
注意上面有兩個事件比較類似:<code>update</code> 和 <code>updateend</code>。都是表示處理的結束,不同的是,update 比 updateend 先觸發。
SB 處理流的方法就是 +/- : appendBuffer, remove。另外還有一個中斷處理函數 <code>abort()</code>。
appendBuffer(ArrayBuffer):用來添加 ArrayBuffer。該 ArrayBuffer 一般是通過 fetch 的 <code>response.arrayBuffer();</code> 來擷取的。
remove(start, end): 用來移除具體某段的 media segments。 @param start/end: 都是時間機關(s)。用來表示具體某段的 media segments 的範圍。
abort(): 用來放棄目前 append 流的操作。不過,該方法的業務場景也比較有限。它隻能用在當 SB 正在更新流的時候。即,此時通過 fetch,已經接受到新流,并且使用 appendBuffer 添加,此為開始的時間。然後到 updateend 事件觸發之前,這段時間之内調用<code>abort()</code>。有一個業務場景是,當使用者移動進度條,而,此時 fetch 已經擷取前一次的 media segments,那麼可以使用 <code>abort</code>放棄該操作,轉而請求新的 media segments。具體可以參考:abort 使用
上面主要介紹了處理音視訊流需要用的 Web 技術,後面章節,我們接入實戰,具體來講一下,如何做到使用 MSE 進行 remux 和 demux。
原文連結:http://www.ivweb.io/topic/58de6dcb4813a86006c9052a