Room details: fix content of attached videos, display picture in high res when user taps on the attachment, play the attached video too.

NOTE: The cache handling has been modified, the user should logout before installing this new version of iOS matrixConsole in order to clear the existing cache.
This commit is contained in:
giomfo
2014-11-19 14:26:41 +01:00
parent 7d32eff0ca
commit 0866b7e799
12 changed files with 407 additions and 211 deletions

View File

@@ -8,11 +8,11 @@
/* Begin PBXBuildFile section */
D648B86A591308736E2D4078 /* libPods-matrixConsole.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8141B1E2401FFCC3C5B99234 /* libPods-matrixConsole.a */; };
F00B5DB91A1B9BCE00EA1C8D /* CustomImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = F00B5DB81A1B9BCE00EA1C8D /* CustomImageView.m */; };
F01628C119E29C660071C473 /* default-profile.png in Resources */ = {isa = PBXBuildFile; fileRef = F01628BC19E29C660071C473 /* default-profile.png */; };
F01628C319E29C660071C473 /* logo.png in Resources */ = {isa = PBXBuildFile; fileRef = F01628BE19E29C660071C473 /* logo.png */; };
F024098219E7D177006E741B /* tab_recents@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F024098119E7D177006E741B /* tab_recents@2x.png */; };
F02BCE231A1A5A2B00543B47 /* play.png in Resources */ = {isa = PBXBuildFile; fileRef = F02BCE221A1A5A2B00543B47 /* play.png */; };
F02D707319F1CE4A007B47D3 /* CustomTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F02D707219F1CE4A007B47D3 /* CustomTableViewCell.m */; };
F02D707619F1DC9E007B47D3 /* RoomMemberTableCell.m in Sources */ = {isa = PBXBuildFile; fileRef = F02D707519F1DC9E007B47D3 /* RoomMemberTableCell.m */; };
F03C47111A02952800E445AB /* CustomAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = F03C47101A02952800E445AB /* CustomAlert.m */; };
F03EF5F619F171EB00A0EE52 /* HomeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F03EF5EB19F171EB00A0EE52 /* HomeViewController.m */; };
@@ -52,12 +52,12 @@
13057A57E74FD5504196F47F /* Pods-matrixConsole.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-matrixConsole.release.xcconfig"; path = "Pods/Target Support Files/Pods-matrixConsole/Pods-matrixConsole.release.xcconfig"; sourceTree = "<group>"; };
8141B1E2401FFCC3C5B99234 /* libPods-matrixConsole.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-matrixConsole.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B7EC7E45C718BF2BBCE0CF48 /* Pods-matrixConsole.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-matrixConsole.debug.xcconfig"; path = "Pods/Target Support Files/Pods-matrixConsole/Pods-matrixConsole.debug.xcconfig"; sourceTree = "<group>"; };
F00B5DB71A1B9BCE00EA1C8D /* CustomImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomImageView.h; sourceTree = "<group>"; };
F00B5DB81A1B9BCE00EA1C8D /* CustomImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomImageView.m; sourceTree = "<group>"; };
F01628BC19E29C660071C473 /* default-profile.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "default-profile.png"; sourceTree = "<group>"; };
F01628BE19E29C660071C473 /* logo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = logo.png; sourceTree = "<group>"; };
F024098119E7D177006E741B /* tab_recents@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "tab_recents@2x.png"; sourceTree = "<group>"; };
F02BCE221A1A5A2B00543B47 /* play.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = play.png; sourceTree = "<group>"; };
F02D707119F1CE4A007B47D3 /* CustomTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomTableViewCell.h; sourceTree = "<group>"; };
F02D707219F1CE4A007B47D3 /* CustomTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomTableViewCell.m; sourceTree = "<group>"; };
F02D707419F1DC9E007B47D3 /* RoomMemberTableCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RoomMemberTableCell.h; sourceTree = "<group>"; };
F02D707519F1DC9E007B47D3 /* RoomMemberTableCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomMemberTableCell.m; sourceTree = "<group>"; };
F03C470F1A02952800E445AB /* CustomAlert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomAlert.h; sourceTree = "<group>"; };
@@ -175,8 +175,8 @@
F03EF5FC19F1762000A0EE52 /* View */ = {
isa = PBXGroup;
children = (
F02D707119F1CE4A007B47D3 /* CustomTableViewCell.h */,
F02D707219F1CE4A007B47D3 /* CustomTableViewCell.m */,
F00B5DB71A1B9BCE00EA1C8D /* CustomImageView.h */,
F00B5DB81A1B9BCE00EA1C8D /* CustomImageView.m */,
F02D707419F1DC9E007B47D3 /* RoomMemberTableCell.h */,
F02D707519F1DC9E007B47D3 /* RoomMemberTableCell.m */,
F03EF5FD19F1762000A0EE52 /* RoomMessageTableCell.h */,
@@ -401,7 +401,6 @@
files = (
F07A80DB19DD9DE700B621A1 /* AppDelegate.m in Sources */,
F03EF5FF19F1762000A0EE52 /* RoomMessageTableCell.m in Sources */,
F02D707319F1CE4A007B47D3 /* CustomTableViewCell.m in Sources */,
F07A80D819DD9DE700B621A1 /* main.m in Sources */,
F05B955F19DEED8A008761B0 /* MatrixHandler.m in Sources */,
F03EF5FB19F171EB00A0EE52 /* SettingsViewController.m in Sources */,
@@ -412,6 +411,7 @@
F03EF5F919F171EB00A0EE52 /* RecentsViewController.m in Sources */,
F03C47111A02952800E445AB /* CustomAlert.m in Sources */,
F02D707619F1DC9E007B47D3 /* RoomMemberTableCell.m in Sources */,
F00B5DB91A1B9BCE00EA1C8D /* CustomImageView.m in Sources */,
F0D3C30C1A011EF10000D49E /* AppSettings.m in Sources */,
F03EF5F719F171EB00A0EE52 /* LoginViewController.m in Sources */,
F0D3C30F1A01330F0000D49E /* SettingsTableViewCell.m in Sources */,

View File

@@ -44,7 +44,7 @@
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Lq0-jR-3i5" id="iJp-sA-hG6">
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="default-profile.png" translatesAutoresizingMaskIntoConstraints="NO" id="uhu-R0-9NH">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="default-profile.png" translatesAutoresizingMaskIntoConstraints="NO" id="uhu-R0-9NH" customClass="CustomImageView">
<rect key="frame" x="8" y="5" width="40" height="40"/>
<constraints>
<constraint firstAttribute="width" constant="40" id="ZJO-eO-xOQ"/>
@@ -60,7 +60,7 @@
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="mvK-ez-meg" userLabel="Attachment View">
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="mvK-ez-meg" userLabel="Attachment View" customClass="CustomImageView">
<rect key="frame" x="59" y="28" width="184" height="14"/>
</imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="play.png" translatesAutoresizingMaskIntoConstraints="NO" id="vF4-rq-4Rn">
@@ -155,7 +155,7 @@
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QZT-V8-yqJ" userLabel="Attachment View">
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QZT-V8-yqJ" userLabel="Attachment View" customClass="CustomImageView">
<rect key="frame" x="357" y="28" width="184" height="14"/>
</imageView>
<imageView hidden="YES" userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="play.png" translatesAutoresizingMaskIntoConstraints="NO" id="0Bl-Sv-Q2H">
@@ -168,7 +168,7 @@
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="Pq8-lB-cZM">
<rect key="frame" x="439" y="25" width="20" height="20"/>
</activityIndicatorView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="default-profile.png" translatesAutoresizingMaskIntoConstraints="NO" id="mks-jh-AiZ">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="default-profile.png" translatesAutoresizingMaskIntoConstraints="NO" id="mks-jh-AiZ" customClass="CustomImageView">
<rect key="frame" x="552" y="5" width="40" height="40"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="sGR-sG-6fA"/>
@@ -322,7 +322,7 @@
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="kac-Vi-XbY" id="prD-ap-cDD">
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="default-profile.png" translatesAutoresizingMaskIntoConstraints="NO" id="RW8-nh-DTj">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="default-profile.png" translatesAutoresizingMaskIntoConstraints="NO" id="RW8-nh-DTj" customClass="CustomImageView">
<rect key="frame" x="8" y="5" width="40" height="40"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="40" id="4c3-8w-4Tg"/>

View File

@@ -21,6 +21,7 @@ extern NSString *const kMediaManagerPrefixForDummyURL;
// The callback blocks
typedef void (^blockMediaManager_onImageReady)(UIImage *image);
typedef void (^blockMediaManager_onMediaReady)(NSString *cacheFilePath);
typedef void (^blockMediaManager_onError)(NSError *error);
@interface MediaManager : NSObject
@@ -32,13 +33,18 @@ typedef void (^blockMediaManager_onError)(NSError *error);
// Load a picture from the local cache or download it if it is not available yet.
// In this second case a mediaLoader reference is returned in order to let the user cancel this action.
+ (id)loadPicture:(NSString *)pictureURL
success:(blockMediaManager_onImageReady)success
failure:(blockMediaManager_onError)failure;
success:(blockMediaManager_onImageReady)success
failure:(blockMediaManager_onError)failure;
// Prepare a media from the local cache or download it if it is not available yet.
// In this second case a mediaLoader reference is returned in order to let the user cancel this action.
+ (id)prepareMedia:(NSString *)mediaURL
mimeType:(NSString *)mimeType
success:(blockMediaManager_onMediaReady)success
failure:(blockMediaManager_onError)failure;
+ (void)cancel:(id)mediaLoader;
+ (NSString *)cachePictureWithData:(NSData *)imageData forURL:(NSString *)pictureURL;
+ (NSString *)cacheMediaData:(NSData *)mediaData forURL:(NSString *)mediaURL mimeType:(NSString *)mimeType;
+ (void)clearCacheForURL:(NSString *)mediaURL;
+ (void)clearCache;
@end

View File

@@ -18,15 +18,16 @@
NSString *const kMediaManagerPrefixForDummyURL = @"dummyUrl-";
static NSString* pictureCachePath = nil;
static NSString *pictureDir = @"picturecache";
static NSString* mediaCachePath = nil;
static NSString *mediaDir = @"mediacache";
static MediaManager *sharedMediaManager = nil;
@interface MediaLoader : NSObject <NSURLConnectionDataDelegate> {
NSString *mediaURL;
NSString *mimeType;
blockMediaManager_onImageReady onImageReady;
blockMediaManager_onMediaReady onMediaReady;
blockMediaManager_onError onError;
NSMutableData *downloadData;
@@ -41,20 +42,46 @@ static MediaManager *sharedMediaManager = nil;
- (void)downloadPicture:(NSString*)pictureURL
success:(blockMediaManager_onImageReady)success
failure:(blockMediaManager_onError)failure {
// Download picture content
[self downloadMedia:pictureURL mimeType:@"image/jpeg" success:^(NSString *cacheFilePath) {
if (success) {
NSData* imageContent = [NSData dataWithContentsOfFile:cacheFilePath options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:nil];
if (imageContent) {
UIImage *image = [UIImage imageWithData:imageContent];
if (image) {
success(image);
} else {
NSLog(@"ERROR: picture download failed: %@", pictureURL);
if (failure){
failure(nil);
}
}
}
}
} failure:^(NSError *error) {
failure(error);
}];
}
- (void)downloadMedia:(NSString*)aMediaURL
mimeType:(NSString *)aMimeType
success:(blockMediaManager_onMediaReady)success
failure:(blockMediaManager_onError)failure {
// Report provided params
mediaURL = pictureURL;
onImageReady = success;
mediaURL = aMediaURL;
mimeType = aMimeType;
onMediaReady = success;
onError = failure;
// Start downloading the picture
NSURL *url = [NSURL URLWithString:pictureURL];
// Start downloading
NSURL *url = [NSURL URLWithString:aMediaURL];
downloadData = [[NSMutableData alloc] init];
downloadConnection = [[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:url] delegate:self];
}
- (void)cancel {
// Reset blocks
onImageReady = nil;
onMediaReady = nil;
onError = nil;
// Cancel potential connection
if (downloadConnection) {
@@ -71,7 +98,7 @@ static MediaManager *sharedMediaManager = nil;
#pragma mark -
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"ERROR: picture download failed: %@, %@", error, mediaURL);
NSLog(@"ERROR: media download failed: %@, %@", error, mediaURL);
if (onError) {
onError (error);
}
@@ -83,18 +110,15 @@ static MediaManager *sharedMediaManager = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// CAUTION: Presently only picture are supported
// Set downloaded image
UIImage *image = [UIImage imageWithData:downloadData];
if (image) {
if (downloadData.length) {
// Cache the downloaded data
[MediaManager cachePictureWithData:downloadData forURL:mediaURL];
NSString *cacheFilePath = [MediaManager cacheMediaData:downloadData forURL:mediaURL mimeType:mimeType];
// Call registered block
if (onImageReady) {
onImageReady(image);
if (onMediaReady) {
onMediaReady(cacheFilePath);
}
} else {
NSLog(@"ERROR: picture download failed: %@", mediaURL);
NSLog(@"ERROR: media download failed: %@", mediaURL);
if (onError){
onError(nil);
}
@@ -184,56 +208,73 @@ static MediaManager *sharedMediaManager = nil;
return ret;
}
+ (id)prepareMedia:(NSString *)mediaURL
mimeType:(NSString *)mimeType
success:(blockMediaManager_onMediaReady)success
failure:(blockMediaManager_onError)failure {
id ret = nil;
// Check cache
NSString* filename = [MediaManager getCacheFileNameFor:mediaURL mimeType:mimeType];
if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
if (success) {
// Reply synchronously
success (filename);
}
}
else if ([mediaURL hasPrefix:kMediaManagerPrefixForDummyURL] == NO) {
// Create a media loader to download media content
MediaLoader *mediaLoader = [[MediaLoader alloc] init];
[mediaLoader downloadMedia:mediaURL mimeType:mimeType success:success failure:failure];
ret = mediaLoader;
} else {
NSLog(@"Load tmp media from cache failed: %@", mediaURL);
if (failure){
failure(nil);
}
}
return ret;
}
+ (void)cancel:(id)mediaLoader {
[((MediaLoader*)mediaLoader) cancel];
}
+ (NSString*)cachePictureWithData:(NSData*)imageData forURL:(NSString *)pictureURL {
NSString* filename = [MediaManager getCacheFileNameFor:pictureURL];
+ (NSString*)cacheMediaData:(NSData*)mediaData forURL:(NSString *)mediaURL mimeType:(NSString *)mimeType {
NSString* filename = [MediaManager getCacheFileNameFor:mediaURL mimeType:mimeType];
if ([imageData writeToFile:filename atomically:YES]) {
if ([mediaData writeToFile:filename atomically:YES]) {
return filename;
} else {
return nil;
}
}
+ (void)clearCacheForURL:(NSString *)mediaURL {
NSString* filename = [MediaManager getCacheFileNameFor:mediaURL];
NSError *error = nil;
if (filename && [[NSFileManager defaultManager] fileExistsAtPath:filename]) {
if (![[NSFileManager defaultManager] removeItemAtPath:filename error:&error]) {
NSLog(@"Fails to delete cached picture: %@", error);
}
}
}
+ (void)clearCache {
NSError *error = nil;
if (!pictureCachePath) {
if (!mediaCachePath) {
// compute the path
pictureCachePath = [MediaManager getCachePath];
mediaCachePath = [MediaManager getCachePath];
}
if (pictureCachePath) {
if (![[NSFileManager defaultManager] removeItemAtPath:pictureCachePath error:&error]) {
NSLog(@"Fails to delete picture cache dir : %@", error);
if (mediaCachePath) {
if (![[NSFileManager defaultManager] removeItemAtPath:mediaCachePath error:&error]) {
NSLog(@"Fails to delete media cache dir : %@", error);
} else {
NSLog(@"Picture cache : deleted !");
NSLog(@"Media cache : deleted !");
}
} else {
NSLog(@"Picture cache does not exist");
NSLog(@"Media cache does not exist");
}
pictureCachePath = nil;
mediaCachePath = nil;
}
#pragma mark - Cache handling
+ (UIImage*)loadCachePicture:(NSString*)pictureURL {
UIImage* res = nil;
NSString* filename = [MediaManager getCacheFileNameFor:pictureURL];
NSString* filename = [MediaManager getCacheFileNameFor:pictureURL mimeType:@"image/jpeg"];
if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) {
NSData* imageContent = [NSData dataWithContentsOfFile:filename options:(NSDataReadingMappedAlways | NSDataReadingUncached) error:nil];
@@ -246,26 +287,41 @@ static MediaManager *sharedMediaManager = nil;
}
+ (NSString*)getCachePath {
NSString *mediaCachePath = nil;
NSString *cachePath = nil;
if (!pictureCachePath) {
if (!mediaCachePath) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cacheRoot = [paths objectAtIndex:0];
pictureCachePath = [cacheRoot stringByAppendingPathComponent:pictureDir];
mediaCachePath = [cacheRoot stringByAppendingPathComponent:mediaDir];
if (![[NSFileManager defaultManager] fileExistsAtPath:pictureCachePath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:pictureCachePath withIntermediateDirectories:NO attributes:nil error:nil];
if (![[NSFileManager defaultManager] fileExistsAtPath:mediaCachePath]) {
[[NSFileManager defaultManager] createDirectoryAtPath:mediaCachePath withIntermediateDirectories:NO attributes:nil error:nil];
}
}
mediaCachePath = pictureCachePath;
cachePath = mediaCachePath;
return mediaCachePath;
return cachePath;
}
+ (NSString*)getCacheFileNameFor:(NSString*)pictureURL {
NSString* baseFileName = [[MediaManager getCachePath] stringByAppendingPathComponent:@"ima"];
return [NSString stringWithFormat:@"%@%lu.jpg", baseFileName, (unsigned long)pictureURL.hash];
+ (NSString*)getCacheFileNameFor:(NSString*)mediaURL mimeType:(NSString *)mimeType {
NSString *fileName;
if ([mimeType isEqualToString:@"image/jpeg"]) {
fileName = [NSString stringWithFormat:@"ima%lu.jpg", (unsigned long)mediaURL.hash];
} else if ([mimeType isEqualToString:@"video/mp4"]) {
fileName = [NSString stringWithFormat:@"video%lu.mp4", (unsigned long)mediaURL.hash];
} else if ([mimeType isEqualToString:@"video/quicktime"]) {
fileName = [NSString stringWithFormat:@"video%lu.mov", (unsigned long)mediaURL.hash];
} else {
NSString *extension = @"";
NSArray *components = [mediaURL componentsSeparatedByString:@"."];
if (components && components.count > 1) {
extension = [components lastObject];
}
fileName = [NSString stringWithFormat:@"%lu.%@", (unsigned long)mediaURL.hash, extension];
}
return [[MediaManager getCachePath] stringByAppendingPathComponent:fileName];
}
@end

View File

@@ -16,11 +16,12 @@
#import <UIKit/UIKit.h>
// Customize UITableViewCell in order to handle Table cell with UIImageView based on remote url
@interface CustomTableViewCell : UITableViewCell
@property (strong, nonatomic) IBOutlet UIImageView *pictureView;
// Customize UIImageView in order to let UIImageView handle automatically remote url
@interface CustomImageView : UIImageView
@property (strong, nonatomic) NSString *placeholder;
@property (strong, nonatomic) NSString *pictureURL;
@property (strong, nonatomic) NSString *imageURL;
// Information about the media represented by this image (image, video...)
@property (strong, nonatomic) NSDictionary *mediaInfo;
@end

View File

@@ -0,0 +1,76 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "CustomImageView.h"
#import "MediaManager.h"
@interface CustomImageView () {
id imageLoader;
UIActivityIndicatorView *loadingWheel;
}
@end
@implementation CustomImageView
- (void)setImageURL:(NSString *)imageURL {
// Cancel media loader in progress (if any)
if (imageLoader) {
[MediaManager cancel:imageLoader];
imageLoader = nil;
}
_imageURL = imageURL;
// Reset image view
self.image = nil;
if (_placeholder) {
// Set picture placeholder
self.image = [UIImage imageNamed:_placeholder];
}
// Consider provided url to update image view
if (imageURL) {
// Load picture
if (loadingWheel == nil) {
loadingWheel = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
loadingWheel.center = self.center;
[self addSubview:loadingWheel];
}
[loadingWheel startAnimating];
imageLoader = [MediaManager loadPicture:imageURL
success:^(UIImage *image) {
[loadingWheel stopAnimating];
self.image = image;
}
failure:^(NSError *error) {
[loadingWheel stopAnimating];
NSLog(@"Failed to download image (%@): %@", imageURL, error);
}];
}
}
- (void)dealloc {
if (imageLoader) {
[MediaManager cancel:imageLoader];
imageLoader = nil;
}
if (loadingWheel) {
[loadingWheel removeFromSuperview];
loadingWheel = nil;
}
}
@end

View File

@@ -1,65 +0,0 @@
/*
Copyright 2014 OpenMarket Ltd
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "CustomTableViewCell.h"
#import "MediaManager.h"
@interface CustomTableViewCell () {
id userPictureLoader;
}
@end
@implementation CustomTableViewCell
- (void)setPictureURL:(NSString *)pictureURL {
// Cancel media loader in progress (if any)
if (userPictureLoader) {
[MediaManager cancel:userPictureLoader];
userPictureLoader = nil;
}
_pictureURL = pictureURL;
// Reset image view
_pictureView.image = nil;
if (_placeholder) {
// Set picture placeholder
_pictureView.image = [UIImage imageNamed:_placeholder];
}
// Consider provided url to update image view
if (pictureURL) {
// Load picture
userPictureLoader = [MediaManager loadPicture:pictureURL
success:^(UIImage *image) {
_pictureView.image = image;
}
failure:nil];
}
// Round image view
[_pictureView.layer setCornerRadius:_pictureView.frame.size.width / 2];
_pictureView.clipsToBounds = YES;
}
- (void)dealloc
{
if (userPictureLoader) {
[MediaManager cancel:userPictureLoader];
userPictureLoader = nil;
}
}
@end

View File

@@ -15,13 +15,14 @@
*/
#import <UIKit/UIKit.h>
#import "CustomTableViewCell.h"
#import "CustomImageView.h"
@class MXRoomMember;
@class MXRoom;
// Room Member Table View Cell
@interface RoomMemberTableCell : CustomTableViewCell
@interface RoomMemberTableCell : UITableViewCell
@property (strong, nonatomic) IBOutlet CustomImageView *pictureView;
@property (weak, nonatomic) IBOutlet UILabel *userLabel;
@property (weak, nonatomic) IBOutlet UIProgressView *userPowerLevel;
@property (weak, nonatomic) IBOutlet UILabel *lastActiveAgoLabel;

View File

@@ -22,8 +22,11 @@
- (void)setRoomMember:(MXRoomMember *)roomMember withRoom:(MXRoom *)room {
if (room && roomMember) {
self.userLabel.text = [room.state memberName:roomMember.userId];
self.placeholder = @"default-profile";
self.pictureURL = roomMember.avatarUrl;
self.pictureView.placeholder = @"default-profile";
self.pictureView.imageURL = roomMember.avatarUrl;
// Round image view
[self.pictureView.layer setCornerRadius:self.pictureView.frame.size.width / 2];
self.pictureView.clipsToBounds = YES;
// Shade invited users
if (roomMember.membership == MXMembershipInvite) {

View File

@@ -15,12 +15,13 @@
*/
#import <UIKit/UIKit.h>
#import "CustomTableViewCell.h"
#import "CustomImageView.h"
// Room Message Table View Cell
@interface RoomMessageTableCell : CustomTableViewCell
@interface RoomMessageTableCell : UITableViewCell
@property (strong, nonatomic) IBOutlet CustomImageView *pictureView;
@property (weak, nonatomic) IBOutlet UITextView *messageTextView;
@property (strong, nonatomic) IBOutlet UIImageView *attachmentView;
@property (strong, nonatomic) IBOutlet CustomImageView *attachmentView;
@property (strong, nonatomic) IBOutlet UIImageView *playIconView;
@property (weak, nonatomic) IBOutlet UILabel *dateTimeLabel;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *msgTextViewWidthConstraint;
@@ -29,8 +30,6 @@
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *dateTimeLabelTopConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachmentViewTopAlignmentConstraint;
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *attachmentViewBottomAlignmentConstraint;
@property (strong, nonatomic) NSString *attachedImageURL;
@end
@interface IncomingMessageTableCell : RoomMessageTableCell

View File

@@ -17,45 +17,7 @@
#import "RoomMessageTableCell.h"
#import "MediaManager.h"
@interface RoomMessageTableCell () {
id attachmentLoader;
}
@end
@implementation RoomMessageTableCell
- (void)setAttachedImageURL:(NSString *)attachedImageURL {
// Cancel media loader in progress (if any)
if (attachmentLoader) {
[MediaManager cancel:attachmentLoader];
attachmentLoader = nil;
}
_attachedImageURL = attachedImageURL;
// Reset image view
_attachmentView.image = nil;
// Consider provided url to update image view
if (attachedImageURL) {
// Load picture
attachmentLoader = [MediaManager loadPicture:attachedImageURL
success:^(UIImage *image) {
_attachmentView.image = image;
}
failure:^(NSError *error) {
NSLog(@"Failed to download attachment (%@): %@", _attachedImageURL, error);
}];
}
}
- (void)dealloc
{
if (attachmentLoader) {
[MediaManager cancel:attachmentLoader];
attachmentLoader = nil;
}
}
@end

View File

@@ -65,6 +65,11 @@ NSString *const kFailedEventId = @"failedEventId";
NSArray *members;
id membersListener;
// Attachment handling
CustomImageView *highResImage;
NSString *AVAudioSessionCategory;
MPMoviePlayerController *videoPlayer;
// Date formatter (nil if dateTimeLabel is hidden)
NSDateFormatter *dateFormatter;
@@ -122,11 +127,16 @@ NSString *const kFailedEventId = @"failedEventId";
#endif
// Clear temporary cached attachments (used for local echo)
NSUInteger index = tmpCachedAttachments.count;
NSError *error = nil;
while (index--) {
[MediaManager clearCacheForURL:[tmpCachedAttachments objectAtIndex:index]];
if (![[NSFileManager defaultManager] removeItemAtPath:[tmpCachedAttachments objectAtIndex:index] error:&error]) {
NSLog(@"Fail to delete cached media: %@", error);
}
}
tmpCachedAttachments = nil;
[self hideAttachmentView];
messages = nil;
if (messagesListener) {
[mxRoom unregisterListener:messagesListener];
@@ -230,10 +240,8 @@ NSString *const kFailedEventId = @"failedEventId";
#ifdef TEMPORARY_PATCH_INITIAL_SYNC
// FIXME: this method should be removed when SDK will fix the initial sync issue
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([@"isInitialSyncDone" isEqualToString:keyPath])
{
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([@"isInitialSyncDone" isEqualToString:keyPath]) {
MatrixHandler *mxHandler = [MatrixHandler sharedHandler];
if ([mxHandler isInitialSyncDone]) {
[_activityIndicator stopAnimating];
@@ -259,7 +267,7 @@ NSString *const kFailedEventId = @"failedEventId";
// gestureRecognizer should begin only if tap is outside members list
return !CGRectContainsPoint(frame, [gestureRecognizer locationInView:self.membersView]);
}
return NO;
return YES;
}
#pragma mark - Internal methods
@@ -448,6 +456,8 @@ NSString *const kFailedEventId = @"failedEventId";
}
}
# pragma mark - Room members
- (void)showHideRoomMembers:(id)sender {
// Check whether the members list is displayed
if (members) {
@@ -571,7 +581,119 @@ NSString *const kFailedEventId = @"failedEventId";
members = nil;
}
#pragma mark - keyboard handling
# pragma mark - Attachment handling
- (void)showAttachmentView:(UIGestureRecognizer *)gestureRecognizer {
CustomImageView *attachment = (CustomImageView*)gestureRecognizer.view;
// Retrieve attachment information
NSDictionary *content = attachment.mediaInfo;
NSString *msgtype = content[@"msgtype"];
if ([msgtype isEqualToString:kMXMessageTypeImage]) {
NSString *url =content[@"url"];
if (url.length) {
highResImage = [[CustomImageView alloc] initWithFrame:self.membersView.frame];
highResImage.contentMode = UIViewContentModeScaleAspectFit;
highResImage.backgroundColor = [UIColor blackColor];
highResImage.imageURL = url;
[self.view addSubview:highResImage];
// Add tap recognizer to hide attachment
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(hideAttachmentView)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[highResImage addGestureRecognizer:tap];
highResImage.userInteractionEnabled = YES;
}
} else if ([msgtype isEqualToString:kMXMessageTypeVideo]) {
NSString *url =content[@"url"];
if (url.length) {
NSString *mimetype = nil;
if (content[@"info"]) {
mimetype = content[@"info"][@"mimetype"];
}
AVAudioSessionCategory = [[AVAudioSession sharedInstance] category];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
videoPlayer = [[MPMoviePlayerController alloc] init];
if (videoPlayer != nil) {
videoPlayer.scalingMode = MPMovieScalingModeAspectFit;
[self.view addSubview:videoPlayer.view];
[videoPlayer setFullscreen:YES animated:NO];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(moviePlayerPlaybackDidFinishNotification:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(moviePlayerWillExitFullscreen:)
name:MPMoviePlayerWillExitFullscreenNotification
object:videoPlayer];
[MediaManager prepareMedia:url mimeType:mimetype success:^(NSString *cacheFilePath) {
if (cacheFilePath) {
if (tmpCachedAttachments == nil) {
tmpCachedAttachments = [NSMutableArray array];
}
if ([tmpCachedAttachments indexOfObject:cacheFilePath]) {
[tmpCachedAttachments addObject:cacheFilePath];
}
}
videoPlayer.contentURL = [NSURL fileURLWithPath:cacheFilePath];
[videoPlayer play];
} failure:^(NSError *error) {
[self hideAttachmentView];
//Alert user
[[AppDelegate theDelegate] showErrorAsAlert:error];
}];
}
}
} else if ([msgtype isEqualToString:kMXMessageTypeAudio]) {
} else if ([msgtype isEqualToString:kMXMessageTypeLocation]) {
}
}
- (void)hideAttachmentView {
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerWillExitFullscreenNotification object:nil];
if (highResImage) {
[highResImage removeFromSuperview];
highResImage = nil;
}
// Restore audio category
if (AVAudioSessionCategory) {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategory error:nil];
}
if (videoPlayer) {
[videoPlayer stop];
[videoPlayer setFullscreen:NO];
[videoPlayer.view removeFromSuperview];
videoPlayer = nil;
}
}
- (void)moviePlayerWillExitFullscreen:(NSNotification*)notification {
if (notification.object == videoPlayer) {
[self hideAttachmentView];
}
}
- (void)moviePlayerPlaybackDidFinishNotification:(NSNotification *)notification {
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *resultValue = [notificationUserInfo objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
MPMovieFinishReason reason = [resultValue intValue];
// error cases
if (reason == MPMovieFinishReasonPlaybackError) {
NSError *mediaPlayerError = [notificationUserInfo objectForKey:@"error"];
if (mediaPlayerError) {
NSLog(@"Playback failed with error description: %@", [mediaPlayerError localizedDescription]);
[self hideAttachmentView];
//Alert user
[[AppDelegate theDelegate] showErrorAsAlert:mediaPlayerError];
}
}
}
#pragma mark - Keyboard handling
- (void)onKeyboardWillShow:(NSNotification *)notif {
NSValue *rectVal = notif.userInfo[UIKeyboardFrameEndUserInfoKey];
@@ -744,6 +866,11 @@ NSString *const kFailedEventId = @"failedEventId";
}
}
// Remove all gesture recognizer
while (cell.attachmentView.gestureRecognizers.count) {
[cell.attachmentView removeGestureRecognizer:cell.attachmentView.gestureRecognizers[0]];
}
// Check whether the previous message has been sent by the same user.
// We group together messages from the same user. The user's picture and name are displayed only for the first message.
// We consider a new chunk when the user is different from the previous message's one.
@@ -763,8 +890,10 @@ NSString *const kFailedEventId = @"failedEventId";
cell.messageTextView.contentInset = UIEdgeInsetsZero;
// Set user's picture
cell.placeholder = @"default-profile";
cell.pictureURL = [mxRoom.state memberWithUserId:mxEvent.userId].avatarUrl;
cell.pictureView.placeholder = @"default-profile";
cell.pictureView.imageURL = [mxRoom.state memberWithUserId:mxEvent.userId].avatarUrl;
[cell.pictureView.layer setCornerRadius:cell.pictureView.frame.size.width / 2];
cell.pictureView.clipsToBounds = YES;
} else {
// Adjust display of other messages of the chunk
cell.pictureView.hidden = YES;
@@ -835,6 +964,7 @@ NSString *const kFailedEventId = @"failedEventId";
enableLinkDetection = NO;
}
} else {
// Adjust constraint constant
cell.msgTextViewWidthConstraint.constant = contentSize.width;
// Align attachment inside text view by considering text view edge inset
cell.attachmentViewTopAlignmentConstraint.constant = ROOM_MESSAGE_CELL_IMAGE_MARGIN + cell.messageTextView.contentInset.top;
@@ -850,24 +980,38 @@ NSString *const kFailedEventId = @"failedEventId";
NSString *msgtype = mxEvent.content[@"msgtype"];
if ([msgtype isEqualToString:kMXMessageTypeImage] || [msgtype isEqualToString:kMXMessageTypeVideo]) {
NSString *url = mxEvent.content[@"thumbnail_url"];
NSString *url = nil;
if ([msgtype isEqualToString:kMXMessageTypeVideo]) {
cell.playIconView.hidden = NO;
if (mxEvent.content[@"info"]) {
url = mxEvent.content[@"info"][@"thumbnail_url"];
}
} else {
url = mxEvent.content[@"thumbnail_url"];
}
if (url == nil) {
url = mxEvent.content[@"url"];
}
cell.attachedImageURL = url;
cell.attachmentView.imageURL = url;
if ([msgtype isEqualToString:kMXMessageTypeVideo]) {
cell.playIconView.hidden = NO;
}
// Add tap recognizer to open attachment
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(showAttachmentView:)];
[tap setNumberOfTouchesRequired:1];
[tap setNumberOfTapsRequired:1];
[tap setDelegate:self];
[cell.attachmentView addGestureRecognizer:tap];
// Store attachment content description used in showAttachmentView:
cell.attachmentView.mediaInfo = mxEvent.content;
} else {
cell.attachedImageURL = nil;
cell.attachmentView.imageURL = nil;
}
}
} else {
// Text message will be displayed in textView with max width
cell.msgTextViewWidthConstraint.constant = ROOM_MESSAGE_CELL_MAX_TEXTVIEW_WIDTH;
// Cancel potential attachment loading
cell.attachedImageURL = nil;
cell.attachmentView.imageURL = nil;
cell.attachmentView.hidden = YES;
cell.playIconView.hidden = YES;
@@ -907,7 +1051,16 @@ NSString *const kFailedEventId = @"failedEventId";
if ([msgtype isEqualToString:kMXMessageTypeImage] || [msgtype isEqualToString:kMXMessageTypeVideo]) {
CGFloat width, height;
width = height = 0;
NSDictionary *thumbInfo = mxEvent.content[@"thumbnail_info"];
NSDictionary *thumbInfo = nil;
if ([msgtype isEqualToString:kMXMessageTypeVideo]) {
if (mxEvent.content[@"info"]) {
thumbInfo = mxEvent.content[@"info"][@"thumbnail_info"];
}
} else {
thumbInfo = mxEvent.content[@"thumbnail_info"];
}
if (thumbInfo) {
width = [thumbInfo[@"w"] integerValue] + 2 * ROOM_MESSAGE_CELL_IMAGE_MARGIN;
height = [thumbInfo[@"h"] integerValue] + 2 * ROOM_MESSAGE_CELL_IMAGE_MARGIN;
@@ -1192,7 +1345,7 @@ NSString *const kFailedEventId = @"failedEventId";
dateFormatter = nil;
} else {
// dateTime will be visible
NSString *dateFormat = @"MMM dd HH:mm";
NSString *dateFormat = @"MMM dd HH:mm";
dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:[[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0]]];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
@@ -1293,11 +1446,13 @@ NSString *const kFailedEventId = @"failedEventId";
// We store temporarily the image in cache, use the localId to build temporary url
NSString *dummyURL = [NSString stringWithFormat:@"%@%@", kMediaManagerPrefixForDummyURL, localEventId];
NSData *imageData = UIImageJPEGRepresentation(image, 0.5);
[MediaManager cachePictureWithData:imageData forURL:dummyURL];
if (tmpCachedAttachments == nil) {
tmpCachedAttachments = [NSMutableArray array];
NSString *cacheFilePath = [MediaManager cacheMediaData:imageData forURL:dummyURL mimeType:@"image/jpeg"];
if (cacheFilePath) {
if (tmpCachedAttachments == nil) {
tmpCachedAttachments = [NSMutableArray array];
}
[tmpCachedAttachments addObject:cacheFilePath];
}
[tmpCachedAttachments addObject:dummyURL];
NSMutableDictionary *thumbnailInfo = [[NSMutableDictionary alloc] init];
[thumbnailInfo setValue:@"image/jpeg" forKey:@"mimetype"];
[thumbnailInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)image.size.width] forKey:@"w"];
@@ -1558,9 +1713,10 @@ NSString *const kFailedEventId = @"failedEventId";
[mxHandler.mxRestClient uploadContent:thumbnailData mimeType:@"image/jpeg" timeout:30 success:^(NSString *url) {
// Prepare content of attached video
NSMutableDictionary *videoContent = [[NSMutableDictionary alloc] init];
NSMutableDictionary *videoInfo = [[NSMutableDictionary alloc] init];
[videoContent setValue:@"m.video" forKey:@"msgtype"];
[videoContent setValue:url forKey:@"thumbnail_url"];
[videoContent setValue:thumbnailInfo forKey:@"thumbnail_info"];
[videoInfo setValue:url forKey:@"thumbnail_url"];
[videoInfo setValue:thumbnailInfo forKey:@"thumbnail_info"];
// Convert video container to mp4
AVURLAsset* videoAsset = [AVURLAsset URLAssetWithURL:selectedVideo options:nil];
@@ -1575,12 +1731,12 @@ NSString *const kFailedEventId = @"failedEventId";
NSArray *supportedFileTypes = exportSession.supportedFileTypes;
if ([supportedFileTypes containsObject:AVFileTypeMPEG4]) {
exportSession.outputFileType = AVFileTypeMPEG4;
[videoContent setValue:@"video/mp4" forKey:@"mimetype"];
[videoInfo setValue:@"video/mp4" forKey:@"mimetype"];
} else {
NSLog(@"Unexpected case: MPEG-4 file format is not supported");
// we send QuickTime movie file by default
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
[videoContent setValue:@"video/quicktime" forKey:@"mimetype"];
[videoInfo setValue:@"video/quicktime" forKey:@"mimetype"];
}
// Export video file and send it
[exportSession exportAsynchronouslyWithCompletionHandler:^{
@@ -1593,13 +1749,13 @@ NSString *const kFailedEventId = @"failedEventId";
nil]
];
[videoContent setValue:[NSNumber numberWithDouble:(1000 * CMTimeGetSeconds(asset.duration))] forKey:@"duration"];
[videoInfo setValue:[NSNumber numberWithDouble:(1000 * CMTimeGetSeconds(asset.duration))] forKey:@"duration"];
NSArray *videoTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
if (videoTracks.count > 0) {
AVAssetTrack *videoTrack = [videoTracks objectAtIndex:0];
CGSize videoSize = videoTrack.naturalSize;
[videoContent setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)videoSize.width] forKey:@"w"];
[videoContent setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)videoSize.height] forKey:@"h"];
[videoInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)videoSize.width] forKey:@"w"];
[videoInfo setValue:[NSNumber numberWithUnsignedInteger:(NSUInteger)videoSize.height] forKey:@"h"];
}
// Upload the video
@@ -1607,9 +1763,10 @@ NSString *const kFailedEventId = @"failedEventId";
[[NSFileManager defaultManager] removeItemAtPath:[tmpVideoLocation path] error:nil];
if (videoData) {
if (videoData.length < UPLOAD_FILE_SIZE) {
[videoContent setValue:[NSNumber numberWithUnsignedInteger:videoData.length] forKey:@"size"];
[mxHandler.mxRestClient uploadContent:videoData mimeType:videoContent[@"mimetype"] timeout:30 success:^(NSString *url) {
[videoInfo setValue:[NSNumber numberWithUnsignedInteger:videoData.length] forKey:@"size"];
[mxHandler.mxRestClient uploadContent:videoData mimeType:videoInfo[@"mimetype"] timeout:30 success:^(NSString *url) {
[videoContent setValue:url forKey:@"url"];
[videoContent setValue:videoInfo forKey:@"info"];
[videoContent setValue:@"Video" forKey:@"body"];
[self postMessage:videoContent withLocalEventId:localEventId];
} failure:^(NSError *error) {