NIMKitMediaFetcher.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. //
  2. // NIMKitPhotoFetcher.m
  3. // NIMKit
  4. //
  5. // Created by chris on 2016/11/12.
  6. // Copyright © 2016年 NetEase. All rights reserved.
  7. //
  8. #import "NIMKitMediaFetcher.h"
  9. #import <MobileCoreServices/MobileCoreServices.h>
  10. #import "NIMKitFileLocationHelper.h"
  11. #import "NIMMessageMaker.h"
  12. #import "NIMGlobalMacro.h"
  13. #import "NIMKitDependency.h"
  14. #import "TZImageManager.h"
  15. #import "NIMKitProgressHUD.h"
  16. @interface NIMKitMediaPickerController : TZImagePickerController
  17. @end
  18. @interface NIMKitMediaFetcher()<TZImagePickerControllerDelegate,UINavigationControllerDelegate,UIImagePickerControllerDelegate>
  19. @property (nonatomic,copy) NIMKitLibraryFetchResult libraryResultHandler;
  20. @property (nonatomic,copy) NIMKitCameraFetchResult cameraResultHandler;
  21. @property (nonatomic,strong) UIImagePickerController *imagePicker;
  22. @property (nonatomic,strong) NIMKitMediaPickerController *assetsPicker;
  23. @end
  24. @implementation NIMKitMediaFetcher
  25. - (instancetype)init
  26. {
  27. self = [super init];
  28. if (self) {
  29. _mediaTypes = @[(NSString *)kUTTypeMovie,(NSString *)kUTTypeImage];
  30. _limit = 9;
  31. }
  32. return self;
  33. }
  34. - (void)fetchPhotoFromLibrary:(NIMKitLibraryFetchResult)result
  35. {
  36. __weak typeof(self) weakSelf = self;
  37. [self setUpPhotoLibrary:^(NIMKitMediaPickerController * _Nullable picker) {
  38. if (picker && weakSelf) {
  39. weakSelf.assetsPicker = picker;
  40. weakSelf.libraryResultHandler = result;
  41. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:picker animated:YES completion:nil];
  42. }else{
  43. result(nil,nil,PHAssetMediaTypeUnknown);
  44. }
  45. }];
  46. }
  47. - (void)fetchMediaFromCamera:(NIMKitCameraFetchResult)result
  48. {
  49. if ([self initCamera]) {
  50. self.cameraResultHandler = result;
  51. #if TARGET_IPHONE_SIMULATOR
  52. NSAssert(0, @"not supported");
  53. #elif TARGET_OS_IPHONE
  54. self.imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
  55. self.imagePicker.videoQuality = UIImagePickerControllerQualityTypeHigh;
  56. [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:self.imagePicker animated:YES completion:nil];
  57. #endif
  58. }
  59. }
  60. - (void)setUpPhotoLibrary:(void(^)(NIMKitMediaPickerController * _Nullable picker)) handler
  61. {
  62. __weak typeof(self) weakSelf = self;
  63. [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status){
  64. dispatch_async(dispatch_get_main_queue(), ^{
  65. if (status == PHAuthorizationStatusRestricted || status == PHAuthorizationStatusDenied) {
  66. [[[UIAlertView alloc] initWithTitle:nil
  67. message:@"相册权限受限"
  68. delegate:nil
  69. cancelButtonTitle:@"确定"
  70. otherButtonTitles:nil] show];
  71. if(handler) handler(nil);
  72. }
  73. if (status == PHAuthorizationStatusAuthorized) {
  74. NIMKitMediaPickerController *vc = [[NIMKitMediaPickerController alloc] initWithMaxImagesCount:self.limit delegate:weakSelf];
  75. [LCTZImageConfigHelper setDefaultTZImageConfig:vc];
  76. vc.showSelectedIndex = YES; //显示图片序号
  77. vc.allowPickingVideo = [_mediaTypes containsObject:(NSString *)kUTTypeMovie];
  78. if(handler) handler(vc);
  79. }
  80. });
  81. }];
  82. }
  83. - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
  84. {
  85. NSString *mediaType = info[UIImagePickerControllerMediaType];
  86. if ([mediaType isEqualToString:(NSString *)kUTTypeMovie]) {
  87. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  88. NSURL *inputURL = [info objectForKey:UIImagePickerControllerMediaURL];
  89. NSString *outputFileName = [NIMKitFileLocationHelper genFilenameWithExt:@"mp4"];
  90. NSString *outputPath = [NIMKitFileLocationHelper filepathForVideo:outputFileName];
  91. AVURLAsset *asset = [AVURLAsset URLAssetWithURL:inputURL options:nil];
  92. AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:asset
  93. presetName:AVAssetExportPresetMediumQuality];
  94. session.outputURL = [NSURL fileURLWithPath:outputPath];
  95. session.outputFileType = AVFileTypeMPEG4; // 支持安卓某些机器的视频播放
  96. session.shouldOptimizeForNetworkUse = YES;
  97. session.videoComposition = [self getVideoComposition:asset]; //修正某些播放器不识别视频Rotation的问题
  98. [session exportAsynchronouslyWithCompletionHandler:^(void)
  99. {
  100. dispatch_async(dispatch_get_main_queue(), ^{
  101. if (session.status == AVAssetExportSessionStatusCompleted)
  102. {
  103. self.cameraResultHandler(outputPath,nil);
  104. }
  105. else
  106. {
  107. self.cameraResultHandler(nil,nil);
  108. }
  109. self.cameraResultHandler = nil;
  110. });
  111. }];
  112. });
  113. }else{
  114. UIImage *image = info[UIImagePickerControllerOriginalImage];
  115. self.cameraResultHandler(nil,image);
  116. self.cameraResultHandler = nil;
  117. }
  118. [picker dismissViewControllerAnimated:YES completion:nil];
  119. }
  120. #pragma mark - 相册回调
  121. - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingPhotos:(NSArray<UIImage *> *)photos sourceAssets:(NSArray *)assets isSelectOriginalPhoto:(BOOL)isSelectOriginalPhoto infos:(NSArray<NSDictionary *> *)infos
  122. {
  123. if (isSelectOriginalPhoto)
  124. {
  125. [self requestAssets:[assets mutableCopy]];
  126. }
  127. else
  128. {
  129. if (self.libraryResultHandler) {
  130. self.libraryResultHandler(photos,nil,PHAssetMediaTypeImage);
  131. }
  132. }
  133. }
  134. - (void)imagePickerController:(TZImagePickerController *)picker didFinishPickingVideo:(UIImage *)coverImage sourceAssets:(id)asset{
  135. NSMutableArray *items = [[NSMutableArray alloc] initWithArray:@[asset]];
  136. [self requestAssets:items];
  137. }
  138. - (void)requestAssets:(NSMutableArray *)assets
  139. {
  140. if (!assets.count) {
  141. return;
  142. }
  143. __weak typeof(self) weakSelf = self;
  144. [NIMKitProgressHUD show];
  145. [self requestAsset:assets.firstObject handler:^(NSString *path, PHAssetMediaType type) {
  146. [NIMKitProgressHUD dismiss];
  147. if (weakSelf.libraryResultHandler)
  148. {
  149. weakSelf.libraryResultHandler(nil,path,type);
  150. }
  151. NIMKit_Dispatch_Async_Main(^{
  152. [assets removeObjectAtIndex:0];
  153. [weakSelf requestAssets:assets];
  154. })
  155. }];
  156. }
  157. - (void)requestAsset:(PHAsset *)asset handler:(void(^)(NSString *,PHAssetMediaType)) handler
  158. {
  159. if (asset.mediaType == PHAssetMediaTypeVideo) {
  160. PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
  161. options.version = PHVideoRequestOptionsVersionCurrent;
  162. options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
  163. [[PHImageManager defaultManager] requestExportSessionForVideo:asset options:options exportPreset:AVAssetExportPresetHighestQuality resultHandler:^(AVAssetExportSession * _Nullable exportSession, NSDictionary * _Nullable info) {
  164. NSString *outputFileName = [NIMKitFileLocationHelper genFilenameWithExt:@"mp4"];
  165. NSString *outputPath = [NIMKitFileLocationHelper filepathForVideo:outputFileName];
  166. exportSession.outputURL = [NSURL fileURLWithPath:outputPath];
  167. exportSession.outputFileType = AVFileTypeMPEG4;
  168. exportSession.shouldOptimizeForNetworkUse = YES;
  169. [exportSession exportAsynchronouslyWithCompletionHandler:^(void)
  170. {
  171. dispatch_async(dispatch_get_main_queue(), ^{
  172. if (exportSession.status == AVAssetExportSessionStatusCompleted)
  173. {
  174. handler(outputPath, PHAssetMediaTypeVideo);
  175. }
  176. else
  177. {
  178. handler(nil,PHAssetMediaTypeVideo);
  179. }
  180. });
  181. }];
  182. }];
  183. }
  184. if (asset.mediaType == PHAssetMediaTypeImage)
  185. {
  186. [asset requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) {
  187. NSString *path = contentEditingInput.fullSizeImageURL.relativePath;
  188. handler(path,contentEditingInput.mediaType);
  189. }];
  190. }
  191. }
  192. #pragma mark - Private
  193. - (void)setMediaTypes:(NSArray *)mediaTypes
  194. {
  195. _mediaTypes = mediaTypes;
  196. _imagePicker.mediaTypes = mediaTypes;
  197. _assetsPicker.allowPickingVideo = [mediaTypes containsObject:(NSString *)kUTTypeMovie];
  198. }
  199. - (AVMutableVideoComposition *)getVideoComposition:(AVAsset *)asset
  200. {
  201. AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
  202. AVMutableComposition *composition = [AVMutableComposition composition];
  203. AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
  204. CGSize videoSize = videoTrack.naturalSize;
  205. BOOL isPortrait_ = [self isVideoPortrait:asset];
  206. if(isPortrait_) {
  207. videoSize = CGSizeMake(videoSize.height, videoSize.width);
  208. }
  209. composition.naturalSize = videoSize;
  210. videoComposition.renderSize = videoSize;
  211. videoComposition.frameDuration = CMTimeMakeWithSeconds( 1 / videoTrack.nominalFrameRate, 600);
  212. AVMutableCompositionTrack *compositionVideoTrack;
  213. compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
  214. [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil];
  215. AVMutableVideoCompositionLayerInstruction *layerInst;
  216. layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
  217. [layerInst setTransform:videoTrack.preferredTransform atTime:kCMTimeZero];
  218. AVMutableVideoCompositionInstruction *inst = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
  219. inst.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
  220. inst.layerInstructions = [NSArray arrayWithObject:layerInst];
  221. videoComposition.instructions = [NSArray arrayWithObject:inst];
  222. return videoComposition;
  223. }
  224. - (BOOL) isVideoPortrait:(AVAsset *)asset
  225. {
  226. BOOL isPortrait = NO;
  227. NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
  228. if([tracks count] > 0) {
  229. AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
  230. CGAffineTransform t = videoTrack.preferredTransform;
  231. // Portrait
  232. if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0)
  233. {
  234. isPortrait = YES;
  235. }
  236. // PortraitUpsideDown
  237. if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0) {
  238. isPortrait = YES;
  239. }
  240. // LandscapeRight
  241. if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0)
  242. {
  243. isPortrait = NO;
  244. }
  245. // LandscapeLeft
  246. if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0)
  247. {
  248. isPortrait = NO;
  249. }
  250. }
  251. return isPortrait;
  252. }
  253. - (BOOL)initCamera{
  254. if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
  255. [[[UIAlertView alloc] initWithTitle:nil
  256. message:@"检测不到相机设备"
  257. delegate:nil
  258. cancelButtonTitle:@"确定"
  259. otherButtonTitles:nil] show];
  260. return NO;
  261. }
  262. NSString *mediaType = AVMediaTypeVideo;
  263. AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
  264. if(authStatus == AVAuthorizationStatusRestricted || authStatus == AVAuthorizationStatusDenied){
  265. [[[UIAlertView alloc] initWithTitle:nil
  266. message:@"相机权限受限"
  267. delegate:nil
  268. cancelButtonTitle:@"确定"
  269. otherButtonTitles:nil] show];
  270. return NO;
  271. }
  272. self.imagePicker = [[UIImagePickerController alloc] init];
  273. self.imagePicker.delegate = self;
  274. self.imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
  275. self.imagePicker.mediaTypes = self.mediaTypes;
  276. return YES;
  277. }
  278. - (void)originalPhotoWithAsset:(id)asset completion:(void (^)(UIImage *photo,NSDictionary *info,BOOL isDegraded))completion {
  279. PHImageRequestOptions *option = [[PHImageRequestOptions alloc]init];
  280. option.networkAccessAllowed = YES;
  281. option.synchronous = YES;
  282. [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:option resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
  283. BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
  284. if (downloadFinined && result) {
  285. result = [self fixOrientation:result];
  286. BOOL isDegraded = [[info objectForKey:PHImageResultIsDegradedKey] boolValue];
  287. if (completion) completion(result,info,isDegraded);
  288. }
  289. }];
  290. }
  291. /// 修正图片转向
  292. - (UIImage *)fixOrientation:(UIImage *)aImage {
  293. // No-op if the orientation is already correct
  294. if (aImage.imageOrientation == UIImageOrientationUp)
  295. return aImage;
  296. // We need to calculate the proper transformation to make the image upright.
  297. // We do it in 2 steps: Rotate if Left/Right/Down, and then flip if Mirrored.
  298. CGAffineTransform transform = CGAffineTransformIdentity;
  299. switch (aImage.imageOrientation) {
  300. case UIImageOrientationDown:
  301. case UIImageOrientationDownMirrored:
  302. transform = CGAffineTransformTranslate(transform, aImage.size.width, aImage.size.height);
  303. transform = CGAffineTransformRotate(transform, M_PI);
  304. break;
  305. case UIImageOrientationLeft:
  306. case UIImageOrientationLeftMirrored:
  307. transform = CGAffineTransformTranslate(transform, aImage.size.width, 0);
  308. transform = CGAffineTransformRotate(transform, M_PI_2);
  309. break;
  310. case UIImageOrientationRight:
  311. case UIImageOrientationRightMirrored:
  312. transform = CGAffineTransformTranslate(transform, 0, aImage.size.height);
  313. transform = CGAffineTransformRotate(transform, -M_PI_2);
  314. break;
  315. default:
  316. break;
  317. }
  318. switch (aImage.imageOrientation) {
  319. case UIImageOrientationUpMirrored:
  320. case UIImageOrientationDownMirrored:
  321. transform = CGAffineTransformTranslate(transform, aImage.size.width, 0);
  322. transform = CGAffineTransformScale(transform, -1, 1);
  323. break;
  324. case UIImageOrientationLeftMirrored:
  325. case UIImageOrientationRightMirrored:
  326. transform = CGAffineTransformTranslate(transform, aImage.size.height, 0);
  327. transform = CGAffineTransformScale(transform, -1, 1);
  328. break;
  329. default:
  330. break;
  331. }
  332. // Now we draw the underlying CGImage into a new context, applying the transform
  333. // calculated above.
  334. CGContextRef ctx = CGBitmapContextCreate(NULL, aImage.size.width, aImage.size.height,
  335. CGImageGetBitsPerComponent(aImage.CGImage), 0,
  336. CGImageGetColorSpace(aImage.CGImage),
  337. CGImageGetBitmapInfo(aImage.CGImage));
  338. CGContextConcatCTM(ctx, transform);
  339. switch (aImage.imageOrientation) {
  340. case UIImageOrientationLeft:
  341. case UIImageOrientationLeftMirrored:
  342. case UIImageOrientationRight:
  343. case UIImageOrientationRightMirrored:
  344. // Grr...
  345. CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.height,aImage.size.width), aImage.CGImage);
  346. break;
  347. default:
  348. CGContextDrawImage(ctx, CGRectMake(0,0,aImage.size.width,aImage.size.height), aImage.CGImage);
  349. break;
  350. }
  351. // And now we just create a new UIImage from the drawing context
  352. CGImageRef cgimg = CGBitmapContextCreateImage(ctx);
  353. UIImage *img = [UIImage imageWithCGImage:cgimg];
  354. CGContextRelease(ctx);
  355. CGImageRelease(cgimg);
  356. return img;
  357. }
  358. #pragma clang diagnostic pop
  359. @end
  360. @implementation NIMKitMediaPickerController
  361. - (void)viewWillAppear:(BOOL)animated
  362. {
  363. [super viewWillAppear:animated];
  364. [UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
  365. }
  366. @end