ios – AVPlayer使用资源加载器委托停止大型视频文件

我正在使用 this approach保存视频文件的AVPlayer的缓冲区数据.在这个问题 Saving buffer data of AVPlayer中找到答案.

iPhone和iPad – iOS 8.1.3

我做了必要的更改,播放视频,它的工作非常好,除非我尝试播放一个很长的视频(11-12分钟长,大约85mb),视频将在连接完成加载后大约4分钟停止.我收到一个playBufferEmpty和播放器项目停止通知的事件.

这是代码的要点

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缓冲区时,正在释放某些东西.

然而,当缓冲区为空时,播放器的状态是好的,视频资源可以播放,视频数据是好的

如果我通过查询来限制WiFi,并减慢连接速度,只要连接在视频结束后几分钟内没有完成加载,视频就会播放.

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

我应该提到,如果我将其作为普通的http网址资源播放,这个长长的视频播放效果很好,并且在保存到设备并从那里加载之后也会播放.

解决方法

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

相关文章

UITabBarController 是 iOS 中用于管理和显示选项卡界面的一...
UITableView的重用机制避免了频繁创建和销毁单元格的开销,使...
Objective-C中,类的实例变量(instance variables)和属性(...
从内存管理的角度来看,block可以作为方法的传入参数是因为b...
WKWebView 是 iOS 开发中用于显示网页内容的组件,它是在 iO...
OC中常用的多线程编程技术: 1. NSThread NSThread是Objecti...