AVPlayer使用资源加载器委托在大型视频文件上暂停



我使用这种方法来保存AVPlayer的视频文件缓冲区数据。在这个问题中找到答案保存AVPlayer的缓冲区数据。

iPhone和iPad-iOS 8.1.3

我做了必要的更改来播放视频,它运行得很好,除了当我尝试播放一个很长的视频(11-12分钟长,大约85mb大小)时,视频将在连接加载完成后大约4分钟停止。我收到一个playbackBufferEmpty事件和一个玩家项目暂停通知。

这是代码的要点

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状态良好,视频资产可播放,视频数据良好

如果我通过charles关闭wifi并放慢连接速度,只要视频结束后几分钟内连接没有完成加载,视频就会播放。

如果我在已完成加载事件上设置了连接nil,那么当shouldWaitForLoadingOfRequestedResource再次激发时,资源加载器将激发一个新的连接。在这种情况下,加载将重新开始,视频将继续播放。

我应该提到的是,如果我把这个长视频作为普通的http url资产播放,它会播放得很好,而且在保存到设备并从那里加载后也会播放得好。

当资源加载器委托启动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;
}

最新更新