// _____ _
// |_ _| |_ _ _ ___ ___ _ __ __ _
// | | | ' \| '_/ -_) -_) ' \/ _` |_
// |_| |_||_|_| \___\___|_|_|_\__,_(_)
//
// Threema iOS Client
// Copyright (c) 2015-2020 Threema GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License, version 3,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#import "SendMediaAction.h"
#import
#import "UserSettings.h"
#import "AppDelegate.h"
#import "Threema-Swift.h"
#import
#import "ModalPresenter.h"
#import "UIDefines.h"
#import "FileMessageSender.h"
#import "MBProgressHUD.h"
#import "BundleUtil.h"
#import "UTIConverter.h"
#import "MediaConverter.h"
#import "MessageSender.h"
#ifdef DEBUG
static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
static const DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif
@interface SendMediaAction ()
@property UIImagePickerController *picker;
@property BOOL pickedVideoSent;
@property BOOL pickedVideoSaved;
@property BOOL cancelled;
@property NSURL *pickedVideoURL;
@property NSMutableSet *videoEncoders;
@property NSMutableSet *fileMessageSenders;
@property MBProgressHUD *videoEncodeProgressHUD;
@property PhotosAccessHelper *helper;
@property NSTimer *sequentialSendTimer;
@property dispatch_semaphore_t sequentialSema;
@end
@implementation SendMediaAction
- (instancetype)init
{
self = [super init];
if (self) {
self.videoEncoders = [NSMutableSet set];
self.fileMessageSenders = [NSMutableSet set];
}
return self;
}
- (NSArray *)diffSelection:(NSArray *)assets fromPreviouslySelected:(NSArray *)prevSelected {
if (prevSelected == nil) {
return assets;
}
NSMutableArray *urlItems = [[NSMutableArray alloc] init];
for (int i = 0; i < prevSelected.count; i++) {
if ([MediaPreviewViewController isURLItemWithItem:prevSelected[i]]) {
[urlItems addObject:prevSelected[i]];
}
}
long selectionCount = assets.count + urlItems.count;
NSMutableArray *selection = [[NSMutableArray alloc] initWithCapacity:selectionCount];
for (int i = 0; i < urlItems.count; i++) {
[selection insertObject:urlItems[i] atIndex:i];
}
for (long i = 0; i < assets.count; i++) {
long found = [MediaPreviewViewController containsWithAsset:assets[i] itemList:prevSelected];
if (found == -1) {
[selection insertObject:assets[i] atIndex:i + urlItems.count];
} else {
[selection insertObject:prevSelected[found] atIndex:i + urlItems.count];
}
}
return selection;
}
- (void)executeAction {
if (_mediaPickerType == MediaPickerChooseExisting) {
[self showPhotoPicker:nil];
} else if (_mediaPickerType == MediaPickerTakePhoto) {
_picker = [[UIImagePickerController alloc] init];
_picker.delegate = self;
if (_mediaPickerType == MediaPickerChooseExisting)
_picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
else
_picker.sourceType = UIImagePickerControllerSourceTypeCamera;
NSMutableArray *myMediaTypes = [NSMutableArray array];
NSArray *availableMediaTypes = [UIImagePickerController availableMediaTypesForSourceType:_picker.sourceType];
if ([availableMediaTypes containsObject:(NSString *)kUTTypeImage]) {
[myMediaTypes addObject:(NSString *)kUTTypeImage];
}
if ([availableMediaTypes containsObject:(NSString *)kUTTypeMovie]) {
[myMediaTypes addObject:(NSString *)kUTTypeMovie];
}
_picker.mediaTypes = myMediaTypes;
_picker.videoMaximumDuration = [MediaConverter videoMaxDurationAtCurrentQuality];
/* Always request high quality from UIImagePickerController, and transcode by ourselves later */
_picker.videoQuality = UIImagePickerControllerQualityTypeHigh;
UIImagePickerControllerCameraFlashMode flashmode = UIImagePickerControllerCameraFlashModeAuto;
if ([[[[AppGroup userDefaults] dictionaryRepresentation] allKeys] containsObject:@"cameraFlashMode"]) {
flashmode = (UIImagePickerControllerCameraFlashMode) [[AppGroup userDefaults] integerForKey:@"cameraFlashMode"];
}
_picker.cameraFlashMode = flashmode;
CGRect rect = [self.chatViewController.view convertRect:self.chatViewController.chatBar.addButton.frame fromView:self.chatViewController.chatBar];
[ModalPresenter present:_picker on:self.chatViewController fromRect:rect inView:self.chatViewController.view completion:nil];
} else {
DDLogError(@"Invalid MediaPickerType");
}
}
- (void)showPhotoPicker:(NSArray *)lastSelection {
__block NSArray *prevSelected = [[NSArray alloc] init];
int limit = 50;
_helper = [[PhotosAccessHelper alloc] initWithCompletion:^(NSArray * _Nonnull assets, DKImagePickerController * pickerController) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MediaShareStoryboard" bundle:nil];
MediaPreviewViewController *selectionViewController = [storyboard instantiateViewControllerWithIdentifier:@"MediaShareController"];
if (assets.count > 0) {
if ([assets.firstObject isKindOfClass:DKAsset.class]) {
UIViewController *dismissable = self.chatViewController.presentedViewController.presentingViewController;
NSArray *initialSelection = [self diffSelection:assets fromPreviouslySelected:prevSelected];
[selectionViewController initWithMediaWithDataArray:initialSelection delegate:self completion:^(NSArray *selection, BOOL sendAsFile, NSArray *captions) {
dispatch_async(dispatch_get_main_queue(), ^{
[dismissable dismissViewControllerAnimated:true completion:nil];
});
[self sendAssets:selection asFile:sendAsFile withCaptions: captions];
} returnToMe: ^(NSArray *defaultSelection, NSArray *prevSelection) {
dispatch_async(dispatch_get_main_queue(), ^{
[selectionViewController dismissViewControllerAnimated:true completion:nil];
prevSelected = [NSMutableArray arrayWithArray:prevSelection];
[pickerController setDefaultSelectedAssets:defaultSelection];
[((ThreemaImagePickerControllerDefaultUIDelegate *) pickerController.UIDelegate) updateButton];
});
} addMore: ^(NSArray *defaultSelection, NSArray *prevSelection) {
dispatch_async(dispatch_get_main_queue(), ^{
[selectionViewController dismissViewControllerAnimated:true completion:nil];
prevSelected = [NSMutableArray arrayWithArray:prevSelection];
[pickerController setDefaultSelectedAssets:defaultSelection];
[((ThreemaImagePickerControllerDefaultUIDelegate *) pickerController.UIDelegate) updateButton];
});
}];
dispatch_async(dispatch_get_main_queue(), ^{
selectionViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self.chatViewController.presentedViewController presentViewController:selectionViewController animated:YES completion:nil];
});
} else {
if (lastSelection != nil) {
assets = [lastSelection arrayByAddingObjectsFromArray:assets];
}
[selectionViewController initWithMediaWithDataArray:assets delegate:self completion:^(NSArray *selection, BOOL sendAsFile, NSArray *captions) {
dispatch_async(dispatch_get_main_queue(), ^{
[selectionViewController dismissViewControllerAnimated:true completion:nil];
});
[self sendAssets:selection asFile:sendAsFile withCaptions: captions];
} returnToMe: ^(__unused NSArray *defaultSelection, NSArray *prevSelection) {
dispatch_async(dispatch_get_main_queue(), ^{
[selectionViewController dismissViewControllerAnimated:true completion:nil];
});
[self clearTemporaryDirectoryItems:prevSelection];
} addMore: ^(__unused NSArray *defaultSelection, NSArray *prevSelection) {
dispatch_async(dispatch_get_main_queue(), ^{
prevSelected = [NSMutableArray arrayWithArray:prevSelection];
_helper = [[PhotosAccessHelper alloc] initWithCompletion:^(NSArray * _Nonnull defaultAssets, __unused DKImagePickerController *returnedPickerController) {
NSArray *newAssets = [prevSelection arrayByAddingObjectsFromArray:defaultAssets];
[selectionViewController resetMediaToDataArray:newAssets reloadData:true];
}];
[_helper showPickerWithViewController:selectionViewController limit:50-prevSelection.count];
});
}];
dispatch_async(dispatch_get_main_queue(), ^{
[selectionViewController setBackIsCancel:true];
[self.chatViewController presentViewController:selectionViewController animated:YES completion:nil];
});
}
}
}];
dispatch_async(dispatch_get_main_queue(), ^{
[_helper showPickerWithViewController:self.chatViewController limit:limit];
});
}
- (DKImagePickerController *)setupDKImagePickerController {
DKImagePickerController *pickerController = [DKImagePickerController new];
pickerController.assetType = DKImagePickerControllerAssetTypeAllAssets;
pickerController.showsCancelButton = YES;
pickerController.showsEmptyAlbums = NO;
pickerController.allowMultipleTypes = YES;
pickerController.autoDownloadWhenAssetIsInCloud = YES;
pickerController.defaultSelectedAssets = @[];
pickerController.sourceType = DKImagePickerControllerSourceTypePhoto;
pickerController.maxSelectableCount = 50;
pickerController.UIDelegate = [[ThreemaImagePickerControllerDefaultUIDelegate alloc] init];
pickerController.allowsLandscape = YES;
return pickerController;
}
- (void)showAssetPickerWithAssets:(NSArray *)assets {
DKImagePickerController *pickerController = [self setupDKImagePickerController];
[self.chatViewController presentViewController:pickerController animated:YES completion:nil];
}
- (void)showPreviewForAssets:(NSArray *)assets {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MediaShareStoryboard" bundle:nil];
MediaPreviewViewController *selectionViewController = [storyboard instantiateViewControllerWithIdentifier:@"MediaShareController"];
selectionViewController.backIsCancel = true;
[selectionViewController initWithMediaWithDataArray:assets delegate:self completion:^(NSArray *selection, BOOL sendAsFile, NSArray *captions) {
[selectionViewController dismissViewControllerAnimated:true completion:nil];
[self sendAssets:selection asFile:sendAsFile withCaptions: captions];
} returnToMe: ^(__unused NSArray *defaultSelection, NSArray *prevSelection) {
dispatch_async(dispatch_get_main_queue(), ^{
[selectionViewController dismissViewControllerAnimated:true completion:nil];
});
[self clearTemporaryDirectoryItems:prevSelection];
} addMore: ^(NSArray *defaultSelection, NSArray *prevSelection) {
DKImagePickerController *pickerController = [self setupDKImagePickerController];
pickerController.defaultSelectedAssets = defaultSelection;
[selectionViewController presentViewController:pickerController animated:YES completion:nil];
__weak typeof(pickerController) weakPickerController = pickerController;
[pickerController setDidSelectAssets:^(NSArray * __nonnull selectedAssetes) {
[weakPickerController dismissViewControllerAnimated:true completion:nil];
NSArray *selection = [self diffSelection:selectedAssetes fromPreviouslySelected:prevSelection];
[selectionViewController resetMediaToDataArray:selection reloadData:true];
}];
}];
[self.chatViewController presentViewController:selectionViewController animated:YES completion:nil];
}
- (void)prepareAssets:(NSArray *)assets withTarget:(NSMutableArray *)itemArray asFile:(bool) sendAsFile {
for (int i = 0; i < assets.count; i++) {
@autoreleasepool {
if ([assets[i] isKindOfClass:PHAsset.class]) {
PHAsset *asset = assets[i];
if (asset.mediaType == PHAssetMediaTypeImage) {
URLSenderItem *item = [self getSenderItemForImageAsset:asset asFile:sendAsFile];
[itemArray addObject:item];
} else {
URLSenderItem *item = [self getSenderItemForVideoAsset:asset asFile:sendAsFile];
[itemArray addObject:item];
}
} else {
NSURL *url = assets[i];
NSString *uti = [UTIConverter utiForFileURL:url];
if ([UTIConverter conformsToImageType:uti] || [UTIConverter conformsToMovieType:uti]) {
URLSenderItem *item = [URLSenderItemCreator getSenderItemFor:url];
if (item != nil) {
[itemArray addObject:item];
} else {
DDLogError(@"Could not create URLSenderItem from media asset");
}
} else {
DDLogError(@"Unrecognized media asset");
}
}
dispatch_async(dispatch_get_main_queue(), ^{
NSString *text = NSLocalizedString(@"processing_items_progress", nil);
[self incrementVideoProgressHUDWithText:text placeholderIncluded:true];
});
}
}
}
- (void)sendItems:(NSMutableArray *)itemArray asFile:(bool)sendAsFile withCaptions:(NSArray *)captions {
long k = itemArray.count;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
_sequentialSema = dispatch_semaphore_create(1);
NSString *correlationID = [ImageURLSenderItemCreator createCorrelationID];
for (long i = 0; i < k; i++) {
@autoreleasepool {
dispatch_semaphore_wait(_sequentialSema, DISPATCH_TIME_FOREVER);
FileMessageSender *sender = [[FileMessageSender alloc] init];
[self.fileMessageSenders addObject:sender];
if (self.cancelled) {
dispatch_async(dispatch_get_main_queue(), ^{
[self hideVideoEncodeProgressHUD];
});
return;
}
if ([itemArray[i] isKindOfClass:[URLSenderItem class]]) {
URLSenderItem *item = itemArray[i];
if (![captions[i] isEqualToString:@""]) {
item.caption = captions[i];
}
sender.uploadProgressDelegate = self;
[sender sendItem:item
inConversation:self.chatViewController.conversation
requestId:nil
correlationId:correlationID];
} else {
// Video
AVAsset *item = itemArray[i];
[self sendVideoAsset:item onCompletion:^{
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
_sequentialSendTimer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(checkVideoDone)
userInfo:nil
repeats:YES];
if (![captions[i] isEqualToString:@""]) {
[MessageSender sendMessage:captions[i]
inConversation:self.chatViewController.conversation
async:false
quickReply:false
requestId:nil
onCompletion:^(__unused TextMessage *message, __unused Conversation *conv) {
;//nop
}];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
NSString *text = NSLocalizedString(@"sending_item", nil);
[self incrementVideoProgressHUDWithText:text placeholderIncluded:false];
});
}
}
}
- (void)sendAssets:(NSArray *)assets asFile:(bool)sendAsFile withCaptions:(NSArray *)captions {
NSMutableArray *itemArray = [[NSMutableArray alloc] init];
NSString *text = NSLocalizedString(@"processing_items_progress", nil);
[self showVideoEncodeProgressHUDWithUnitCount:assets.count text:text];
self.videoEncodeProgressHUD.progressObject.totalUnitCount = assets.count;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) {
[self prepareAssets:assets withTarget:itemArray asFile:sendAsFile];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *text = NSLocalizedString(@"sending_item", nil);
[self hideVideoEncodeProgressHUD];
[self showVideoEncodeProgressHUDWithUnitCount:assets.count text:text];
});
[self sendItems:itemArray asFile:sendAsFile withCaptions:captions];
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self hideVideoEncodeProgressHUD];
});
[self clearTemporaryDirectoryItems:assets];
});
}
- (void)clearTemporaryDirectoryItems: (NSArray *)items {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0), ^(void){
DDLogInfo(@"Started clearing items in temporary directory.");
NSFileManager *defaultManager = NSFileManager.defaultManager;
NSError *err;
for (int i = 0; i < items.count; i++) {
if ([items[i] isKindOfClass:NSURL.class]) {
[defaultManager removeItemAtURL:items[i] error:&err];
if (err != nil) {
DDLogError(@"Could not clear item in temporary directory. Error: %@", err.description);
}
}
}
});
}
- (void)checkVideoDone {
bool done = true;
for (SDAVAssetExportSession *exportSession in self.videoEncoders) {
done = exportSession.progress == 1.0;
}
if (self.videoEncoders.count == 0 && done) {
[_sequentialSendTimer invalidate];
}
}
- (URLSenderItem *)getSenderItemForImageAsset:(PHAsset *)asset asFile:(bool)sendAsFile {
__block URLSenderItem *item;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
PHImageManager *imageManager = [PHImageManager defaultManager];
PHImageRequestOptions *options = [PHImageRequestOptions new];
options.networkAccessAllowed = YES;
options.version = PHImageRequestOptionsVersionCurrent;
[imageManager requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, __unused UIImageOrientation orientation, __unused NSDictionary *info) {
if (imageData) {
NSArray *resources = [PHAssetResource assetResourcesForAsset:asset];
NSString *orgFilename = @"File";
if (resources.count > 0) {
orgFilename = ((PHAssetResource*)resources[0]).originalFilename;
}
if (sendAsFile) {
item = [URLSenderItem itemWithData:imageData fileName:orgFilename type:dataUTI renderType:@0 sendAsFile:true];
} else {
ImageURLSenderItemCreator *itemCreator = [[ImageURLSenderItemCreator alloc] init];
item = [itemCreator senderItemFrom:imageData uti:dataUTI];
}
dispatch_semaphore_signal(sema);
}
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return item;
}
- (URLSenderItem *)getSenderItemForVideoAsset:(PHAsset *)asset asFile:(bool)sendAsFile {
PHImageManager *imageManager = [PHImageManager defaultManager];
__block URLSenderItem *senderItem;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.version = PHVideoRequestOptionsVersionCurrent;
options.networkAccessAllowed = YES;
[imageManager requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset *videoAsset, __unused AVAudioMix *audioMix, __unused NSDictionary *info) {
VideoURLSenderItemCreator *senderCreator = [[VideoURLSenderItemCreator alloc] init];
senderItem = [senderCreator senderItemFromAsset:videoAsset];
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return senderItem;
}
#pragma mark - Image picker controller delegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[self storeFlashConfigurationFor:picker];
if ([[info objectForKey:UIImagePickerControllerMediaType] isEqualToString:(NSString*)kUTTypeImage]) {
/* image picked */
UIImage *pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [UserSettings sharedUserSettings].autoSaveMedia) {
[[AlbumManager shared] saveWithImage:pickedImage];
}
if (picker.sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
/* need to ask for confirmation when picking images from library, as it's much too
easy to send the wrong picture with one tap */
PreviewImageViewController *previewVc = [self.chatViewController.storyboard instantiateViewControllerWithIdentifier:@"PreviewImage"];
previewVc.delegate = self.chatViewController;
previewVc.image = pickedImage;
[picker pushViewController:previewVc animated:YES];
} else {
NSURL *imageUrl = [PhotosAccessHelper storeImageToTmprDirWithImageData:pickedImage];
NSArray *array = [NSArray arrayWithObject:imageUrl];
[picker dismissViewControllerAnimated:true completion:^{
[self showPreviewForAssets:array];
}];
}
} else if ([[info objectForKey:UIImagePickerControllerMediaType] isEqualToString:(NSString*)kUTTypeMovie]) {
/* video picked */
_pickedVideoURL = [info objectForKey:UIImagePickerControllerMediaURL];
_pickedVideoSent = NO;
_pickedVideoSaved = NO;
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera && [UserSettings sharedUserSettings].autoSaveMedia) {
[[AlbumManager shared] saveMovieToLibraryWithMovieURL:_pickedVideoURL completionHandler:^(BOOL success) {
_pickedVideoSaved = YES;
if (_pickedVideoSent && _pickedVideoSaved)
[[NSFileManager defaultManager] removeItemAtURL:_pickedVideoURL error:nil];
}];
} else {
_pickedVideoSaved = YES;
}
/* check video duration - if this has come from the photo library, the video may be longer than
videoMaximumDuration (it is not enforced if allowEditing = NO, but we don't want to enable that
as we don't need the image cropping UI) */
if ([MediaConverter isVideoDurationValidAtUrl:_pickedVideoURL]) {
NSArray *array = [NSArray arrayWithObject:_pickedVideoURL];
[picker dismissViewControllerAnimated:true completion:^{
[self showPreviewForAssets:array];
}];
} else {
/* video too long - must present editor */
UIVideoEditorController *videoEditor = [[UIVideoEditorController alloc] init];
videoEditor.videoQuality = UIImagePickerControllerQualityTypeHigh;
videoEditor.videoMaximumDuration = [MediaConverter videoMaxDurationAtCurrentQuality] * 60;
videoEditor.videoPath = [_pickedVideoURL path];
videoEditor.delegate = self;
[ModalPresenter dismissPresentedControllerOn:self.chatViewController animated:YES completion:^{
_picker = nil;
[self.chatViewController presentViewController:videoEditor animated:YES completion:nil];
}];
}
}
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[self storeFlashConfigurationFor:picker];
[ModalPresenter dismissPresentedControllerOn:self.chatViewController animated:YES completion:^{
_picker = nil;
}];
}
- (void)storeFlashConfigurationFor:(UIImagePickerController *) picker {
if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) {
UIImagePickerControllerCameraFlashMode flashmode = picker.cameraFlashMode;
[[AppGroup userDefaults] setInteger:flashmode forKey:@"cameraFlashMode"];
[[AppGroup userDefaults] synchronize];
}
}
#pragma mark - Media sending utility functions
- (void)sendPickedImage:(UIImage *)image {
if (_picker != nil) {
[ModalPresenter dismissPresentedControllerOn:self.chatViewController animated:YES completion:^{
_picker = nil;
}];
}
if (image == nil) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
ImageURLSenderItemCreator *item = [[ImageURLSenderItemCreator alloc] init];
URLSenderItem *senderItem = [item senderItemFromImage:image];
FileMessageSender *sender = [[FileMessageSender alloc] init];
[sender sendItem:senderItem inConversation:self.chatViewController.conversation requestId:nil];
});
}
- (void)video:(NSString*)videoPath didFinishSavingWithError:(NSError*)error contextInfo:(void*)contextInfo {
_pickedVideoSaved = YES;
if (_pickedVideoSent && _pickedVideoSaved)
[[NSFileManager defaultManager] removeItemAtURL:_pickedVideoURL error:nil];
}
- (void)sendPickedVideo {
if (_picker != nil) {
_picker = nil;
}
[self.chatViewController dismissViewControllerAnimated:YES completion:nil];
if (_pickedVideoURL == nil)
return;
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:_pickedVideoURL options:nil];
[self sendVideoAsset:asset onCompletion:^{
_pickedVideoSent = YES;
if (_pickedVideoSent && _pickedVideoSaved)
[[NSFileManager defaultManager] removeItemAtURL:_pickedVideoURL error:nil];
}];
}
- (void)sendVideoAsset:(AVAsset*)asset onCompletion:(void(^)(void))onCompletion {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
VideoURLSenderItemCreator *senderCreator = [[VideoURLSenderItemCreator alloc] init];
senderCreator.encodeProgressDelegate = self;
SDAVAssetExportSession *exportSession = [senderCreator getExportSessionFor:asset];
if (exportSession == nil) {
DDLogError(@"VideoURL was nil.");
return;
}
[self.videoEncoders addObject:exportSession];
URLSenderItem *senderItem = [senderCreator senderItemFrom:asset on:exportSession];
FileMessageSender *sender = [[FileMessageSender alloc] init];
sender.uploadProgressDelegate = self;
[sender sendItem:senderItem inConversation:self.chatViewController.conversation requestId:nil];
if (onCompletion != nil) {
onCompletion();
}
});
}
- (void)videoExportSessionWithExportSession:(SDAVAssetExportSession * _Nonnull)exportSession {
float progress = exportSession.progress;
dispatch_async(dispatch_get_main_queue(), ^{
if (progress == 1.0f) {
[self.videoEncoders removeObject:exportSession];
}
});
}
- (void)showVideoEncodeProgressHUDWithUnitCount:(long)unitCount text:(NSString *)text {
if (self.videoEncodeProgressHUD == nil) {
self.videoEncodeProgressHUD = [MBProgressHUD showHUDAddedTo:self.chatViewController.view animated:YES];
self.videoEncodeProgressHUD.mode = MBProgressHUDModeAnnularDeterminate;
[self.videoEncodeProgressHUD.button setTitle:NSLocalizedString(@"cancel", nil) forState:UIControlStateNormal];
[self.videoEncodeProgressHUD.button addTarget:self action:@selector(progressHUDCancelPressed) forControlEvents:UIControlEventTouchUpInside];
self.videoEncodeProgressHUD.progressObject = [[NSProgress alloc] init];
self.videoEncodeProgressHUD.progressObject.totalUnitCount = unitCount;
long current = self.videoEncodeProgressHUD.progressObject.completedUnitCount;
long total = self.videoEncodeProgressHUD.progressObject.totalUnitCount;
[self updateHUDTextWithCompleted:current total:total text:text];
}
}
- (void)hideVideoEncodeProgressHUD {
if (self.videoEncodeProgressHUD != nil) {
[self.videoEncodeProgressHUD hideAnimated:YES];
self.videoEncodeProgressHUD = nil;
}
}
- (void)incrementVideoProgressHUDWithText:(NSString *)text placeholderIncluded:(bool)placeholder {
if (self.videoEncodeProgressHUD != nil) {
self.videoEncodeProgressHUD.progressObject.completedUnitCount++;
long current = self.videoEncodeProgressHUD.progressObject.completedUnitCount;
long total = self.videoEncodeProgressHUD.progressObject.totalUnitCount;
if (!placeholder) {
text = [text stringByAppendingString:@" %d/%d"];
}
[self updateHUDTextWithCompleted:current total:total text:text];
}
}
- (void)updateHUDTextWithCompleted:(long)completed total:(long)total text:(NSString *)text {
self.videoEncodeProgressHUD.label.text = [NSString stringWithFormat:text, completed, total];
}
- (void)removeFileMessageSender:(FileMessageSender*)videoMessageSender {
dispatch_async(dispatch_get_main_queue(), ^{
[self.fileMessageSenders removeObject:videoMessageSender];
});
}
- (void)progressHUDCancelPressed {
for (FileMessageSender *fileMessageSender in self.fileMessageSenders) {
[fileMessageSender uploadShouldCancel];
}
for (SDAVAssetExportSession *exportSession in self.videoEncoders) {
[exportSession cancelExport];
}
__unused bool val = [VideoURLSenderItemCreator cleanTemporaryDirectory];
self.cancelled = true;
}
#pragma mark - Video editor delegate
- (void)videoEditorController:(UIVideoEditorController *)editor didSaveEditedVideoToPath:(NSString *)editedVideoPath {
/* delete original video file */
[[NSFileManager defaultManager] removeItemAtURL:_pickedVideoURL error:nil];
_pickedVideoURL = [NSURL fileURLWithPath:editedVideoPath];
[self sendPickedVideo];
}
- (void)videoEditorController:(UIVideoEditorController *)editor didFailWithError:(NSError *)error {
DDLogWarn(@"Video editor failed: %@", error);
[editor dismissViewControllerAnimated:YES completion:nil];
}
- (void)videoEditorControllerDidCancel:(UIVideoEditorController *)editor {
[editor dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - UploadProgressDelegate
- (void)blobMessageSender:(BlobMessageSender*)blobMessageSender uploadFailedForMessage:(BaseMessage *)message error:(UploadError)error {
dispatch_async(dispatch_get_main_queue(), ^{
if (error == UploadErrorCancelled) {
return;
}
NSString *errorTitle = [BundleUtil localizedStringForKey:@"error_sending_failed"];
NSString *errorMessage = [FileMessageSender messageForError:error];
[UIAlertTemplate showAlertWithOwner:[[AppDelegate sharedAppDelegate] currentTopViewController] title:errorTitle message:errorMessage actionOk:nil];
});
self.cancelled = true;
dispatch_semaphore_signal(_sequentialSema);
}
- (void)blobMessageSender:(BlobMessageSender*)blobMessageSender uploadSucceededForMessage:(BaseMessage *)message {
if ([blobMessageSender isKindOfClass:FileMessageSender.class]) {
[self removeFileMessageSender:(FileMessageSender*)blobMessageSender];
}
dispatch_semaphore_signal(_sequentialSema);
}
- (BOOL)blobMessageSenderUploadShouldCancel:(BlobMessageSender*)blobMessageSender {
return NO;
}
- (void)blobMessageSender:(BlobMessageSender*)blobMessageSender uploadProgress:(NSNumber *)progress forMessage:(BaseMessage *)message {
if ([blobMessageSender isKindOfClass:FileMessageSender.class]) {
[self removeFileMessageSender:(FileMessageSender*)blobMessageSender];
}
}
@end