iOS 加载大量本地视频优化
Posted 长沙火山
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 加载大量本地视频优化相关的知识,希望对你有一定的参考价值。
一、问题的产生
1.1 发现问题
最近开发了一个视频剪辑的APP,其中有这么一个功能,对原视频进行剪辑编辑好之后,可以直接导出到相册,同时APP也会将这个视频保存到本地(沙盒),然后APP专门有一个“我的作品”界面来展示所有存储在本地的视频。在自己做测试的时候,发现点击“我的作品”页面,需要比较长的时间才能响应,而且视频越多响应时间越长。于是我猜想可能是因为同时加载大量视频导致的。
1.2 分析代码
(1) 从沙盒中读取所有视频
- (NSMutableArray *)getVideoUrlFromSandbox
NSMutableArray *tempArr = [[NSMutableArray alloc] init];
NSString *documentPath = [FileTools createDirectoryInDocumentDirectory:@"/Videos"];
NSArray *fileNames = [[NSFileManager defaultManager] subpathsAtPath: documentPath];
for (NSString *fileName in fileNames)
NSString *filePath = [documentPath stringByAppendingFormat:@"/%@", fileName];
[tempArr addObject:filePath];
return tempArr;
(2) 根据视频的URL,获取视频封面、视频大小、视频时长等信息存储在model中
- (void)loadVideoFromArray:(NSMutableArray *)videoUrls
for (NSString *filePath in videoUrls)
SNHVideoModel *model = [[SNHVideoModel alloc] init];
model.filePath = filePath;
model.assetUrl = [NSURL fileURLWithPath:filePath];
model.videoImage = [[SNHVideoTool shared] getVideoThumbnail:filePath];
model.videoName = @"视频";
model.videoSize = [[SNHVideoTool shared] getVideoSize:filePath];
model.duration = [[SNHVideoTool shared] getVideoDuration:filePath];
model.createDate = [[SNHVideoTool shared] getVideoCreateDate:filePath];
[self.arrVideos addObject: model];
[self.tableView reloadData];
(3) tableView 显示
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
static NSString *identifier = @"CELL";
VideoListCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil)
cell = [[VideoListCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
SNHVideoModel *model = self.arrVideos[indexPath.row];
model.videoName = [NSString stringWithFormat:@"视频:%ld", (long)indexPath.row];
cell.videoModel = model;
return cell;
(4) tableViewCell 代码
- (void)setVideoModel:(SNHVideoModel *)videoModel
[self.videoImageView setImage:videoModel.videoImage];
self.videoNameLabel.text = videoModel.videoName;
self.videoSizeLabel.text = videoModel.videoSize;
self.createDateLabel.text = videoModel.createDate;
self.durationLabel.text = videoModel.duration;
1.3 问题总结
因为代码比较多,不能一一贴出来,只展示了主要的代码片段。从上面的几段代码似乎看出问题所在,下面小编带大家一起来仔细分析一下问题。
model.videoImage = [[SNHVideoTool shared] getVideoThumbnail:filePath]; //获取视频封面图片
model.videoName = @"视频";
model.videoSize = [[SNHVideoTool shared] getVideoSize:filePath]; //获取视频大小
model.duration = [[SNHVideoTool shared] getVideoDuration:filePath]; //获取视频时长
model.createDate = [[SNHVideoTool shared] getVideoCreateDate:filePath]; //获取视频创建日期
上面这段根据视频URL获取视频相关信息是比较费时的,我们可以测试一下这段代码执行需要多长时间:
- (void)loadVideoFromArray:(NSMutableArray *)videoUrls
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (NSString *filePath in videoUrls)
SNHVideoModel *model = [[SNHVideoModel alloc] init];
model.filePath = filePath;
model.assetUrl = [NSURL fileURLWithPath:filePath];
model.videoImage = [[SNHVideoTool shared] getVideoThumbnail:filePath];
model.videoName = @"视频";
model.videoSize = [[SNHVideoTool shared] getVideoSize:filePath];
model.duration = [[SNHVideoTool shared] getVideoDuration:filePath];
model.createDate = [[SNHVideoTool shared] getVideoCreateDate:filePath];
[self.arrVideos addObject: model];
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"加载视频耗时时间:%f", end - start);
[self.tableView reloadData];
//日志输出: 加载视频耗时时间:1.634362
经过测试,小编一次性加载30个短视频,这段代码执行花费了1.634362毫秒;当测试一次性加载60个短视频,这段代码执行会花费2.831086毫秒。或许有人会觉得这个时间很短,那么我们来测试一下,如果将这段耗时的代码注释,来测一测代码的执行时间。
- (void)loadVideoFromArray:(NSMutableArray *)videoUrls
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
for (NSString *filePath in videoUrls)
SNHVideoModel *model = [[SNHVideoModel alloc] init];
// model.filePath = filePath;
// model.assetUrl = [NSURL fileURLWithPath:filePath];
// model.videoImage = [[SNHVideoTool shared] getVideoThumbnail:filePath];
// model.videoName = @"视频";
// model.videoSize = [[SNHVideoTool shared] getVideoSize:filePath];
// model.duration = [[SNHVideoTool shared] getVideoDuration:filePath];
// model.createDate = [[SNHVideoTool shared] getVideoCreateDate:filePath];
[self.arrVideos addObject: model];
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"加载视频耗时时间:%f", end - start);
[self.tableView reloadData];
//日志输出: 加载视频耗时时间:0.000055
有对比才有伤害,通过对比我们可以发现这两个相差很多个数量级。如果不做优化,随着视频越来越多,变成了成百上千个视频需要读取,那将是灾难性的体验。
通过分析代码我们来总结一下,因为根据视频URL读取视频的相关信息比较耗时,所以一次性读取大量的视频会造成卡顿,而且视频越多会越卡。
二、问题的解决
2.1 采用分页加载视频
很显然,造成卡顿的问题,最主要的是一次性加载大量视频,那么要解决这个问题,我们自然而然就想到了应该分批加载视频,我们可以采用分页加载的思路。分页加载我们再熟悉不过了,只不过以前都是用来加载网络数据,那么可以加载本地数据么?小编很自信的告诉你,可以的。
总体思路如下:
- 一次性读取所有视频的 filePath,用videoUrls数组存储;
- 设置两个变量:pageNum和pageSize;
- 利用MJRefresh每次从videoUrls数组中取10条数据,然后将这10条数据显示在列表上;
- 将获取视频相关信息的耗时任务放在UITableViewCell里面进行;
这样就实现了分页加载的效果。
(1) 创建下拉刷新和上拉加载更多
//下拉刷新
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^
self.pageNum = 1;
[self.arrVideos removeAllObjects];
[self initData];
[self.tableView.mj_footer resetNoMoreData];
[self.tableView.mj_header endRefreshing];
];
//上拉加载更多
self.tableView.mj_footer = [MJRefreshAutoNormalFooter footerWithRefreshingBlock:^
NSInteger start = self.pageNum * self.pageSize;
NSInteger end = start + self.pageSize - 1;
NSMutableArray *tempVideoUrls = [self arrayObjectFromIndex:start toIndex:end];
[self.arrVideos addObjectsFromArray:tempVideoUrls];
[self.tableView reloadData];
[self.tableView.mj_footer endRefreshing];
if (end > self.videoUrls.count)
[self.tableView.mj_footer endRefreshingWithNoMoreData];
self.pageNum ++;
];
(2) 第一次默认加载10条数据
- (void)initData
self.videoUrls = [self getVideoUrlFromSandbox];
NSMutableArray *tempVideoUrls = [self arrayObjectFromIndex:0 toIndex:self.pageSize];
[self.arrVideos addObjectsFromArray:tempVideoUrls];
[self.tableView reloadData];
(3) 从沙盒中读取所有视频
- (NSMutableArray *)getVideoUrlFromSandbox
NSMutableArray *tempArr = [[NSMutableArray alloc] init];
NSString *documentPath = [FileTools createDirectoryInDocumentDirectory:@"/Videos"];
NSArray *fileNames = [[NSFileManager defaultManager] subpathsAtPath: documentPath];
for (NSString *fileName in fileNames)
NSString *filePath = [documentPath stringByAppendingFormat:@"/%@", fileName];
[tempArr addObject:filePath];
return tempArr;
(4) 采用分页加载的原理,每次取10条数据
- (NSMutableArray *)arrayObjectFromIndex:(NSInteger)index toIndex:(NSInteger)end
NSMutableArray *tempArr = [[NSMutableArray alloc] init];
for (NSInteger i=index; i<=end; i++)
if (end < self.videoUrls.count)
[tempArr addObject:self.videoUrls[i]];
else
break;
return tempArr;
(5) 在TableViewCell里面获取视频的相关信息
- (void)setFilePath:(NSString *)filePath
[self.videoImageView setImage:[[SNHVideoTool shared] getVideoThumbnail:filePath]];
self.videoNameLabel.text = [NSString stringWithFormat:@"视频%ld",(long)self.index+1];
self.videoSizeLabel.text = [[SNHVideoTool shared] getVideoSize:filePath];
self.createDateLabel.text = [[SNHVideoTool shared] getVideoCreateDate:filePath];
self.durationLabel.text = [[SNHVideoTool shared] getVideoDuration:filePath];
这样,我们就将一次性加载全部视频改造成了分页加载视频,是不是这样就将问题解决了呢?小编重新运行了一下程序,发现比之前体验好多了,没有明显的卡顿了,但是总感觉还是有一点点卡顿。
2.2 异步加载耗时任务
小编之前提到过根据视频的 filePath 获取视频的相关信息比较耗时,那么在tableView里面一次性加载10条视频,因为也是比较耗时的,也会造成一点点卡顿。那么这个时候,我们应该想到用异步线程来处理耗时任务。改造一下TableViewCell中的代码:
- (void)setFilePath:(NSString *)filePath
dispatch_async(dispatch_get_global_queue(0, 0), ^
UIImage *image = [[SNHVideoTool shared] getVideoThumbnail:filePath];
NSString *videoSize = [[SNHVideoTool shared] getVideoSize:filePath];
NSString *createDate= [[SNHVideoTool shared] getVideoCreateDate:filePath];
NSString *duration = [[SNHVideoTool shared] getVideoDuration:filePath];
dispatch_async(dispatch_get_main_queue(), ^
[self.videoImageView setImage:image];
self.videoNameLabel.text = [NSString stringWithFormat:@"视频%ld",(long)self.index+1];
self.videoSizeLabel.text = videoSize;
self.createDateLabel.text = createDate;
self.durationLabel.text = duration;
);
);
这样就将问题彻底解决了,列表能无卡顿加载,能够丝滑般的滑动。
以上是关于iOS 加载大量本地视频优化的主要内容,如果未能解决你的问题,请参考以下文章
EasyNVR因为拉流失败导致反复出现大量视频不在线或不稳定的情况优化
sh 将大型视频文件转换为优化的480p视频,而不会失去大量的质量