Commit 8a345102 authored by 肖兰月's avatar 肖兰月

支持单个视频的裁剪

parent 9c17a2da
......@@ -43,7 +43,6 @@
903996931F447604005E77C2 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 903996921F447604005E77C2 /* Photos.framework */; };
90A74B85203287C200D84C2A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 90A74B83203287C200D84C2A /* Localizable.strings */; };
90CE84AE1C3A89EF003D0779 /* TZImageManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 90CE84AD1C3A89EF003D0779 /* TZImageManager.m */; };
90CE84B71C3BABB6003D0779 /* TZVideoPlayerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 90CE84B61C3BABB6003D0779 /* TZVideoPlayerController.m */; };
9F763A4A1FA071D000D9E526 /* TZImagePickerController.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9F763A421FA071CF00D9E526 /* TZImagePickerController.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9F763A501FA072E500D9E526 /* TZImagePickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 900EEFF71C2BBF9500EA709B /* TZImagePickerController.m */; };
9F763A521FA072E500D9E526 /* TZPhotoPickerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 900EEFFD1C2BD58B00EA709B /* TZPhotoPickerController.m */; };
......@@ -72,6 +71,11 @@
9F763A761FA0741D00D9E526 /* TZImageManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 90CE84AC1C3A89EF003D0779 /* TZImageManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
9F763A771FA0741D00D9E526 /* TZImageCropManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 6DC84D081DF5358500A107A9 /* TZImageCropManager.h */; };
9F763A781FA0741D00D9E526 /* TZLocationManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 902938D61EE2D02C000F2F8F /* TZLocationManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
B606A9C4265FD53200F5493E /* TZVideoCropController.m in Sources */ = {isa = PBXBuildFile; fileRef = B606A9C2265FD53200F5493E /* TZVideoCropController.m */; };
B606A9CE265FD5F700F5493E /* TZVideoPlayerController.m in Sources */ = {isa = PBXBuildFile; fileRef = 90CE84B61C3BABB6003D0779 /* TZVideoPlayerController.m */; };
B662C36F2662914D006A672A /* TZVideoEditedPreviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B662C36E2662914D006A672A /* TZVideoEditedPreviewController.m */; };
B662C37326629179006A672A /* TZVideoEditedPreviewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B662C36E2662914D006A672A /* TZVideoEditedPreviewController.m */; };
B6977B0A265FDB280028BA31 /* TZVideoCropController.m in Sources */ = {isa = PBXBuildFile; fileRef = B606A9C2265FD53200F5493E /* TZVideoCropController.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
......@@ -182,6 +186,10 @@
90CE84B61C3BABB6003D0779 /* TZVideoPlayerController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZVideoPlayerController.m; sourceTree = "<group>"; };
9F763A421FA071CF00D9E526 /* TZImagePickerController.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TZImagePickerController.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9F763A451FA071D000D9E526 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B606A9C2265FD53200F5493E /* TZVideoCropController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TZVideoCropController.m; sourceTree = "<group>"; };
B606A9C3265FD53200F5493E /* TZVideoCropController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TZVideoCropController.h; sourceTree = "<group>"; };
B662C36D2662914D006A672A /* TZVideoEditedPreviewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TZVideoEditedPreviewController.h; sourceTree = "<group>"; };
B662C36E2662914D006A672A /* TZVideoEditedPreviewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TZVideoEditedPreviewController.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
......@@ -320,6 +328,10 @@
90CE84B61C3BABB6003D0779 /* TZVideoPlayerController.m */,
6D46082F1DFFC60D004FB009 /* TZGifPhotoPreviewController.h */,
6D4608301DFFC60D004FB009 /* TZGifPhotoPreviewController.m */,
B606A9C3265FD53200F5493E /* TZVideoCropController.h */,
B606A9C2265FD53200F5493E /* TZVideoCropController.m */,
B662C36D2662914D006A672A /* TZVideoEditedPreviewController.h */,
B662C36E2662914D006A672A /* TZVideoEditedPreviewController.m */,
6DD050291DF659ED0057C78D /* TZProgressView.h */,
6DD0502A1DF659ED0057C78D /* TZProgressView.m */,
90CE84AC1C3A89EF003D0779 /* TZImageManager.h */,
......@@ -573,10 +585,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B606A9CE265FD5F700F5493E /* TZVideoPlayerController.m in Sources */,
B662C36F2662914D006A672A /* TZVideoEditedPreviewController.m in Sources */,
9038D5911C3974F0007DE549 /* TZTestCell.m in Sources */,
900EF00F1C2C0B1100EA709B /* TZAssetModel.m in Sources */,
900EEFFE1C2BD58B00EA709B /* TZPhotoPickerController.m in Sources */,
90CE84B71C3BABB6003D0779 /* TZVideoPlayerController.m in Sources */,
900EF0021C2BD7E400EA709B /* TZAssetCell.m in Sources */,
6DD0502B1DF659ED0057C78D /* TZProgressView.m in Sources */,
6D32B9BD1CD83640005CE1E0 /* LxGridViewFlowLayout.m in Sources */,
......@@ -587,6 +600,7 @@
901F2295215CABD600F604ED /* FLAnimatedImage.m in Sources */,
900E65841C2BB8D5003D9A9E /* AppDelegate.m in Sources */,
9005E2B3257A462E002A8682 /* UIView+TZLayout.m in Sources */,
B606A9C4265FD53200F5493E /* TZVideoCropController.m in Sources */,
901F2296215CABD600F604ED /* FLAnimatedImageView.m in Sources */,
9019FD8D21EC25D7009ADEAE /* TZImageUploadOperation.m in Sources */,
900E65811C2BB8D5003D9A9E /* main.m in Sources */,
......@@ -619,6 +633,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B662C37326629179006A672A /* TZVideoEditedPreviewController.m in Sources */,
B6977B0A265FDB280028BA31 /* TZVideoCropController.m in Sources */,
901CC3FB21CB757500C55443 /* TZImageRequestOperation.m in Sources */,
9F763A501FA072E500D9E526 /* TZImagePickerController.m in Sources */,
9F763A521FA072E500D9E526 /* TZPhotoPickerController.m in Sources */,
......
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
\ No newline at end of file
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "hx_videoedit_left@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "hx_videoedit_right@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -80,6 +80,9 @@
- (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion;
- (PHImageRequestID)getOriginalPhotoDataWithAsset:(PHAsset *)asset progressHandler:(void (^)(double progress, NSError *error, BOOL *stop, NSDictionary *info))progressHandler completion:(void (^)(NSData *data,NSDictionary *info,BOOL isDegraded))completion;
/// Get Image For VideoURL
- (UIImage *)getImageWithVideoURL:(NSURL *)videoURL;
/// Save photo 保存照片
- (void)savePhotoWithImage:(UIImage *)image completion:(void (^)(PHAsset *asset, NSError *error))completion;
- (void)savePhotoWithImage:(UIImage *)image location:(CLLocation *)location completion:(void (^)(PHAsset *asset, NSError *error))completion;
......@@ -96,6 +99,7 @@
/// Export video 导出视频 presetName: 预设名字,默认值是AVAssetExportPreset640x480
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure;
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure;
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure;
/// 新的导出视频API,解决iOS14 iCloud视频导出失败的问题,未大量测试,请大家多多测试,有问题群里反馈
- (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure;
/// 得到视频原始文件地址
......
......@@ -498,6 +498,22 @@ static dispatch_once_t onceToken;
}];
}
- (UIImage *)getImageWithVideoURL:(NSURL *)videoURL {
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
if (!asset) {
return nil;
}
AVAssetImageGenerator *generator =[[AVAssetImageGenerator alloc] initWithAsset:asset];
generator.appliesPreferredTrackTransform = YES;
generator.apertureMode = AVAssetImageGeneratorApertureModeEncodedPixels;
CFTimeInterval time = 0.1;
CGImageRef imageRef = [generator copyCGImageAtTime:CMTimeMake(time, 60) actualTime:NULL error:nil];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef];
CGImageRelease(imageRef);
return image;
}
#pragma mark - Save photo
- (void)savePhotoWithImage:(UIImage *)image completion:(void (^)(PHAsset *asset, NSError *error))completion {
......@@ -635,19 +651,27 @@ static dispatch_once_t onceToken;
}
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
[self getVideoOutputPathWithAsset:asset presetName:presetName timeRange:kCMTimeRangeZero success:success failure:failure];
}
- (void)startExportVideoWithVideoAsset:(AVURLAsset *)videoAsset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
[self startExportVideoWithVideoAsset:videoAsset timeRange:kCMTimeRangeZero presetName:presetName success:success failure:failure];
}
- (void)getVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
if (@available(iOS 14.0, *)) {
[self requestVideoOutputPathWithAsset:asset presetName:presetName success:success failure:failure];
[self requestVideoOutputPathWithAsset:asset presetName:presetName timeRange:timeRange success:success failure:failure];
return;
}
[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:[self getVideoRequestOptions] resultHandler:^(AVAsset* avasset, AVAudioMix* audioMix, NSDictionary* info){
// NSLog(@"Info:\n%@",info);
AVURLAsset *videoAsset = (AVURLAsset*)avasset;
// NSLog(@"AVAsset URL: %@",myAsset.URL);
[self startExportVideoWithVideoAsset:videoAsset presetName:presetName success:success failure:failure];
[self startExportVideoWithVideoAsset:videoAsset timeRange:timeRange presetName:presetName success:success failure:failure];
}];
}
- (void)startExportVideoWithVideoAsset:(AVURLAsset *)videoAsset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
- (void)startExportVideoWithVideoAsset:(AVURLAsset *)videoAsset timeRange:(CMTimeRange)timeRange presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
if (!presetName) {
presetName = AVAssetExportPresetMediumQuality;
}
......@@ -663,6 +687,9 @@ static dispatch_once_t onceToken;
// Optimize for network use.
session.shouldOptimizeForNetworkUse = true;
if (!CMTimeRangeEqual(timeRange, kCMTimeRangeZero)) {
session.timeRange = timeRange;
}
NSArray *supportedTypeArray = session.supportedFileTypes;
if ([supportedTypeArray containsObject:AVFileTypeMPEG4]) {
......@@ -706,7 +733,7 @@ static dispatch_once_t onceToken;
}
}
- (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
- (void)requestVideoOutputPathWithAsset:(PHAsset *)asset presetName:(NSString *)presetName timeRange:(CMTimeRange)timeRange success:(void (^)(NSString *outputPath))success failure:(void (^)(NSString *errorMessage, NSError *error))failure {
if (!presetName) {
presetName = AVAssetExportPresetMediumQuality;
}
......@@ -715,6 +742,9 @@ static dispatch_once_t onceToken;
exportSession.outputURL = [NSURL fileURLWithPath:outputPath];
exportSession.shouldOptimizeForNetworkUse = NO;
exportSession.outputFileType = AVFileTypeMPEG4;
if (!CMTimeRangeEqual(timeRange, kCMTimeRangeZero)) {
exportSession.timeRange = timeRange;
}
[exportSession exportAsynchronouslyWithCompletionHandler:^{
[self handleVideoExportResult:exportSession outputPath:outputPath success:success failure:failure];
}];
......
......@@ -2,6 +2,7 @@
"OK" = "حسنا";
"Back" = "الى الخلف";
"Done" = "فعله";
"Edit" = "تعديل";
"Sorry" = "آسف";
"Cancel" = "إلغاء";
"Setting" = "ضبط";
......
......@@ -2,6 +2,7 @@
"OK" = "Добре";
"Back" = "обратно";
"Done" = "Свършен";
"Edit" = "редактиране";
"Sorry" = "съжалявам";
"Cancel" = "Отказ";
"Setting" = "обстановка";
......
......@@ -2,6 +2,7 @@
"OK" = "OK";
"Back" = "Zadní";
"Done" = "Hotovo";
"Edit" = "Upravit";
"Sorry" = "Promiňte";
"Cancel" = "zrušení";
"Setting" = "Nastavení";
......
......@@ -2,6 +2,7 @@
"OK" = "OK";
"Back" = "Zurück";
"Done" = "Erledigt";
"Edit" = "Bearbeiten";
"Sorry" = "Es tut uns leid";
"Cancel" = "Stornieren";
"Setting" = "Rahmen";
......
......@@ -2,6 +2,7 @@
"OK" = "Εντάξει";
"Back" = "Πίσω";
"Done" = "Εγινε";
"Edit" = "επεξεργασία";
"Sorry" = "Συγνώμη";
"Cancel" = "Ματαίωση";
"Setting" = "Σύνθεση";
......
......@@ -2,6 +2,7 @@
"OK" = "DE ACUERDO";
"Back" = "Espalda";
"Done" = "Hecho";
"Edit" = "επεξεργασία";
"Sorry" = "Lo siento";
"Cancel" = "Cancelar";
"Setting" = "Ajuste";
......
......@@ -2,6 +2,7 @@
"OK" = "D'accord";
"Back" = "Retour";
"Done" = "Terminé";
"Edit" = "Éditer";
"Sorry" = "Pardon";
"Cancel" = "Annuler";
"Setting" = "Réglage";
......
......@@ -2,6 +2,7 @@
"OK" = "בסדר";
"Back" = "חזור";
"Done" = "בוצע";
"Edit" = "לַעֲרוֹך"
"Sorry" = "מצטער";
"Cancel" = "בטל";
"Setting" = "הגדרה";
......
......@@ -2,6 +2,7 @@
"OK" = "ok";
"Back" = "Indietro";
"Done" = "Fatto";
"Edit" = "modificare";
"Sorry" = "scusate";
"Cancel" = "Annulla";
"Setting" = "Ambientazione";
......
......@@ -2,6 +2,7 @@
"OK" = "OK";
"Back" = "バック";
"Done" = "完了";
"Edit" = "編集する";
"Sorry" = "ごめんなさい";
"Cancel" = "キャンセル";
"Setting" = "設定";
......
......@@ -2,6 +2,7 @@
"OK" = "그래";
"Back" = "뒤로";
"Done" = "완료";
"Edit" = "편집하다";
"Sorry" = "미안해요";
"Cancel" = "취소";
"Setting" = "설정";
......
......@@ -2,6 +2,7 @@
"OK" = "승인";
"Back" = "뒤로";
"Done" = "끝난";
"Edit" = "편집하다";
"Sorry" = "죄송합니다";
"Cancel" = "취소";
"Setting" = "환경";
......
......@@ -2,6 +2,7 @@
"OK" = "OK";
"Back" = "Terug";
"Done" = "Gedaan";
"Edit" = "Bewerk";
"Sorry" = "Sorry";
"Cancel" = "annuleren";
"Setting" = "omgeving";
......
......@@ -2,6 +2,7 @@
"OK" = "dobrze";
"Back" = "Z powrotem";
"Done" = "Gotowe";
"Edit" = "edytować";
"Sorry" = "Przepraszam";
"Cancel" = "Anuluj";
"Setting" = "Oprawa";
......
......@@ -2,6 +2,7 @@
"OK" = "Está bem";
"Back" = "De volta";
"Done" = "Feito";
"Edit" = "editar";
"Sorry" = "Desculpa";
"Cancel" = "Cancelar";
"Setting" = "Configuração";
......
......@@ -2,6 +2,7 @@
"OK" = "O.K";
"Back" = "Înapoi";
"Done" = "Terminat";
"Edit" = "Editați | ×";
"Sorry" = "scuze";
"Cancel" = "Anulare";
"Setting" = "reglaj";
......
......@@ -2,6 +2,7 @@
"OK" = "Хорошо";
"Back" = "назад";
"Done" = "Готово";
"Edit" = "редактировать";
"Sorry" = "сожалею";
"Cancel" = "отменить";
"Setting" = "настройка";
......
......@@ -2,6 +2,7 @@
"OK" = "OK";
"Back" = "späť";
"Done" = "hotový";
"Edit" = "upraviť";
"Sorry" = "Prepáč";
"Cancel" = "Zrušiť";
"Setting" = "nastavenie";
......
......@@ -2,6 +2,7 @@
"OK" = "ok";
"Back" = "Tillbaka";
"Done" = "Gjort";
"Edit" = "redigera";
"Sorry" = "Förlåt";
"Cancel" = "Annullera";
"Setting" = "Miljö";
......
......@@ -2,6 +2,7 @@
"OK" = "ตกลง";
"Back" = "กลับ";
"Done" = "เสร็จสิ้น";
"Edit" = "แก้ไข";
"Sorry" = "ขอโทษ";
"Cancel" = "ยกเลิก";
"Setting" = "การตั้งค่า";
......
......@@ -2,6 +2,7 @@
"OK" = "tamam";
"Back" = "Geri";
"Done" = "tamam";
"Edit" = "Düzenle";
"Sorry" = "Afedersiniz";
"Cancel" = "İptal etmek";
"Setting" = "Ayar";
......
......@@ -2,6 +2,7 @@
"OK" = "в порядку";
"Back" = "Назад";
"Done" = "Виконано";
"Edit" = "редагувати";
"Sorry" = "Вибачте";
"Cancel" = "Скасувати";
"Setting" = "Налаштування";
......
......@@ -2,6 +2,7 @@
"OK" = "Xác nhận";
"Back" = "Quay lại";
"Done" = "Hoàn thành";
"Edit" = "biên tập";
"Sorry" = "Xin lỗi";
"Cancel" = "Hủy";
"Setting" = "Cài đặt";
......
......@@ -78,6 +78,23 @@
/// Default is YES, if set NO, user can't picking video.
/// 默认为YES,如果设置为NO,用户将不能选择视频
@property (nonatomic, assign) BOOL allowPickingVideo;
/// Default is NO, if set YES, user can edit video.
/// 默认为NO,如果设置为YES, 用户能编辑视频
@property (nonatomic, assign) BOOL allowEditVideo;
/// Export quality of cropped video, Default is AVAssetExportPresetMediumQuality
/// 裁剪视频的导出质量,默认是 AVAssetExportPresetMediumQuality
@property (nonatomic, assign) NSString *presetName;
/// Default is 30s. If it exceeds the video duration, it is the video duration.The minimum duration of video crop is 1s.
/// 默认是30s,如果超过视频时长,则为视频时长,小于1s不裁剪
@property (nonatomic, assign) NSInteger maxCropVideoDuration;
/// Default is NO, if set YES, The edited video will be automatically saved to the album.
/// 默认为NO,如果设置为YES,编辑后的视频会自动保存到相册
@property (nonatomic, assign) BOOL saveEditedVideoToAlbum;
/// Default is NO / 默认为NO,为YES时可以多选视频/gif/图片,和照片共享最大可选张数maxImagesCount的限制
@property (nonatomic, assign) BOOL allowPickingMultipleVideo;
......@@ -176,7 +193,8 @@
/// 【自定义各页面/组件的样式】在界面初始化/组件setModel完成后调用,允许外界修改样式等
@property (nonatomic, copy) void (^photoPickerPageUIConfigBlock)(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine);
@property (nonatomic, copy) void (^photoPreviewPageUIConfigBlock)(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel);
@property (nonatomic, copy) void (^videoPreviewPageUIConfigBlock)(UIButton *playButton, UIView *toolBar, UIButton *doneButton);
@property (nonatomic, copy) void (^videoPreviewPageUIConfigBlock)(UIButton *playButton, UIView *toolBar, UIButton *editBtn, UIButton *doneButton);
@property (nonatomic, copy) void (^videoEditViewPageUIConfigBlock)(UIButton *playButton,UILabel *cropVideoDurationLabel, UIButton *editButton, UIButton *doneButton);
@property (nonatomic, copy) void (^gifPreviewPageUIConfigBlock)(UIView *toolBar, UIButton *doneButton);
@property (nonatomic, copy) void (^albumPickerPageUIConfigBlock)(UITableView *tableView);
@property (nonatomic, copy) void (^assetCellDidSetModelBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView);
......@@ -184,7 +202,8 @@
/// 【自定义各页面/组件的frame】在界面viewDidLayoutSubviews/组件layoutSubviews后调用,允许外界修改frame等
@property (nonatomic, copy) void (^photoPickerPageDidLayoutSubviewsBlock)(UICollectionView *collectionView, UIView *bottomToolBar, UIButton *previewButton, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel, UIView *divideLine);
@property (nonatomic, copy) void (^photoPreviewPageDidLayoutSubviewsBlock)(UICollectionView *collectionView, UIView *naviBar, UIButton *backButton, UIButton *selectButton, UILabel *indexLabel, UIView *toolBar, UIButton *originalPhotoButton, UILabel *originalPhotoLabel, UIButton *doneButton, UIImageView *numberImageView, UILabel *numberLabel);
@property (nonatomic, copy) void (^videoPreviewPageDidLayoutSubviewsBlock)(UIButton *playButton, UIView *toolBar, UIButton *doneButton);
@property (nonatomic, copy) void (^videoPreviewPageDidLayoutSubviewsBlock)(UIButton *playButton, UIView *toolBar, UIButton *editButton, UIButton *doneButton);
@property (nonatomic, copy) void (^videoEditViewPageDidLayoutSubviewsBlock)(UIButton *playButton, UILabel *cropVideoDurationLabel, UIButton *cancelButton, UIButton *doneButton);
@property (nonatomic, copy) void (^gifPreviewPageDidLayoutSubviewsBlock)(UIView *toolBar, UIButton *doneButton);
@property (nonatomic, copy) void (^albumPickerPageDidLayoutSubviewsBlock)(UITableView *tableView);
@property (nonatomic, copy) void (^assetCellDidLayoutSubviewsBlock)(TZAssetCell *cell, UIImageView *imageView, UIImageView *selectImageView, UILabel *indexLabel, UIView *bottomView, UILabel *timeLength, UIImageView *videoImgView);
......@@ -233,6 +252,8 @@
@property (nonatomic, copy) NSString *fullImageBtnTitleStr;
@property (nonatomic, copy) NSString *settingBtnTitleStr;
@property (nonatomic, copy) NSString *processHintStr;
@property (nonatomic, copy) NSString *editBtnTitleStr;
@property (nonatomic, copy) NSString *editViewCancelBtnTitleStr;
/// Icon theme color, default is green color like wechat, the value is r:31 g:185 b:34. Currently only support image selection icon when showSelectedIndex is YES. If you need it, please set it as soon as possible
/// icon主题色,默认是微信的绿色,值是r:31 g:185 b:34。目前仅支持showSelectedIndex为YES时的图片选中icon。如需要,请尽早设置它。
......@@ -246,6 +267,7 @@
@property (nonatomic, copy) void (^didFinishPickingPhotosWithInfosHandle)(NSArray<UIImage *> *photos,NSArray *assets,BOOL isSelectOriginalPhoto,NSArray<NSDictionary *> *infos);
@property (nonatomic, copy) void (^imagePickerControllerDidCancelHandle)(void);
@property (nonatomic, copy) void (^didFinishPickingVideoHandle)(UIImage *coverImage,PHAsset *asset);
@property (nonatomic, copy) void (^didFinishPickingAndEditingVideoHandle)(UIImage *coverImage,NSString *outputPath,NSString *errorMsg);
@property (nonatomic, copy) void (^didFinishPickingGifImageHandle)(UIImage *animatedImage,id sourceAssets);
@property (nonatomic, weak) id<TZImagePickerControllerDelegate> pickerDelegate;
......@@ -285,6 +307,16 @@
// 如果allowPickingMultipleVideo是YES,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingVideo:(UIImage *)coverImage sourceAssets:(PHAsset *)asset;
// If allowEditVideo is YES and allowPickingMultipleVideo is NO, When user picking a video, this callback will be called.
// If allowPickingMultipleVideo is YES, video editing is not supported, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
// 当allowEditVideo是YES且allowPickingMultipleVideo是NO是,如果用户选择了一个视频,下面的代理方法会被执行
// 如果allowPickingMultipleVideo是YES,则不支持编辑视频,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingAndEditingVideo:(UIImage *)coverImage outputPath:(NSString *)outputPath error:(NSString *)errorMsg;
// When saving the edited video to the album fails, this callback will be called.
// 编辑后的视频自动保存到相册失败时,下面的代理方法会被执行
- (void)imagePickerController:(TZImagePickerController *)picker didFailToSaveEditedVideoWithError:(NSError *)error;
// If user picking a gif image and allowPickingMultipleVideo is NO, this callback will be called.
// If allowPickingMultipleVideo is YES, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
// 如果用户选择了一个gif图片且allowPickingMultipleVideo是NO,下面的代理方法会被执行
......
......@@ -14,6 +14,7 @@
#import "TZAssetCell.h"
#import "UIView+TZLayout.h"
#import "TZImageManager.h"
#import "TZVideoCropController.h"
@interface TZImagePickerController () {
NSTimer *_timer;
......@@ -282,6 +283,8 @@
self.statusBarStyle = UIStatusBarStyleLightContent;
self.cannotSelectLayerColor = [[UIColor whiteColor] colorWithAlphaComponent:0.8];
self.allowCameraLocation = YES;
self.presetName = AVAssetExportPresetMediumQuality;
self.maxCropVideoDuration = 30;
self.iconThemeColor = [UIColor colorWithRed:31 / 255.0 green:185 / 255.0 blue:34 / 255.0 alpha:1.0];
[self configDefaultBtnTitle];
......@@ -352,6 +355,7 @@
self.fullImageBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Full image"];
self.settingBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Setting"];
self.processHintStr = [NSBundle tz_localizedStringForKey:@"Processing..."];
self.editBtnTitleStr = [NSBundle tz_localizedStringForKey:@"Edit"];
}
- (void)setShowSelectedIndex:(BOOL)showSelectedIndex {
......@@ -708,6 +712,13 @@
}
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
if ([self.topViewController isKindOfClass:TZVideoPlayerController.class] && self.topViewController.presentedViewController) {
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskAll;
}
@end
......
//
// TZVideoCropController.h
// TZImagePickerController
//
// Created by 肖兰月 on 2021/5/27.
// Copyright © 2021 谭真. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class TZAssetModel,TZImagePickerController;
@interface TZVideoCropController : UIViewController<UIViewControllerTransitioningDelegate>
@property (nonatomic, strong) TZAssetModel *model;
@property (nonatomic, weak) TZImagePickerController *imagePickerVc;
@end
@protocol TZVideoEditViewDelegate <NSObject>
- (void)editViewCropRectBeginChange;
- (void)editViewCropRectEndChange;
@end
@interface TZVideoEditView : UIView
@property (strong, nonatomic) UIImageView *beginImgView;
@property (strong, nonatomic) UIImageView *endImgView;
@property (strong, nonatomic) UIView *indicatorLine;
@property (assign, nonatomic) CGFloat videoDuration;
@property (assign, nonatomic) NSInteger maxCropVideoDuration;
@property (assign, nonatomic) CGRect cropRect;
@property (assign, nonatomic) CGFloat allImgWidth;
@property (assign, nonatomic) CGFloat minCropRectWidth;
@property (nonatomic, weak) id<TZVideoEditViewDelegate> delegate;
- (void)resetIndicatorLine;
- (void)indicatorLineAnimateWithDuration:(NSTimeInterval)duration cropRect:(CGRect)cropRect;
@end
@interface TZVideoPictureCell : UICollectionViewCell
@property (strong, nonatomic) UIImageView *imgView;
@end
NS_ASSUME_NONNULL_END
//
// TZVideoCropController.m
// TZImagePickerController
//
// Created by 肖兰月 on 2021/5/27.
// Copyright © 2021 谭真. All rights reserved.
//
#import "TZVideoCropController.h"
#import <MediaPlayer/MediaPlayer.h>
#import "UIView+TZLayout.h"
#import "TZImageManager.h"
#import "TZAssetModel.h"
#import "TZImagePickerController.h"
@interface TZVideoCropController ()<TZVideoEditViewDelegate,UICollectionViewDelegate, UICollectionViewDataSource> {
AVPlayer *_player;
AVPlayerLayer *_playerLayer;
UIButton *_playButton;
UIImage *_cover;
NSString *_outputPath;
NSString *_errorMsg;
UIButton *_cancelButton;
UIButton *_doneButton;
UIProgressView *_progress;
UILabel *_cropVideoDurationLabel;
AVAssetImageGenerator *_imageGenerator;
AVAsset *_asset;
CGFloat _collectionViewBeginOffsetX;
BOOL _isPlayed;
CGFloat _itemW;
BOOL _isDraging;
UIStatusBarStyle _originStatusBarStyle;
}
// iCloud无法同步提示UI
@property (nonatomic, strong) UIView *iCloudErrorView;
@property (strong, nonatomic) UICollectionView *collectionView;
@property (strong, nonatomic) TZVideoEditView *videoEditView;
@property (strong, nonatomic) NSMutableArray *videoImgArray;
@property (strong, nonatomic) NSArray *imageTimes;
@property (strong, nonatomic) NSTimer *timer;
@end
@implementation TZVideoCropController
#define VideoEditLeftMargin 40
#define PanImageWidth 10
#define MinCropVideoDuration 1
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
[self configMoviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayer) name:UIApplicationWillResignActiveNotification object:nil];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self stopTimer];
}
- (void)configMoviePlayer {
[[TZImageManager manager] getPhotoWithAsset:_model.asset completion:^(UIImage *photo, NSDictionary *info, BOOL isDegraded) {
BOOL iCloudSyncFailed = !photo && [TZCommonTools isICloudSyncError:info[PHImageErrorKey]];
self.iCloudErrorView.hidden = !iCloudSyncFailed;
}];
[[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
self->_asset = playerItem.asset;
self->_player = [AVPlayer playerWithPlayerItem:playerItem];
self->_playerLayer = [AVPlayerLayer playerLayerWithPlayer:self->_player];
self->_playerLayer.frame = self.view.bounds;
[self.view.layer addSublayer:self->_playerLayer];
[self configPlayButton];
[self configBottomToolBar];
if (self.imagePickerVc.allowEditVideo) {
[self configVideoImageCollectionView];
[self configVideoEditView];
[self generateVideoImage];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayer) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem];
});
}];
}
- (void)configPlayButton {
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted];
[_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_playButton];
}
- (void)configBottomToolBar {
_cropVideoDurationLabel = UILabel.new;
_cropVideoDurationLabel.textAlignment = NSTextAlignmentCenter;
_cropVideoDurationLabel.textColor = UIColor.whiteColor;
_cropVideoDurationLabel.font = [UIFont systemFontOfSize:12];
[self.view addSubview:_cropVideoDurationLabel];
_cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
_cancelButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_cancelButton setTitle:[NSBundle tz_localizedStringForKey:@"Cancel"] forState:0];
[_cancelButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[_cancelButton addTarget:self action:@selector(cancelButtonClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_cancelButton];
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside];
[_doneButton setTitle:self.imagePickerVc.doneBtnTitleStr forState:UIControlStateNormal];
[_doneButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[self.view addSubview:_doneButton];
if (self.imagePickerVc.videoEditViewPageUIConfigBlock) {
self.imagePickerVc.videoEditViewPageUIConfigBlock(_playButton, _cropVideoDurationLabel, _cancelButton, _doneButton);
}
}
- (void)configVideoImageCollectionView {
_itemW = (self.view.tz_width - VideoEditLeftMargin * 2 - 2 * PanImageWidth) / 10.0;
UICollectionViewFlowLayout *layout = UICollectionViewFlowLayout.new;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.itemSize = CGSizeMake(_itemW, _itemW * 2);
layout.minimumLineSpacing = 0;
layout.minimumInteritemSpacing = 0;
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.dataSource = self;
_collectionView.delegate = self;
_collectionView.contentInset = UIEdgeInsetsMake(0, VideoEditLeftMargin + PanImageWidth, 0, VideoEditLeftMargin + PanImageWidth);
_collectionView.clipsToBounds = NO;
_collectionView.showsHorizontalScrollIndicator = NO;
_collectionView.alwaysBounceHorizontal = YES;
[_collectionView registerClass:TZVideoPictureCell.class forCellWithReuseIdentifier:@"TZVideoPictureCell"];
[self.view addSubview:_collectionView];
}
- (void)configVideoEditView {
_videoEditView = TZVideoEditView.new;
_videoEditView.backgroundColor = UIColor.clearColor;
_videoEditView.delegate = self;
_videoEditView.maxCropVideoDuration = self.imagePickerVc.maxCropVideoDuration;
[self.view addSubview:_videoEditView];
}
- (UIStatusBarStyle)preferredStatusBarStyle {
if (self.imagePickerVc && [self.imagePickerVc isKindOfClass:[TZImagePickerController class]]) {
return self.imagePickerVc.statusBarStyle;
}
return [super preferredStatusBarStyle];
}
#pragma mark - Layout
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height;
CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0;
CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height;
CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom;
_cancelButton.frame = CGRectMake(12, self.view.tz_height - toolBarHeight, 44, 44);
_doneButton.frame = CGRectMake(self.view.tz_width - 44 - 12, self.view.tz_height - toolBarHeight, 44, 44);
_playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight);
CGFloat collectionViewH = (self.view.tz_width - VideoEditLeftMargin * 2 - 2 * PanImageWidth) / 10.0 * 2;
_collectionView.frame = CGRectMake(0, self.view.tz_height - collectionViewH - toolBarHeight - statusBarHeight, self.view.tz_width, collectionViewH);
_videoEditView.frame = _collectionView.frame;
_cropVideoDurationLabel.frame = CGRectMake(0, _videoEditView.tz_bottom, self.view.tz_width, 20);
CGFloat playerLayerHeight = CGRectGetMinY(_collectionView.frame) - statusBarHeight * 2;
CGFloat playerLayerWidth = self.view.tz_width/self.view.tz_height * playerLayerHeight;
CGFloat playerLayerLeft = (self.view.tz_width - playerLayerWidth) / 2.0;
CGRect playerLayerFrame = CGRectMake(playerLayerLeft, statusBarHeight, playerLayerWidth, playerLayerHeight);
_playerLayer.frame = playerLayerFrame;
_playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, playerLayerHeight - statusBarAndNaviBarHeight);
if (self.imagePickerVc.videoEditViewPageDidLayoutSubviewsBlock) {
self.imagePickerVc.videoEditViewPageDidLayoutSubviewsBlock(_playButton, _cropVideoDurationLabel, _cancelButton, _doneButton);
}
}
- (void)generateVideoImage {
_imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_asset];
_imageGenerator.appliesPreferredTrackTransform = YES;
_imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
_imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
_imageGenerator.maximumSize = CGSizeMake(100, 100);
NSTimeInterval durationSeconds = self.model.asset.duration;
self.videoEditView.videoDuration = durationSeconds;
NSUInteger imageCount = 10;
CGFloat maxCropWidth = self.view.tz_width - (VideoEditLeftMargin + PanImageWidth) * 2;
if (durationSeconds <= MinCropVideoDuration) return;
if (durationSeconds <= self.imagePickerVc.maxCropVideoDuration) {
imageCount = 10;
self.videoEditView.allImgWidth = maxCropWidth;
_cropVideoDurationLabel.text = [NSString stringWithFormat:@"已选取%.fs",durationSeconds];
} else {
CGFloat singleWidthSecond = maxCropWidth / self.imagePickerVc.maxCropVideoDuration;
CGFloat allImgWidth = singleWidthSecond * durationSeconds;
self.videoEditView.allImgWidth = allImgWidth;
imageCount = allImgWidth / _itemW;
_cropVideoDurationLabel.text = [NSString stringWithFormat:@"已选取%lds",(long)self.imagePickerVc.maxCropVideoDuration];
}
Float64 frameRate = [[_asset tracksWithMediaType:AVMediaTypeVideo][0] nominalFrameRate];;
NSMutableArray *times = NSMutableArray.array;
NSTimeInterval intervalSecond = durationSeconds/imageCount;
CMTime timeFrame;
for (NSInteger i = 0; i < imageCount; i++) {
timeFrame = CMTimeMake(intervalSecond * i *frameRate, frameRate);
NSValue *timeValue = [NSValue valueWithCMTime:timeFrame];
[times addObject:timeValue];
}
self.videoImgArray = NSMutableArray.new;
self.imageTimes = times;
typeof(self) weakSelf = self;
[_imageGenerator generateCGImagesAsynchronouslyForTimes:times completionHandler:^(CMTime requestedTime, CGImageRef _Nullable image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * _Nullable error) {
if (image) {
UIImage *img = [[UIImage alloc] initWithCGImage:image];
[weakSelf.videoImgArray addObject:img];
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.collectionView reloadData];
});
}
}];
}
#pragma mark - UICollectiobViewDataSource & UIcollectionViewDelegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.videoImgArray.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
TZVideoPictureCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"TZVideoPictureCell" forIndexPath:indexPath];
cell.imgView.image = self.videoImgArray[indexPath.item];
return cell;
}
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (!_isDraging) return;
CGFloat offsetX = scrollView.contentOffset.x;
if (offsetX - _collectionViewBeginOffsetX >= self.view.tz_width) {
[self.collectionView setContentOffset:CGPointMake(self.view.tz_width + _collectionViewBeginOffsetX, 0) animated:NO];
} else if (_collectionViewBeginOffsetX - offsetX >= self.view.tz_width) {
[self.collectionView setContentOffset:CGPointMake(_collectionViewBeginOffsetX - self.view.tz_width, 0) animated:NO];
}
[self editViewCropRectBeginChange];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
_isDraging = YES;
_collectionViewBeginOffsetX = scrollView.contentOffset.x;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
_isDraging = NO;
[self editViewCropRectEndChange];
}
#pragma mark - TZVideoEditViewDelegate
- (void)editViewCropRectBeginChange {
[self stopTimer];
[_playerLayer.player seekToTime:[self getCropStartTime] toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
NSTimeInterval second = [self getCropVideoDuration];
_cropVideoDurationLabel.text = [NSString stringWithFormat:@"已选取%.fs",second];
}
- (void)editViewCropRectEndChange {
if (_isPlayed) {
[self starTimer];
}
}
#pragma mark - Click Event
- (void)playButtonClick {
CMTime currentTime = _player.currentItem.currentTime;
CMTime durationTime = _player.currentItem.duration;
if (_player.rate == 0.0f) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"TZ_VIDEO_PLAY_NOTIFICATION" object:_player];
if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)];
_isPlayed = YES;
[self starTimer];
[_playButton setImage:nil forState:UIControlStateNormal];
} else {
_isPlayed = NO;
[self stopTimer];
[self pausePlayer];
}
}
- (void)cancelButtonClick {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)doneButtonClick {
if ([[TZImageManager manager] isAssetCannotBeSelected:_model.asset]) {
return;
}
[self stopTimer];
TZImagePickerController *imagePickerVc = self.imagePickerVc;
[imagePickerVc showProgressHUD];
[[TZImageManager manager] getVideoOutputPathWithAsset:_model.asset presetName:imagePickerVc.presetName timeRange:[self getCropVideoTimeRange] success:^(NSString *outputPath) {
[imagePickerVc hideProgressHUD];
self->_outputPath = outputPath;
[self dismissAndCallDelegateMethod];
} failure:^(NSString *errorMessage, NSError *error) {
[imagePickerVc hideProgressHUD];
self->_errorMsg = errorMessage;
[self dismissAndCallDelegateMethod];
}];
}
- (void)dismissAndCallDelegateMethod {
[self dismissViewControllerAnimated:NO completion:^{
[self callDelegateMethod];
}];
[self.imagePickerVc dismissViewControllerAnimated:YES completion:nil];
}
- (void)callDelegateMethod {
if (_outputPath) {
NSURL *videoURL = [NSURL fileURLWithPath:_outputPath];
if (self.imagePickerVc.saveEditedVideoToAlbum) {
[[TZImageManager manager] saveVideoWithUrl:videoURL completion:^(PHAsset *asset, NSError *error) {
if (error) { // 视频保存失败
if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFailToSaveEditedVideoWithError:)]) {
[self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFailToSaveEditedVideoWithError:error];
}
}
}];
}
UIImage *coverImage = [[TZImageManager manager] getImageWithVideoURL:videoURL];
if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) {
[self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFinishPickingAndEditingVideo:coverImage outputPath:_outputPath error:nil];
}
if (self.imagePickerVc.didFinishPickingAndEditingVideoHandle) {
self.imagePickerVc.didFinishPickingAndEditingVideoHandle(coverImage, _outputPath, nil);
}
} else {
if ([self.imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) {
[self.imagePickerVc.pickerDelegate imagePickerController:self.imagePickerVc didFinishPickingAndEditingVideo:nil outputPath:nil error:_errorMsg];
}
if (self.imagePickerVc.didFinishPickingAndEditingVideoHandle) {
self.imagePickerVc.didFinishPickingAndEditingVideoHandle(nil, nil, _errorMsg);
}
}
}
#pragma mark - private method
- (CMTime)getCropStartTime {
NSTimeInterval second = [self getCropVideoStartSecond];
if (second > self.model.asset.duration) {
second = roundf(self.model.asset.duration);
}
return CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale);
}
- (CMTimeRange)getCropVideoTimeRange {
NSTimeInterval startSecond = [self getCropVideoStartSecond];
CMTime start = CMTimeMakeWithSeconds(startSecond, _playerLayer.player.currentTime.timescale);
NSTimeInterval second = [self getCropVideoDuration];
CMTime duration = CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale);
return CMTimeRangeMake(start, duration);
}
- (NSTimeInterval)getCropVideoDuration {
CGFloat rectW = self.videoEditView.cropRect.size.width;
CGFloat contentW = self.videoEditView.allImgWidth;
CGFloat second = rectW / contentW * roundf(self.model.asset.duration);
return roundf(second);
}
- (NSTimeInterval)getCropVideoStartSecond {
CGFloat offsetX = self.collectionView.contentOffset.x;
CGFloat contentW = self.videoEditView.allImgWidth;
CGFloat cropRectX = self.videoEditView.cropRect.origin.x - VideoEditLeftMargin - PanImageWidth;
NSTimeInterval second = (offsetX + cropRectX) / contentW * roundf(self.model.asset.duration);
if (second < 0) second = 0;
return roundf(second);
}
- (CMTime)getTimeOfSeek {
NSTimeInterval second = [self getCropVideoStartSecond];
if (second > self.model.asset.duration) {
second = roundf(self.model.asset.duration);
}
return CMTimeMakeWithSeconds(second, _playerLayer.player.currentTime.timescale);
}
- (void)starTimer {
[self stopTimer];
NSTimeInterval timeInterval = [self getCropVideoDuration];
self.timer = [NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(playCropVideo) userInfo:nil repeats:YES];
[self.timer fire];
}
- (void)stopTimer {
if (self.timer) {
[self.videoEditView resetIndicatorLine];
[_player pause];
[self.timer invalidate];
self.timer = nil;
}
}
- (void)playCropVideo {
[_player seekToTime:[self getCropStartTime] toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero];
[_player play];
[self.videoEditView indicatorLineAnimateWithDuration:[self getCropVideoDuration] cropRect:self.videoEditView.cropRect];
}
#pragma mark - Notification Method
- (void)pausePlayer {
[_player pause];
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
}
#pragma mark - lazy
- (UIView *)iCloudErrorView{
if (!_iCloudErrorView) {
_iCloudErrorView = [[UIView alloc] initWithFrame:CGRectMake(0, [TZCommonTools tz_statusBarHeight] + 44 + 10, self.view.tz_width, 28)];
UIImageView *icloud = [[UIImageView alloc] init];
icloud.image = [UIImage tz_imageNamedFromMyBundle:@"iCloudError"];
icloud.frame = CGRectMake(20, 0, 28, 28);
[_iCloudErrorView addSubview:icloud];
UILabel *label = [[UILabel alloc] init];
label.frame = CGRectMake(53, 0, self.view.tz_width - 63, 28);
label.font = [UIFont systemFontOfSize:10];
label.textColor = [UIColor whiteColor];
label.text = [NSBundle tz_localizedStringForKey:@"iCloud sync failed"];
[_iCloudErrorView addSubview:label];
[self.view addSubview:_iCloudErrorView];
_iCloudErrorView.hidden = YES;
}
return _iCloudErrorView;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
return UIInterfaceOrientationMaskPortrait;
}
- (void)dealloc {
NSLog(@"%s",__func__);
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma clang diagnostic pop
@end
@implementation TZVideoEditView {
UILabel *_dragingLabel;
CGFloat _itemWidth;
CGFloat _beginOffsetX;
CGFloat _endOffsetX;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initSubViews];
}
return self;
}
- (void)initSubViews {
_indicatorLine = UIView.new;
_indicatorLine.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.7];
[self addSubview:_indicatorLine];
_beginImgView = UIImageView.new;
_beginImgView.image = [UIImage imageNamed:@"leftVideoEdit"];
_beginImgView.userInteractionEnabled = YES;
_beginImgView.tag = 0;
UIPanGestureRecognizer *beginPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
[_beginImgView addGestureRecognizer:beginPanGesture];
[self addSubview:_beginImgView];
_endImgView = UIImageView.new;
_endImgView.image = [UIImage imageNamed:@"rightVideoEdit"];
_endImgView.userInteractionEnabled = YES;
_endImgView.tag = 1;
UIPanGestureRecognizer *endPanGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
[_endImgView addGestureRecognizer:endPanGesture];
[self addSubview:_endImgView];
}
- (void)layoutSubviews {
_beginImgView.frame = CGRectMake(VideoEditLeftMargin, 0, PanImageWidth, self.tz_height);
_indicatorLine.frame = CGRectMake(_beginImgView.tz_right - 2, 2, 2, self.tz_height - 4);
_endImgView.frame = CGRectMake(self.tz_width - PanImageWidth - VideoEditLeftMargin, 0, PanImageWidth, self.tz_height);
self.cropRect = CGRectMake(VideoEditLeftMargin + PanImageWidth, 0, self.tz_width - VideoEditLeftMargin * 2 - PanImageWidth * 2, self.tz_height);
}
- (void)setAllImgWidth:(CGFloat)allImgWidth {
_allImgWidth = allImgWidth;
if ((NSInteger)roundf(self.videoDuration) <= 0) {
self.minCropRectWidth = allImgWidth;
return;
}
CGFloat scale = MinCropVideoDuration / self.videoDuration;
self.minCropRectWidth = scale * allImgWidth;
}
- (void)setCropRect:(CGRect)cropRect {
_cropRect = cropRect;
self.beginImgView.tz_left = cropRect.origin.x - PanImageWidth;
self.indicatorLine.tz_left = cropRect.origin.x - self.indicatorLine.tz_width;
self.endImgView.tz_left = CGRectGetMaxX(cropRect);
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, rect);
CGPoint topPoints[] = {
CGPointMake(self.cropRect.origin.x, 0),
CGPointMake(CGRectGetMaxX(self.cropRect), 0)
};
CGPoint bottomPoints[] = {
CGPointMake(self.cropRect.origin.x, self.tz_height),
CGPointMake(CGRectGetMaxX(self.cropRect), self.tz_height)
};
CGContextAddLines(context, topPoints, 2);
CGContextAddLines(context, bottomPoints, 2);
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 4.0);
CGContextDrawPath(context, kCGPathStroke);
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
CGRect beginImgViewFrame = self.beginImgView.frame;
beginImgViewFrame.origin.x -= PanImageWidth;
beginImgViewFrame.size.width += PanImageWidth * 2;
if (CGRectContainsPoint(beginImgViewFrame, point)) return self.beginImgView;
CGRect endImgViewFrame = self.endImgView.frame;
endImgViewFrame.origin.x -= PanImageWidth;
endImgViewFrame.size.width += PanImageWidth * 2;
if (CGRectContainsPoint(endImgViewFrame, point)) return self.endImgView;
return nil;
}
#pragma mark - private
- (void)indicatorLineAnimateWithDuration:(NSTimeInterval)duration cropRect:(CGRect)cropRect {
[self resetIndicatorLine];
[UIView animateWithDuration:duration delay:.0 options:UIViewAnimationOptionCurveLinear animations:^{
self.indicatorLine.tz_left = CGRectGetMaxX(cropRect);
} completion:nil];
}
- (void)resetIndicatorLine {
[self.indicatorLine.layer removeAllAnimations];
self.indicatorLine.tz_left = CGRectGetMinX(self.cropRect) - self.indicatorLine.tz_width;
}
- (void)panGestureAction:(UIGestureRecognizer *)gesture {
CGPoint point = [gesture locationInView:self];
CGRect rect = self.cropRect;
CGFloat minCropRectLeft = VideoEditLeftMargin + PanImageWidth;
switch (gesture.view.tag) {
case 0: { // 左边拖拽
CGFloat maxX = self.endImgView.tz_left - self.minCropRectWidth;
point.x = MAX(minCropRectLeft, MIN(point.x, maxX));
point.y = 0;
rect.size.width = CGRectGetMaxX(rect) - point.x;
rect.origin.x = point.x;
} break;
case 1: { // 右边拖拽
minCropRectLeft = CGRectGetMaxX(self.beginImgView.frame) + self.minCropRectWidth;
CGFloat maxX = self.tz_width - VideoEditLeftMargin - PanImageWidth;
point.x = MAX(minCropRectLeft, MIN(point.x, maxX));
point.y = 0;
rect.size.width = (point.x - rect.origin.x);
} break;
default:break;
}
self.cropRect = rect;
switch (gesture.state) {
case UIGestureRecognizerStateBegan:
case UIGestureRecognizerStateChanged: {
if (self.delegate && [self.delegate respondsToSelector:@selector(editViewCropRectBeginChange)]) {
[self.delegate editViewCropRectBeginChange];
}
} break;
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled: {
if (self.delegate && [self.delegate respondsToSelector:@selector(editViewCropRectEndChange)]) {
[self.delegate editViewCropRectEndChange];
}
} break;
default: break;
}
}
@end
@implementation TZVideoPictureCell
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self initSubViews];
}
return self;
}
- (void)initSubViews {
_imgView = [[UIImageView alloc] initWithFrame:self.bounds];
_imgView.contentMode = UIViewContentModeScaleAspectFill;
_imgView.clipsToBounds = YES;
[self.contentView addSubview:_imgView];
}
@end
//
// TZVideoEditedPreviewController.h
// TZImagePickerController
//
// Created by 肖兰月 on 2021/5/29.
// Copyright © 2021 谭真. All rights reserved.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface TZVideoEditedPreviewController : UIViewController
@property (nonatomic, copy) NSURL *videoURL;
@end
NS_ASSUME_NONNULL_END
//
// TZVideoEditedPreviewController.m
// TZImagePickerController
//
// Created by 肖兰月 on 2021/5/29.
// Copyright © 2021 谭真. All rights reserved.
//
#import "TZVideoEditedPreviewController.h"
#import <MediaPlayer/MediaPlayer.h>
#import "TZImageManager.h"
#import "TZImagePickerController.h"
#import "UIView+TZLayout.h"
@interface TZVideoEditedPreviewController () {
AVPlayer *_player;
AVPlayerLayer *_playerLayer;
UIButton *_playButton;
UIImage *_cover;
UIView *_toolBar;
UIButton *_doneButton;
UIProgressView *_progress;
UIStatusBarStyle _originStatusBarStyle;
}
@end
@implementation TZVideoEditedPreviewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
[self configMoviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:UIApplicationWillResignActiveNotification object:nil];
}
- (void)configMoviePlayer {
_player = [AVPlayer playerWithURL:self.videoURL];
_playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
[self.view.layer addSublayer:_playerLayer];
[self configPlayButton];
[self configBottomToolBar];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pausePlayerAndShowNaviBar) name:AVPlayerItemDidPlayToEndTimeNotification object:self->_player.currentItem];
}
- (void)configPlayButton {
_playButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlayHL"] forState:UIControlStateHighlighted];
[_playButton addTarget:self action:@selector(playButtonClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_playButton];
}
- (void)configBottomToolBar {
_toolBar = [[UIView alloc] initWithFrame:CGRectZero];
CGFloat rgb = 34 / 255.0;
_toolBar.backgroundColor = [UIColor colorWithRed:rgb green:rgb blue:rgb alpha:0.7];
_doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
_doneButton.titleLabel.font = [UIFont systemFontOfSize:16];
[_doneButton addTarget:self action:@selector(doneButtonClick) forControlEvents:UIControlEventTouchUpInside];
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
if (tzImagePickerVc) {
[_doneButton setTitle:tzImagePickerVc.doneBtnTitleStr forState:UIControlStateNormal];
[_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal];
} else {
[_doneButton setTitle:[NSBundle tz_localizedStringForKey:@"Done"] forState:UIControlStateNormal];
[_doneButton setTitleColor:[UIColor colorWithRed:(83/255.0) green:(179/255.0) blue:(17/255.0) alpha:1.0] forState:UIControlStateNormal];
}
[_doneButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled];
[_toolBar addSubview:_doneButton];
[self.view addSubview:_toolBar];
}
#pragma mark - Layout
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height;
CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0;
CGFloat statusBarAndNaviBarHeight = statusBarHeight + self.navigationController.navigationBar.tz_height;
CGFloat toolBarHeight = 44 + [TZCommonTools tz_safeAreaInsets].bottom;
_toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight);
_doneButton.frame = CGRectMake(self.view.tz_width - 44 - 12, 0, 44, 44);
_playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight);
_playerLayer.frame = self.view.bounds;
}
#pragma mark - Click Event
- (void)playButtonClick {
CMTime currentTime = _player.currentItem.currentTime;
CMTime durationTime = _player.currentItem.duration;
if (_player.rate == 0.0f) {
if (currentTime.value == durationTime.value) [_player.currentItem seekToTime:CMTimeMake(0, 1)];
[_player play];
_toolBar.hidden = YES;
[_playButton setImage:nil forState:UIControlStateNormal];
} else {
[self pausePlayerAndShowNaviBar];
}
}
- (void)doneButtonClick {
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - Notification Method
- (void)pausePlayerAndShowNaviBar {
[_player pause];
_toolBar.hidden = NO;
[_playButton setImage:[UIImage tz_imageNamedFromMyBundle:@"MMVideoPreviewPlay"] forState:UIControlStateNormal];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma clang diagnostic pop
@end
......@@ -13,15 +13,19 @@
#import "TZAssetModel.h"
#import "TZImagePickerController.h"
#import "TZPhotoPreviewController.h"
#import "TZVideoCropController.h"
@interface TZVideoPlayerController () {
AVPlayer *_player;
AVPlayerLayer *_playerLayer;
UIButton *_playButton;
UIImage *_cover;
NSString *_outputPath;
NSString *_errorMsg;
UIView *_toolBar;
UIButton *_doneButton;
UIButton *_editButton;
UIProgressView *_progress;
UIStatusBarStyle _originStatusBarStyle;
......@@ -71,6 +75,7 @@
if (!isDegraded && photo) {
self->_cover = photo;
self->_doneButton.enabled = YES;
self->_editButton.enabled = YES;
}
}];
[[TZImageManager manager] getVideoWithAsset:_model.asset completion:^(AVPlayerItem *playerItem, NSDictionary *info) {
......@@ -131,8 +136,21 @@
[_toolBar addSubview:_doneButton];
[self.view addSubview:_toolBar];
if (tzImagePickerVc && tzImagePickerVc.allowEditVideo && roundf(self.model.asset.duration) > 1) {
_editButton = [UIButton buttonWithType:UIButtonTypeCustom];
_editButton.titleLabel.font = [UIFont systemFontOfSize:16];
if (!_cover) {
_editButton.enabled = NO;
}
[_editButton addTarget:self action:@selector(editButtonClick) forControlEvents:UIControlEventTouchUpInside];
[_editButton setTitle:tzImagePickerVc.editBtnTitleStr forState:UIControlStateNormal];
[_editButton setTitleColor:tzImagePickerVc.oKButtonTitleColorNormal forState:UIControlStateNormal];
[_editButton setTitleColor:tzImagePickerVc.oKButtonTitleColorDisabled forState:UIControlStateDisabled];
[_toolBar addSubview:_editButton];
}
if (tzImagePickerVc.videoPreviewPageUIConfigBlock) {
tzImagePickerVc.videoPreviewPageUIConfigBlock(_playButton, _toolBar, _doneButton);
tzImagePickerVc.videoPreviewPageUIConfigBlock(_playButton, _toolBar, _editButton, _doneButton);
}
}
......@@ -148,6 +166,7 @@
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
BOOL isFullScreen = self.view.tz_height == [UIScreen mainScreen].bounds.size.height;
CGFloat statusBarHeight = isFullScreen ? [TZCommonTools tz_statusBarHeight] : 0;
......@@ -157,10 +176,11 @@
_toolBar.frame = CGRectMake(0, self.view.tz_height - toolBarHeight, self.view.tz_width, toolBarHeight);
_doneButton.frame = CGRectMake(self.view.tz_width - 44 - 12, 0, 44, 44);
_playButton.frame = CGRectMake(0, statusBarAndNaviBarHeight, self.view.tz_width, self.view.tz_height - statusBarAndNaviBarHeight - toolBarHeight);
TZImagePickerController *tzImagePickerVc = (TZImagePickerController *)self.navigationController;
if (tzImagePickerVc.allowEditVideo) {
_editButton.frame = CGRectMake(12, 0, 44, 44);
}
if (tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock) {
tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock(_playButton, _toolBar, _doneButton);
tzImagePickerVc.videoPreviewPageDidLayoutSubviewsBlock(_playButton, _toolBar, _editButton, _doneButton);
}
}
......@@ -182,33 +202,78 @@
}
}
- (void)editButtonClick {
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
TZVideoCropController *videoCropVc = [[TZVideoCropController alloc] init];
videoCropVc.model = self.model;
videoCropVc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
videoCropVc.modalPresentationStyle = UIModalPresentationFullScreen;
videoCropVc.modalPresentationCapturesStatusBarAppearance = YES;
videoCropVc.imagePickerVc = imagePickerVc;
[self presentViewController:videoCropVc animated:YES completion:nil];
}
- (void)doneButtonClick {
if ([[TZImageManager manager] isAssetCannotBeSelected:_model.asset]) {
return;
}
if (self.navigationController) {
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
if (imagePickerVc.autoDismiss) {
[self.navigationController dismissViewControllerAnimated:YES completion:^{
[self callDelegateMethod];
}];
} else {
[self callDelegateMethod];
}
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
if (imagePickerVc.allowEditVideo) {
[imagePickerVc showProgressHUD];
[[TZImageManager manager] getVideoOutputPathWithAsset:_model.asset presetName:imagePickerVc.presetName success:^(NSString *outputPath) {
[imagePickerVc hideProgressHUD];
self->_outputPath = outputPath;
[self dismissAndCallDelegateMethod];
} failure:^(NSString *errorMessage, NSError *error) {
[imagePickerVc hideProgressHUD];
self->_errorMsg = errorMessage;
[self dismissAndCallDelegateMethod];
}];
} else {
[self dismissViewControllerAnimated:YES completion:^{
[self dismissAndCallDelegateMethod];
}
}
- (void)dismissAndCallDelegateMethod {
UIViewController *vc = self.navigationController;
if (!vc) {
vc = self;
}
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
if (imagePickerVc.autoDismiss) {
[vc dismissViewControllerAnimated:YES completion:^{
[self callDelegateMethod];
}];
} else {
[self callDelegateMethod];
}
}
- (void)callDelegateMethod {
TZImagePickerController *imagePickerVc = (TZImagePickerController *)self.navigationController;
if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingVideo:sourceAssets:)]) {
[imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingVideo:_cover sourceAssets:_model.asset];
}
if (imagePickerVc.didFinishPickingVideoHandle) {
imagePickerVc.didFinishPickingVideoHandle(_cover,_model.asset);
if (imagePickerVc.allowEditVideo) {
if (_outputPath) {
if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) {
[imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingAndEditingVideo:self->_cover outputPath:self->_outputPath error:nil];
}
if (imagePickerVc.didFinishPickingAndEditingVideoHandle) {
imagePickerVc.didFinishPickingAndEditingVideoHandle(self->_cover, self->_outputPath, nil);
}
} else {
if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingAndEditingVideo:outputPath:error:)]) {
[imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingAndEditingVideo:nil outputPath:nil error:self->_errorMsg];
}
if (imagePickerVc.didFinishPickingAndEditingVideoHandle) {
imagePickerVc.didFinishPickingAndEditingVideoHandle(nil, nil, self->_errorMsg);
}
}
} else {
if ([imagePickerVc.pickerDelegate respondsToSelector:@selector(imagePickerController:didFinishPickingVideo:sourceAssets:)]) {
[imagePickerVc.pickerDelegate imagePickerController:imagePickerVc didFinishPickingVideo:_cover sourceAssets:_model.asset];
}
if (imagePickerVc.didFinishPickingVideoHandle) {
imagePickerVc.didFinishPickingVideoHandle(_cover,_model.asset);
}
}
}
......
......@@ -21,11 +21,13 @@
#import <MobileCoreServices/MobileCoreServices.h>
#import "FLAnimatedImage.h"
#import "TZImageUploadOperation.h"
#import "TZVideoEditedPreviewController.h"
@interface ViewController ()<TZImagePickerControllerDelegate,UICollectionViewDataSource,UICollectionViewDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate> {
NSMutableArray *_selectedPhotos;
NSMutableArray *_selectedAssets;
BOOL _isSelectOriginalPhoto;
BOOL _isAllowEditVideo;
CGFloat _itemWH;
CGFloat _margin;
......@@ -132,9 +134,13 @@
return _selectedPhotos.count;
}
if (!self.allowPickingMuitlpleVideoSwitch.isOn) {
for (PHAsset *asset in _selectedAssets) {
if (asset.mediaType == PHAssetMediaTypeVideo) {
return _selectedPhotos.count;
if (_isAllowEditVideo) {
return 1;
} else {
for (PHAsset *asset in _selectedAssets) {
if (asset.mediaType == PHAssetMediaTypeVideo) {
return _selectedPhotos.count;
}
}
}
}
......@@ -150,7 +156,9 @@
cell.gifLable.hidden = YES;
} else {
cell.imageView.image = _selectedPhotos[indexPath.item];
cell.asset = _selectedAssets[indexPath.item];
if (!_isAllowEditVideo) {
cell.asset = _selectedAssets[indexPath.item];
}
cell.deleteBtn.hidden = NO;
}
if (!self.allowPickingGifSwitch.isOn) {
......@@ -193,6 +201,11 @@
} else {
[self pushTZImagePickerController];
}
} else if (_isAllowEditVideo) { // preview edited video / 预览编辑后的视频
TZVideoEditedPreviewController *vc = [[TZVideoEditedPreviewController alloc] init];
vc.videoURL = _selectedAssets[indexPath.item];
vc.modalPresentationStyle = UIModalPresentationFullScreen;
[self presentViewController:vc animated:YES completion:nil];
} else { // preview photos or video / 预览照片或者视频
PHAsset *asset = _selectedAssets[indexPath.item];
BOOL isVideo = NO;
......@@ -280,6 +293,10 @@
imagePickerVc.allowTakePicture = self.showTakePhotoBtnSwitch.isOn; // 在内部显示拍照按钮
imagePickerVc.allowTakeVideo = self.showTakeVideoBtnSwitch.isOn; // 在内部显示拍视频按
imagePickerVc.videoMaximumDuration = 10; // 视频最大拍摄时间
imagePickerVc.allowEditVideo = YES; // 允许编辑视频
// imagePickerVc.saveEditedVideoToCollection = YES; // 编辑后的视频是否自动保存到相册
// imagePickerVc.maxCropVideoDuration = 30; // 裁剪视频的最大时长
// imagePickerVc.presetName = AVAssetExportPresetMediumQuality // 编辑后的视频的导出质量
[imagePickerVc setUiImagePickerControllerSettingBlock:^(UIImagePickerController *imagePickerController) {
imagePickerController.videoQuality = UIImagePickerControllerQualityTypeHigh;
}];
......@@ -633,6 +650,31 @@
// _collectionView.contentSize = CGSizeMake(0, ((_selectedPhotos.count + 2) / 3 ) * (_margin + _itemWH));
}
// If allowEditVideo is YES and allowPickingMultipleVideo is NO, When user picking a video, this callback will be called.
// If allowPickingMultipleVideo is YES, video editing is not supported, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
// 当allowEditVideo是YES且allowPickingMultipleVideo是NO是,如果用户选择了一个视频,下面的代理方法会被执行
// 如果allowPickingMultipleVideo是YES,则不支持编辑视频,将会调用imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
- (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingAndEditingVideo:(UIImage *)coverImage outputPath:(NSString *)outputPath error:(NSString *)errorMsg {
_isAllowEditVideo = YES;
self->_selectedPhotos = [NSMutableArray arrayWithArray:@[coverImage]];
self->_selectedAssets = [NSMutableArray arrayWithArray:@[[NSURL fileURLWithPath:outputPath]]];
if (outputPath) {
// NSData *data = [NSData dataWithContentsOfFile:outputPath];
NSLog(@"视频导出到本地完成,outputPath为:%@",outputPath);
// Export completed, send video here, send by outputPath or NSData
// 导出完成,在这里写上传代码,通过路径或者通过NSData上传
} else {
NSLog(@"视频导出失败:%@",errorMsg);
}
[self.collectionView reloadData];
}
// If user fail to save edited, this callback will be called.
// 如果用户保存编辑好的视频失败,将会调用
- (void)imagePickerController:(TZImagePickerController *)picker didFailToSaveEditedVideoWithError:(NSError *)error {
NSLog(@"编辑后的视频自动保存到相册失败:%@",error.description);
}
// If user picking a gif image and allowPickingMultipleVideo is NO, this callback will be called.
// If allowPickingMultipleVideo is YES, will call imagePickerController:didFinishPickingPhotos:sourceAssets:isSelectOriginalPhoto:
// 如果用户选择了一个gif图片且allowPickingMultipleVideo是NO,下面的代理方法会被执行
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment