| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- // Copyright (c) 2019 Tencent. All rights reserved.
- #import "UGCKitAssetLoadingController.h"
- #import <Photos/Photos.h>
- #import "UGCKitReporterInternal.h"
- #import "UGCKitMem.h"
- #import "SDKHeader.h"
- #import "UGCKitPieProgressView.h"
- @interface UGCKitAssetLoadingController () <TXVideoJoinerListener> {
- BOOL _loadingIsInterrupt;
- AssetType _assetType;
- TXVideoJoiner * _videoJoiner;
- NSString *_joinedVideoPath;
- NSMutableDictionary<NSNumber *, NSNumber *> *_progressCache; // key: loading index, value: progress
- float _prevLoadingProgress;
- }
- @property (nonatomic, strong) UGCKitPieProgressView *loadingProgressView;
- @property (nonatomic, strong) IBOutlet UILabel *loadingLabel;
- @property (nonatomic, strong) NSArray *assets;
- @property (nonatomic, strong) NSMutableArray *imagesToEdit;
- @property (nonatomic, strong) AVMutableComposition *mutableComposition;
- @property (nonatomic, strong) AVMutableVideoComposition *mutableVideoComposition;
- @property (nonatomic, strong) UGCKitTheme *theme;
- @end
- @implementation UGCKitAssetLoadingController
- - (instancetype)initWithTheme:(UGCKitTheme *)theme
- {
- if (self = [self initWithNibName:nil bundle:nil]) {
- _theme = theme;
- }
- return self;
- }
- - (void)viewDidLoad {
- [super viewDidLoad];
- _progressCache = [[NSMutableDictionary alloc] init];
- _loadingProgressView = [[UGCKitPieProgressView alloc] initWithFrame:CGRectMake(0, 0, 78, 78)];
- _loadingProgressView.tintColor = _theme.progressColor;
- [_loadingProgressView addConstraint:[NSLayoutConstraint constraintWithItem:_loadingProgressView
- attribute:NSLayoutAttributeWidth
- relatedBy:NSLayoutRelationEqual
- toItem:nil
- attribute:NSLayoutAttributeWidth
- multiplier:1
- constant:78]];
- [_loadingProgressView addConstraint:[NSLayoutConstraint constraintWithItem:_loadingProgressView
- attribute:NSLayoutAttributeHeight
- relatedBy:NSLayoutRelationEqual
- toItem:nil
- attribute:NSLayoutAttributeHeight
- multiplier:1
- constant:78]];
- _loadingProgressView.translatesAutoresizingMaskIntoConstraints = NO;
- self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
- target:self
- action:@selector(onCancel:)];
- [self.view addSubview:_loadingProgressView];
- [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_loadingProgressView
- attribute:NSLayoutAttributeCenterX
- relatedBy:NSLayoutRelationEqual
- toItem:self.view
- attribute:NSLayoutAttributeCenterX
- multiplier:1
- constant:0]];
- [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_loadingProgressView
- attribute:NSLayoutAttributeCenterY
- relatedBy:NSLayoutRelationEqual
- toItem:self.view
- attribute:NSLayoutAttributeCenterY
- multiplier:1
- constant:-70]];
- _loadingLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 20)];
- _loadingLabel.textColor = _theme.titleColor;
- _loadingLabel.textAlignment = NSTextAlignmentCenter;
- _loadingLabel.translatesAutoresizingMaskIntoConstraints = NO;
- [self.view addSubview:_loadingLabel];
- [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_loadingLabel
- attribute:NSLayoutAttributeCenterX
- relatedBy:NSLayoutRelationEqual
- toItem:self.view
- attribute:NSLayoutAttributeCenterX
- multiplier:1
- constant:0]];
- [self.view addConstraint:[NSLayoutConstraint constraintWithItem:_loadingLabel
- attribute:NSLayoutAttributeTop
- relatedBy:NSLayoutRelationEqual
- toItem:_loadingProgressView
- attribute:NSLayoutAttributeBottom
- multiplier:1
- constant:20]];
- // Do any additional setup after loading the view from its nib.
- self.title = [_theme localizedString:@"UGCKit.MediaPicker.ChoosingVideo"];
- self.view.backgroundColor = UIColor.blackColor;
- self.loadingLabel.text = [_theme localizedString:@"UGCKit.Loading.Decoding"];
- TXPreviewParam *param = [[TXPreviewParam alloc] init];
- param.videoView = [UIView new];
- _videoJoiner = [[TXVideoJoiner alloc] initWithPreview:param];
- _videoJoiner.joinerDelegate = self;
- }
- - (void)viewWillAppear:(BOOL)animated
- {
- [super viewWillAppear:animated];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(onAudioSessionEvent:)
- name:AVAudioSessionInterruptionNotification
- object:nil];
- }
- - (void)viewWillDisappear:(BOOL)animated
- {
- [super viewWillDisappear:animated];
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- - (IBAction)onCancel:(id)sender {
- _loadingIsInterrupt = YES;
- [self.navigationController popViewControllerAnimated:YES];
- // if (self.completion) {
- // self.completion([UGCKitResult cancelledResult]);
- // }
- }
- - (void) onAudioSessionEvent: (NSNotification *) notification
- {
- NSDictionary *info = notification.userInfo;
- AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue];
- if (type == AVAudioSessionInterruptionTypeBegan) {
- _loadingIsInterrupt = YES;
- [self exportAssetError:nil];
- }
- }
- - (void)didReceiveMemoryWarning {
- [super didReceiveMemoryWarning];
- // Dispose of any resources that can be recreated.
- }
- - (void)exportAssetList:(NSArray<PHAsset *> *)assets assetType:(AssetType)assetType;
- {
- _assets = assets;
- _assetType = assetType;
- if (_assetType == AssetType_Video) {
- _avAssets = [NSMutableArray array];
- }else{
- _imagesToEdit = [NSMutableArray array];
- }
- [self exportAssetInternal];
- }
- - (void)_exportVideo:(PHAsset*)expAsset index:(NSInteger)index completion:(void(^)(AVAsset *asset, NSError *error))completion {
- PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init];
- // 最高质量的视频
- options.deliveryMode = PHVideoRequestOptionsDeliveryModeHighQualityFormat;
- // 可从iCloud中获取图片
- options.networkAccessAllowed = YES;
- // 如果是iCloud的视频,可以获取到下载进度
- WEAKIFY(self);
- options.progressHandler = ^(double progress, NSError * _Nullable error, BOOL * _Nonnull stop, NSDictionary * _Nullable info) {
- STRONGIFY(self);
- if (self) {
- [self loadingCloudVideoProgress:progress index:index];
- *stop = self->_loadingIsInterrupt;
- } else {
- *stop = YES;
- }
- };
- [[PHImageManager defaultManager] requestAVAssetForVideo:expAsset options:options resultHandler:^(AVAsset * _Nullable avAsset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
- if (avAsset) {
- completion(avAsset, nil);
- } else {
- NSError *error = info[PHImageErrorKey];
- completion(nil, error);
- }
- }];
- }
- - (void)_exportPhoto:(PHAsset*)expAsset index:(NSInteger)index completion:(void(^)(UIImage *image, NSError *error))completion {
- PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
- options.version = PHImageRequestOptionsVersionCurrent;
- options.networkAccessAllowed = YES;
- options.resizeMode = PHImageRequestOptionsResizeModeExact;
- options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
- //sync requests are automatically processed this way regardless of the specified mode
- //originRequestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
- WEAKIFY(self);
- options.progressHandler = ^(double progress, NSError *__nullable error, BOOL *stop, NSDictionary *__nullable info){
- STRONGIFY(self);
- if (self) {
- [self loadingCloudVideoProgress:progress index:index];
- *stop = self->_loadingIsInterrupt;
- } else {
- *stop = YES;
- }
- };
- CGSize maximumSize = CGSizeMake(1280, 1280);
- [[PHImageManager defaultManager] requestImageForAsset:expAsset targetSize:maximumSize contentMode:PHImageContentModeDefault options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
- if (result) {
- completion(result, nil);
- } else {
- NSError *error = info[PHImageErrorKey];
- completion(nil, error);
- }
- }];
- }
- - (void)exportAssetInternal
- {
- [_progressCache removeAllObjects];
- _prevLoadingProgress = 0;
- // Initialize before async operations
- for (NSInteger i = 0; i < _assets.count; ++i) {
- _progressCache[@(i)] = @0;
- }
- __block NSError *blockError = nil;
- dispatch_group_t grp = dispatch_group_create();
- for (NSInteger i = 0; i < _assets.count; ++i) {
- PHAsset *asset = _assets[i];
- if (blockError) {
- break;
- }
- dispatch_group_enter(grp);
- if (_assetType == AssetType_Video) {
- [self _exportVideo:asset index:i completion:^(AVAsset *asset, NSError *error) {
- if (error) {
- blockError = error;
- NSLog(@"Error: %@", error);
- } else {
- @synchronized (self->_avAssets) {
- [self->_avAssets addObject:asset];
- }
- }
- dispatch_group_leave(grp);
- }];
- } else {
- [self _exportPhoto:asset index:i completion:^(UIImage *image, NSError *error) {
- if (error) {
- blockError = error;
- NSLog(@"Error: %@", error);
- } else {
- @synchronized (self->_imagesToEdit) {
- [self->_imagesToEdit addObject:image];
- }
- }
- dispatch_group_leave(grp);
- }];
- }
- }
- dispatch_group_notify(grp, dispatch_get_main_queue(), ^{
- if (blockError) {
- UGCKitResult *result = [[UGCKitResult alloc] init];
- if (blockError.code == 3072 /* PHPhotosErrorUserCancelled */) {
- result.cancelled = YES;
- } else {
- result.code = blockError.code;
- result.info = blockError.userInfo;
- }
- if (self.completion) {
- self.completion(result);
- }
- return;
- }
- if (self->_assetType == AssetType_Video) {
- if (self->_avAssets.count == 1 || !self.combineVideos){
- UGCKitMedia *media = [UGCKitMedia mediaWithAVAsset:self->_avAssets.firstObject];
- UGCKitResult *result = [[UGCKitResult alloc] init];
- result.media = media;
- if (self.completion) {
- self.completion(result);
- }
- } else {
- [self joinVideoAssets];
- }
- } else {
- UGCKitMedia *media = [UGCKitMedia mediaWithImages:self->_imagesToEdit canvasSize:CGSizeMake(720,1280)];
- UGCKitResult *result = [[UGCKitResult alloc] init];
- result.media = media;
- if (self.completion) {
- self.completion(result);
- }
- }
- });
- }
- - (void)exportAssetError:(NSError *)error
- {
- NSString *errorMessage = error ? error.localizedDescription : [_theme localizedString:@"UGCKit.MediaPicker.HintVideoExportingFailed"];
- UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:errorMessage preferredStyle:UIAlertControllerStyleAlert];
- UIAlertAction *ok = [UIAlertAction actionWithTitle:[_theme localizedString:@"UGCKit.Common.OK"] style:0 handler:^(UIAlertAction * _Nonnull action) {
- [self.navigationController dismissViewControllerAnimated:YES completion:nil];
- }];
- [alert addAction:ok];
- [self presentViewController:alert animated:YES completion:nil];
- }
- - (void)loadingCloudVideoProgress:(float)progress index:(NSInteger)index
- {
- @synchronized (_progressCache) {
- _progressCache[@(index)] = @(progress);
- }
- __block float total = 0.0;
- [_progressCache enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, NSNumber * _Nonnull obj, BOOL * _Nonnull stop) {
- total += obj.floatValue;
- }];
- total /= _progressCache.count;
- if (_prevLoadingProgress >= total) {
- return;
- }
- _prevLoadingProgress = total;
- NSString *progressText = [_theme localizedString:@"UGCKit.MediaPicker.VideoDownloadingFromiCloud"];
- dispatch_async(dispatch_get_main_queue(), ^{
- self.loadingLabel.text = progressText;
- self.loadingProgressView.progress = total;
- // self.loadingProgressView.image = [UIImage imageNamed:[NSString stringWithFormat:@"video_record_share_loading_%d", (int)(total * 8)]];
- });
- }
- - (CGSize)getVideoSize:(CGSize)sourceSize;
- {
- CGSize videoSize = CGSizeMake(sourceSize.width, sourceSize.height);
- if (videoSize.height >= videoSize.width) {
- if([self supportCompressSize:CGSizeMake(720, 1280) videoSize:videoSize]){
- videoSize = [self compress:CGSizeMake(720, 1280) videoSize:videoSize];
- }
- }else{
- if([self supportCompressSize:CGSizeMake(1280, 720) videoSize:videoSize]){
- videoSize = [self compress:CGSizeMake(1280, 720) videoSize:videoSize];
- }
- }
- return videoSize;
- }
- //判断是否需要压缩图片
- -(BOOL)supportCompressSize:(CGSize)compressSize videoSize:(CGSize)videoSize
- {
- if (videoSize.width >= compressSize.width && videoSize.height >= compressSize.height) {
- return YES;
- }
- if (videoSize.width >= compressSize.height && videoSize.height >= compressSize.width) {
- return YES;
- }
- return NO;
- }
- //获得压缩后图片大小
- - (CGSize)compress:(CGSize)compressSize videoSize:(CGSize)videoSize
- {
- CGSize size = CGSizeZero;
- if (compressSize.height / compressSize.width >= videoSize.height / videoSize.width) {
- size.width = compressSize.width;
- size.height = compressSize.width * videoSize.height / videoSize.width;
- }else{
- size.height = compressSize.height;
- size.width = compressSize.height * videoSize.width / videoSize.height;
- }
- return size;
- }
- - (UIImage*)scaleImage:(UIImage *)image scaleToSize:(CGSize)size{
- UIGraphicsBeginImageContext(size);
- [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
- UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
- return scaledImage;
- }
- #define degreesToRadians( degrees ) ( ( degrees ) / 180.0 * M_PI )
- - (void)performWithAsset:(AVAsset*)asset rotate:(CGFloat)angle
- {
- AVMutableVideoCompositionInstruction *instruction = nil;
- AVMutableVideoCompositionLayerInstruction *layerInstruction = nil;
- CGAffineTransform t1;
- CGAffineTransform t2;
-
- AVAssetTrack *assetVideoTrack = nil;
- AVAssetTrack *assetAudioTrack = nil;
- // Check if the asset contains video and audio tracks
- if ([[asset tracksWithMediaType:AVMediaTypeVideo] count] != 0) {
- assetVideoTrack = [asset tracksWithMediaType:AVMediaTypeVideo][0];
- }
- if ([[asset tracksWithMediaType:AVMediaTypeAudio] count] != 0) {
- assetAudioTrack = [asset tracksWithMediaType:AVMediaTypeAudio][0];
- }
-
- CMTime insertionPoint = kCMTimeZero;
- NSError *error = nil;
-
-
- // Step 1
- // Create a composition with the given asset and insert audio and video tracks into it from the asset
- if (!self.mutableComposition) {
-
- // Check whether a composition has already been created, i.e, some other tool has already been applied
- // Create a new composition
- self.mutableComposition = [AVMutableComposition composition];
-
- // Insert the video and audio tracks from AVAsset
- if (assetVideoTrack != nil) {
- AVMutableCompositionTrack *compositionVideoTrack = [self.mutableComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
- [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration]) ofTrack:assetVideoTrack atTime:insertionPoint error:&error];
- }
- if (assetAudioTrack != nil) {
- AVMutableCompositionTrack *compositionAudioTrack = [self.mutableComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
- [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, [asset duration]) ofTrack:assetAudioTrack atTime:insertionPoint error:&error];
- }
-
- }
-
-
- // Step 2
- // Translate the composition to compensate the movement caused by rotation (since rotation would cause it to move out of frame)
- if (angle == 90)
- {
- t1 = CGAffineTransformMakeTranslation(assetVideoTrack.naturalSize.height, 0.0);
- }else if (angle == -90){
- t1 = CGAffineTransformMakeTranslation(0.0, assetVideoTrack.naturalSize.width);
- } else {
- return;
- }
- // Rotate transformation
- t2 = CGAffineTransformRotate(t1, degreesToRadians(angle));
-
-
- // Step 3
- // Set the appropriate render sizes and rotational transforms
- if (!self.mutableVideoComposition) {
-
- // Create a new video composition
- self.mutableVideoComposition = [AVMutableVideoComposition videoComposition];
- self.mutableVideoComposition.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height,assetVideoTrack.naturalSize.width);
- self.mutableVideoComposition.frameDuration = CMTimeMake(1, 30);
-
- // The rotate transform is set on a layer instruction
- instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
- instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [self.mutableComposition duration]);
- layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:(self.mutableComposition.tracks)[0]];
- [layerInstruction setTransform:t2 atTime:kCMTimeZero];
-
- } else {
-
- self.mutableVideoComposition.renderSize = CGSizeMake(self.mutableVideoComposition.renderSize.height, self.mutableVideoComposition.renderSize.width);
-
- // Extract the existing layer instruction on the mutableVideoComposition
- instruction = (AVMutableVideoCompositionInstruction*)(self.mutableVideoComposition.instructions)[0];
- layerInstruction = (AVMutableVideoCompositionLayerInstruction*)(instruction.layerInstructions)[0];
-
- // Check if a transform already exists on this layer instruction, this is done to add the current transform on top of previous edits
- CGAffineTransform existingTransform;
-
- if (![layerInstruction getTransformRampForTime:[self.mutableComposition duration] startTransform:&existingTransform endTransform:NULL timeRange:NULL]) {
- [layerInstruction setTransform:t2 atTime:kCMTimeZero];
- } else {
- // Note: the point of origin for rotation is the upper left corner of the composition, t3 is to compensate for origin
- CGAffineTransform t3 = CGAffineTransformMakeTranslation(-1*assetVideoTrack.naturalSize.height/2, 0.0);
- CGAffineTransform newTransform = CGAffineTransformConcat(existingTransform, CGAffineTransformConcat(t2, t3));
- [layerInstruction setTransform:newTransform atTime:kCMTimeZero];
- }
-
- }
-
-
- // Step 4
- // Add the transform instructions to the video composition
- instruction.layerInstructions = @[layerInstruction];
- self.mutableVideoComposition.instructions = @[instruction];
-
-
- // Step 5
- // Notify AVSEViewController about rotation operation completion
- // [[NSNotificationCenter defaultCenter] postNotificationName:AVSEEditCommandCompletionNotification object:self];
- }
- - (void)joinVideoAssets {
- _joinedVideoPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"outputJoin.mp4"];
- int ret = 0;
- NSArray *videoAssets = _avAssets;
- if ([videoAssets.firstObject isKindOfClass:[NSString class]]) {
- ret = [_videoJoiner setVideoPathList:videoAssets];
- } else {
- ret = [_videoJoiner setVideoAssetList:videoAssets];
- }
- _videoJoiner.joinerDelegate = self;
- if (ret == 0) {
- [_videoJoiner joinVideo:VIDEO_COMPRESSED_720P videoOutputPath:_joinedVideoPath];
- _loadingLabel.text = [_theme localizedString:@"UGCKit.Media.VideoSynthesizing"];
- self.loadingProgressView.progress = 0;
- // self.loadingProgressView.image = [UIImage imageNamed:[NSString stringWithFormat:@"video_record_share_loading_%d", 0]];
- } else {
- if (self.completion) {
- UGCKitResult *result = [[UGCKitResult alloc] init];
- result.code = ret;
- result.info = @{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Join failed: %d", ret]};
- self.completion(result);
- }
- }
- }
- #pragma mark TXVideoJoinerListener
- -(void) onJoinProgress:(float)progress
- {
- self.loadingProgressView.progress = progress;
- }
- -(void) onJoinComplete:(TXJoinerResult *)joinResult
- {
- if (joinResult.retCode == JOINER_RESULT_OK) {
- UGCKitResult *result = [[UGCKitResult alloc] init];
- result.media = [UGCKitMedia mediaWithVideoPath: _joinedVideoPath];
- if (self.completion) {
- self.completion(result);
- }
- } else {
- UGCKitResult *result = [[UGCKitResult alloc] init];
- result.code = joinResult.retCode;
- result.info = @{NSLocalizedDescriptionKey: joinResult.descMsg};
- if (self.completion) {
- self.completion(result);
- }
- }
- [UGCKitReporter report:UGCKitReportItem_videojoiner userName:nil code:joinResult.retCode msg:joinResult.descMsg];
- }
- @end
|