iOS视频拖动预览优化实录

想做一个微信朋友圈上传视频时视频裁剪编辑类似的功能,拖动视频和拖动裁剪范围,本来以为做起来很简单,但是实际操作中在优化上面还有很多改进。

0E81D8D7B33A85C731B2F4A100E73742.png

一、视频封面截取

进入界面之后,首先要生成区域2的视频的缩略图

//截图
- (UIImage*)getVideoPreViewImageFromVideoPath:(NSString*)videoPath withAtTime:(float)atTime {
    if (!videoPath) {
        return nil;
    }
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:videoPath] options:nil];
    if ([asset tracksWithMediaType:AVMediaTypeVideo].count == 0) {
        return nil;
    }
    AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    gen.appliesPreferredTrackTransform = YES;
    gen.requestedTimeToleranceAfter = kCMTimeZero;
    gen.requestedTimeToleranceBefore = kCMTimeZero;
    CMTime time = CMTimeMakeWithSeconds(atTime, 600);
    NSError *error = nil;
    CMTime actualTime;
    CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
    UIImage *img = [[UIImage alloc] initWithCGImage:image];
    UIGraphicsBeginImageContext(CGSizeMake([[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].width, [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].height));//asset.naturalSize.width, asset.naturalSize.height)
    [img drawInRect:CGRectMake(0, 0, [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].width, [[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize].height)];
    UIImage *scaledImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CGImageRelease(image);
    return scaledImage;
}

因为我们裁剪视频的时间范围是6s到10s,为了铺满下面的整个scrollview,同时保持每张图片的宽度一致,所以设置了固定的图片宽度

//图片宽
float imgWidth = self.maxWidth/10.0;
for (int i = 0; i< self.coverImgs.count; i++) {
     UIImageView *imageView = [[UIImageView alloc] initWithImage:[self.coverImgs objectAtIndex:i]];
     [imageView setFrame:CGRectMake(i*imgWidth, 0, imgWidth, 50)];
     [self.scrollView addSubview:imageView];
}
[self.scrollView setContentSize:CGSizeMake(imgWidth*self.coverImgs.count, 50)];

为了铺满scrollview,又兼顾上传时长视频和短视频两种区别,所以在裁剪图片的时机上面有区别

- (void)getCoverImgs {
    NSMutableArray *imageArrays = [NSMutableArray array];
    self.videoDuration = [self durationWithVideo:self.videoUrlStr];
    //大于11s
    if (self.videoDuration>11.0) {
        //每隔1s截取一张图片
        for (int i = 0; i < self.videoDuration-1; i++) {
            UIImage *image = [self getVideoPreViewImageFromVideoPath:self.videoUrlStr withAtTime:i+0.1];
            [imageArrays addObject:image];
        }
    }
    else{
        //截取11张
        for (int i = 0; i < 11; i++) {
            UIImage *image = [self getVideoPreViewImageFromVideoPath:self.videoUrlStr withAtTime:self.videoDuration*i/12.0+0.1];
            [imageArrays addObject:image];
        }
    }
    
    self.coverImgs = [NSArray arrayWithArray:imageArrays];
    self.cover = [imageArrays objectAtIndex:0];
}

这样就保证了不管长视频还是短视频都可以铺满自己的时长范围,超过10s时可以滚动,不超过10s就以自己的长度为准。

二、视频裁剪起点

在区域1中,根据视频的长宽比,以一边铺满,一边滚动为准,在滚动的时候,计算偏移量来取视频裁剪的起点

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView.tag == kCoverImageScrollTag) {
        self.clipPoint = CGPointMake(scrollView.contentOffset.x*720.0/kScreenWidth, scrollView.contentOffset.y*720.0/kScreenWidth);
    }
}

三、左右拖动

在区域2中,通过左右两边的拖动,主要就是调用AVPlayer的seekToTime来实现,其中AVPlayer的seekToTime有两种,这里采取精确的方式

- (void)seekToTime:(CMTime)time toleranceBefore:(CMTime)toleranceBefore toleranceAfter:(CMTime)toleranceAfter completionHandler:(void (^)(BOOL finished))completionHandler

