一個視頻首屏的優化過程

前言

我司的官網首頁——貝聊官網,首屏有一個自動播放的背景視頻,一直被詬病視頻加載慢、播放卡。剛開始以為是文件太大,或者是網速太慢,但當我去優化它的時候,發現并沒有預想的簡單。本文記錄了優化過程和經驗總結,希望能對讀者有所幫助。

一個視頻首屏的優化過程的圖1

現狀

官網的首頁由6屏組成,首屏主要內容是一個自動循環播放的背景視頻。頁面無緩存時:

  • 視頻畫面需要好幾秒才能出現,期間只能看到頁面的背景色,并且出現第一幀以后,畫面會卡著不動,持續很久,有的時候甚至超過10秒,這種情況在下文統一簡稱第一幀卡

  • 畫面第一幀卡完了以后播放不流暢,體驗起來像幻燈片

初探

要優化視頻播放卡頓的問題,我首先從視頻的文件大小入手。 下載MediaInfo查看視頻文件:

  • 格式:mp4

  • 分辨率:1080P

  • 碼率:4K

  • 大小:7.3M

  • 時長:15秒

4K的碼率在對于在線視頻是非常高的,我使用視頻壓縮工具格式工廠對其進行調整,把碼率壓到畫質可接受的2400,此時文件大小4.4M。眼見文件大小已經瘦身為原來的60%,想必會有明顯的優化效果。

詭異的第一幀

打包發上測試環境,效果卻大跌我的眼鏡:視頻卡頓感比以前減輕了一點,但還是能明顯的感受到不流暢,而視頻的第一幀卡的問題更是幾乎沒有改善。看來通過壓縮碼率降低文件大小的做法貌似是杯水車薪。

深入探索

意識到簡單的減少資源文件大小的方法行不通之后,便上網搜查解決方案,但發現相關文章少之又少,并沒有找出第一幀卡的原因。找不到解決方案,就只好自己摸索摸索了,慢慢的腦里有個猜想:如果把視頻分成多份,瀏覽器只要加載了第一份就可以播放,這樣會不會減輕視頻的第一幀卡的問題呢?

視頻分塊加載

我把原有的視頻切成了兩段,并通過監聽video標簽的ended事件,在第一段播完后修改src切換到第二段,第二段播完后又切換回第一段,并循環這個過程。

這樣雖然給第一幀卡的問題帶來了一定的改善,但是副作用是:切換畫面并不是無縫的,每次切換都會卡一秒左右。

反思

視頻分塊的做法我最終選擇了放棄。原因一方面視頻時長本來才15秒,分塊的意義并不大;另一方面我認為這種方案即使做出來能無縫切換,也不會是最好的方案,因為并沒有解決根本問題(為什么視頻第一幀卡)。

moov位置導致第一幀卡?打破傳說

對于為什么第一幀加載慢,我開始懷疑和mp4格式有關,我搜索了一下,不少文章提及到moov的問題:

一個視頻首屏的優化過程的圖2

mp4雖然支持流傳輸播放,但視頻的“索引”儲存在了moov對象,只有moov下載完視頻才會開始播放。大多mp4文件會把這個moov放在文件頭部,但如果放在了尾部則需要下載完整個文件才能開始播放。參考blog.csdn.net/jinshelj/ar…

我查看了壓縮后的mp4文件,moov的確是在尾部。于是我用ffmpeg,對moov對象做了前置處理。但經過我的測試,發現前置了moov并沒有優化第一幀卡的問題,播放表現和在尾部的時候一樣。并且當文件moov在尾部的時候,視頻在文件下載完之前就開始播了,并無文件下載完才能播一說。

于是我再查閱資料,終于找到了原因:如果服務器本身是支持seek的,那么mp4視頻也是能正常邊下邊播的,參考segmentfault.com/a/119000001…

既然不是moov導致了第一幀卡,那究竟是什么原因呢?

更適合網絡流傳輸的格式——flv

至此第一幀卡的問題還沒有解決,于是我打算換一種視頻格式試試,那么是否存在一個比mp4更適合在線播放的視頻格式呢?

