项目地址:https://github.com/xinle13/uploadVideo.git
基本的选择逻辑如下:
.h
#import
NS_ASSUME_NONNULL_BEGIN
typedef void(^UploadEndBlock)(NSData *videoData);
@interface UploadVideoBtn : UIButton
@property (nonatomic, weak)UIViewController *targetVC;
@property (nonatomic, strong)NSData *videoData;
@property (nonatomic, copy)UploadEndBlock uploadEndBlock;
@property (nonatomic, assign)CGFloat progressValue;
@end
NS_ASSUME_NONNULL_END
.m
#import "UploadVideoBtn.h"
#import
#import
#import
#import // 1. 导入头文件 iOS 9 新增
#import "WLCircleProgressView.h"
#define VIDEOCACHEPATH [NSTemporaryDirectory() stringByAppendingPathComponent:@"videoCache"]
// 检测block是否可用
#define BLOCK_EXEC(block, ...) if (block) { block(__VA_ARGS__); }
@interface UploadVideoBtn ()
@property (nonatomic, strong) WLCircleProgressView *progressView2;
@property (nonatomic, strong)UIImagePickerController *imagePicker;
@property (nonatomic, strong)NSString *filePath;
@end
@implementation UploadVideoBtn
-(void)dealloc{
NSLog(@"~~~UploadVideoBtn销毁~~~");
}
- (instancetype)init
{
self = [super init];
if (self) {
[self addSubview:self.progressView2];
self.imagePicker = [[UIImagePickerController alloc] init];
self.imagePicker.delegate = self;
self.imageView.contentMode = UIViewContentModeScaleAspectFill;
[self setImage:[UIImage imageNamed:(@"kyc_uploadVideo")] forState:(UIControlStateNormal)];
[self addTarget:self action:@selector(actionVideo) forControlEvents:(UIControlEventTouchUpInside)];
}
return self;
}
- (void)actionVideo {
//1.0 点击播放视频的按钮 此处可以播放或者重新选择
if (self.videoData) {
[self play];
return;
}
UIAlertController *alertController = \
[UIAlertController alertControllerWithTitle:@""
message:@"上传视频"
preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *photoAction = \
[UIAlertAction actionWithTitle:@"从视频库选择"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"从视频库选择");
self.imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
self.imagePicker.mediaTypes = @[(NSString *)kUTTypeMovie];
self.imagePicker.allowsEditing = NO;
[self.targetVC presentViewController:self.imagePicker animated:YES completion:nil];
}];
UIAlertAction *cameraAction = \
[UIAlertAction actionWithTitle:@"录像"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"录像");
self.imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
self.imagePicker.cameraDevice = UIImagePickerControllerCameraDeviceRear;
self.imagePicker.mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
self.imagePicker.videoQuality = UIImagePickerControllerQualityType640x480;
self.imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModeVideo;
self.imagePicker.allowsEditing = YES;
[self.targetVC presentViewController:self.imagePicker animated:YES completion:nil];
}];
UIAlertAction *cancelAction = \
[UIAlertAction actionWithTitle:@"取消"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * _Nonnull action) {
NSLog(@"取消");
}];
[alertController addAction:photoAction];
[alertController addAction:cameraAction];
[alertController addAction:cancelAction];
[self.targetVC presentViewController:alertController animated:YES completion:nil];
}
#pragma mark - UIImagePickerDelegate方法
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
//获取用户选择或拍摄的是照片还是视频
UIVideoEditorController *videoEditor = nil;
NSString *mediaType = info[UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {
NSURL *URL = info[UIImagePickerControllerMediaURL];
//1.0 如果是拍摄的视频, 则把视频保存在系统多媒体库中(方便后期查证)
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
NSLog(@"video path: %@", info[UIImagePickerControllerMediaURL]);
NSURL *URL = info[UIImagePickerControllerMediaURL];
//ios 8.0+
NSError *error;
[[PHPhotoLibrary sharedPhotoLibrary] performChangesAndWait:^{
[PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:URL];
NSLog(@"%@",error);
} error:&error];
if([UIVideoEditorController canEditVideoAtPath:URL.path]){
videoEditor = [[UIVideoEditorController alloc]init];
videoEditor.videoPath = URL.path;
videoEditor.delegate = self;
}
}
//3.0 写入本地缓存,方便播放使用
NSString *mediaName = [self getVideoNameBaseCurrentTime];
NSLog(@"将视频存入缓存 mediaName: %@", mediaName);
self.filePath = [VIDEOCACHEPATH stringByAppendingPathComponent:mediaName];
[self saveVideoFromPath:info[UIImagePickerControllerMediaURL] toCachePath:self.filePath];
//2.0 压缩视频 & 保存视频数据 & 获取第一张图片
UIImage *img = [self getVideoPreViewImageWithPath:URL];
NSString *zipFileName = [NSString stringWithFormat:@"zip%@",mediaName];
NSString *zipPath = [VIDEOCACHEPATH stringByAppendingPathComponent:zipFileName];
[self compressVideoAccroding:info[UIImagePickerControllerMediaURL] withOutputUrl:zipPath SuccessBlock:^(NSData * videoData) {
dispatch_async(dispatch_get_main_queue(), ^{
//主线程界面刷新
if (videoData.length > 20 * 1024 * 1024) {
NSLog(@"~~~视频超过20M,请重新选择~~~");
return;
}
[self setImage:img forState:(UIControlStateNormal)];
self.videoData = videoData;
BLOCK_EXEC(self.uploadEndBlock,videoData);
});
}];
}
//[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
/**
* 视频压缩
* @param originFilePath 视频资源的原始路径
* @param outputPath 输出路径
*/
-(void)compressVideoAccroding:(NSURL *)originFilePath withOutputUrl:(NSString *)outputPath SuccessBlock:(void(^)(NSData *))successBlock {
//转码配置
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:originFilePath options:nil];
AVAssetExportSession *exportSession= [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputURL = [NSURL fileURLWithPath:outputPath];
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
int exportStatus = exportSession.status;
NSLog(@"转码状态:%d",exportStatus);
switch (exportStatus) {
case AVAssetExportSessionStatusFailed:
{
// log error to text view
NSError *exportError = exportSession.error;
NSLog (@"AVAssetExportSessionStatusFailed: %@", exportError);
break;
}
case AVAssetExportSessionStatusCompleted:
{
if (outputPath) {
NSURL *url = [NSURL fileURLWithPath:outputPath];
NSData *videoData = [NSData dataWithContentsOfURL:url];
NSLog(@"视频转码成功:%@",[self getSizeWithData:videoData]);
BLOCK_EXEC(successBlock,videoData)
}
}
}
}];
}
//以当前时间合成视频名称
- (NSString *)getVideoNameBaseCurrentTime {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyyMMddHHmmss"];
return [[dateFormatter stringFromDate:[NSDate date]] stringByAppendingString:@".MP4"];
}
#pragma mark - videoEditorControllerDelegate方法
- (void)videoEditorController:(UIVideoEditorController *)editor didSaveEditedVideoToPath:(NSString *)editedVideoPath{
NSLog(@"%@",editedVideoPath);
[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
[PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:[NSURL fileURLWithPath:editedVideoPath]];
} completionHandler:^(BOOL success, NSError * _Nullable error) {
if (success) {
NSLog(@"保存成功");
}
if (error) {
NSLog(@"%@",error);
}
}];
[editor dismissViewControllerAnimated:YES completion:nil];
}
//获取视频的第一帧截图, 返回UIImage (需要导入AVFoundation.h)
- (UIImage*) getVideoPreViewImageWithPath:(NSURL *)videoPath
{
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoPath options:nil];
AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
gen.appliesPreferredTrackTransform = YES;
CMTime time = CMTimeMakeWithSeconds(0.0, 600);
NSError *error = nil;
CMTime actualTime;
CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
UIImage *img = [[UIImage alloc] initWithCGImage:image];
return img;
}
#pragma mark -- 缓存到本地
//将视频保存到缓存路径中
- (void)saveVideoFromPath:(NSString *)videoPath toCachePath:(NSString *)path {
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:VIDEOCACHEPATH]) {
NSLog(@"路径不存在, 创建路径");
[fileManager createDirectoryAtPath:VIDEOCACHEPATH
withIntermediateDirectories:YES
attributes:nil
error:nil];
} else {
NSLog(@"路径存在");
}
NSError *error;
[fileManager copyItemAtPath:videoPath toPath:path error:&error];
if (error) {
NSLog(@"文件保存到缓存失败");
}
}
-(void)removeTempVideoData{
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:self.filePath error:&error];
if (error) {
NSLog(@"temp文件删除失败:%@",error);
}else{
NSLog(@"temp文件删除成功");
}
}
#pragma mark -- 播放视频
-(void)play{
// 本地资源文件
NSString *filePath = self.filePath;
// 2. 创建视频播放控制器
AVPlayerViewController *playerViewController = [[AVPlayerViewController alloc] init];
// 3. 设置视频播放器 (这里为了简便,使用了URL方式,同样支持playerWithPlayerItem:的方式)
playerViewController.player = [AVPlayer playerWithURL:[NSURL fileURLWithPath:filePath]];
// 4. modal展示
[self.targetVC presentViewController:playerViewController animated:YES completion:nil];
// 5. 开始播放 : 默认不会自动播放
[playerViewController.player play];
}
#pragma mark -- 加载的loading
-(WLCircleProgressView *)progressView2{
if (_progressView2 == nil) {
WLCircleProgressView *circleProgress2 = [WLCircleProgressView viewWithFrame:CGRectMake(0, 0, 30, 30) circlesSize:CGRectMake(28, 2, 24, 24)];
circleProgress2.layer.cornerRadius = 10;
//阴影
circleProgress2.backgroundColor = [UIColor clearColor];
circleProgress2.backCircle.shadowColor = [UIColor grayColor].CGColor;
circleProgress2.backCircle.shadowRadius = 3;
circleProgress2.backCircle.shadowOffset = CGSizeMake(0, 0);
circleProgress2.backCircle.shadowOpacity = 1;
circleProgress2.backCircle.fillColor = [UIColor colorWithRed:151/255.0 green:151/255.0 blue:151/255.0 alpha:0.8].CGColor;
circleProgress2.backCircle.strokeColor = [UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1].CGColor;
circleProgress2.foreCircle.lineCap = @"butt";
circleProgress2.foreCircle.strokeColor = [UIColor colorWithRed:223/255.0 green:223/255.0 blue:223/255.0 alpha:1].CGColor;;
circleProgress2.progressValue = 0.2;
circleProgress2.hidden = YES;
_progressView2 = circleProgress2;
}
return _progressView2;
}
-(void)setProgressValue:(CGFloat)progressValue{
dispatch_async(dispatch_get_main_queue(), ^{
//主线程界面刷新
_progressValue = progressValue;
if (progressValue == 0 || progressValue >= 1.0) {
self.progressView2.hidden = YES;
}else{
self.progressView2.hidden = NO;
self.progressView2.progressValue = progressValue;
}
});
}
/**获取文件大小*/
-(NSString *)getSizeWithData:(NSData *)data{
double convertedValue = data.length;
int multiplyFactor = 0;
NSArray *tokens = [NSArray arrayWithObjects:@"bytes",@"KB",@"MB",@"GB",@"TB",@"PB", @"EB", @"ZB", @"YB",nil];
while (convertedValue > 1024) {
convertedValue /= 1024;
multiplyFactor++;
}
return [NSString stringWithFormat:@"%.2f %@",convertedValue, [tokens objectAtIndex:multiplyFactor]];
}
@end
附带文件上传的af方法
/**
1.Data:要上传文件的二进制数据
2.DataName:接口文件的参数名称(img,file,video等后端接口人员定义的)
3.FileName:本地文件名,自定义即可
4.MimeType参考类型:
4.1.图片 fileName:@"test.png" mimeType:@"image/png"
4.2.视频 fileName:@"test.mov" mimeType:@"video/quicktime"
*/
+(void)xlrequestApiWithMethodUpload:(NSString *)url
param:(NSMutableDictionary *)param
Data:(NSData *)data
DataName:(NSString *)dataName
FileName:(NSString *)fileName
MimeType:(NSString *)mimeType
progress:(nullable void (^)(NSProgress * _Nonnull))uploadProgressBlock
thenSuccess:(void (^)(id responseObject))success
fail:(void (^)(void))fail
{
AFHTTPSessionManager *manager = [self singleManager];
[self setHTTPHeadWithAFHTTPSessionManager:manager Params:param];
[manager.requestSerializer setValue:@"multipart/form-data" forHTTPHeaderField:@"Content-Type"];
[manager POST:url parameters:param headers:nil constructingBodyWithBlock:^(id _Nonnull formData) {
/* 上传数据拼接 */
[formData appendPartWithFileData:data name:dataName fileName:fileName mimeType:mimeType];
} progress:^(NSProgress * _Nonnull uploadProgress) {
uploadProgressBlock(uploadProgress);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[PopTool stop];
id response;
if (responseObject) {
response = [NSJSONSerialization JSONObjectWithData:responseObject options:(NSJSONReadingMutableContainers) error:nil];
}
if (response == nil) {
[self dealWithError:nil];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self dealWithResponseObject:responseObject URL:url];
!success ? : success(response);
});
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[PopTool stop];
dispatch_async(dispatch_get_main_queue(), ^{
[self dealWithError:error];
// [XLUtil showMessage:RDLocalizedString(@"网络错误")];
!fail ? : fail();
});
}];
}