边下边播总结(一)

概述

最近批改了我的项目中的视频播放性能, 由之前的全量下载完再播, 改为了边下边播的形式. 因为咱们我的项目中的视频在收回时都进行了加密, 所以整个过程其实就是边下载边解密边播放.

边下边播的技术计划, 网上的博客很容易搜到, 不外乎两种形式, 内置本地代理服务器AVAssetResourceLoader. 咱们采取了零碎提供的AVAssetResourceLoader这一计划.

计划原理

具体的AVAssetResourceLoader实现原理网上能够找到很多逻辑图, 如下图(来自网络)所示.

这里联合咱们的理论代码简略的介绍一个这个图片.

在平时应用AVPlayer播放url时, 咱们会这样创立一个播放器(简略)

let videoAsset = AVURLAsset(url: "http://resource_url/xxxxx")let item = AVPlayerItem(asset: videoAsset)let player = AVPlayer(playerItem: item)

如果咱们这样设置播放, 整个播放的外部流程其实都咱们都是不可见的, 视频的下载和缓存等, 咱们只能通过已知的一些办法,来管制播放器的播放暂停等.

如果想要实现咱们我的项目中想要的成果, 边下载边播放, 同时, 咱们可能须要接手视频的缓存这一模块, 所以咱们就必须得能进入到整个播放流程中, AVAssetResourceLoader其实就算是苹果给咱们留的一个小口子, 而后通过设置恪守AVAssetResourceLoaderDelegate这一协定的代理对象, 接手数据处理的这一过程(包含获取数据和向播放器填充数据).

videoAsset.resourceLoader.setDelegate(self, queue: queue)

注意事项

  1. 要进入到 AVAssetResourceLoader的代理回调, 除了要给videoAsset.resourceLoader设置delegate之外, 还须要把咱们的url改为不能辨认的scheme. 咱们一遍的资源门路都是http或者https, 咱们须要把url的scheme改为不能辨认的(公有的), 比方http://resource/xxx/xxx.mp4改为http-prefix://reource/xxxx/xxx.mp4
  2. url门路的最初必须要有视频的后缀, 相似.mp4, 我之前应用的资源门路是没有后缀的, 导致了播放器无奈起播.

AVAssetResourceLoaderDelegate

AVAssetResourceLoaderDelegate有两个罕用的回调办法如下

// MARK: - AVAssetResourceLoaderDelegatefunc resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {}func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {}

当播放器开始播放的时候, 会通过shouldWaitForLoadingOfRequestedResource这个回调办法向咱们索要数据, 具体所要数据的信息细节都封装在loadingRequest外面.
因为这个回调会走很屡次, 上图中示意的是要保存起来每一次的loadingRequest, 但在理论我的项目中, 我应用了不太一样的策略, 我把每一次loadingRequest都对应一个worker对象来解决, 这样每次索要数据, 都有一个独自的worker来解决绝对应的网络申请(暂不思考缓存), 这样比拟条理. 同时咱们也须要保留起咱们的worker, 因为如果播放器须要反对进度条拖动时, 须要手动seek到某一个地位, 这样会触发didCancel这个回调, 所以咱们也须要把咱们对应的worker外部停掉.

回调解决

当咱们收到一个回调时, 咱们次要关注这个AVAssetResourceLoadingRequest类型的loadingRequest.

他外部有一个dataRequest属性, dataRequest中有requestedOffset, requestedLength等一些有用信息. 咱们通过requestedOffset和requestedLength构建出咱们的Range, 塞到申请头外面去, 获取相应range的数据.

当咱们的player开始播放时, 收到的第一个回调, requestedOffset=0,requestedLength=2, 也就是索要0-1这两个字节, 这次申请其实能够了解为一个嗅探申请, 目标是为了失去视频的相干信息, 文件大小, 类型等.

guard request.contentInformationRequest == nil else {    if request.dataRequest?.requestsAllDataToEndOfResource == false {        request.contentInformationRequest?.contentLength = totalLen    } else {        request.contentInformationRequest?.contentLength = Int64(data.count)   }   request.contentInformationRequest?.isByteRangeAccessSupported = true   request.contentInformationRequest?.contentType = "video/mp4"   request.finishLoading()   return}

上述代码就是第一个嗅探申请的解决形式, 通过request.contentInformationRequest==nil, 判断出是第一个嗅探申请, 而后咱们须要填充request的contentInformationRequest, 而后填充信息完结调用finishLoading(), 以后的loadingRequest就完结了.

第一个嗅探申请完结后, 如果咱们返回的没有问题, 那播放器会立即进行下一个回调, 开始所要视频数据, 我在我的项目中测试时, 第二个申请个别都是0-xxx(文件大小-1), 索要整个文件, 这时咱们dataTask类型的申请,期待服务器一片一片的返回数据, 没收到一部分数据后调用dataRequest.respond(with: data), 全副收取结束之后调用request.finishLoading().

其实这就是最根本的数据填充的逻辑, 除了第一个嗅探申请非凡解决一下, 前面的就是收到数据, 就填充回dataRequest, 索要的数据全副填充结束, 调用finishLoading.

在咱们申请整个文件的过程中, 有时候会发现一种景象, 就是respond一部分数据之后, loadingRequest被cancel了, 而后又开始索要很前面的range的数据, 其实这能够了解为一个寻找文件的moov的过程, 文件的moov可能在文件头, 也可能在文件尾部.moov外面定义视频的时间尺度,时长,显示个性以及每个轨道信息等, 这一部分能够通过理解mp4文件头格局来多做一下理解.

咱们不论他索要的是那一部分数据, 只有咱们申请到对应的数据, respond回去就没问题.

补充

那这么简略的逻辑对于咱们本人的我的项目来说难点是什么呢,这里简略形容一下.

后面有说到咱们我的项目中的资源都是通过加密的, 应用了AES的加密算法, 这样咱们在承受到数据之后, 是不能间接返回给dataRequest的, 须要咱们先解密, 而后简略的说咱们应用的加密策略是每16字节是一个加密片段, 但申请返回的数据并不能保障每次都是16倍数, 所以咱们解决16的倍数能力进行解密这一个问题, 而后还有一个range的修改问题, 打比方咱们须要1-10这10个字节的数据, 然而我申请头的range是不能间接写1-10的, 因为依照咱们每16个字节是一个加密片段, 咱们须要的1-10, 在0-15这个片段中, 所以咱们必须要先申请下来0-15这一个片段, 而后解密, 再从中拿出1-10, 填充回去. 当然了还有一些细节就不开展叙述了, 等有机会联合我的项目独自聊一聊AES这个解密办法.

总结

下面就是在实现边下边播过程中总结到的一些小点, 当然每个人在理论我的项目可能会遇到不一样的问题. 同时本文没有波及到数据的缓存, github上也有很多不错的缓存计划, 大家能够看看.

感激浏览.