做者:江敏熙 贝聊前端开发工程师
本文同时发布于我的博客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