我搜索很多相關資料,flv是一種非常簡潔,天生具備流式特征,非常適合網絡流傳輸的格式。如果說mp4視頻的“索引”是整個一起存儲的,那么flv的“索引”則是分段存儲的。打個簡單的例子:看一個視頻的開頭,mp4需要下載整個視頻的“索引”才能開始播放,flv只需要下載開頭部分的“索引”。

瀏覽器并沒有原生支持flv解碼,一般是通過flash來完成。但是,來自嗶哩嗶哩的開源插件——flv.js,能讓video標簽支持flv的播放。為了嘗試flv能否改善第一幀卡的問題,我引入了flv.js,并把原來的視頻轉為flv格式。flv.js壓縮后只占100KB+,使用起來也非常方便,代碼實例如下:

const flvjs = require('../fiv.js'); if (flvjs.isSupported()) {     var videoElement = $bgVideo[0];     var flvPlayer = flvjs.createPlayer({         type: 'flv', url: src // 視頻的地址     }); flvPlayer.attachMediaElement(videoElement); flvPlayer.load(); flvPlayer.play(); } 復制代碼

測試了一下,結果讓我驚喜。第一幀卡的問題解決了,但是播放依然是不流暢,體驗像幻燈片。雖然問題沒有完全解決,而且flv.js不兼容IE10以下的瀏覽器,但至少獲得了實質性的進展。接著只要集中精力解決視頻播放不流暢的問題就可以了。

揪出搶占網速的兇手

播放不流暢的問題相對簡單,原因要么是下載太慢,要么就是文件太大。而文件已經被壓縮,就只需要研究為什么下載慢了。

這個時候我開始把眼光投在頁面的靜態資源上,看看能否對一些占用大的資源做優化。靜態資源在發布生產時已被工具壓縮過,已經沒有什么再壓縮的空間。我的思路是盡量讓首屏看見的資源立刻加載,而第一屏外的資源延遲加載。打開瀏覽器的開發者工具,發現大部分的資源文件都很小,只有一個文件特別大,高達900+KB,打開發現是一個動圖,并且不在第一屏內。這個時候就要考慮怎么樣才能把圖片放在視頻下載完之后才加載。基本思路是視頻下載完之后,再把圖片標簽動態添加到頁面中。

我查閱了video標簽的原生事件。 在眾多事件中,suspend是比較適用于當前場景的,表現為當視頻下載完成后觸發。 但suspend的兼容性并不好,在IE 9等低版本瀏覽器下不能觸發,而progress事件卻沒有這個問題。progress事件在視頻下載時觸發,假設一個視頻下載耗時10秒,那10秒內每秒都會觸發progress。

通過監聽progress事件和設置定時器來判斷視頻是否加載完,達到動圖延遲加載的目的。具體代碼如下:

/** 監聽事件progress,觸發后設置定時器。每一次progress事件觸發便會清空上一次的定時器。 假如在一秒內progress都沒有觸發,則視為下載完成,觸發callback同時刪除綁定。 **/ // $bgVideo[0]是視頻的dom節點 afterDownload($bgVideo[0], function() { $bgImg.removeClass('hiden'); }); function afterDownload(video, callback) { // 計時器 var callbackTimer = null; // progress事件回調函數。監聽progress,直到一秒間不觸發progress才執行callback var progressCallBack = function() { clearTimeout(callbackTimer); // 設定1秒的定時器,觸發后刪除綁定,刪除定時器 callbackTimer = setTimeout(function() { callback(); video.removeEventListener('progress', progressCallBack); }, 1000); }; // 綁定事件 video.addEventListener('progress', progressCallBack); } 復制代碼

在調試的過程中,上面的代碼還有點小問題。如果視頻已經被緩存,progress事件有時候不會觸發,suspend事件也有同樣的情況。我猜測是視頻下載太快,addEventListener還沒有執行就已經下載完了。我嘗試把事件的綁定寫在html上:

<video onprogress="progressCallBack" .../> 復制代碼

采用這種做法以后,在本地調試時不斷按F5刷新也不會出現問題,但是發布到服務器上卻偶爾會出現問題,事件progress又沒有被觸發。最后我選擇一種簡單粗暴的方法,在頁面初始化時在設置一個3秒的定時器:

function afterDownload(video, callback) { var callbackTimer = null; var progressCallBack = function() { ... }; // 防止chrome在緩存的情況下,不觸發progress callbackTimer = setTimeout(function() { callback(); clearTimeout(callbackTimer); video.removeEventListener('progress', progressCallBack); }, 3000); video.addEventListener('progress', progressCallBack); } 復制代碼

在進入頁面后,3秒內不觸發progress事件,就認為視頻已被緩存,直接執行callback并刪除綁定和定時器。問題就此解決了。

雖然此做法能解決問題,但是我覺得實現做法不太完美。如果您有更好的方法,請在下面留言?

好用又免費的視頻壓縮工具——小丸工具箱

經過上述的優化,首頁的視頻播放效果已經好了很多,但是還是有偶爾卡頓的情況。之前雖然已經使用格式工廠壓縮了一遍視頻,但考慮到市面上還有很多其他的視頻壓縮工具,于是再去百度里多找了一下,發現一款口碑不錯的工具,叫“小丸工具箱”。

一個視頻首屏的優化過程的圖3
小丸工具箱相對之前的格式工廠,可以直接去除音頻流(需求里視頻不需要聲音),這樣視頻體積更小了;操作更傻瓜化了,使用者只需要修改選項里的CRF和分辨率,基本上已經能完成多數情況的壓縮需求。關于CRF,引用小丸作者的話介紹一下:

CRF(Const Quality, 固定質量),這種碼率控制方式是非常優秀的,以至于可以無需2pass壓制,即即使1pass也能實現非常好的碼率分配利用。像質量模式的壓制方式在其他編碼器也有(如xvid或者壓制rmvb的ERP),但據我所知都只是“固定量化(Const Quantization)”x264的CRF在量化的基礎上,根據人的視覺心理學更為合理地分配碼率,其目標是讓人在看視頻的時候,視頻的質量盡可能地統一,但碼率達到盡可能的有效利用。 CRF模式還有個優勢,很多人在壓片的時候不清楚應該給視頻壓到多少碼率才比較好。CRF就是按需要來分配碼率的,故其實就省下了到底要多少碼率的苦惱。

這里附上小丸工具箱的入門操作教程

最后使用小丸工具箱嘗試不同了的CRF值,壓制之后再肉眼對比,在畫質和文件體積間找出一個平衡,把視頻在1080P分辨率下壓到了3.4M。 而我再嘗試降低分辨率,發現當分辨率降為720P時,畫質相差得并不明顯。最終選擇了分辨率720p,CRF23的壓縮參數,此時視頻壓制到了2M,相較一開始的7.3M簡直是暴瘦。

總結

至此,視頻能夠快速呈現,流暢播放。同時也發現,無論是使用mp4格式,還是flv格式,第一幀卡的問題都已經不存在了。對于此情況,我用Chrome的限速功能測試過,只有在網速不夠用的情況下(要么網速太慢,要么視頻文件太大),mp4格式視頻才會出現第一幀卡的問題。由于我們已經把視頻的大小壓到了足夠小,并且對大圖做了延遲加載的處理,此時flv和mp4的差距已經微不足道了。最終我把flv.js撤了下來,統一使用mp4文件播放。

尾聲

本文記錄了我對于首屏的整個優化過程和當中得到的一些經驗,過程磕磕碰碰,希望能幫助讀者少走些彎路。同時我認為優化這個事是永無止境的,特別是我對于視頻壓縮方面的知識較為薄弱,如果文中有什么不對的地方,或者好的建議,請讀者們不吝賜教。


作者:貝聊科技
來源:掘金

登錄后免費查看全文
立即登錄
App下載
技術鄰APP
工程師必備
  • 項目客服
  • 培訓客服
  • 平臺客服

TOP