仿微博视频边下边播之封装播放器

 

来源:NewPan(@盼盼_HKbuy)  git

连接:http://www.jianshu.com/p/0d4588a7540fgithub

 

Tips:此次的内容分为两篇文章讲述数组

 

0一、[iOS]仿微博视频边下边播之封装播放器 讲述如何封装一个实现了边下边播而且缓存的视频播放器。缓存

0二、[iOS]仿微博视频边下边播之滑动TableView自动播放 讲述如何实如今tableView中滑动播放视频,而且是流畅,不阻塞线程,没有任何卡顿的实现滑动播放视频。同时也将讲述当tableView滚动时,以什么样的策略,来肯定究竟哪个cell应该播放视频。服务器

 


 

微博视频的特色:微信

  • 秒拍团队主要致力于视频处理,微博的视频播放功能是由秒拍提供技术支持的。微博的视频通常都是不限时长的,因此它的特色是边下边播。网络

  • 说到视频播放就不能不提微信的短视频,微信的短视频限制时长为15秒,通过微信团队处理后,一个短视频的体积能控制在2MB之内。因此微信的视频是先下载,再读取下载好的视频文件进行播放,也就是所谓的先下后播。这个功能,微信的同行已经把源码分享出来了,(http://www.jianshu.com/p/3d5ccbde0de1)。session

我找了不少资料,没有找到彻底意义上,实现了微博首页列表视频边下边播功能的资料。可是我本身项目中又有这个需求,因此只能本身动手。最后实现的效果以下:框架

这个列表视频边下边播包含如下主要的功能点:ide

 

  • 01.必须是边下边播。

  • 02.若是缓存好的视频是完整的,就要把这个视频保存起来,下次再次加载这个视频的时候,就先检查本地有没有缓存好的视频。这一点对于节省用户流量,提高用户体验很重要。要实现这一点,也就是说,咱们要手动干预系统播放器加载数据的内部实现,这个细节后面再讲。

  • 03.不阻塞线程,不卡顿,滑动如丝顺滑,这是保证用户体验最重要的一点。

  • 04.当tableView滚动时,以什么样的策略,来肯定究竟哪个cell应该播放视频。

 

可能你着急赶项目,只想尽快的把这个功能集成到你的项目,那么请你直接去(https://github.com/Chris-Pan/JPVideoPlayer)上下载源码。须要说明的是,我上面说的功能点的第一和第二点,不用你关心,我已经帮你处理封装好了。可是,第三和第四点,须要你本身结合你本身的项目来定制,我只提供了模板和巨细无比的注释。

 

接下来就来看看我是怎么实现这些功能的。

 

第1、AVPlayer基本使用?

 

首先从最基本的封装播放器开始。

 

0一、AVPlayer?

 

AVPlayer播放视频须要涉及如下几个类:

 

  • AVURLAsset,是AVAsset的子类,负责网络链接,请求数据。

  • AVPlayerItem,会创建媒体资源动态视角的数据模型并保存AVPlayer播放资源的状态。说白了,就是数据管家。

  • AVPlayer,播放器,将数据解码处理成为图像和声音。

  • AVPlayerLayer,图像层,AVPlayer的图像要经过AVPlayerLayer呈现。

 

须要注意的是,AVPlayer的模式是,你不要主动调用play方法播放视频,而是等待AVPlayerItem告诉你,我已经准备好播放了,你如今能够播放了,因此咱们要监听AVPlayerItem的状态,经过添加监听者的方式获取AVPlayerItem的状态:

 

// 添加监听

[_currentPlayerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];

 

在监听结果中处理播放逻辑。当监听到播放器已经准备好播放的时候,就能够调用play方法。

 

注意点:若是视频还没准备好播放,你就把AVPlayerLayer图层添加到cell上,那么在播放器尚未准备好播放以前,负责显示的图像的图层会变成黑色,直到准备好播放,拿到数据,才会出现画面。这在列表中自动播放是应该极力避免的。因此,要等待播放器有图像输出的时候再添加显示的预览图层到cell上。

 

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{

  if ([keyPath isEqualToString:@"status"]) {

      AVPlayerItem *playerItem = (AVPlayerItem *)object;

      AVPlayerItemStatus status = playerItem.status;

      switch (status) {

          case AVPlayerItemStatusUnknown:{

 

          }

              break;

          case AVPlayerItemStatusReadyToPlay:{

              [self.player play];

              self.player.muted = self.mute;

              // 显示图像逻辑

              [self handleShowViewSublayers];

 

          }

              break;

          case AVPlayerItemStatusFailed:{

 

          }

              break;

          default:

              break;

      }

  }

}

 

到这里就能够播放一个网络或者本地视频了。可是,在播放过程当中:创建链接–>请求数据–>统筹数据–>数据解码–>输出图像和声音,这些过程都是AVFoundation框架下,我上面列举的那些类自动帮咱们完成的。

 

系统处理.png

 

要实现边下边播,并实现缓存功能,就必须拿到播放器的数据,也就是必须手动干预数据加载的过程。咱们须要在网络层和解码层中间,插入一个咱们本身须要的功能块,也就是我下图中的红色模块。

 

手动干预.png

 

0二、AVAssetResourceLoaderDelegate?

 

  • 要实如今播放器请求中插入本身的模块的功能,咱们须要借助于AVAssetResourceLoaderDelegate。咱们用到的AVURLAsset下有一个AVAssetResourceLoader属性。

 

@property (nonatomic, readonly) AVAssetResourceLoader *resourceLoader;

 

  • 这个AVAssetResourceLoader是负责数据加载的,最最重要的是咱们只要遵照了AVAssetResourceLoaderDelegate,就能够成为它的代理,成为它的代理之后,数据加载都会经过代理方法询问咱们。这样,咱们就找到切入口干预数据的加载了。

 

-(BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;

-(void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest;

 

  • 在正式进入数据干预以前,咱们先看一个很重要的东西。咱们知道视频数据都是容量巨大的连续媒体数据,因此请求数据的时候,咱们要将请求策略置为streaming。这个策略的含义是,将容量巨大的连续媒体数据进行分段,分割为数量众多的小文件进行传递。

 

- (NSURL *)getSchemeVideoURL:(NSURL *)url{

  // NSURLComponents用来替代NSMutableURL,能够readwrite修改URL。这里经过更改请求策略,将容量巨大的连续媒体数据进行分段

  // 分割为数量众多的小文件进行传递。采用了一个不断更新的轻量级索引文件来控制分割后小媒体文件的下载和播放,可同时支持直播和点播

  NSURLComponents *components = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];

  components.scheme = @"streaming";

  return [components URL];

}

 

第2、手动干预系统播放器加载数据?

 

0一、如何使用NSURLSession来下载大文件?

 

在NSURLSession以前,你们都是使用NSURLConnection。现在在Xcode7中,NSURLConnection已经成为过时的类目了,咱们经常使用的AFNNetwork也完全抛弃了NSURLConnection,转向NSURLSession。如今看一下怎么使用NSURLSession:

 

// 替代NSMutableURL, 能够动态修改scheme

NSURLComponents *actualURLComponents = [[NSURLComponents alloc] initWithURL:url resolvingAgainstBaseURL:NO];

actualURLComponents.scheme = @"http";

 

// 建立请求

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[actualURLComponents URL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20.0];

 

// 修改请求数据范围

if (offset > 0 && self.videoLength > 0) {

    [request addValue:[NSString stringWithFormat:@"bytes=%ld-%ld",(unsigned long)offset, (unsigned long)self.videoLength - 1] forHTTPHeaderField:@"Range"];

}

 

// 重置

[self.session invalidateAndCancel];

 

// 建立Session,并设置代理

self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];

 

// 建立会话对象

NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];

 

// 开始下载

[dataTask resume];

 

咱们能够在NSURLSession的代理方法中得到下载的数据,拿到下载的数据之后,咱们使用NSOutputStream,将数据写入到硬盘中存放临时文件的文件夹。在请求结束的时候,咱们判断是否成功下载好文件,若是下载成功,就把这个文件转移到咱们的存储成功文件的文件夹。若是下载失败,就把临时数据删除。

 

// 1.接收到服务器响应的时候

-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler;

 

// 2.接收到服务器返回数据的时候调用,会调用屡次

-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;

 

// 3.请求结束的时候调用(成功|失败),若是失败那么error有值

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;

 

0二、AVAssetResourceLoader的代理?

 

为了更好的封装性和可维护性,新建一个文件,让这个文件负责和系统播放器对接数据。上面说到,只要这个文件遵照了AVAssetResourceLoaderDelegate协议,他就有资格代理系统播放器请求数据。而且系统会经过

 

-(BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest;

 

这个代理方法,把下载请求loadingRequest传给咱们。拿到请求之后,首先把请求用一个数组保存起来。为何要用数组保存起来?由于,当咱们拿到请求去下载数据,到数据下载好,这个过程须要的时间是不肯定的。

 

拿到请求之后,咱们就须要调用上面封装的NSURLSession下载器来下载文件。

 

- (void)dealLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest{

    NSURL *interceptedURL = [loadingRequest.request URL];

    NSRange range = NSMakeRange(loadingRequest.dataRequest.currentOffset, MAXFLOAT);

 

    if (self.manager) {

        if (self.manager.downLoadingOffset > 0)

            [self processPendingRequests];

 

        // 若是新的rang的起始位置比当前缓存的位置还大300k,则从新按照range请求数据

        if (self.manager.offset + self.manager.downLoadingOffset + 1024*300  range.location) {

            [self.manager setUrl:interceptedURL offset:range.location];

        }

    }

    else{

        self.manager = [JPDownloadManager new];

        self.manager.delegate = self;

        [self.manager setUrl:interceptedURL offset:0];

    }

}

 

若是文件有下载好,就去检查下载好的数据长度有没有知足请求数据须要的长度,若是知足,就从硬盘的临时文件中取出对应的数据,并把这段数据填充给请求,而后把这个请求从请求列表数组中移除。播放器拿到了这段数据,就能够开始解码播放了。

 

// 判断这次请求的数据是否处理彻底, 和填充数据

- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest{

    // 请求起始点

    long long startOffset = dataRequest.requestedOffset;

 

    // 当前请求点

    if (dataRequest.currentOffset != 0)

        startOffset = dataRequest.currentOffset;

 

    // 播放器拖拽后大于已经缓存的数据

    if (startOffset > (self.manager.offset + self.manager.downLoadingOffset))

        return NO;

 

    // 播放器拖拽后小于已经缓存的数据

    if (startOffset = endOffset;

 

    return didRespondFully;

  }

 

至此,手动干预播放视频的流程就走完了。已经能够正常播放视频了。

 

JPVideoPlayer.png

 

0三、加载缓存数据逻辑?

 

接下来要作的就是实现,当下次播放同一个视频的时候,先去检查硬盘里有没有这个文件的缓存。借助于NSFileManager,咱们能够查找指定的路径有没有存在指定的文件,从而判断有没有缓存能够启用。

 

NSFileManager *manager = [NSFileManager defaultManager];

NSString *savePath = [self fileSavePath];

savePath = [savePath stringByAppendingPathComponent:self.suggestFileName];

if ([manager fileExistsAtPath:savePath]) {

    // 已经存在这个下载好的文件了

    return;

}

 

至此,播放器封装完毕。

 

我将在下一篇文章 [iOS]仿微博视频边下边播之滑动TableView自动播放 ,讲述如何实如今tableView中滑动播放视频,而且是流畅,不阻塞线程,没有任何卡顿的实现滑动播放视频。同时也将讲述当tableView滚动时,以什么样的策略,来肯定究竟哪个cell应该播放视频。

 

0三、更新

 

  • 2016.10.09 :

    处理在切换视频的短暂时间内, 当前播放视频的cell吸取了滑动事件, 若是滑动当前播放视频的cell, 会致使tableView没法接收到滑动事件, 形成tableView假死。 感谢提供bug的朋友@大墙66370 具体见个人Github JPVideoPlayer(https://github.com/Chris-Pan/JPVideoPlayer)

相关文章
相关标签/搜索