這兩張圖不難看出,在默認情況下 mp4 使用一次 http 請求所有的視頻數據,Youtube 則分次請求。當然這個描述很不專業,但確實形象。造成這種差異的是 video 不支持流式的視頻數據,Youtube 採用的是流式的視頻容器 webm,而 mp4 是非流式的。那如何解釋清楚流式的視頻數據呢,從專業的角度三言兩語很難說清楚,但用大白話翻譯過來就是流式的視頻數據支持分段獨立播放,非流式的不可以。換句話說一個10M的視頻文件,流式的視頻可以把0~1M的數據請求回來單獨播放,但是非流式的不可以。
上面我們描述了視頻格式的不同,接下來我們要說的是第一張圖中的視頻加載是瀏覽器來控制的,通過給 video 的 src 屬性配置視頻地址,觸發播放之後瀏覽器就會開始下載了,JS幹涉不了。而 Youtube 的視頻加載是通過JS來控制的,各位可以再次看下第二張圖的網絡請求類型:xhr,足以證明這一點。
上面兩點搞清楚之後我們就該說下清晰度切換的事情了。這個需求大家都不陌生,但是直接使用 mp4 格式做無縫清晰度切換,難度還挺大的。先解釋下「無縫清晰度切換」的概念:從播放一個解析度的視頻到另一個解析度且保證畫面、聲音不停頓的平滑切換過程。了解了這個概念,大家應該知道了用 video 無縫切換 mp4 有多難。一方面,video 是不支持流式的視頻格式的,一方面,video 的加載是不受JS控制的。通過切換 video 的 src 屬性,必然會導致畫面中斷、重新請求視頻數據等。有的同學想到說利用兩個 video 再結合 z-index 來搞,但是當你生成另一個video去加載視頻的時候,無法保證兩個畫面是嚴格一致的,即使將原來的畫面暫停到一個時刻,用另一個視頻通過 currentTime 屬性與之同步,切換仍然看到畫面閃爍,基本無法和 Youtube 無縫切換的體驗匹敵。而且還會造成更多流量的浪費,背後的原因大家可以研究下 mp4 容器和 webm 容器的異同,也可以看下視頻解碼相關的文章。
還有一種方法就是將 mp4 格式統統轉碼到流式的視頻格式比如 hls、webm 等。不過這種看上去可行的方式實際上會帶來很大的成本開銷,如將大量視頻做轉碼會消耗高昂的機器資源、雙倍存儲的費用、CDN的雙倍費用等等。其實我們也是在這種背景下研究出來新的技術問題解決清晰度無縫切換的。
首先,我們改變對 mp4 視頻的播放流程,不再直接使用 video 的 src 來播放,因為我們沒有任何可以操作的空間。video不僅支持 src 屬性還支持 Blob 對象,我們就是利用後者。播放的流程如下:
圖1.3 mp4 視頻新播放流程
來請求 mp4 視頻數據,這樣可以結合視頻 Range 服務,做到精確加載。
編寫解析器將加載回來的部分 mp4 視頻數據進行解復用
將解復用的視頻數據轉成 fmp4 格式並傳遞給 MediaSource
使用 video 進行解碼完成播放
然後在做清晰度切換的時候流程如下:
圖1.4 mp4視頻清晰度切換原理示意圖