上海建站提供商,网站建设沛宣,管理咨询公司注册,佛山设计网站文章目录 IdiotAVplayer 实现视频切片缓存一 iOS视频边下边播原理一 分片下载的实现1 分片下载的思路2 IdiotAVplayer 实现架构 三 IdiotAVplayer 代码解析IdiotPlayerIdiotResourceLoaderIdiotDownLoader IdiotAVplayer 实现视频切片缓存
一 iOS视频边下边播原理
初始化AVUR… 文章目录 IdiotAVplayer 实现视频切片缓存一 iOS视频边下边播原理一 分片下载的实现1 分片下载的思路2 IdiotAVplayer 实现架构 三 IdiotAVplayer 代码解析IdiotPlayerIdiotResourceLoaderIdiotDownLoader IdiotAVplayer 实现视频切片缓存
一 iOS视频边下边播原理
初始化AVURLAsset 的时候将资源链接中的http替换成其他字符串并且将AVURLAsset的resourceLoader 设置代理对象然后该代理对象实现AVAssetResourceLoaderDelegate 的代理方法
#pragma mark - AVAssetResourceLoaderDelegate
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {return YES;
}- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {
}在代理方法中实现资源的下载保存 并将下载好的资源塞给 loadingRequest 实现视频的播放
一 分片下载的实现
简单的实现方案就是将一个视频从头开始下载或者从当前下载到的位置开始下载然后下载到结束 这种方案对于短视频是可以的因为短视频总共也没有多大即使我们快进从头下载开始到快进的地方也没有多少流量用户体验影响不大但是仍然浪费了中间的流量。 如果一个视频比较大用户进行快进操作的话从开头下载到用户快进的地方需要的时间很长这时候如果能根据用户快进的进度根据用户的需要进行资源下载那就是一个好的方案了。
1 分片下载的思路
步骤 1 首先根据链接获取本地资源 2 根据获取到的本地资源和视频请求request对比计算需要新下载的资源 片段。 3 将本地的资源或者下载好的资源分片塞给请求对象request
2 IdiotAVplayer 实现架构
IdiotAVPlayer 负责实现视频播放功能
IdiotResourceLoader 负责实现 AVAssetResourceLoaderDelegate代理 方法 负责将数据塞给AVAssetResourceLoadingRequest 请求并管理AVAssetResourceLoadingRequest 请求添加移除塞数据快进的处理
IdiotDownLoader 负责 资源片段的获取需要下载的片段的计算 NSURLSessionDelegate 代理方法的实现并将下载好的数据传给IdiotResourceLoader, 还负责在读取本地数据的时候将占用内存较大的视频资源分片读取到内存中传给 IdiotResourceLoader避免造成因为资源较大而产生的内存撑爆问题
IdiotFileManager 负责管理下载的资源
三 IdiotAVplayer 代码解析
创建播放器并设置resouceLoader代理
IdiotPlayer _resourceLoader [[IdiotResourceLoader alloc] init];_resourceLoader.delegate self;AVURLAsset * playerAsset [AVURLAsset URLAssetWithURL:[_currentUrl idiotSchemeURL] options:nil];[playerAsset.resourceLoader setDelegate:_resourceLoader queue:_queue];_playerItem [AVPlayerItem playerItemWithAsset:playerAsset];IdiotResourceLoader
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {[self addLoadingRequest:loadingRequest];DLogDebug(loadingRequest %,loadingRequest)return YES;
}- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {[self removeLoadingRequest:loadingRequest];
}- (void)removeLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSArray * temptaskList [NSArray arrayWithArray:self.taskList];dispatch_semaphore_signal(semaphore);IdiotResourceTask * deleteTask nil;for (IdiotResourceTask * task in temptaskList) {if ([task.loadingRequest isEqual:loadingRequest]) {deleteTask task;break;}}if (deleteTask) {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList removeObject:deleteTask];dispatch_semaphore_signal(semaphore);}}- (void)addLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {if (self.currentResource) {if (loadingRequest.dataRequest.requestedOffset self.currentResource.requestOffset loadingRequest.dataRequest.requestedOffset self.currentResource.requestOffset self.currentResource.cacheLength) {IdiotResourceTask * task [[IdiotResourceTask alloc] init];task.loadingRequest loadingRequest;task.resource self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);[self processRequestList];}else{if (self.seek) {[self newTaskWithLoadingRequest:loadingRequest];}else{IdiotResourceTask * task [[IdiotResourceTask alloc] init];task.loadingRequest loadingRequest;task.resource self.currentResource;NSLog(哈哈哈哈哈啊哈哈这里这里这里添加22222 %lld %lld %p\n, loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.currentOffset, task);dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);}}}else {[self newTaskWithLoadingRequest:loadingRequest];}
}- (void)newTaskWithLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {long long fileLength 0;if (self.currentResource) {fileLength self.currentResource.fileLength;self.currentResource.cancel YES;}IdiotResource * resource [[IdiotResource alloc] init];resource.requestURL loadingRequest.request.URL;resource.requestOffset loadingRequest.dataRequest.requestedOffset;resource.resourceType IdiotResourceTypeTask;if (fileLength 0) {resource.fileLength fileLength;}IdiotResourceTask * task [[IdiotResourceTask alloc] init];task.loadingRequest loadingRequest;task.resource resource;self.currentResource resource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);printf(哈哈哈这里事创建的这里事创建的%lld %lld %lld %p %p\n, resource.requestOffset, loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.currentOffset, loadingRequest, task);[IdiotDownLoader share].delegate self;[[IdiotDownLoader share] start:self.currentResource];self.seek NO;
}- (void)stopResourceLoader{[[IdiotDownLoader share] cancel];
}- (void)processRequestList {synchronized (self) {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSArray * temptaskList [NSArray arrayWithArray:self.taskList];dispatch_semaphore_signal(semaphore);for (IdiotResourceTask * task in temptaskList) {NSInvocationOperation * invoke [[NSInvocationOperation alloc] initWithTarget:self selector:selector(finishLoadingWithLoadingRequest:) object:task];[_playQueue addOperation:invoke];}}
}- (void)finishLoadingWithLoadingRequest:(IdiotResourceTask *)task {//填充信息task.loadingRequest.contentInformationRequest.contentType video/mp4;task.loadingRequest.contentInformationRequest.byteRangeAccessSupported YES;task.loadingRequest.contentInformationRequest.contentLength task.resource.fileLength;if (task.resource.fileLength 0) {DLogDebug(requestTask.fileLength 0);}//读文件填充数据long long cacheLength task.resource.cacheLength;long long requestedOffset task.loadingRequest.dataRequest.requestedOffset;if (task.loadingRequest.dataRequest.currentOffset ! 0) {requestedOffset task.loadingRequest.dataRequest.currentOffset;}printf(哈哈哈1111执行执行执行%lld点 %lld 一 %lld %p %p\n, task.loadingRequest.dataRequest.requestedOffset,task.loadingRequest.dataRequest.currentOffset, task.resource.requestOffset, task.loadingRequest, task);printf(哈哈哈数量数量%ld\n, self.taskList.count);for (IdiotResourceTask *task1 in self.taskList) {printf(哈哈哈啦啊啦这里这里数组里的%p %lld\n,task1, task.resource.requestOffset);}if (requestedOffset task.resource.requestOffset) {printf(哈哈哈1111返回%lld点 %lld 一 %lld %p %p\n, task.loadingRequest.dataRequest.requestedOffset,task.loadingRequest.dataRequest.currentOffset, task.resource.requestOffset, task.loadingRequest, task);return;}long long paddingOffset requestedOffset - task.resource.requestOffset;long long canReadLength cacheLength - paddingOffset;printf(哈哈哈能获取到的能获取到的%lld \n, canReadLength);if (canReadLength 0) {printf(哈哈哈返回222222 %lld\n, canReadLength);return;}long long respondLength MIN(canReadLength, task.loadingRequest.dataRequest.requestedLength);NSFileHandle * handle [IdiotFileManager fileHandleForReadingAtPath:task.resource.cachePath];[handle seekToFileOffset:paddingOffset];[task.loadingRequest.dataRequest respondWithData:[handle readDataOfLength:[[NSNumber numberWithLongLong:respondLength] unsignedIntegerValue]]];printf(哈哈哈匹配到匹配到%lld \n,respondLength);[handle closeFile];//如果完全响应了所需要的数据则完成long long nowendOffset requestedOffset canReadLength;long long reqEndOffset task.loadingRequest.dataRequest.requestedOffset task.loadingRequest.dataRequest.requestedLength;printf(哈哈哈差别差别%lld\n,reqEndOffset - nowendOffset);if (nowendOffset reqEndOffset) {[task.loadingRequest finishLoading];printf(哈哈哈移除移除移除%lld %lld\n, nowendOffset, reqEndOffset);[self removeLoadingRequest:task.loadingRequest];return;}}#pragma mark - DownLoaderDataDelegate
- (void)didReceiveData:(IdiotDownLoader *__weak)downLoader{[self processRequestList];if (self.delegate[self.delegate respondsToSelector:selector(didCacheProgressChange:)]) {__weak typeof(self) weakself self;dispatch_async(dispatch_get_main_queue(), ^{__strong typeof(weakself) strongself weakself;NSMutableArray * caches [downLoader.resources mutableCopy];[caches addObject:self.currentResource];[strongself.delegate didCacheProgressChange:caches];});}}
下面单独介绍各个方法的实现 if (self.currentResource) {if (loadingRequest.dataRequest.requestedOffset self.currentResource.requestOffset loadingRequest.dataRequest.requestedOffset self.currentResource.requestOffset self.currentResource.cacheLength) {IdiotResourceTask * task [[IdiotResourceTask alloc] init];task.loadingRequest loadingRequest;task.resource self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);[self processRequestList];}else{if (self.seek) {[self newTaskWithLoadingRequest:loadingRequest];}else{IdiotResourceTask * task [[IdiotResourceTask alloc] init];task.loadingRequest loadingRequest;task.resource self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);}}}else {[self newTaskWithLoadingRequest:loadingRequest];}
}
上面方法中的判断条件 self.currentResource 说明执行过newTaskWithLoadingRequest 方法了因为在该方法中设置了self.currentResource说明就不是第一次执行addLoadingRequest 添加request了loadingRequest.dataRequest.requestedOffset self.currentResource.requestOffset loadingRequest.dataRequest.requestedOffset self.currentResource.requestOffset self.currentResource.cacheLength 该判断条件说明 新请求的offset 是大于当前的offset, 但是小于当前的offset cachelength 说明 当前的的本地资源是有一部分是可以塞给当前的 request的 所以在创建了新的任务task的同时还执行了 [self processRequestList]; 方法。下面的 else中 if (self.seek) 说明当前的request是因为用户拖拽进度条触发的所以要重新创建一个source ,因为一个拖拽就会引起一个不连续的下载片段而在IdiotAvplayer的设计中每一个资源片段都要有一个resouce, 所以要执行newTaskWithLoadingRequest 方法 else说明不是拖拽的则直接添加新的任务即可等到新的下载好的资源到来就会去塞给新添加的请求而新的下载是不会停止的直到到达资源的最后。 - (void)finishLoadingWithLoadingRequest:(IdiotResourceTask *)task {//填充信息task.loadingRequest.contentInformationRequest.contentType video/mp4;task.loadingRequest.contentInformationRequest.byteRangeAccessSupported YES;task.loadingRequest.contentInformationRequest.contentLength task.resource.fileLength;if (task.resource.fileLength 0) {DLogDebug(requestTask.fileLength 0);}//读文件填充数据long long cacheLength task.resource.cacheLength;long long requestedOffset task.loadingRequest.dataRequest.requestedOffset;if (task.loadingRequest.dataRequest.currentOffset ! 0) {requestedOffset task.loadingRequest.dataRequest.currentOffset;}if (requestedOffset task.resource.requestOffset) {/*task.resource 是第一次播放或者拖拽才会创建的对象其 requestOffset就是对应的那次请求的offset这里的判断条件 requestedOffset task.resource.requestOffset 说明 该request是 创建 resouce 之前的request那么该resouce 对应的资源中满足该request所以就返回*/ return;}long long paddingOffset requestedOffset - task.resource.requestOffset;long long canReadLength cacheLength - paddingOffset;if (canReadLength 0) {如果该resouce offset resouce的资源长度仍然小与request 的offset说明该资源完全在request的前面无法满足该request返回return;}long long respondLength MIN(canReadLength, task.loadingRequest.dataRequest.requestedLength);NSFileHandle * handle [IdiotFileManager fileHandleForReadingAtPath:task.resource.cachePath];[handle seekToFileOffset:paddingOffset];[task.loadingRequest.dataRequest respondWithData:[handle readDataOfLength:[[NSNumber numberWithLongLong:respondLength] unsignedIntegerValue]]];[handle closeFile];//如果完全响应了所需要的数据则完成long long nowendOffset requestedOffset canReadLength;long long reqEndOffset task.loadingRequest.dataRequest.requestedOffset task.loadingRequest.dataRequest.requestedLength;if (nowendOffset reqEndOffset) {[task.loadingRequest finishLoading];[self removeLoadingRequest:task.loadingRequest];return;}}
如下图分片缓存的资源在沙盒中的保存形式是根据offset 分别保存的
IdiotDownLoader
- (void)start:(IdiotResource *)task {if (self.currentDataTask) {[self.currentDataTask cancel];}[self.taskDic setObject:task forKey:[NSString stringWithFormat:%zd,task.requestOffset]];//获取本地资源BOOL refresh NO;while (!self.writing!refresh) {self.resources [IdiotFileManager getResourceWithUrl:task.requestURL];refresh YES;}IdiotResource * resource nil;//找出对应的资源if (!self.resources.count) {//本地无资源resource [[IdiotResource alloc] init];resource.requestURL task.requestURL;resource.requestOffset task.requestOffset;resource.fileLength task.fileLength;resource.cachePath task.cachePath;resource.cacheLength 0;resource.resourceType IdiotResourceTypeNet;//网络资源[self.resources addObject:resource];}else{//本地有资源for (IdiotResource * obj in self.resources) {if (task.requestOffset obj.requestOffsettask.requestOffset obj.requestOffsetobj.cacheLength) {/*该判断条件说明当前任务offset比获取的本地分片资源offset大比本地分片资源offsetcachelength小在本地资源中间有重合的地方*/resource obj;break;}}if (task.requestOffset resource.requestOffsetresource.resourceType IdiotResourceTypeNet) {/*该resouce 是从上面的判断条件中获取的该判断说明当前任务比获取到的本地resouce offset大并且是网路请求资源说明本地没有资源需要重新下载这里新建一个IdiotResource并且设置offsettask.offset就是为了从当前任务的offset开始下载否则会中本得resouce 的offset开始下载这样就会导致下载的比我们需要的多并且用户会有一个卡住的体验因为下载的不是用户需要的offset这里这样写保证下载的offset就是用户需要的并且避免流量浪费 */long long adjustCacheLength task.requestOffset - resource.requestOffset;IdiotResource * net [[IdiotResource alloc] init];net.requestURL task.requestURL;net.requestOffset task.requestOffset;net.fileLength task.fileLength;net.cachePath task.cachePath;net.cacheLength resource.cacheLength - adjustCacheLength;net.resourceType IdiotResourceTypeNet;//网络资源resource.cacheLength adjustCacheLength;NSInteger index [self.resources indexOfObject:resource]1;[self.resources insertObject:net atIndex:index];resource net;}}self.currentResource resource;[self fetchDataWith:task Resource:self.currentResource];}
- (void)fetchFromLocal:(IdiotResource *)sliceRequest withResource:(IdiotResource *)resource{if (sliceRequest.requestOffset resource.requestOffset) {sliceRequest.cachePath resource.cachePath;sliceRequest.fileLength resource.fileLength;sliceRequest.cacheLength resource.cacheLength;//直接开始下一个资源获取if (self.delegate [self.delegate respondsToSelector:selector(didReceiveData:)]) {[self.delegate didReceiveData:self];}[self willNextResource:sliceRequest];return;}NSFileHandle * readHandle [IdiotFileManager fileHandleForReadingAtPath:resource.cachePath];unsigned long long seekOffset sliceRequest.requestOffset resource.requestOffset?0:sliceRequest.requestOffset-resource.requestOffset;[readHandle seekToFileOffset:seekOffset];//文件过大可分次读取long long canReadLength resource.cacheLength-seekOffset;NSUInteger bufferLength 5242880; //长度大于5M分次返回数据/*如果本地资源比较大就分片塞数据如果一下将整个资源读取到内存中就会造成内存撑爆导致严重的卡顿*/while (canReadLength bufferLength) {//长度大于1M分次返回数据canReadLength - bufferLength;NSData * responseData [readHandle readDataOfLength:bufferLength];[self didReceiveLocalData:responseData requestTask:sliceRequest complete:canReadLength0?YES:NO];}if (canReadLength ! 0) {NSData * responseData [readHandle readDataOfLength:[[NSNumber numberWithLongLong:canReadLength] unsignedIntegerValue]];[readHandle closeFile];[self didReceiveLocalData:responseData requestTask:sliceRequest complete:YES];}else{[readHandle closeFile];}}