AVPlayer使用资源加载器委托来拖延大型video文件

我正在使用这种方法来保存AVPlayer的video文件的缓冲区数据。 作为这个问题的答案find保存AVPlayer的缓冲区数据 。

iPhone和iPad – iOS 8.1.3

我做了必要的修改来播放video,除非我尝试播放一个非常长的video(11-12分钟长,大约85mb),video将在连接完成后大约4分钟停止播放。 我得到一个事件为播放缓冲空间和播放器项目停滞通知。

这是代码的要点

viewController.m @property (nonatomic, strong) NSMutableData *videoData; @property (nonatomic, strong) NSURLConnection *connection; @property (nonatomic, strong) AVURLAsset *vidAsset; @property (nonatomic, strong) AVPlayerItem *playerItem; @property (nonatomic, strong) AVPlayerLayer *avlayer; @property (nonatomic, strong) NSHTTPURLResponse *response; @property (nonatomic, strong) NSMutableArray *pendingRequests; /** Startup a Video */ - (void)startVideo { self.vidAsset = [AVURLAsset URLAssetWithURL:[self videoURLWithCustomScheme:@"streaming"] options:nil]; [self.vidAsset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; self.pendingRequests = [NSMutableArray array]; // Init Player Item self.playerItem = [AVPlayerItem playerItemWithAsset:self.vidAsset]; [self.playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:NULL]; self.player = [[AVPlayer alloc] initWithPlayerItem:self.playerItem]; // Init a video Layer self.avlayer = [AVPlayerLayer playerLayerWithPlayer:self.player]; [self.avlayer setFrame:self.view.frame]; [self.view.layer addSublayer:self.avlayer]; } - (NSURL *)getRemoteVideoURL { NSString *urlString = [@"http://path/to/your/long.mp4"]; return [NSURL URLWithString:urlString]; } - (NSURL *)videoURLWithCustomScheme:(NSString *)scheme { NSURLComponents *components = [[NSURLComponents alloc] initWithURL:[self getRemoteVideoURL] resolvingAgainstBaseURL:NO]; components.scheme = scheme; return [components URL]; } /** NSURLConnection Delegate Methods */ - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSLog(@"didReceiveResponse"); self.videoData = [NSMutableData data]; self.response = (NSHTTPURLResponse *)response; [self processPendingRequests]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { NSLog(@"Received Data - appending to video & processing request"); [self.videoData appendData:data]; [self processPendingRequests]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSLog(@"connectionDidFinishLoading::WriteToFile"); [self processPendingRequests]; [self.videoData writeToFile:[self getVideoCachePath:self.vidSelected] atomically:YES]; } /** AVURLAsset resource loader methods */ - (void)processPendingRequests { NSMutableArray *requestsCompleted = [NSMutableArray array]; for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests) { [self fillInContentInformation:loadingRequest.contentInformationRequest]; BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; if (didRespondCompletely) { [requestsCompleted addObject:loadingRequest]; [loadingRequest finishLoading]; } } [self.pendingRequests removeObjectsInArray:requestsCompleted]; } - (void)fillInContentInformation:(AVAssetResourceLoadingContentInformationRequest *)contentInformationRequest { if (contentInformationRequest == nil || self.response == nil) { return; } NSString *mimeType = [self.response MIMEType]; CFStringRef contentType = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, (__bridge CFStringRef)(mimeType), NULL); contentInformationRequest.byteRangeAccessSupported = YES; contentInformationRequest.contentType = CFBridgingRelease(contentType); contentInformationRequest.contentLength = [self.response expectedContentLength]; } - (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest { long long startOffset = dataRequest.requestedOffset; if (dataRequest.currentOffset != 0) { startOffset = dataRequest.currentOffset; } // Don't have any data at all for this request if (self.videoData.length < startOffset) { NSLog(@"NO DATA FOR REQUEST"); return NO; } // This is the total data we have from startOffset to whatever has been downloaded so far NSUInteger unreadBytes = self.videoData.length - (NSUInteger)startOffset; // Respond with whatever is available if we can't satisfy the request fully yet NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes); [dataRequest respondWithData:[self.videoData subdataWithRange:NSMakeRange((NSUInteger)startOffset, numberOfBytesToRespondWith)]]; long long endOffset = startOffset + dataRequest.requestedLength; BOOL didRespondFully = self.videoData.length >= endOffset; return didRespondFully; } - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { if (self.connection == nil) { NSURL *interceptedURL = [loadingRequest.request URL]; NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:interceptedURL resolvingAgainstBaseURL:NO]; actualURLComponents.scheme = @"http"; NSURLRequest *request = [NSURLRequest requestWithURL:[actualURLComponents URL]]; self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; [self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; [self.connection start]; } [self.pendingRequests addObject:loadingRequest]; return YES; } - (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest { NSLog(@"didCancelLoadingRequest"); [self.pendingRequests removeObject:loadingRequest]; } /** KVO */ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == StatusObservationContext) { AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue]; if (status == AVPlayerStatusReadyToPlay) { [self initHud]; [self play:NO]; } else if (status == AVPlayerStatusFailed) { NSLog(@"ERROR::AVPlayerStatusFailed"); } else if (status == AVPlayerItemStatusUnknown) { NSLog(@"ERROR::AVPlayerItemStatusUnknown"); } } else if (context == CurrentItemObservationContext) { } else if (context == RateObservationContext) { } else if (context == BufferObservationContext){ } else if (context == playbackLikelyToKeepUp) { if (self.player.currentItem.playbackLikelyToKeepUp) } } else if (context == playbackBufferEmpty) { if (self.player.currentItem.playbackBufferEmpty) { NSLog(@"Video Asset is playable: %d", self.videoAsset.isPlayable); NSLog(@"Player Item Status: %ld", self.player.currentItem.status); NSLog(@"Connection Request: %@", self.connection.currentRequest); NSLog(@"Video Data: %lu", (unsigned long)self.videoData.length); } } else if(context == playbackBufferFull) { } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } 

问题似乎是连接完成加载一段时间后,播放器项缓冲区变空。 我现在的想法是,当连接完成加载和搞乱playerItem缓冲区时,某些东西被释放。

但是当缓冲区变空时,playerItem状态是好的,video资源是可播放的,video数据是好的

如果我通过charles调节wifi并放慢连接速度,只要连接在video结束后的几分钟内没有完成加载,video就会播放。

如果我在完成的加载事件中设置了连接nil,则当shouldWaitForLoadingOfRequestedResource再次触发时,资源加载器将启动一个新的连接。 在这种情况下,加载重新开始,video将继续播放。

我应该提到,这个长video播放罚款,如果我把它作为一个普通的HTTPurl资产,并保存到设备,并从那里加载后,播放良好。

当资源加载器委托触发NSURLConnection时,连接会将NSData保存到挂起的请求并处理它们。 当连接完成加载时,资源加载器重新承担处理加载请求的责任。 该代码是将加载请求添加到挂起的请求数组,但问题是他们没有被处理。 改变了以下方法,它的工作原理。

 //AVAssetResourceLoader - (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest { if(isLoadingComplete == YES) { //NSLog(@"LOADING WAS COMPLETE"); [self.pendingRequests addObject:loadingRequest]; [self processPendingRequests]; return YES; } if (self.connection == nil) { NSURL *interceptedURL = [loadingRequest.request URL]; NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:interceptedURL resolvingAgainstBaseURL:NO]; actualURLComponents.scheme = @"http"; self.request = [NSURLRequest requestWithURL:[actualURLComponents URL]]; self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; [self.connection setDelegateQueue:[NSOperationQueue mainQueue]]; isLoadingComplete = NO; [self.connection start]; } [self.pendingRequests addObject:loadingRequest]; return YES; }