[iOS]仿微博视频边下边播之封装播放器
Tips:这次的内容分为两篇文章讲述 01、[iOS]仿微博视频边下边播之封装播放器 讲述如何封装一个实现了边下边播并且缓存的视频播放器。 02、[iOS]仿微博视频边下边播之滑动TableView自动播放 讲述如何实现在tableView中滑动播放视频,并且是流畅,不阻塞线程,没有任何卡顿的实现滑动播放视频。同时也将讲述当tableView滚动时,以什么样的策略,来确定究竟哪一个cell应该播放视频。
微博视频的特点:
- 秒拍团队主要致力于视频处理,微博的视频播放功能是由秒拍提供技术支持的。微博的视频一般都是不限时长的,所以它的特点是边下边播。
- 说到视频播放就不能不提微信的短视频,微信的短视频限制时长为15秒,经过微信团队处理后,一个短视频的体积能控制在2MB以内。所以微信的视频是先下载,再读取下载好的视频文件进行播放,也就是所谓的先下后播。这个功能,微信的同行已经把源码分享出来了,在这里。
我找了很多资料,没有找到完全意义上,实现了微博首页列表视频边下边播功能的资料。但是我自己项目中又有这个需求,所以只能自己动手。最后实现的效果如下:
这个列表视频边下边播包含以下主要的功能点:
- 01.必须是边下边播。
- 02.如果缓存好的视频是完整的,就要把这个视频保存起来,下次再次加载这个视频的时候,就先检查本地有没有缓存好的视频。这一点对于节省用户流量,提升用户体验很重要。要实现这一点,也就是说,我们要手动干预系统播放器加载数据的内部实现,这个细节后面再讲。
- 03.不阻塞线程,不卡顿,滑动如丝顺滑,这是保证用户体验最重要的一点。
- 04.当tableView滚动时,以什么样的策略,来确定究竟哪一个cell应该播放视频。
可能你着急赶项目,只想尽快的把这个功能集成到你的项目,那么请你直接去 Github 上下载源码。需要说明的是,我上面说的功能点的第一和第二点,不用你关心,我已经帮你处理封装好了。但是,第三和第四点,需要你自己结合你自己的项目来定制,我只提供了模板和巨细无比的注释。
接下来就来看看我是怎么实现这些功能的。
第一、AVPlayer基本使用?
首先从最基本的封装播放器开始。
01、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<NSString *,id> *)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框架下,我上面列举的那些类自动帮我们完成的。
要实现边下边播,并实现缓存功能,就必须拿到播放器的数据,也就是必须手动干预数据加载的过程。我们需要在网络层和解码层中间,插入一个我们自己需要的功能块,也就是我下图中的红色模块。
02、AVAssetResourceLoaderDelegate?
要实现在播放器请求中插入自己的模块的功能,我们需要借助于AVAssetResourceLoaderDelegate。我们用到的AVURLAsset下有一个AVAssetResourceLoader属性。
[@property](https://my.oschina.net/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]; }
第二、手动干预系统播放器加载数据?
01、如何使用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;
02、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.offset > 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 < self.manager.offset) return NO; NSData *fileData = [NSData dataWithContentsOfFile:_videoPath options:NSDataReadingMappedIfSafe error:nil]; NSInteger unreadBytes = self.manager.downLoadingOffset - self.manager.offset - (NSInteger)startOffset; NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes); [dataRequest respondWithData:[fileData subdataWithRange:NSMakeRange((NSUInteger)startOffset- self.manager.offset, (NSUInteger)numberOfBytesToRespondWith)]]; long long endOffset = startOffset + dataRequest.requestedOffset; BOOL didRespondFully = (self.manager.offset + self.manager.downLoadingOffset) >= endOffset; return didRespondFully; }
至此,手动干预播放视频的流程就走完了。已经可以正常播放视频了。
03、加载缓存数据逻辑?
接下来要做的就是实现,当下次播放同一个视频的时候,先去检查硬盘里有没有这个文件的缓存。借助于NSFileManager,我们可以查找指定的路径有没有存在指定的文件,从而判断有没有缓存可以启用。
NSFileManager *manager = [NSFileManager defaultManager];
NSString *savePath = [self fileSavePath]; savePath = [savePath stringByAppendingPathComponent:self.suggestFileName]; if ([manager fileExistsAtPath:savePath]) { // 已经存在这个下载好的文件了 return; }
至此,播放器封装完毕。
我将在下一篇文章 [iOS]仿微博视频边下边播之滑动TableView自动播放 ,讲述如何实现在tableView中滑动播放视频,并且是流畅,不阻塞线程,没有任何卡顿的实现滑动播放视频。同时也将讲述当tableView滚动时,以什么样的策略,来确定究竟哪一个cell应该播放视频。
03、更新
- 2016.10.09 : 处理在切换视频的短暂时间内, 当前播放视频的cell吸收了滑动事件, 如果滑动当前播放视频的cell, 会导致tableView无法接收到滑动事件, 造成tableView假死。 感谢提供bug的朋友@大墙66370 具体见我的Github JPVideoPlayer。
我的文章集合
下面这个链接是我所有文章的一个集合目录。这些文章凡是涉及实现的,每篇文章中都有GIT地址,GIT上都有源码。如果某篇文章刚好在你的实际开发中帮到你,又或者提供一种不同的实现思路,让你觉得有用,那就看看这句话 “坚持每天点赞的人,99%都是帅哥美女,再也不用单身了?”
我的文章集合索引
你还可以关注我自己维护的简书专题[iOS开发心得](http://www.jianshu.com/users/e2f2d779c022/latest_articles)。这个专题的文章都是实打实的干货。
######如果你有问题,除了在文章最后留言,还可以在微博@盼盼_HKbuy上给我留言,以及访问我的Github。
转载于:https://my.oschina.net/newpan/blog/777054
[iOS]仿微博视频边下边播之封装播放器相关推荐
- 仿微博视频边下边播之封装播放器
来源:NewPan(@盼盼_HKbuy) 链接:http://www.jianshu.com/p/0d4588a7540f Tips:这次的内容分为两篇文章讲述 01.[iOS]仿微博视频边下边播之封 ...
- [iOS]仿微博视频边下边播之滑动 TableView 自动播放
注意:框架已经迭代到2.0版本,我重新架构了整个框架,API 也得到了更好的设计,我为 2.0 版本的实现写了一篇文章 [iOS]如何重新架构 JPVideoPlayer ?.此文中的实现思路仍然是一 ...
- [iOS]仿微博视频边下边播之滑动TableView自动播放
Tips:这次的内容分为两篇文章讲述 01.[iOS]仿微博视频边下边播之封装播放器 讲述如何封装一个实现了边下边播并且缓存的视频播放器. 02.[iOS]仿微博视频边下边播之滑动TableView自 ...
- iOS开发之仿微博视频边下边播之自定义AVPlayer播放器, 边下边播解剖。视频处理流程,建立连接-请求数据-统筹数据-解码数据-视频呈现
Tips:这次的内容分为两篇文章讲述 01.[iOS]仿微博视频边下边播之封装播放器 讲述如何封装一个实现了边下边播并且缓存的视频播放器. 02.[iOS]仿微博视频边下边播之滑动TableView自 ...
- java 视频边下边播,VideoViewDemo android 播放器,支持边下边播 238万源代码下载- www.pudn.com...
文件名称: VideoViewDemo下载 收藏√ [ 5 4 3 2 1 ] 开发工具: Java 文件大小: 194 KB 上传时间: 2014-09-19 下载次数: 5 详细说明:a ...
- 迅雷9边下边播绑定第三方播放器
迅雷9边下边播绑定第三方播放器 测试版本(迅雷9绿色版) 测试播放器 测试版本(迅雷9绿色版) 测试播放器 目前测试的播放器为potplayer.爱奇艺万能播放器,再加上又重新开始更新的QQ影音,之前 ...
- 迅雷极速版修改边下边播的默认播放器为PotPlayer
在使用前先将原有的迅雷自带的迅雷看看播放器卸载 首先打开迅雷安装路径下的以下文件夹,(每个人有每个人的安装路径) D:\迅雷极速版\Program 在该文件夹下新建一个文本文档,将其名字和后缀名改为 ...
- 0930 视频边下边播/蓝牙库/阿里博客/afnetworking详细/小程序工具
iOS视频边下边播–缓存播放数据流 简书: http://www.jianshu.com/p/990ee3db0563 简单易用的蓝牙库,基于CoreBluetooth的封装,并兼容ios和mac o ...
- 超级播(去广告播放器) v1.14.912.1 官方版
超级播(去广告播放器) v1.14.912.1 官方版 软件大小:4.12MB 软件语言:简体中文 软件类别:影音播放 软件授权:官方版 应用平台:/Win8/Win7/WinXP 是一款给力的去广告 ...
- 酷播云H5播放器与JS之间交互的实例
酷播云H5播放器与JS之间交互的实例 <!doctype html> <html> <head> <meta charset="utf-8" ...
最新文章
- LeetCode刷题记录10——434. Number of Segments in a String(easy)
- 第二十二课.DeepGraphLibrary(三)
- Orace 11g 监听 配置修改 说明
- openstack中手动修改虚拟机IP后不能通信
- 年方二八,人生的路口之上
- Django(part41)--中间键Middleware
- VUE.JS 组件化开发实践
- 考研重庆邮电大学计算机跨专业,重庆邮电大学计算机考研难度
- (转)python3 urllib.request.urlopen() 错误UnicodeEncodeError: 'ascii' codec can't encode characters...
- 开源的全面胜利背后,那些被遗忘的人性问题
- Python3调用新浪微博API抓取数据
- 迭代重心法 matlab,重心法
- [e袋购APP]高校物业管理的特点
- 安装XAMPP遇到的问题(一)——要求关闭UAC以及权限问题
- 03、单线通讯—SIF通讯协议(一线通)案例二
- python 减法函数_python之函数
- 新建了个文件写代码报 Delete `␍`
- 《经济学通识》十二、谈环保
- 视频号直播间微信小程序开发制作介绍
- Golang开发的CMS内容发布系统
热门文章
- 魔兽争霸无法在这个计算机,win10魔兽争霸三无法初始化directx怎么办_win10魔兽争霸三不能初始化directx解决步骤...
- 小波阈值去噪c语言程序,小波阈值去噪MATLAB程序
- 【光纤专题】单模光纤和多模光纤有哪些不同之处?《建议收藏》
- 2011腾讯QQ通用BUG和变态小秘密!
- 斐讯w2Android Wear,有人研究过W2的连接QRcode吗?
- matlab 画qpsk眼图,MATLABQPSK在AWGN信道下的仿真
- 计算机处理器(CPU)基础
- 文本检测综述(2017 ~ 2021 持续更新)
- 关于三栏式布局的几种方式
- bigbluebutton视频直播服务器调用接口