1.概述
iOS短视频SDK是适用于iOS平台的短视频SDK。使用此SDK可以实现短视频相关功能。
1.1 功能特性
功能 | 描述 |
---|---|
拍摄 | 支持短视频拍摄 |
摄像头切换 | 支持拍摄过程中切换摄像头 |
断点拍摄 | 支持拍摄过程中可暂停 |
美颜滤镜 | 支持拍摄加入美颜滤镜 |
视频时长剪辑 | 支持选取视频时长剪辑 |
视频区域剪辑 | 支持选取视频区域剪辑 |
视频倍速调整 | 支持选取视频倍速调整 |
多视频合成 | 支持多视频合成 |
视频水印 | 支持根据时段,位置添加视频水印 |
气泡文字 | 支持根据时段,位置添加气泡文字 |
音频插入 | 支持对视频插入背景音乐 |
视频大小修改 | 支持修改视频分辨率 |
图片合成视频 | 支持多图片合成视频,添加动效 |
视频特效 | 支持视频特效添加 |
1.2 阅读对象
本文档为技术文档,需要阅读者:
-
具备基本的iOS开发能力
-
准备接入CC视频的短视频SDK
2.开发准备
2.1 开发环境
-
Xcode:苹果官方IDE
-
iOS8+
2.2 SDK使用说明
首先,需要下载最新版本的SDK,下载地址为:VOD_iOS_ShortVideo_SDK,然后导入SDK包到工程,在相应地方引入头文件#import "DWShortSDK"
名称 | 描述 |
---|---|
Demo | 使用SDK的示例源码 |
doc | 存放的是iOS短视频SDK开发指南html文档 |
include | SDK开放的头文件及静态库 |
3.快速集成
注:快速集成主要提供的是拍摄功能
3.1 拍摄快速集成
3.1.1 SDK添加到项目后,在info.plist文件设置麦克风 相机 相册 图片权限
<!-- 相册 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册</string>
<!-- 相机 -->
<key>NSCameraUsageDescription</key>
<string>App需要您的同意,才能访问相机</string>
<!-- 麦克风 -->
<key>NSMicrophoneUsageDescription</key>
<string>App需要您的同意,才能访问麦克风</string>
<!-- 图片 -->
<key>NSPhotoLibraryAddUsageDescription</key>
<string>App需要您的同意,才能保存图片到您的相册</string>
3.1.2 基于GPUImage 建议用cocoapods导入 也可手动导入 确保工程中只导入一次GPUImage
3.1.3 GPUImage引入后 修改以下部分:
1.GPUImageMovieWriter.h文件中添加isNeedBreakAudioWhiter属性
@property (nonatomic, assign) BOOL isNeedBreakAudioWhiter;
2.GPUImageMovieWriter.m文件中第377行代码修改如下:
if (CMTIME_IS_INVALID(startTime))
{
if (_isNeedBreakAudioWhiter) {
}else{
runSynchronouslyOnContextQueue(_movieWriterContext, ^{
if ((audioInputReadyCallback == NULL) && (assetWriter.status != AVAssetWriterStatusWriting))
{
[assetWriter startWriting];
}
[assetWriter startSessionAtSourceTime:currentSampleTime];
startTime = currentSampleTime;
});
}
}
3.GPUImageMovieWriter初始化时设置 isNeedBreakAudioWhiter =YES;
具体详情参见demo
3.1.3 初始化相机
//录制相关
self.videoCamera = [[GPUImageVideoCamera alloc] initWithSessionPreset:AVCaptureSessionPreset1280x720
cameraPosition:AVCaptureDevicePositionBack];
if ([self.videoCamera.inputCamera lockForConfiguration:nil]) {
//自动对焦
if ([self.videoCamera.inputCamera isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus]) {
[self.videoCamera.inputCamera setFocusMode:AVCaptureFocusModeContinuousAutoFocus];
}
//自动曝光
if ([self.videoCamera.inputCamera isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
[self.videoCamera.inputCamera setExposureMode:AVCaptureExposureModeContinuousAutoExposure];
}
//自动白平衡
if ([self.videoCamera.inputCamera isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance]) {
[self.videoCamera.inputCamera setWhiteBalanceMode:AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance];
}
[self.videoCamera.inputCamera unlockForConfiguration];
}
//输出方向为竖屏
self.videoCamera.outputImageOrientation = UIInterfaceOrientationPortrait;
//防止允许声音通过的情况下,避免录制第一帧黑屏闪屏
[self.videoCamera addAudioInputsAndOutputs];
self.videoCamera.horizontallyMirrorFrontFacingCamera = YES;
self.videoCamera.horizontallyMirrorRearFacingCamera = NO;
//相机开始运行
[self.videoCamera startCameraCapture];
3.1.4 初始化moviewriter
//苹果默认是MOV格式
NSString *path =[self getVideoSaveFilePathString:@".MOV" addPathArray:YES];
unlink([path UTF8String]);
self.videoURL = [NSURL fileURLWithPath:path];
//写入
CGSize size = CGSizeZero;
if (self.scale == 0) {
//9:16
size = CGSizeMake(720.0, 1280.0);
}else if (self.scale == 1){
//3:4
size = CGSizeMake(720.0, 960.0);
}else{
//1:1
size = CGSizeMake(720.0, 720.0);
}
self.movieWriter = [[GPUImageMovieWriter alloc] initWithMovieURL:self.videoURL size:size];
//设置为liveVideo
self.movieWriter.isNeedBreakAudioWhiter =YES;
self.movieWriter.encodingLiveVideo = YES;
self.movieWriter.shouldPassthroughAudio =YES;
//根据实际需求,添加滤镜链
if (self.filter) {
if (self.isBeautyFilter) {
if (self.isBilateralExist) {
[self.bilateralFilter addTarget:self.movieWriter];
}else{
[self.brightnessFilter addTarget:self.movieWriter];
}
}else{
[self.filter addTarget:self.movieWriter];
}
}else{
if (self.isBeautyFilter) {
if (self.isBilateralExist) {
[self.bilateralFilter addTarget:self.movieWriter];
}else{
[self.brightnessFilter addTarget:self.movieWriter];
}
}else{
[self.videoCamera addTarget:self.movieWriter];
}
}
//设置声音
self.videoCamera.audioEncodingTarget = self.movieWriter;
4.功能使用
4.1 删除功能具体使用
BOOL isSuccess = [DWShortTool dw_deleteFileWithFilePath:filePath];
if (self.pathArray.count == 0) {
return YES;
}
if (isSuccess) {
[self.pathArray removeLastObject];
UIView * view = [self.viewArray lastObject];
[view removeFromSuperview];
[self.viewArray removeLastObject];
//要减去相应的录制时间
double recordTime =[[self.secondsArray lastObject] doubleValue];
self.totalTime -= recordTime;
[self.secondsArray removeLastObject];
[self.bottomView setRecordTime:self.totalTime];
NSLog(@"余下录制时间%f__%f",self.totalTime,recordTime);
}
4.2 相机功能具体使用
//闪光灯的使用
//前置摄像头不打开闪光灯
if (self.videoCamera.inputCamera.position == AVCaptureDevicePositionFront) {
return;
}
button.selected = !button.selected;
if (self.videoCamera.inputCamera.position == AVCaptureDevicePositionBack) {
if (self.videoCamera.inputCamera.torchMode ==AVCaptureTorchModeOn) {
[self.videoCamera.inputCamera lockForConfiguration:nil];
[self.videoCamera.inputCamera setTorchMode:AVCaptureTorchModeOff];
[self.videoCamera.inputCamera setFlashMode:AVCaptureFlashModeOff];
[self.videoCamera.inputCamera unlockForConfiguration];
}else{
[self.videoCamera.inputCamera lockForConfiguration:nil];
[self.videoCamera.inputCamera setTorchMode:AVCaptureTorchModeOn];
[self.videoCamera.inputCamera setFlashMode:AVCaptureFlashModeOn];
[self.videoCamera.inputCamera unlockForConfiguration];
}
}
//前后摄像头切换
[self.videoCamera rotateCamera];
4.3 美颜滤镜功能具体使用
//再添加新的滤镜效果前,首先移除响应链上的滤镜
[self removeAllTarget];
if (type == 0) {
self.filter = nil;
}
//根据滤镜效果,添加合适的滤镜。
//这里的滤镜效果仅供参考,更多效果请详见GPUImage功能介绍。
if (type == 1) {
GPUImageSaturationFilter * saturationFilter = [[GPUImageSaturationFilter alloc]init];
saturationFilter.saturation = 2;
self.filter = saturationFilter;
}
if (type == 2) {
GPUImageSaturationFilter * saturationFilter = [[GPUImageSaturationFilter alloc]init];
saturationFilter.saturation = 0.7;
self.filter = saturationFilter;
}
if (type == 3) {
GPUImageWhiteBalanceFilter * whiteBalanceFilter = [[GPUImageWhiteBalanceFilter alloc]init];
whiteBalanceFilter.temperature = 4500;
self.filter = whiteBalanceFilter;
}
if (type == 4) {
GPUImageSepiaFilter * sepiaFilter = [[GPUImageSepiaFilter alloc]init];
self.filter = sepiaFilter;
}
if (type == 5) {
GPUImageExposureFilter * exposureFilter = [[GPUImageExposureFilter alloc]init];
exposureFilter.exposure = 0.3;
self.filter = exposureFilter;
}
//添加新的滤镜响应链
if (self.filter) {
if (self.isBeautyFilter) {
if (self.isBilateralExist) {
[self.videoCamera addTarget:self.filter];
[self.filter addTarget:self.brightnessFilter];
[self.brightnessFilter addTarget:self.bilateralFilter];
[self.bilateralFilter addTarget:self.filterView];
}else{
[self.videoCamera addTarget:self.filter];
[self.filter addTarget:self.brightnessFilter];
[self.brightnessFilter addTarget:self.filterView];
}
}else{
[self.videoCamera addTarget:self.filter];
[self.filter addTarget:self.filterView];
}
}else{
if (self.isBeautyFilter) {
if (self.isBilateralExist) {
[self.videoCamera addTarget:self.brightnessFilter];
[self.brightnessFilter addTarget:self.bilateralFilter];
[self.bilateralFilter addTarget:self.filterView];
}else{
[self.videoCamera addTarget:self.brightnessFilter];
[self.brightnessFilter addTarget:self.filterView];
}
}else{
[self.videoCamera addTarget:self.filterView];
}
}
}
4.4 暂停or开始录制
//开始录制
//UI修改
[self hiddenAllView:YES];
[self.bottomView setStyle:2];
if (self.isDelay) {
//__weak typeof(self) weakSelf = self;
//开始延迟计时
DWDelayView * delayView = [[DWDelayView alloc]init];
[delayView beginAnimation];
delayView.finish = ^{
//进度条
[self initProgressView];
self.seconds = 0;
[self gcdTimer];
//开始录制
[self initMovieWriter];
[self.movieWriter startRecording];
};
return;
}
//进度条
[self initProgressView];
self.seconds = 0;
//启动计时器
[self gcdTimer];
//开始录制
[self initMovieWriter];
[self.movieWriter startRecording];
//暂停录制
//UI修改
[self hiddenAllView:NO];
[self.bottomView setStyle:3];
//移除定时器
[self removeTimer];
//暂停写入
[self.movieWriter finishRecording];
self.videoCamera.audioEncodingTarget = nil;
[self.secondsArray addObject:[NSString stringWithFormat:@"%f",self.seconds]];
//保存视频到本地
[self savePhotosAlbum:self.videoURL];
4.5 断点拍摄功能具体使用
断点拍摄后的几个小视频可以合成为一个视频,DWShortTool提供了接口(4.10.4)用于此功能。
//延迟执行,等待视频写入完成。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//合成视频输出路径
NSString * path = [self getVideoSaveFilePathString:@".MOV" addPathArray:NO];
self.hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
self.hud.removeFromSuperViewOnHide = YES;
self.hud.label.text = @"视频生成中";
[DWShortTool dw_compositeAndExportVideos:self.pathArray withVideoSize:CGSizeZero outPath:path outputFileType:AVFileTypeQuickTimeMovie presetName:AVAssetExportPresetHighestQuality didComplete:^(NSError *error, NSURL *compressionFileURL) {
[self.hud hideAnimated:YES];
if (!error) {
DWShortVideoEditViewController * shortVideoEditVC = [[DWShortVideoEditViewController alloc]init];
shortVideoEditVC.videoURL = [NSURL fileURLWithPath:path];
[self presentViewController:shortVideoEditVC animated:YES completion:nil];
}else{
[@"合成失败,请重试" showAlert];
}
}];
});
4.6 视频剪辑功能的使用
通过设置裁剪时间,裁剪范围,裁剪倍速来对视频进行剪辑处理。DWShortTool提供了接口(4.10.2)用于此功能。
NSString *videoPath = [self createFilePath];
MBProgressHUD * hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.removeFromSuperViewOnHide = YES;
[DWShortTool dw_videoSizeAndTimeCropAndExportVideo:self.videoURL.absoluteString
size:self.cropView.scaleFrame.size
point:self.cropView.scaleFrame.origin
range:CMTimeRangeMake(self.cropView.start, self.cropView.duration)
videoRate:self.cropView.speed
withOutPath:videoPath
outputFileType:AVFileTypeQuickTimeMovie
presetName:AVAssetExportPresetHighestQuality
didComplete:^(NSError *error, NSURL *compressionFileURL) {
[hud hideAnimated:YES];
if (error) {
[error.localizedDescription showAlert];
return;
}
//剪辑完成,跳转编辑页面
DWShortVideoEditViewController * shortVideoEditVC = [[DWShortVideoEditViewController alloc]init];
shortVideoEditVC.videoURL = compressionFileURL;
[self presentViewController:shortVideoEditVC animated:YES completion:nil];
}];
4.7 插入背景音乐功能的使用
可以设置新的背景音乐插入到原视频中。DWShortTool提供了接口(4.10.5)用于此功能。
//判断是否选择了音频
BOOL select = NO;
for (NSDictionary * dict in self.musicListArray) {
if ([[dict objectForKey:@"isSelect"] boolValue]) {
select = YES;
break;
}
}
if (!select) {
self.musicDict = nil;
[self addStickerWithVideoUrl:videoUrl];
return;
}
//插入音频
NSString * outPath = [self createFilePath];
// NSURL * outPathUrl = [NSURL fileURLWithPath:outPath];
[DWShortTool dw_insertAudioAndExportVideo:videoUrl.absoluteString
withAudioPath:[self.musicDict objectForKey:@"audioPath"]
originalVolume:[[self.musicDict objectForKey:@"originalVolume"] floatValue]
insertVolume:[[self.musicDict objectForKey:@"insertVolume"] floatValue]
timeRange:CMTimeRangeMake([[self.musicDict objectForKey:@"start"] CMTimeValue], [[self.musicDict objectForKey:@"duration"] CMTimeValue])
outPath:outPath
outputFileType:OUTPUTFILETYPE
presetName:PRESETNAME
didComplete:^(NSError *error, NSURL *compressionFileURL) {
if (error) {
[self.editAndUploadVC endEditWithSuccess:NO];
[error.localizedDescription showAlert];
return;
}
}];
4.8 贴纸功能的使用
通过设置贴纸图片,贴纸位置(注意,贴纸point,size的参数为百分比),以及贴纸出现的时间范围为原视频增加视频贴纸。DWShortTool提供了接口(4.10.3)用于此功能。
if (!self.canEdit) {
return;
}
//判断是否添加贴纸
if (self.stickerArray.count == 0) {
[self addBubbleVideoUrl:videoUrl];
return;
}
NSString * outPath = [self createFilePath];
NSArray * images = @[@"icon_sticker_1.png",@"icon_sticker_2.png",@"icon_sticker_3.png",@"icon_sticker_4.png",@"icon_sticker_5.png"];
NSMutableArray * stickerImages = [NSMutableArray array];
NSMutableArray * stickerImagePoints = [NSMutableArray array];
NSMutableArray * stickerImageSizes = [NSMutableArray array];
NSMutableArray * rotateAngles = [NSMutableArray array];
NSMutableArray * timeRanges = [NSMutableArray array];
for (NSDictionary * dict in self.stickerArray) {
UIImage * stickerImage = [UIImage imageNamed:[images objectAtIndex:[[dict objectForKey:@"index"] integerValue]]];
LINConversionView * conversionView = [dict objectForKey:@"object"];
CGRect frame = conversionView.frame;
CGAffineTransform transform = conversionView.transform;
CGFloat rotate = [self getAngleFromAffineTransform:transform];
CGFloat sizeLength = [self getStickerSideLengthWithView:conversionView];
//计算偏移量
CGFloat offset = (conversionView.frame.size.width - sizeLength) / 2.0;
CGPoint offsetPoint = CGPointMake(frame.origin.x + offset, frame.origin.y + offset);
//百分比
CGPoint stickerImagePoint = CGPointMake((offsetPoint.x - self.videoBackground.origin.x) / self.videoBackground.size.width, (offsetPoint.y - self.videoBackground.origin.y) / self.videoBackground.size.height);
CGSize stickerImageSize = CGSizeMake(sizeLength / self.videoBackground.size.width, sizeLength / self.videoBackground.size.height);
CMTime start = [[dict objectForKey:@"start"] CMTimeValue];
CMTime duration = [[dict objectForKey:@"duration"] CMTimeValue];
//如果需要整个视频都添加贴纸,传kCMTimeZero即可。
if (CMTimeCompare(duration, self.videoDuration) == 0) {
duration = kCMTimeZero;
}
CMTimeRange timeRange = CMTimeRangeMake(start, duration);
[stickerImages addObject:stickerImage];
[stickerImagePoints addObject:[NSValue valueWithCGPoint:stickerImagePoint]];
[stickerImageSizes addObject:[NSValue valueWithCGSize:stickerImageSize]];
[rotateAngles addObject:[NSNumber numberWithFloat:rotate]];
[timeRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
}
[DWShortTool dw_addStickerAndExportVideo:videoUrl.absoluteString
withStickerImages:stickerImages
stickerImagePoints:stickerImagePoints
stickerImageSizes:stickerImageSizes
rotateAngles:rotateAngles
timeRanges:timeRanges
outPath:outPath
outputFileType:OUTPUTFILETYPE
presetName:PRESETNAME
didComplete:^(NSError *error, NSURL *compressionFileURL) {
if (error) {
[self.editAndUploadVC endEditWithSuccess:NO];
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self addBubbleVideoUrl:compressionFileURL];
});
}];
4.9 气泡文字功能的使用
添加逻辑同4.8,贴纸图片需要通过背景图与文字生成。
if (!self.canEdit) {
return;
}
if (self.bubbleArray.count == 0) {
[self.editAndUploadVC endEditWithSuccess:YES];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:videoUrl completionBlock:^(NSURL *assetURL, NSError *error)
{
}];
return;
}
NSString * outPath = [self createFilePath];
NSMutableArray * bubbleImages = [NSMutableArray array];
NSMutableArray * bubbleImagePoints = [NSMutableArray array];
NSMutableArray * bubbleImageSizes = [NSMutableArray array];
NSMutableArray * rotateAngles = [NSMutableArray array];
NSMutableArray * timeRanges = [NSMutableArray array];
for (NSDictionary * dict in self.bubbleArray) {
LINConversionView * conversionView = [dict objectForKey:@"object"];
CGRect frame = conversionView.frame;
CGAffineTransform transform = conversionView.transform;
CGFloat rotate = [self getAngleFromAffineTransform:transform];
CGFloat sizeLength = [self getStickerSideLengthWithView:conversionView];
//计算偏移量
CGFloat offset = (conversionView.frame.size.width - sizeLength) / 2.0;
CGPoint offsetPoint = CGPointMake(frame.origin.x + offset, frame.origin.y + offset);
//获取贴纸所占视频区域的百分比
CGPoint bubbleImagePoint = CGPointMake((offsetPoint.x - self.videoBackground.origin.x) / self.videoBackground.size.width, (offsetPoint.y - self.videoBackground.origin.y) / self.videoBackground.size.height);
CGSize bubbleImageSize = CGSizeMake(sizeLength / self.videoBackground.size.width, sizeLength / self.videoBackground.size.height);
CMTime start = [[dict objectForKey:@"start"] CMTimeValue];
CMTime duration = [[dict objectForKey:@"duration"] CMTimeValue];
//如果需要整个视频都添加贴纸,传kCMTimeZero即可。
if (CMTimeCompare(duration, self.videoDuration) == 0) {
duration = kCMTimeZero;
}
CMTimeRange timeRange = CMTimeRangeMake(start, duration);
//生成贴纸图片
[bubbleImages addObject:[self bubbleImageWithBubbleDict:dict]];
[bubbleImagePoints addObject:[NSValue valueWithCGPoint:bubbleImagePoint]];
[bubbleImageSizes addObject:[NSValue valueWithCGSize:bubbleImageSize]];
[rotateAngles addObject:[NSNumber numberWithFloat:rotate]];
[timeRanges addObject:[NSValue valueWithCMTimeRange:timeRange]];
}
[DWShortTool dw_addStickerAndExportVideo:videoUrl.absoluteString
withStickerImages:bubbleImages
stickerImagePoints:bubbleImagePoints
stickerImageSizes:bubbleImageSizes
rotateAngles:rotateAngles
timeRanges:timeRanges
outPath:outPath
outputFileType:OUTPUTFILETYPE
presetName:PRESETNAME
didComplete:^(NSError *error, NSURL *compressionFileURL) {
if (error) {
[self.editAndUploadVC endEditWithSuccess:NO];
return;
}
[self.editAndUploadVC endEditWithSuccess:YES];
//保存视频到本地相册
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeVideoAtPathToSavedPhotosAlbum:compressionFileURL completionBlock:^(NSURL *assetURL, NSError *error)
{
}];
}];
//生成气泡文字图片
-(UIImage *)bubbleImageWithBubbleDict:(NSDictionary *)dict
{
NSArray * images = @[@"icon_bubble_1.png",@"icon_bubble_2.png",@"icon_bubble_3.png",@"icon_bubble_4.png",@"icon_bubble_5.png"];
//总体生成图片
UIImage * bubbleImage = [UIImage imageNamed:[images objectAtIndex:[[dict objectForKey:@"index"] integerValue]]];
LINConversionView * conversionView = [dict objectForKey:@"object"];
DWBubbleInputView * inputView = (DWBubbleInputView *)conversionView.contentView;
UIImageView * bubbleImageView = [[UIImageView alloc]init];
CGFloat sizeLength = [self getStickerSideLengthWithView:conversionView];
bubbleImageView.frame = CGRectMake(0, 0, sizeLength, sizeLength);
bubbleImageView.image = bubbleImage;
UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(bubbleImageView.frame.size.width * 32 / BUBBLEWIDTH, bubbleImageView.frame.size.height * 102 / BUBBLEWIDTH, bubbleImageView.frame.size.width - (bubbleImageView.frame.size.width * 32 / BUBBLEWIDTH) * 2, bubbleImageView.frame.size.height * 49 / BUBBLEWIDTH)];
label.text = inputView.placeholderLabel.text;
label.font = inputView.placeholderLabel.font;
label.textColor = inputView.placeholderLabel.textColor;
label.textAlignment = inputView.placeholderLabel.textAlignment;
label.numberOfLines = 2;
label.adjustsFontSizeToFitWidth = YES;
[bubbleImageView addSubview:label];
UIGraphicsBeginImageContextWithOptions(bubbleImageView.bounds.size, NO, 0);
if ([bubbleImageView respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
[bubbleImageView drawViewHierarchyInRect:bubbleImageView.bounds afterScreenUpdates:YES];
}else{
[bubbleImageView.layer renderInContext:UIGraphicsGetCurrentContext()];
}
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
4.10 工具类DWShortTool接口功能
4.10.1 视频压缩
/// 压缩视频
/// @param videoPath 视频路径
/// @param outPath 输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后回调
+ (void)dw_compressionAndExportVideo:(NSString *)videoPath
withOutPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL) )completeBlock;
4.10.2 视频剪辑
4.10.2.1 视频时长剪辑 视频区域不变
/// 视频时长剪辑 视频区域不变
/// @param videoPath 视频路径
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param timeRange 截取视频的时间范围
/// @param completeBlock 完成后回调
+ (void)dw_videoTimeCropAndExportVideo:(NSString *)videoPath
withOutPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
range:(CMTimeRange)timeRange
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.2.2 视频区域剪裁 视频时长不变
/// 视频区域剪裁 视频时长不变
/// @param videoPath 视频路径
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param videoSize 剪裁区域
/// @param videoPoint 剪裁起点
/// @param shouldScale 是否拉伸 YES拉伸 NO不拉伸 剪裁黑背景
/// @param completeBlock 剪裁完成后的回调
+ (void)dw_videoSizeCropAndExportVideo:(NSString *)videoPath
withOutPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
size:(CGSize)videoSize
point:(CGPoint)videoPoint
shouldScale:(BOOL)shouldScale
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.2.3 视频区域兼时长剪裁
/// 视频区域兼时长剪裁
/// @param videoPath 视频路径
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param videoSize 剪裁区域
/// @param videoPoint 剪裁起点
/// @param shouldScale 是否拉伸 YES拉伸 NO不拉伸 剪裁黑背景
/// @param timeRange 截取视频的时间范围
/// @param completeBlock 剪裁完成后的回调
+ (void)dw_videoSizeAndTimeCropAndExportVideo:(NSString *)videoPath
withOutPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
size:(CGSize)videoSize
point:(CGPoint)videoPoint
shouldScale:(BOOL)shouldScale
range:(CMTimeRange)timeRange
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.2.4 视频区域,时长,倍速剪裁
/// 视频区域,时长,倍速剪裁
/// @param videoPath 视频路径
/// @param videoSize 剪裁区域,CGSizeZero不裁剪
/// @param videoPoint 剪裁起点
/// @param timeRange 截取视频的时间范围,kCMTimeRangeZero,不裁剪时长
/// @param videoRate 截取视频倍速
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+ (void)dw_videoSizeAndTimeCropAndExportVideo:(NSString *)videoPath
size:(CGSize)videoSize
point:(CGPoint)videoPoint
range:(CMTimeRange)timeRange
videoRate:(CGFloat)videoRate
withOutPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.3 新增视频水印
4.10.3.1 添加单个视频水印
/// 添加单个视频水印
/// @param videoPath 视频路径
/// @param stickerImage 水印图片
/// @param stickerImagePoint 水印位置,point为水印所占视频位置的百分比。eg:CGPointMake(0.1, 0.1)
/// @param stickerImageSize 水印大小,为水印所占视频大小的百分比。eg:CGSizeMake(0.4, 0.4)
/// @param rotateAngle 水印旋转弧度。eg:M_PI_2
/// @param timeRange 添加水印的时间范围,kCMTimeRangeZero时,全部时长都添加水印
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+ (void)dw_addStickerAndExportVideo:(NSString *)videoPath
withStickerImage:(UIImage *)stickerImage
stickerImagePoint:(CGPoint)stickerImagePoint
stickerImageSize:(CGSize)stickerImageSize
rotateAngle:(CGFloat)rotateAngle
timeRange:(CMTimeRange)timeRange
outPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.3.2 批量添加视频水印
/// 批量添加视频水印
/// @param videoPath 视频路径
/// @param stickerImages 水印图片数组
/// @param stickerImagePoints 水印位置数组,points为水印所占视频位置的百分比数组。eg:@[[NSValue valueWithCGPoint:CGPointMake(0.1, 0.1)]]
/// @param stickerImageSizes 水印大小数组,sizes为水印所占视频大小的百分比数组。eg:@[[NSValue valueWithCGPoint:CGSizeMake(0.4, 0.4)]]
/// @param rotateAngles 水印旋转弧度数据。eg:[NSNumber numberWithFloat:M_PI_2]
/// @param timeRanges 添加水印的时间范围数组,kCMTimeRangeZero时,全部时长都添加水印
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+ (void)dw_addStickerAndExportVideo:(NSString *)videoPath
withStickerImages:(NSArray <UIImage *>*)stickerImages
stickerImagePoints:(NSArray <NSValue *> *)stickerImagePoints
stickerImageSizes:(NSArray <NSValue *> *)stickerImageSizes
rotateAngles:(NSArray <NSNumber *> *)rotateAngles
timeRanges:(NSArray <NSValue *> *)timeRanges
outPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.4 多视频合成
/// 多视频合成
/// @param videosPath 视频路径数组
/// @param videoSize 合成视频尺寸,不设置(CGRectZero)默认取第一个视频大小为基准
/// @param outPath 输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+(void)dw_compositeAndExportVideos:(NSArray <NSString *>*)videosPath
withVideoSize:(CGSize)videoSize
outPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.5 插入音频
/// 插入音频
/// @param videoPath 视频路径
/// @param audioPath 待插入音频路径
/// @param originalVolume 原音频音量
/// @param insertVolume 插入音频音量
/// @param timeRange 插入音频范围,kCMTimeRangeZero时,从0开始添加
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+(void)dw_insertAudioAndExportVideo:(NSString *)videoPath
withAudioPath:(NSString *)audioPath
originalVolume:(CGFloat)originalVolume
insertVolume:(CGFloat)insertVolume
timeRange:(CMTimeRange)timeRange
outPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.6 修改视频分辨率
/// 修改视频分辨率
/// @param videoPath 视频路径
/// @param videoSize 要输出的视频分辨率
/// @param outPath 视频输出路径
/// @param outputFileType 视频格式
/// @param presetName 视频导出质量
/// @param completeBlock 完成后的回调
+ (void)dw_videoSizeChangeRenderAndExportVideo:(NSString *)videoPath
videoSize:(CGSize)videoSize
withOutPath:(NSString *)outPath
outputFileType:(NSString *)outputFileType
presetName:(NSString *)presetName
didComplete:(void(^)(NSError *error,NSURL *compressionFileURL))completeBlock;
4.10.7 其他开放接口
/// 取得缩略图
/// @param videoPath 文件路径
/// @param time 第几秒的缩略图
+(UIImage *)dw_getThumbnailImage:(NSString *)videoPath time:(NSTimeInterval)time;
/// 十六进制色彩
/// @param string 色值
+(UIColor *)dw_colorWithHexString:(NSString *)string;
/// 删除文件
/// @param filePath 文件路径 并返回是否成功
+(BOOL)dw_deleteFileWithFilePath:(NSString *)filePath;
/// 获取文件大小
/// @param filePath 文件路径
+(CGFloat)dw_fileSizeAtPath:(NSString*)filePath;
4.11 图片合成视频功能的使用
通过多张图片及图片特效,转场特效生成一段视频。使用步骤如下:
4.11.1 通过选取的图片,图片特效,转场特效等参数,生成DWPictureNodeModel对象
//图片特效通过DWPictureAnimation类生成
/// 返回图片特效动画数据
/// @param style 特效样式
/// @param pictureDuration 图片特效持续时间
/// @param frontTransitionDuration 转场动画持续时间
/// @param videoSize 合成视频的大小,左滑,右滑特效需要。
/// @param isLast 是否是最后一张图片
+(NSArray *)pictureAnimationCreateWithStyle:(DWPictureAnimationStyle)style
PictureDuration:(CGFloat)pictureDuration
FrontTransitionDuration:(CGFloat)frontTransitionDuration
VideoSize:(CGSize)videoSize
isLast:(BOOL)isLast;
//转场特效通过DWTransitionAnimation类生成
/// 返回转场动画特效
/// @param style 转场动画样式
/// @param beginTime 特效持续时间
/// @param duration 转场动画持续时间
/// @param videoSize 合成视频的大小
/// @param isFront 转场特效是否在图片之前
+(NSArray *)transitionAnimationCreateWithStyle:(DWTransitionAnimationStyle)style
BeginTime:(CGFloat)beginTime
Duration:(CGFloat)duration
VideoSize:(CGSize)videoSize
isFront:(BOOL)isFront;
//生成圆形动画
+(NSArray *)circleMaskAnimationCreateWithDuration:(CGFloat)duration
VideoSize:(CGSize)videoSize;
//DWPictureNodeModel节点对象的创建
DWPictureNodeModel * pictureNodeModel = [[DWPictureNodeModel alloc]init];
pictureNodeModel.image = ...; //图片,非空
pictureNodeModel.beginTime = ...; //图片特效起始时间
pictureNodeModel.duration = ...; //图片特效持续时间
pictureNodeModel.animations = ...; //图片特效及转场特效,DWPictureAnimation,DWTransitionAnimation 生成的特效数组集合。
pictureNodeModel.maskLayer = ...; //图层layer的mask图层,用于辅助完成动画效果
pictureNodeModel.maskAnimationBeginTime = ...; //mask图层特效开始时间
pictureNodeModel.maskAnimationDuration = ...; //mask图层特效持续时间
pictureNodeModel.maskAnimations = ...; //mask图层的特效
pictureNodeModel.isCover = ...; //图层layer的添加顺序
详细实现逻辑,可参考DWShortPictureCompositeViewController中-(NSArray *)resetPictureNodeAniationsWithSize:(CGSize)aniationsSize AndCreateVideo:(BOOL)isCreateVideo;方法的实现逻辑。
###4.11.2 创建DWPictureCompositeInstrument对象,设置适当的参数,生成视频
//根据DWPictureNodeModel节点对象,初始化视频生成器
DWPictureCompositeInstrument * instrument = [[DWPictureCompositeInstrument alloc]init];
instrument.videoSize = ...; //视频分辨率,非空
instrument.videoBackgroundColor = ...; //合成视频底色,默认纯黑
instrument.outPath = ...; 视频导出路径,非空
instrument.pictureNodeModels = ...; //步骤1所生成的节点对象数组
instrument.videoDuration = ...;//视频总时间
//开始生成视频
[instrument startComposite];
//成功or失败回调
instrument.complete = ^(NSError *error, NSURL *completeFileURL) {
if (error) {
//视频生成失败处理及原因
return;
}
//视频生成成功
...
};
详细实现逻辑,可参考DWShortPictureCompositeViewController中-(void)nextButtonAction方法。
4.12 视频特效功能的使用
视频特效功能是在GPUImage的基础上,通过自定义滤镜来实现各种视频特效。添加视频特效的关键代码如下,其他页面逻辑可参考demo中的实现。
//查询最后的非视频特效滤镜
-(GPUImageOutput *)searchEffectOutput:(GPUImageOutput *)output
{
//视频特效滤镜添加顺序
//DWGPUImageShakeFilter
//DWGPUImageFlashFilter
//DWGPUImageSoulOutFilter
//DWGPUImageVertigoFilter
//DWGPUImageScaleFilter
GPUImageOutput * lastoutput = nil;
for (id subTarget in output.targets) {
if (subTarget == self.filterView) {
[self.movieFile removeAllTargets];
lastoutput = output;
break;
}
GPUImageFilter * subFilter = (GPUImageFilter *)subTarget;
if (subFilter == self.effectShowFilter) {
lastoutput = output;
break;
}
if ([subFilter isKindOfClass:[DWGPUImageShakeFilter class]]) {
//跳出递归
[subFilter removeAllTargets];
lastoutput = output;
}else{
lastoutput = [self searchEffectOutput:subFilter];
}
}
return lastoutput;
}
//新增视频特效滤镜
-(GPUImageFilter *)addEffectFiltersWithLastOutput:(GPUImageOutput *)output
{
//视频特效滤镜添加顺序
//DWGPUImageShakeFilter
//DWGPUImageFlashFilter
//DWGPUImageSoulOutFilter
//DWGPUImageVertigoFilter
//DWGPUImageScaleFilter
NSMutableArray * shakeFilterArray = [NSMutableArray array];
NSMutableArray * flashFilterArray = [NSMutableArray array];
NSMutableArray * soulOutFilterArray = [NSMutableArray array];
NSMutableArray * vertigoFilterArray = [NSMutableArray array];
NSMutableArray * scaleFilterArray = [NSMutableArray array];
[self.effectsArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSInteger style = [[obj objectForKey:@"style"] integerValue];
switch (style) {
case 0:{
if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
[shakeFilterArray addObject:effectLoadTime];
}else{
[shakeFilterArray addObjectsFromArray:effectLoadTime];
}
}
}
break;
case 1:{
if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
[flashFilterArray addObject:effectLoadTime];
}else{
[flashFilterArray addObjectsFromArray:effectLoadTime];
}
}
}
break;
case 2:{
if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
[soulOutFilterArray addObject:effectLoadTime];
}else{
[soulOutFilterArray addObjectsFromArray:effectLoadTime];
}
}
}
break;
case 3:{
if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
[vertigoFilterArray addObject:effectLoadTime];
}else{
[vertigoFilterArray addObjectsFromArray:effectLoadTime];
}
}
}
break;
case 4:{
if (![[obj objectForKey:@"alreadyUndo"] boolValue]) {
id effectLoadTime = [obj objectForKey:@"effectLoadTime"];
if ([[effectLoadTime class] isSubclassOfClass:[NSDictionary class]]) {
[scaleFilterArray addObject:effectLoadTime];
}else{
[scaleFilterArray addObjectsFromArray:effectLoadTime];
}
}
}
break;
default:
break;
}
}];
DWGPUImageShakeFilter * shakeFilter = [[DWGPUImageShakeFilter alloc]init];
[shakeFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
DWGPUImageShakeFilter * filter = (DWGPUImageShakeFilter *)output;
if (shakeFilterArray.count == 0) {
return;
}
//判断是否添加滤镜效果
CGFloat currentTime = CMTimeGetSeconds(time);
BOOL isShow = NO;
for (NSDictionary * effectLoadTimeDict in shakeFilterArray) {
CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
if (currentTime >= beginTime && currentTime <= endTime) {
isShow = YES;
break;
}
}
if (isShow) {
filter.time = currentTime;
}else{
filter.time = 0.0;
}
}];
DWGPUImageFlashFilter * flashFilter = [[DWGPUImageFlashFilter alloc]init];
[flashFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
DWGPUImageFlashFilter * filter = (DWGPUImageFlashFilter *)output;
if (flashFilterArray.count == 0) {
return;
}
//判断是否添加滤镜效果
CGFloat currentTime = CMTimeGetSeconds(time);
BOOL isShow = NO;
for (NSDictionary * effectLoadTimeDict in flashFilterArray) {
CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
if (currentTime >= beginTime && currentTime <= endTime) {
isShow = YES;
break;
}
}
if (isShow) {
filter.time = currentTime;
}else{
filter.time = 0.0;
}
}];
DWGPUImageSoulOutFilter * soulOutFilter = [[DWGPUImageSoulOutFilter alloc]init];
[soulOutFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
DWGPUImageSoulOutFilter * filter = (DWGPUImageSoulOutFilter *)output;
if (soulOutFilterArray.count == 0) {
return;
}
//判断是否添加滤镜效果
CGFloat currentTime = CMTimeGetSeconds(time);
BOOL isShow = NO;
for (NSDictionary * effectLoadTimeDict in soulOutFilterArray) {
CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
if (currentTime >= beginTime && currentTime <= endTime) {
isShow = YES;
break;
}
}
if (isShow) {
filter.time = currentTime;
}else{
filter.time = 0.0;
}
}];
DWGPUImageVertigoFilter * vertigoFilter = [[DWGPUImageVertigoFilter alloc]init];
[vertigoFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
DWGPUImageVertigoFilter * filter = (DWGPUImageVertigoFilter *)output;
if (vertigoFilterArray.count == 0) {
return;
}
//判断是否添加滤镜效果
CGFloat currentTime = CMTimeGetSeconds(time);
BOOL isShow = NO;
for (NSDictionary * effectLoadTimeDict in vertigoFilterArray) {
CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
if (currentTime >= beginTime && currentTime <= endTime) {
isShow = YES;
break;
}
}
if (isShow) {
filter.time = currentTime;
}else{
filter.time = 0.0;
}
}];
DWGPUImageScaleFilter * scaleFilter = [[DWGPUImageScaleFilter alloc]init];
[scaleFilter setFrameProcessingCompletionBlock:^(GPUImageOutput * output, CMTime time) {
DWGPUImageScaleFilter * filter = (DWGPUImageScaleFilter *)output;
if (scaleFilterArray.count == 0) {
return;
}
//判断是否添加滤镜效果
CGFloat currentTime = CMTimeGetSeconds(time);
BOOL isShow = NO;
for (NSDictionary * effectLoadTimeDict in scaleFilterArray) {
CGFloat beginTime = [[effectLoadTimeDict objectForKey:@"beginTime"] floatValue];
CGFloat endTime = [[effectLoadTimeDict objectForKey:@"endTime"] floatValue];
if (currentTime >= beginTime && currentTime <= endTime) {
isShow = YES;
break;
}
}
if (isShow) {
filter.time = currentTime;
}else{
filter.time = 0.0;
}
}];
[output addTarget:shakeFilter];
[shakeFilter addTarget:flashFilter];
[flashFilter addTarget:soulOutFilter];
[soulOutFilter addTarget:vertigoFilter];
[vertigoFilter addTarget:scaleFilter];
return scaleFilter;
}
-(void)dealEffectsData
{
// CGFloat duration = CMTimeGetSeconds(self.videoDuration);
for (int i = 0; i < self.effectsArray.count; i++) {
NSMutableDictionary * effectParams = [self.effectsArray objectAtIndex:i];
//选中特效时间段
CGFloat undoBeginTime = [[effectParams objectForKey:@"undoBeginTime"] floatValue];
CGFloat undoEndTime = [[effectParams objectForKey:@"undoEndTime"] floatValue];
NSMutableArray * effectLoadTimeArray = [NSMutableArray array];
//添加自身位置
NSInteger selfLoc = undoBeginTime * 1000000;
NSInteger selfLen = (undoEndTime - undoBeginTime) * 1000000;
[effectLoadTimeArray addObject:[NSValue valueWithRange:NSMakeRange(selfLoc, selfLen)]];
for (int j = i + 1; j < self.effectsArray.count; j++) {
NSDictionary * nextEffectParams = [self.effectsArray objectAtIndex:j];
if ([[nextEffectParams objectForKey:@"alreadyUndo"] boolValue]) {
//已撤销操作
continue;
}
CGFloat nextUndoBeginTime = [[nextEffectParams objectForKey:@"undoBeginTime"] floatValue];
CGFloat nextUndoEndTime = [[nextEffectParams objectForKey:@"undoEndTime"] floatValue];
if (undoBeginTime >= nextUndoBeginTime && undoBeginTime <= nextUndoEndTime) {
if (undoEndTime >= nextUndoEndTime) {
//覆盖部分
NSInteger loc = nextUndoEndTime * 1000000;
NSInteger len = (undoEndTime - nextUndoEndTime) * 1000000;
[effectLoadTimeArray addObject:[NSValue valueWithRange:NSMakeRange(loc, len)]];
}else{
//全部覆盖
}
}else if (nextUndoBeginTime > undoBeginTime && nextUndoBeginTime < undoEndTime) {
if (undoEndTime < nextUndoEndTime) {
//部分覆盖
NSInteger loc = undoBeginTime * 1000000;
NSInteger len = (nextUndoBeginTime - undoBeginTime) * 1000000;
[effectLoadTimeArray addObject:[NSValue valueWithRange:NSMakeRange(loc, len)]];
}else {
//截断的情况
NSInteger fLoc = undoBeginTime * 1000000;
NSInteger fLen = (nextUndoBeginTime - undoBeginTime) * 1000000;
NSRange fRange = NSMakeRange(fLoc, fLen);
NSInteger sLoc = nextUndoEndTime * 1000000;
NSInteger sLen = (undoEndTime - nextUndoEndTime) * 1000000;
NSRange sRange = NSMakeRange(sLoc, sLen);
[effectLoadTimeArray addObject:@[[NSValue valueWithRange:fRange],[NSValue valueWithRange:sRange]]];
}
}else{
}
}
// NSLog(@"effectLoadTimeArray:%@",effectLoadTimeArray);
//处理effectLoadTimeArray 计算交集时间
id effectLoadTime = [self getEffectTimeIntersectionWithArray:effectLoadTimeArray];
[effectParams setValue:effectLoadTime forKey:@"effectLoadTime"];
}
}
4.13 Demo具体使用
Demo是示例源码,可直接用Xcode运行。Demo的设计旨在展示SDK各项功能的使用方法,如果希望应用获得更好的使用体验,要根据需求自行更改。 如果在使用SDK过程中遇到其他问题请联系CC客服进行反馈。
5.API 查询
https://hdgit.bokecc.com/ccvideo/VOD_iOS_ShortVideo_SDK/tree/master/doc/api