TXVideoLoadingController.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. //
  2. // TXVideoLoadingController.m
  3. // TCLVBIMDemo
  4. //
  5. // Created by annidyfeng on 2017/4/17.
  6. // Copyright © 2017年 tencent. All rights reserved.
  7. //
  8. #import "TXVideoLoadingController.h"
  9. //#import <QBImagePickerController/QBImagePickerController.h>
  10. #import "QBImagePickerController.h"
  11. #import "TCVideoCutViewController.h"
  12. #import "TCVideoJoinViewController.h"
  13. #import <Photos/Photos.h>
  14. @interface TXVideoLoadingController ()
  15. @property IBOutlet UIImageView *loadingImageView;
  16. @property AVAssetExportSession *exportSession;
  17. @property NSTimer *timer;
  18. @property NSMutableArray *localPaths;
  19. @property NSArray *videosAssets;
  20. @property NSMutableArray *videosToEditAssets;
  21. @property NSUInteger exportIndex;
  22. @property AVMutableComposition *mutableComposition;
  23. @property AVMutableVideoComposition *mutableVideoComposition;
  24. @end
  25. @implementation TXVideoLoadingController
  26. {
  27. BOOL _loadingIsInterrupt;
  28. }
  29. - (void)viewDidLoad {
  30. [super viewDidLoad];
  31. // Do any additional setup after loading the view from its nib.
  32. self.title = NSLocalizedString(@"TCVideoLoading.ChoosingVideo", nil);
  33. self.view.backgroundColor = UIColor.blackColor;
  34. }
  35. - (void)viewWillAppear:(BOOL)animated
  36. {
  37. [[NSNotificationCenter defaultCenter] addObserver:self
  38. selector:@selector(onAudioSessionEvent:)
  39. name:AVAudioSessionInterruptionNotification
  40. object:nil];
  41. }
  42. - (void)viewWillDisappear:(BOOL)animated
  43. {
  44. [[NSNotificationCenter defaultCenter] removeObserver:self];
  45. }
  46. - (void) onAudioSessionEvent: (NSNotification *) notification
  47. {
  48. NSDictionary *info = notification.userInfo;
  49. AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
  50. if (type == AVAudioSessionInterruptionTypeBegan) {
  51. _loadingIsInterrupt = YES;
  52. [self exportAssetError];
  53. }
  54. }
  55. - (void)didReceiveMemoryWarning {
  56. [super didReceiveMemoryWarning];
  57. // Dispose of any resources that can be recreated.
  58. }
  59. - (void)exportAssetList:(NSArray *)videosAssets
  60. {
  61. _videosAssets = videosAssets;
  62. _exportIndex = 0;
  63. _localPaths = [NSMutableArray new];
  64. self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateProgress) userInfo:nil repeats:YES];
  65. _videosToEditAssets = [NSMutableArray array];
  66. [self exportAssetInternal];
  67. }
  68. - (void)exportAssetInternal
  69. {
  70. if (_exportIndex == _videosAssets.count) {
  71. [self.timer invalidate],
  72. self.timer = nil;
  73. if (!self.composeMode) {
  74. TCVideoCutViewController *vc = [TCVideoCutViewController new];
  75. //vc.videoPath = _localPaths[0];
  76. vc.videoAsset = _videosToEditAssets[0];
  77. if(!_loadingIsInterrupt) [self.navigationController pushViewController:vc animated:YES];
  78. return;
  79. } else {
  80. TCVideoJoinViewController *vc = [TCVideoJoinViewController new];
  81. // vc.videoList = _localPaths;
  82. vc.videoAssertList = _videosToEditAssets;
  83. if(!_loadingIsInterrupt) [self.navigationController pushViewController:vc animated:YES];
  84. return;
  85. }
  86. }
  87. self.mutableComposition = nil;
  88. self.mutableVideoComposition = nil;
  89. //__weak typeof(self) weakSelf = self;
  90. // __block AVAssetExportSession *weakExportSession = _exportSession;
  91. PHAsset *expAsset = _videosAssets[_exportIndex];
  92. [[PHImageManager defaultManager] requestAVAssetForVideo:expAsset options:nil resultHandler:^(AVAsset * _Nullable avAsset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
  93. //SDK内部通过avAsset 读取视频数据,会极大的降低视频loading时间
  94. // LBVideoOrientation or = avAsset.videoOrientation;
  95. // if (or == LBVideoOrientationUp || or == LBVideoOrientationDown) {
  96. // CGFloat angle = 0;
  97. // if (or == LBVideoOrientationUp) angle = 90.0;
  98. // if (or == LBVideoOrientationDown) angle = -90.0;
  99. // [self performWithAsset:avAsset rotate:angle];
  100. // weakExportSession = [[AVAssetExportSession alloc] initWithAsset:self.mutableComposition presetName:AVAssetExportPresetHighestQuality];
  101. // weakExportSession.videoComposition = self.mutableVideoComposition;
  102. // } else {
  103. // weakExportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetHighestQuality];
  104. // }
  105. //
  106. //
  107. // NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES);
  108. // NSString *documentsDirectory = [paths objectAtIndex:0];
  109. // NSString* videoPath = [documentsDirectory stringByAppendingPathComponent:[expAsset orignalFilename]];
  110. // NSFileManager *manager = [NSFileManager defaultManager];
  111. //
  112. // NSError *error;
  113. // if ([manager fileExistsAtPath:videoPath]) {
  114. // BOOL success = [manager removeItemAtPath:videoPath error:&error];
  115. // if (success) {
  116. // NSLog(@"Already exist. Removed!");
  117. // }
  118. // }
  119. // [self.localPaths addObject:videoPath];
  120. //
  121. // NSURL *outputURL = [NSURL fileURLWithPath:videoPath];
  122. // weakExportSession.outputURL = outputURL;
  123. // weakExportSession.outputFileType = AVFileTypeMPEG4;
  124. // [weakExportSession exportAsynchronouslyWithCompletionHandler:^{
  125. // if(weakExportSession.status == AVAssetExportSessionStatusCompleted){
  126. dispatch_async(dispatch_get_main_queue(), ^(void) {
  127. [_videosToEditAssets addObject:avAsset];
  128. _exportIndex++;
  129. [self exportAssetInternal];
  130. });
  131. // }else{
  132. // dispatch_async(dispatch_get_main_queue(), ^(void) {
  133. // [weakSelf exportAssetError];
  134. // });
  135. // }
  136. // }];
  137. //
  138. // _exportSession = weakExportSession;
  139. }];
  140. }
  141. - (void)exportAssetError
  142. {
  143. UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:NSLocalizedString(@"TCVideoLoading.HintVideoExportingFailed", nil) preferredStyle:UIAlertControllerStyleAlert];
  144. UIAlertAction *ok = [UIAlertAction actionWithTitle:NSLocalizedString(@"Common.OK", nil) style:0 handler:^(UIAlertAction * _Nonnull action) {
  145. [self.navigationController dismissViewControllerAnimated:YES completion:nil];
  146. }];
  147. [alert addAction:ok];
  148. [self presentViewController:alert animated:YES completion:nil];
  149. }
  150. - (void)updateProgress
  151. {
  152. dispatch_async(dispatch_get_main_queue(), ^{
  153. CGFloat progress = _exportSession.progress;
  154. if (_exportSession.status == AVAssetExportSessionStatusFailed ||
  155. _exportSession.status == AVAssetExportSessionStatusCompleted) {
  156. progress = 1;
  157. }
  158. CGFloat allp = (progress + _exportIndex)/_videosAssets.count;
  159. self.loadingImageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"video_record_share_loading_%d", (int)(allp * 8)]];
  160. });
  161. }
  162. #define degreesToRadians( degrees ) ( ( degrees ) / 180.0 * M_PI )
  163. - (void)performWithAsset:(AVAsset*)asset rotate:(CGFloat)angle
  164. {
  165. AVMutableVideoCompositionInstruction *instruction = nil;
  166. AVMutableVideoCompositionLayerInstruction *layerInstruction = nil;
  167. CGAffineTransform t1;
  168. CGAffineTransform t2;
  169. AVAssetTrack *assetVideoTrack = nil;
  170. AVAssetTrack *assetAudioTrack = nil;
  171. // Check if the asset contains video and audio tracks
  172. if ([[asset tracksWithMediaType:AVMediaTypeVideo] count] != 0) {
  173. assetVideoTrack = [asset tracksWithMediaType:AVMediaTypeVideo][0];
  174. }
  175. if ([[asset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
  176. assetAudioTrack = [asset tracksWithMediaType:AVMediaTypeAudio][0];
  177. }
  178. CMTime insertionPoint = kCMTimeZero;
  179. NSError *error = nil;
  180. // Step 1
  181. // Create a composition with the given asset and insert audio and video tracks into it from the asset
  182. if (!self.mutableComposition) {
  183. // Check whether a composition has already been created, i.e, some other tool has already been applied
  184. // Create a new composition
  185. self.mutableComposition = [AVMutableComposition composition];
  186. // Insert the video and audio tracks from AVAsset
  187. if (assetVideoTrack != nil) {
  188. AVMutableCompositionTrack *compositionVideoTrack = [self.mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
  189. [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration]) ofTrack:assetVideoTrack atTime:insertionPoint error:&error];
  190. }
  191. if (assetAudioTrack != nil) {
  192. AVMutableCompositionTrack *compositionAudioTrack = [self.mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
  193. [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration]) ofTrack:assetAudioTrack atTime:insertionPoint error:&error];
  194. }
  195. }
  196. // Step 2
  197. // Translate the composition to compensate the movement caused by rotation (since rotation would cause it to move out of frame)
  198. if (angle == 90)
  199. {
  200. t1 = CGAffineTransformMakeTranslation(assetVideoTrack.naturalSize.height, 0.0);
  201. }else if (angle == -90){
  202. t1 = CGAffineTransformMakeTranslation(0.0, assetVideoTrack.naturalSize.width);
  203. } else {
  204. return;
  205. }
  206. // Rotate transformation
  207. t2 = CGAffineTransformRotate(t1, degreesToRadians(angle));
  208. // Step 3
  209. // Set the appropriate render sizes and rotational transforms
  210. if (!self.mutableVideoComposition) {
  211. // Create a new video composition
  212. self.mutableVideoComposition = [AVMutableVideoComposition videoComposition];
  213. self.mutableVideoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
  214. self.mutableVideoComposition.frameDuration = CMTimeMake(1, 30);
  215. // The rotate transform is set on a layer instruction
  216. instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
  217. instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [self.mutableComposition duration]);
  218. layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:(self.mutableComposition.tracks)[0]];
  219. [layerInstruction setTransform:t2 atTime:kCMTimeZero];
  220. } else {
  221. self.mutableVideoComposition.renderSize = CGSizeMake(self.mutableVideoComposition.renderSize.height, self.mutableVideoComposition.renderSize.width);
  222. // Extract the existing layer instruction on the mutableVideoComposition
  223. instruction = (self.mutableVideoComposition.instructions)[0];
  224. layerInstruction = (instruction.layerInstructions)[0];
  225. // Check if a transform already exists on this layer instruction, this is done to add the current transform on top of previous edits
  226. CGAffineTransform existingTransform;
  227. if (![layerInstruction getTransformRampForTime:[self.mutableComposition duration] startTransform:&existingTransform endTransform:NULL timeRange:NULL]) {
  228. [layerInstruction setTransform:t2 atTime:kCMTimeZero];
  229. } else {
  230. // Note: the point of origin for rotation is the upper left corner of the composition, t3 is to compensate for origin
  231. CGAffineTransform t3 = CGAffineTransformMakeTranslation(-1*assetVideoTrack.naturalSize.height/2, 0.0);
  232. CGAffineTransform newTransform = CGAffineTransformConcat(existingTransform, CGAffineTransformConcat(t2, t3));
  233. [layerInstruction setTransform:newTransform atTime:kCMTimeZero];
  234. }
  235. }
  236. // Step 4
  237. // Add the transform instructions to the video composition
  238. instruction.layerInstructions = @[layerInstruction];
  239. self.mutableVideoComposition.instructions = @[instruction];
  240. // Step 5
  241. // Notify AVSEViewController about rotation operation completion
  242. // [[NSNotificationCenter defaultCenter] postNotificationName:AVSEEditCommandCompletionNotification object:self];
  243. }
  244. @end
  245. @implementation PHAsset (My)
  246. - (NSString *)orignalFilename {
  247. NSString *filename;
  248. if ([[PHAssetResource class] instancesRespondToSelector:@selector(assetResourcesForAsset:)]) {
  249. NSArray *resources = [PHAssetResource assetResourcesForAsset:self];
  250. PHAssetResource *resource = resources.firstObject;
  251. if (resources) {
  252. filename = resource.originalFilename;
  253. }
  254. }
  255. if (filename == nil) {
  256. filename = [self valueForKey:@"filename"];
  257. if (filename == nil ||
  258. ![filename isKindOfClass:[NSString class]]) {
  259. filename = [NSString stringWithFormat:@"temp%ld", time(NULL)];
  260. }
  261. }
  262. return filename;
  263. }
  264. @end
  265. //static inline CGFloat RadiansToDegrees(CGFloat radians) {
  266. // return radians * 180 / M_PI;
  267. //};
  268. @implementation AVAsset (My)
  269. @dynamic videoOrientation;
  270. - (LBVideoOrientation)videoOrientation
  271. {
  272. NSArray *videoTracks = [self tracksWithMediaType:AVMediaTypeVideo];
  273. if ([videoTracks count] == 0) {
  274. return LBVideoOrientationNotFound;
  275. }
  276. AVAssetTrack* videoTrack = [videoTracks objectAtIndex:0];
  277. CGAffineTransform txf = [videoTrack preferredTransform];
  278. CGFloat videoAngleInDegree = RadiansToDegrees(atan2(txf.b, txf.a));
  279. LBVideoOrientation orientation = 0;
  280. switch ((int)videoAngleInDegree) {
  281. case 0:
  282. orientation = LBVideoOrientationRight;
  283. break;
  284. case 90:
  285. orientation = LBVideoOrientationUp;
  286. break;
  287. case 180:
  288. orientation = LBVideoOrientationLeft;
  289. break;
  290. case -90:
  291. orientation = LBVideoOrientationDown;
  292. break;
  293. default:
  294. orientation = LBVideoOrientationNotFound;
  295. break;
  296. }
  297. return orientation;
  298. }
  299. @end