一個視頻首屏的優化過程
前言
我司的官網首頁——貝聊官網,首屏有一個自動播放的背景視頻,一直被詬病視頻加載慢、播放卡。剛開始以為是文件太大,或者是網速太慢,但當我去優化它的時候,發現并沒有預想的簡單。本文記錄了優化過程和經驗總結,希望能對讀者有所幫助。
現狀
官網的首頁由6屏組成,首屏主要內容是一個自動循環播放的背景視頻。頁面無緩存時:
視頻畫面需要好幾秒才能出現,期間只能看到頁面的背景色,并且出現第一幀以后,畫面會卡著不動,持續很久,有的時候甚至超過10秒,這種情況在下文統一簡稱第一幀卡
畫面第一幀卡完了以后播放不流暢,體驗起來像幻燈片
初探
要優化視頻播放卡頓的問題,我首先從視頻的文件大小入手。 下載MediaInfo查看視頻文件:
格式:mp4
分辨率:1080P
碼率:4K
大小:7.3M
時長:15秒
4K的碼率在對于在線視頻是非常高的,我使用視頻壓縮工具格式工廠對其進行調整,把碼率壓到畫質可接受的2400,此時文件大小4.4M。眼見文件大小已經瘦身為原來的60%,想必會有明顯的優化效果。
詭異的第一幀
打包發上測試環境,效果卻大跌我的眼鏡:視頻卡頓感比以前減輕了一點,但還是能明顯的感受到不流暢,而視頻的第一幀卡的問題更是幾乎沒有改善。看來通過壓縮碼率降低文件大小的做法貌似是杯水車薪。
深入探索
意識到簡單的減少資源文件大小的方法行不通之后,便上網搜查解決方案,但發現相關文章少之又少,并沒有找出第一幀卡的原因。找不到解決方案,就只好自己摸索摸索了,慢慢的腦里有個猜想:如果把視頻分成多份,瀏覽器只要加載了第一份就可以播放,這樣會不會減輕視頻的第一幀卡的問題呢?
視頻分塊加載
我把原有的視頻切成了兩段,并通過監聽video標簽的ended事件,在第一段播完后修改src切換到第二段,第二段播完后又切換回第一段,并循環這個過程。
這樣雖然給第一幀卡的問題帶來了一定的改善,但是副作用是:切換畫面并不是無縫的,每次切換都會卡一秒左右。
反思
視頻分塊的做法我最終選擇了放棄。原因一方面視頻時長本來才15秒,分塊的意義并不大;另一方面我認為這種方案即使做出來能無縫切換,也不會是最好的方案,因為并沒有解決根本問題(為什么視頻第一幀卡)。
moov位置導致第一幀卡?打破傳說
對于為什么第一幀加載慢,我開始懷疑和mp4格式有關,我搜索了一下,不少文章提及到moov的問題:
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并刪除綁定和定時器。問題就此解決了。
雖然此做法能解決問題,但是我覺得實現做法不太完美。如果您有更好的方法,請在下面留言?
好用又免費的視頻壓縮工具——小丸工具箱
經過上述的優化,首頁的視頻播放效果已經好了很多,但是還是有偶爾卡頓的情況。之前雖然已經使用格式工廠壓縮了一遍視頻,但考慮到市面上還有很多其他的視頻壓縮工具,于是再去百度里多找了一下,發現一款口碑不錯的工具,叫“小丸工具箱”。
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文件播放。
尾聲
本文記錄了我對于首屏的整個優化過程和當中得到的一些經驗,過程磕磕碰碰,希望能幫助讀者少走些彎路。同時我認為優化這個事是永無止境的,特別是我對于視頻壓縮方面的知識較為薄弱,如果文中有什么不對的地方,或者好的建議,請讀者們不吝賜教。
作者:貝聊科技
來源:掘金
工程師必備
- 項目客服
- 培訓客服
- 平臺客服
TOP




















