1.背景:
浏览器iOS客户端的Feed流短视频播放过程中,不少用户反馈视频加载过程太久,导致会没兴趣继续等待下去,从而流失了这部分用户及无法提高用户的人均播放次数。
2.1.替换MPMoviePlayer为AVFoudation(秒开率从16.7%到35%)
根据统计数据,2016年3月时的短视频的秒开率在16.7%左右。我们对浏览器视频加载的过程进行了分析。
1、浏览器依然使用苹果播放器老的API,MPMoviePlayer,没有使用最新的AVFoudation播放器组件。根据WWDC的介绍,AVFoudation组件在性能、灵活性、多源设置、多线程、多实例比MPMoviePlayer优秀非常多。AVFoudation基于Core Audio和Core Video实现,在音视频的性能上做了更多的优化及开放更多的API给使用者使用,例如视频源的关键帧数据获取和视频编辑、录制等。更利于后续短视频的预加载和UGC方面的使用。
在我们测试性能过程中,对两个播放组件的进行了对比。T1的秒开率的对比图如下:(绿色为AVFoudation,蓝色为MPMoviePlayer)
然而我们一直没替换的原因是AVFoudation比MPMoviePlayer的播放出错率高很多,如下图所示(蓝色MPMoviePlayer,绿色AVFoudation)
这个播放出错率的问题一直制约了我们切换系统内核,经过我们多次走查代码,走查统计项,走查用户反馈,我们找到了三个核心的解决方案。
背景:MP内核内部封装有相关的重试机制,而更底层的AV内核不会有相关机制,导致了无网络或者慢网络下,MP内核能通过重试机制一定程度的恢复正常播放。AV内核则会直接出现出错界面。
方案:在主动或者被动获取到错误信息后,在WIFI的网络状态下,进行重试。重试默认值为6次,每次间隔为*2的递增,分别是1秒、2秒、4秒、8秒、16秒、32秒。其中重试机制功能启动和重试次数都有下发CD开关控制。
效果:1.无网络切换有网络下,以前是在无网络下直接出现出错界面。现在是切换过程中重试则可以正常播放。
2.慢网络切换正常网络下,重试后可以更快的正常播放。减少因为loading导致的用户流失率。
3.正常网络切换无网络,以前是直接出错,现在是重试后,只要能恢复正常网络,依然能正常播放。
背景:在IOS9以下的所有版本,播放第一个视频后,不退出当前播放器,立刻播放第二个视频,就会播放出错。
原因:AVPlayerItem主要储存AVURLAssert相关信息,用于作为AVPlayer的元数据使用。当第一个视频播放,AVPlayer使用第一个AVPlayerItem是完全正常的。当AVPlayer存活的情况下,播放第二个视频使用系统API:replaceCurrentItemWithPlayerItem的过程中,第一个item和第二个item的播放格式不同,导致Compositor不一致。AVPlayer内部会直接在KVO的status回调中返回fail状态,直接抛出-11800的错误码。
方案:取消以前代码中replaceCurrentItemWithPlayerItem的使用,在每个视频播放前先析构之前的AVPlayer,重新创建AVPlayer进行设置里面的媒体元数据,确保元数据在同步和异步过程中使用正常。
风险: 有可能对视频T1快开比有一定影响,待灰度进行相关的性能分析得出相关的影响程度。
效果:在IOS9以下的所有版本,播放任何视频,无论在连续播放多个视频都能不会出错。
背景:以上代码是播放出错后点击出错页面重试按钮的处理代码,如果某个视频强制VPS,但是用户是在无网络或者慢网络下,VPS返回失败,则无法设置contentURL。此时如果用户网络恢复了,点击重试按钮后,会一直播放一个错误的kMoviePlayerNoneVideoURL。这样无论用户点击了多少次重试按钮,视频都会一直播放失败。也影响了现在视频的播放出错率。
方案:对重试按钮点击的响应代码进行处理,不在使用contentURL作为播放标准来直接播放,而是使用播放器内movieInfo的元媒体数据进行相关的重试VPS或者换源。
这三个方案执行后,AVFoudation的播放出错率从21.568%降低到5.498%,降幅高达76.1%。比MPMoviePlayer的6.908%还要低1.5%的比例。
从而我们优化后把AVFoudation的所有性能指标都优于MPMoviePlayer,切换了这个内核。也为后面的秒开率优化奠定了基础,秒开率优化需要AVFoudation的高拓展性和易用性。
VPS视频服务的定义:基于目前视频的播放URL是动态变化的,后台服务器下发到客户端的URL都是页面URL,业界的做法是通过这个页面URL打开一个Webview去爬取里面的播放URL。VPS视频服务就是简化了这个步骤,通过VPS服务器,把页面URL解析成播放URL给客户端。
VPS的预加载意义在于能提前把每个Feed流内的短视频在网络允许的情况下更早的获取到实际的播放URL,从而减少中间链路的请求时间和相应时间。同时VPS预加载也同时处理的播放URL失效的情况和重爬机制,避免视频源地址失效后到时用户播放失败。
Metadata的定义:视频的元数据,主要包含了这个视频的基础数据,例如creationdata、duration、height、width、framerate等。
列举现在主流的两种播放格式MP4和FLV的Metadata数据结构:
MP4:
FLV:
元数据是视频播放前首先需要下载的,故我们对元数据进行预加载,可以让视频能更早的进入prepare阶段,减少播放器下载元数据的时间。加速了播放器去加载第一帧元数据的时间。
实验原因:目前依赖于系统的LikeyToKeepUp通知回调来判断T3达到时间,有可能会延误,例如已经播放了十几秒通知才回调。故希望通过干预的方式,提前判断出播放器流畅播放的前提条件。
视频流畅播放条件:
/* 标记为可流畅播放,目前以下几种情形都认定为LikelyToKeepUp:
* 1)AVPlayerItem.isPlaybackLikelyToKeepUp == YES;
* 2)收到bufferFull事件;
* 3)playableDuration >= 2.f;
* 4) currentLoadedPercent占比 >= 20%;
* 5) 最后一秒永远无法达到LikeyToKeepUp的状态
* 6) IOS10中timeControlStatus == AVPlayerTimeControlStatusPlaying
*/
实验目的:预加载超时时间从6秒提升到10秒,目的提高预缓存命中率,降低失败率和超时率,从而提高秒开率。
实验结果: 经过327W的VV实验,预加载命中率从83.813%涨到83.924%,无效率从72.043%降低到71.713%,失败率从7.097%降低到5.636%,超时率从2.766%降低到1.322%。
综合以上实验结果,预加载命中率提升很小,所以秒开率也得不到很大的提高。但是这个优化对超时率和失败率及无效率有了明显的提高,故已经在上周线上进行调整。
2.4.3.优化缓存内核的loadedtime和currenttime(基于个人实验室进行)
实验目的:提高秒开率。
实验结论:loadedtime和currenttime每次调用耗时是0.3毫秒到0.7毫秒间,而每次播放总共调用是4到10次,预计只能提高1.2毫秒到7毫秒间,发现对秒开率提升甚微,但是调整代码影响面比较大,故目前维持现状。
实验目的:1、提高正在工作的预加载working个数,从而让用户能更快的获得媒体数据,提高秒开率。 2、提高已缓存的缓存池数量,从而提高预缓存命中率,降低无用率。 3、提高准备缓存的个数,从而提高预缓存命中率。
实验结论:
1、提高正在工作的预加载working个数,虽然秒开率有所提升,但对第一个视频的网络资源和目前机器的内存资源有较大的影响,会造成较大的卡顿现象。故目前维持了现状。
2、提高已缓存的缓存池数量,经过验证从默认值8个,调整到12个或者16个,秒开率变化很小。故结论是预加载命中率瓶颈不在此。(ABTest测试量是300万VV)
3、提高准备缓存的个数,经过验证从默认值4个,调整到6个或者8个,秒开率依然变化很小。故结论是预加载命中率瓶颈不在此。(ABTest测试量是300万VV)
视频内嵌正文页增加预加载。
经过对前十名域名的秒开率进行了调查,发现了一个域名秒开率在20%以下。
经过多番调查,发现是自媒体视频内嵌正文页,这种情况是没有走预加载流程的,故秒开率非常低。我对VV在10000以上域名是正文页的数量统计,预计是3.3%的占比。这些域名基本秒开率都低于20%,如果通过增加预加载的方式,按75%的占比来算。预估能提高2.5%的秒开率。这个将在下个版本进行优化,同时验证是否对信息流正文页秒开率有所影响。
对视频源前面320KB数据进行预加载,可以让大部分视频能瞬速达到T3的流畅播放标准。我们遇到的问题主要是AVPlayer的网络托管。按之前MPPlayer的机制是无法完成的,MPPlayer更像一个黑盒,不具备流数据的开放。庆幸的是AVPlayer有开通AVAssetResourceLoader的机制,让这一切成为了可能。以下为相关的类图。
网络托管的流程图:
代理对象处理流程
a.当视频播放器向代理请求dataRequest时,判断代理是否已经向服务器发起了请求,如果没有,则发起下载整个视频文件的请求
b.如果代理已经和服务器建立链接,则判断当前的dataRequest请求的offset是否大于当前已经缓存的文件的offset,如果大于则取消当前与服务器的请求,并从offset开始到文件尾向服务器发起请求(此时应该是由于播放器向后拖拽,并且超过了已缓存的数据时才会出现)
c.如果当前的dataRequest请求的offset小于已经缓存的文件的offset,同时大于代理向服务器请求的range的offset,说明有一部分已经缓存的数据可以传给播放器,则将这部分数据返回给播放器(此时应该是由于播放器向前拖拽,请求的数据已经缓存过才会出现)
d.如果当前的dataRequest请求的offset小于代理向服务器请求的range的offset,则取消当前与服务器的请求,并从offset开始到文件尾向服务器发起请求(此时应该是由于播放器向前拖拽,并且超过了已缓存的数据时才会出现)
只要代理重新向服务器发起请求,就会导致缓存的数据不连续,则加载结束后不用将缓存的数据放入本地cache
e.如果代理和服务器的链接超时,重试一次,如果还是错误则通知播放器网络错误
如果服务器返回其他错误,则代理通知播放器网络错误
这里面我们遇到的一个比较的坑是MP4结构的moov预加载,先介绍下MP4的结构。
.MPEG4文件主要是通过atom(box)这种结构单元来构成的(图2),
.每个atom(box)包含header 和 data 2部分
.header包含了当前atom的size(长度)和type(名字)
.data是atom的实际数据,data中可以包含子atom
.当data包含子atom的时候,那么该atom就是container atom了
.在MPEG4中,主要包含:ftyp , moov , mdat 等container atom单元,其中最重要的是moov和mdat2个atom(图3)
.moov包含了该mpeg4文件媒体信息,例如duration ,video track , auto track , video sample 等等一系列信息
.mdat则是包含了实际的视频数据
MP4的预加载处理流程是ftyp => moov => mdat固定流程进行拆包获取stream info ,然后基于stream info 信息,decode frame ,decode audio 进行播放,直至播放完毕。而在于我们预加载moov结构时,moov结构可能在文件的尾部,而且导致预加载失败。故我们在读流过程中发现前面320KB没moov字段则会从尾部开始预加载moov结构数据,确保预加载成功。
参考文档:
https://developer.apple.com/documentation/avfoundation/avassetresourceloader
网友评论