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 采用分页加载视频

很显然,造成卡顿的问题,最主要的是一次性加载大量视频,那么要解决这个问题,我们自然而然就想到了应该分批加载视频,我们可以采用分页加载的思路。分页加载我们再熟悉不过了,只不过以前都是用来加载网络数据,那么可以加载本地数据么?小编很自信的告诉你,可以的。

总体思路如下:

  1. 一次性读取所有视频的 filePath,用videoUrls数组存储;
  2. 设置两个变量:pageNum和pageSize;
  3. 利用MJRefresh每次从videoUrls数组中取10条数据,然后将这10条数据显示在列表上;
  4. 将获取视频相关信息的耗时任务放在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因为拉流失败导致反复出现大量视频不在线或不稳定的情况优化

cocos2dx内存优化

sh 将大型视频文件转换为优化的480p视频,而不会失去大量的质量

一个页面上有大量的图片,加载很慢,你有哪些方法优化这些图片的加载?

如何优化将大量元素动态添加到网页中

可以在 iOS 设备上强制 HTML5 视频预加载吗?