做者:江敏熙 貝聊前端開發工程師
本文同時發佈於我的博客html
我司的官網首頁——貝聊官網,首屏有一個自動播放的背景視頻,一直被詬病視頻加載慢、播放卡。剛開始覺得是文件太大,或者是網速太慢,但當我去優化它的時候,發現並無預想的簡單。本文記錄了優化過程和經驗總結,但願能對讀者有所幫助。 前端
官網的首頁由6屏組成,首屏主要內容是一個自動循環播放的背景視頻。頁面無緩存時:git
要優化視頻播放卡頓的問題,我首先從視頻的文件大小入手。 下載MediaInfo查看視頻文件:github
4K的碼率在對於在線視頻是很是高的,我使用視頻壓縮工具格式工廠對其進行調整,把碼率壓到畫質可接受的2400,此時文件大小4.4M。眼見文件大小已經瘦身爲原來的60%,想必會有明顯的優化效果。chrome
打包發上測試環境,效果卻大跌個人眼鏡:視頻卡頓感比之前減輕了一點,但仍是能明顯的感覺到不流暢,而視頻的第一幀卡的問題更是幾乎沒有改善。看來經過壓縮碼率下降文件大小的作法貌似是杯水車薪。segmentfault
意識到簡單的減小資源文件大小的方法行不通以後,便上網搜查解決方案,但發現相關文章少之又少,並無找出第一幀卡的緣由。找不到解決方案,就只好本身摸索摸索了,慢慢的腦裏有個猜測:若是把視頻分紅多份,瀏覽器只要加載了第一份就能夠播放,這樣會不會減輕視頻的第一幀卡的問題呢?瀏覽器
我把原有的視頻切成了兩段,並經過監聽video標籤的ended事件,在第一段播完後修改src切換到第二段,第二段播完後又切換回第一段,並循環這個過程。緩存
這樣雖然給第一幀卡的問題帶來了必定的改善,可是反作用是:切換畫面並非無縫的,每次切換都會卡一秒左右。bash
視頻分塊的作法我最終選擇了放棄。緣由一方面視頻時長原本才15秒,分塊的意義並不大;另外一方面我認爲這種方案即便作出來能無縫切換,也不會是最好的方案,由於並無解決根本問題(爲何視頻第一幀卡)。服務器
對於爲何第一幀加載慢,我開始懷疑和mp4格式有關,我搜索了一下,很多文章說起到moov的問題:
mp4雖然支持流傳輸播放,但視頻的「索引」儲存在了moov對象,只有moov下載完視頻纔會開始播放。大多mp4文件會把這個moov放在文件頭部,但若是放在了尾部則須要下載完整個文件才能開始播放。參考blog.csdn.net/jinshelj/ar…。
我查看了壓縮後的mp4文件,moov的確是在尾部。因而我使用qt-faststart(基於ffmpeg的moov前置工具),對moov對象作了前置處理。但通過個人測試,發現前置了moov並無優化第一幀卡的問題,播放表現和在尾部的時候同樣。而且當文件moov在尾部的時候,視頻在文件下載完以前就開始播了,並沒有文件下載完才能播一說。
因而我再查閱資料,終於找到了緣由:若是服務器自己是支持seek的,那麼mp4視頻也是能正常邊下邊播的,參考segmentfault.com/a/119000001…
既然不是moov致使了第一幀卡,那到底是什麼緣由呢?
至此第一幀卡的問題尚未解決,因而我打算換一種視頻格式試試,那麼是否存在一個比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文件播放。
本文記錄了我對於首屏的整個優化過程和當中獲得的一些經驗,過程磕磕碰碰,但願能幫助讀者少走些彎路。同時我認爲優化這個事是永無止境的,特別是我對於視頻壓縮方面的知識較爲薄弱,若是文中有什麼不對的地方,或者好的建議,請讀者們不吝賜教。
通過熱心網友的反饋,我發現我對moov的理解存在一些誤差,而且首屏視頻在Chrome播放時,會有兩個問題:
而在IE9和火狐瀏覽器,上述兩個問題並不能重現,因爲我在開發時使用主要使用了火狐,因此疏漏上述兩個問題。我搜索了相關資料,而且用了三個結構順序不一樣的mp4文件作了對比測試,如今給你們分享一下結果:
附上moov後置,moov前置無meta,moov前置有meta的三張mp4文件結構圖:
第二,moov前置了也並不必定就能Fast Streaming,moov.udta.meta裏存放的是視頻的元數據,若是沒有moov.udta.meta,那麼也須要三次請求。而我在測試的過程當中發現,沒有moov.udta.meta但moov前置的文件在chrome須要請求三次,而在Firefox只須要一次,這種狀況我沒有找到相關資料,暫時認爲是不一樣瀏覽器的採起的策略不一樣。
因爲此前使用小丸工具箱壓制出來的是moov後置無meta的文件,使用qt-faststart也僅僅是前置了moov但沒有生成moov.udta.meta,因此在Chrome上出現了三次請求並影響了progress事件的觸發,因此致使了上面兩個問題。 此後改成了使用ffmpeg優化(ffmpeg -i input.mp4 -movflags faststart -acodec copy -vcodec copy output.mp4),在moov下生成了moov.udta.meta,上述兩個問題消失。
參考:
Optimizing MP4 Video for Fast Streaming
Understanding the MPEG-4 movie atom | Adobe Developer Connection