如果不需要滚动范围的精确,推荐使用下面这种不精确的方式更省内部计算量

- (void)seekToTime:(CMTime)time;

如果直接调用这些方式,会发现如果是比较大的视频的话,在拖动的时候会出现卡顿的现象,所以需要使用多线程来进行优化

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                [self.player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
                    if (finished) {
                        [self.player pause];
                        self.playBtn.hidden = NO;
                    }
                }];
            });
            
        });

这样才会避免拖动卡顿现象。

四、底部滚动

在区域2中,如果是视频长度大于10s,就需要滚动选取,而滚动时需要记录偏移量,从而避免在滚动之后,又点击左右按钮造成时间错误。

在开始滚动的额时候,计算偏移量并且减去之前的偏移过的量才可以,在结束滚动,结束拖拽的时候记录当前的偏移量,这样才能保证数据的正确性。通过每个点对应多长时间,来计算滚动的偏移量对应了多长的时间起点。

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView.tag == kCoverImageScrollTag) {
        self.clipPoint = CGPointMake(scrollView.contentOffset.x*720.0/kScreenWidth, scrollView.contentOffset.y*720.0/kScreenWidth);
    }
    else if (scrollView.tag == kClipTimeScrollTag){
        if (scrollView.contentOffset.x>=0) {
            CGFloat addTime = scrollView.contentOffset.x*self.timeScale;
            self.tempStartTime = self.startTime + addTime - self.contentOffsetX*self.timeScale;
            self.tempEndTime = self.endTime + addTime - self.contentOffsetX*self.timeScale;
            CMTime time = CMTimeMakeWithSeconds(self.tempStartTime, self.m_ftp);
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                dispatch_async(dispatch_get_main_queue(), ^{
                    [self.player seekToTime:time toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:^(BOOL finished) {
                        if (finished) {
                            [self.player pause];
                            self.playBtn.hidden = NO;
                        }
                    }];
                });
                
            });
        }
    }
    NSLog(@"offset:%@", NSStringFromCGPoint(scrollView.contentOffset));
    NSLog(@"%f",self.startTime);
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    if (scrollView.tag == kClipTimeScrollTag){
        self.contentOffsetX = scrollView.contentOffset.x;
        self.startTime = self.tempStartTime;
        self.endTime = self.tempEndTime;
    }
}
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
    if (scrollView.tag == kClipTimeScrollTag){
        self.contentOffsetX = scrollView.contentOffset.x;
        self.startTime = self.tempStartTime;
        self.endTime = self.tempEndTime;
    }
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (scrollView.tag == kClipTimeScrollTag){
        self.contentOffsetX = scrollView.contentOffset.x;
        self.startTime = self.tempStartTime;
        self.endTime = self.tempEndTime;
    }
}

五、优化

之前没有进行过优化,会出现数据不太正确的问题,操作不太流畅,所以最好做下以下优化

5.1、拖动时使用多线程

就是上面说的,在seekToTime的时候,使用多线程去更新UI,这样才不至于卡顿的现象。

5.2、导入视频压缩

demo中是为了演示,在实际使用中,在导入视频的时候,需要进行压缩之后再编辑,这样处理速度会更快,对宽带的压力也比较小

5.3、计算拖动位置时间

拖动的时间是最不好掌握的,所以需要计算好拖动起点和终点的坐标,在demo中,两边拖动的浮标是在两侧的,如图标出的红色那样布局的,所以在计算右侧的时候,需要减去图片的宽度。

Demo下载

GitHub下载:https://github.com/DamonHu/HudongBlogDemo/tree/master/VideoClipDrag

Gitosc下载:https://git.oschina.net/DamonHoo/HudongBlogDemo/tree/master/VideoClipDrag

Demo效果演示

Last modification:July 18th, 2017 at 02:23 pm
如果看了这个文章可以让你少加会班,可以请我喝杯可乐
已打赏名单
微信公众号

One comment

  1. 东东

    ss

Leave a Comment