- 最近我们在做一个运动的app 里面用到视频编辑,给大家推荐一个仿抖音视频的demo
[VideoSliderCrop]([https://github.com/1401788197/VideoSliderCrop]
2.设计思路
底部是一个视频播放器 SJVideoPlayView
播放器上面试一个视频裁剪视图 SJVideoCropView
里面包含一个 UICollectionView 用来加载视频缩略图 左右滑块以及进度条
拖动左右滑块、进度条、以及CollectionView的时候,实时把当前进度传递给视频播放器、视频播放器跳转
1、调节视频进度
[self.palyView.player seekToTime:CMTimeMake(time * 1000, 1000) toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
2、获取进度条位置
#pragma mark 手指滑动截取视频
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (!_imageViewSelected) return;
// locationInView:获取到的是手指点击屏幕实时的坐标点;
//translationInView:获取到的是手指移动后,在相对坐标中的偏移量
//previousLocationInView:是获得前一个坐标。
UITouch *touch = (UITouch *)[touches anyObject];
CGPoint lp = [touch locationInView:_collectionView];
CGPoint pp = [touch previousLocationInView:_collectionView];
}
3、手势判断
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
_imageViewSelected = nil;
if ([_delegate respondsToSelector:@selector(cutBarTouchesDidEnd)]) {
[_delegate cutBarTouchesDidEnd];
}
}
#pragma mark 手指滑动截取视频
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
4、视频取帧
#pragma mark 加载视频截图
- (void)loadThumbnailData {
// _durationLaber.text=[NSString stringWithFormat:@"已选取%@s",[NSString stringWithFormat:@"%.0f",_mediaConfig.endTime - _mediaConfig.startTime]];
_selectTime =_mediaConfig.endTime - _mediaConfig.startTime;
CMTime startTime = kCMTimeZero;
NSMutableArray *array = [NSMutableArray array];
CMTime addTime = CMTimeMake(1000,1000);
CGFloat d = _mediaConfig.sourceDuration / (_itemCount-1);
int intd = d * 100;
float fd = intd / 100.0;
addTime = CMTimeMakeWithSeconds(fd, 1000);
CMTime endTime = CMTimeMakeWithSeconds(_mediaConfig.sourceDuration, 1000);
while (CMTIME_COMPARE_INLINE(startTime, <=, endTime)) {
[array addObject:[NSValue valueWithCMTime:startTime]];
startTime = CMTimeAdd(startTime, addTime);
}
// 第一帧取第0.1s 规避有些视频并不是从第0s开始的
array[0] = [NSValue valueWithCMTime:CMTimeMakeWithSeconds(0.1, 1000)];
__weak __typeof(self) weakSelf = self;
__block int index = 0;
[self.imageGenerator generateCGImagesAsynchronouslyForTimes:array completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
if (result == AVAssetImageGeneratorSucceeded) {
UIImage *img = [[UIImage alloc] initWithCGImage:image];
dispatch_sync(dispatch_get_main_queue(), ^{
[weakSelf.imagesArray addObject:img];
if (index==1) {
if (weakSelf.reImgBlock) {
weakSelf.reImgBlock(img);
}
}
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:0];
[weakSelf.collectionView insertItemsAtIndexPaths:@[indexPath]];
index++;
});
}
}];
}
- (AVAssetImageGenerator *)imageGenerator {
if (!_imageGenerator) {
_imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_avAsset];
_imageGenerator.appliesPreferredTrackTransform = YES;
_imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
_imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
_imageGenerator.maximumSize = CGSizeMake(320, 320);
}
return _imageGenerator;
}
5、视频截取保存
#pragma mark 视频裁剪
- (void)notifyDelegateOfDidChange:(BOOL)isUpload{
NSString *timeStr = [StrTool getNowTimeTimestamp3];
NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
self.tempVideoPath = [documents stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mp4",timeStr]];
//
[BluetoothDataManager tailorBlueTooth:_blueToothFilePaths startTime:self.startTime endTime:self.endTime sumTime:self.videoSumTime];
[self deleteTempFile:self.tempVideoPath];
AVAsset *asset = [AVAsset assetWithURL:self.videoUrl];
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]
initWithAsset:asset presetName:AVAssetExportPresetPassthrough];
NSURL *furl = [NSURL fileURLWithPath:self.tempVideoPath];
exportSession.outputURL = furl;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
CMTime start = CMTimeMakeWithSeconds(self.startTime, self.player.currentTime.timescale);
CMTime duration = CMTimeMakeWithSeconds(self.endTime - self.startTime, self.player.currentTime.timescale);;
CMTimeRange range = CMTimeRangeMake(start, duration);
exportSession.timeRange = range;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@",[exportSession error]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
case AVAssetExportSessionStatusCompleted:{
NSLog(@"Export completed");
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
UISaveVideoAtPathToSavedPhotosAlbum([furl relativePath], self,@selector(video:didFinishSavingWithError:contextInfo:), nil);
NSLog(@"编辑后的视频路径: %@",weakSelf.tempVideoPath);
// [weakSelf invalidatePlayer];
[self deleteTempFile:[self.videoUrl absoluteString]];
self.videoUrl = [NSURL fileURLWithPath:self.tempVideoPath];
if (isUpload) {
[self VODUpload];
}else{
[self saveVideo];
}
});
}
break;
default:
NSLog(@"Export other");
break;
}
}];
}
- (void)video:(NSString*)videoPath didFinishSavingWithError:(NSError*)error contextInfo:(void*)contextInfo {
if (error) {
NSLog(@"保存到相册失败:%@",error);
}
else {
NSLog(@"保存到相册成功");
}
}
6、avplayer销毁
self.palyView.playerStatus = CropPlayerStatusPause;
[ self.palyView.player.currentItem cancelPendingSeeks];
[ self.palyView.player.currentItem.asset cancelLoading];
self.palyView.player = nil;