Dropbox sync api large video file upload - ios

I am using Dropbox sync api for downloading text file and upload video file from/to dropbox via my ios application.
I am struggling while uploading heavy video file.While i am uploading video file of duration 15 to 20 minutes its uploaded correctly, but if the duration is more than 25 minutes
then it gets memory waring and app crashes.
I am using this code on upload button action
DBPath *paths=[[DBPath root] childPath:[self.allVideoArray objectAtIndex:Selectedvideo]];
DBFile *createfile=[filesystem createFile:paths error:nil];
NSData *data=[[NSData alloc]initWithContentsOfFile:self.path];
[createfile writeData:data error:nil];
[data relese];
Please some body way me out from this problem. Any help should be appreciable, Thanks in advance.

The problem is that you create an NSData instance containing the entire file. If the file is too big to fit into memory your app will crash. There are better ways to write large files to a DBFile.
Since you have a path to the local file you could do:
DBPath *paths=[[DBPath root] childPath:[self.allVideoArray objectAtIndex:Selectedvideo]];
DBFile *createfile=[filesystem createFile:paths error:nil];
[createFile writeContentsOfFile:self.path shouldSteal:NO error:nil];
Another option would be to read the file at self.path in smaller chunks and use DBFile appendData:error:.
Side note - you really need to check return values to make sure these calls are working or not and make use of the error parameter to log the cause of the problem (if any).

Related

Getting byte Data from File

WHAT IM DOING I am trying to get an audio file (could be up to an hour long. eg. a Podcast) that I've recorded with AVAudioRecorder to be uploaded to our backend. In addition to being uploaded to the server it needs to be able to be "Paused" and "Resumed" if the user chooses. Because of this, I believe, I need to use dataWithBytesNoCopy:buffer on the NSData class to achieve this.
WHERE IM AT I know for a fact I can get the data with using the passed self.mediaURL property:
if (self.mediaURL) {
NSData *audioData = [NSData dataWithContentsOfURL:self.mediaURL];
if (audioData) {
[payloadDic setObject:audioData forKey:#"audioData"];
}
}
However, this will not give me the desired functionality. I am trying to keep track of the bytes uploaded so that I can resume if the user pauses.
QUESTION How can I use the provided self.mediaURL so that I can retrieve the file and be able to calculate the byte length like this example?
Byte *buffer = (Byte*)malloc((long)audioFile.size);
NSUInteger buffered =[rep getBytes:buffer fromOffset:0.0 length:(long)rep.size error:nil];
NSMutableData *body = [[NSMutableData alloc] init];
body = [NSMutableData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
Instead of making things more complicated for yourself by trying to reinvent the wheel, use what the system gives you. NSURLSession lets you do a background upload. You hand the task to the session (created using the background session configuration) and just walk away. The upload takes place in pieces, when it can. No "pause" or "resume" needed; the system takes care of everything. Your app doesn't even have to be running. If authentication is needed, your app will be woken up in the background as required. This architecture is just made for the situation you describe.
If the problem is that you want random access to file data without having to read the whole thing into a massive NSData, use NSFileHandle.

PHAsset + AFNetworking. Unable to upload files to the server on a real device

Currently I'm using the following code to upload files to the server
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// Get file url
[UploadModel getAassetUrl:entity.asset resultHandler:^(NSURL *fileUrl) {
NSError *fileappenderror;
// Append
[formData appendPartWithFileURL:fileUrl name:#"data" error:&fileappenderror];
if (fileappenderror) {
[Sys MyLog: [fileappenderror localizedDescription] ];
}
}];
} error:&urlRequestError];
/*getAassetUrl */
+(void)getAassetUrl: (PHAsset*)mPhasset resultHandler:(void(^)(NSURL *imageUrl))dataResponse{
PHImageRequestOptions * requestOption = [[PHImageRequestOptions alloc] init];
requestOption.synchronous = YES;
requestOption.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
[[PHImageManager defaultManager] requestImageDataForAsset:mPhasset options:requestOption resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
dataResponse([info objectForKey:#"PHImageFileURLKey"]);
}];
}
This approach works on a simulator, but fails on a real device: empty files are uploaded to the server most likely due to failure to read from the local storage.
Log shows the notice
Notice: Sandbox: MyApp(213) deny file-read-data
/private/var/mobile/Media/DCIM/101APPLE/IMG_1570.PNG
I believe this note means that app can't access the file by specified path.
Also I've tried an alternative approach uploading file by appending with NSData which is returned from request PHAsset data. but this approach is unusable in case of large media files. since the entire file is loaded into the memory.
Any thoughts?
You shouldn't use requestImageDataForAsset(_:options:resultHandler:) for large files. Reason being you don't want to load the entire media file into memory, you will quickly run out of memory and the app will crash. This typically means you shouldn't use it for large images or pretty much any video.
In my experience, attempting to upload directly from a PHAsset resource url will fail. Apple doesn't seem to grant us the permissions required to upload direct from PHAsset source files. See forum post here. This is a pain because it forces us to use a ton of extra disk space if we want to upload a video.
In order to get a local file url for a video file that you intend to upload, you'll want to use either:
requestExportSessionForVideo(_:options:exportPreset:resultHandler:)
or
requestAVAssetForVideo(_:options:resultHandler:)
You will use these methods to export a copy of the video file to a location on disk that you control. And upload from that file. Bonus feature: both of these methods will download the file from iCloud if necessary.
Check out the VimeoUpload library for details on all things related to video upload. Disclaimer: I'm one of the authors of the library.
Even if you're not uploading to Vimeo servers, you can use the PHAssetExportSessionOperation and ExportOperation classes included in VimeoUpload to do exactly what you're looking to do. See the repo README for details on obtaining a file url for a PHAsset. It also includes tools for obtaining a file url for an ALAsset.
If you're not interested in using PHAssetExportSessionOperation or ExportOperation, check out their implementations for details on how to use the Apple classes under the hood.
The NSData object returned by requestImageDataForAsset is memory mapped - so the entire file is not loaded into memory. So this method will for without any issues for images.
For videos you should use the appropriate methods requestExportSessionForVideo or requestAVAssetForVideo
If you can limit your deployment target to iOS 9, you should also take a look at the methods of PHAssetResourceManager

Obtain the path of app Group from FileManager

I am trying to update my app for WatchKit and I save a NSKeyedArchiver file to the NSDocumentsDirectory normally.
With updating to app groups I need to store it in the app groups folder. The issue I am having is I cant figure out how to just get the path, and not have it referenced as a file I am looking for.
The way it is set up now is to find the file it gives the path as a NSString
/Users/ME/Library/Developer/CoreSimulator/Devices/43F/data/Containers/Data/Application/5E/Documents/fav
but when I store to app groups, no matter which way I access the folder, it is returned
file:///Users/ME/Library/Developer/CoreSimulator/Devices/43F/data/Containers/Data/Application/5E/Documents/fav
What is the best way to just obtain the path to the shared group, rather than have the app looking for the direct file?
So coffee deprived me had forgotten about the .path for filemanager.
NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com"];
NSString *tmpPath = [NSString stringWithFormat:#"%#", fileManagerURL.path];
NSString *finalPath = [NSString stringWithFormat:#"%#",[string stringByAppendingString:#"/Favourites2"]];
I was running into the same problem. I was going through the whole process of building a string to my save location and now I'm switching over to app groups and using the
NSURL *fileManagerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupID];
Well, the problem is, now instead of a string to the location that starts with "/Users/yourname/Library..." you get "file:///Users/yourname/Library..."
Here's what I did. I created the NSURL. Then I called absoluteString on it.
NSURL groupPath = [[fileManager containerURLForSecurityApplicationGroupIdentifier:groupID] absoluteString];
I now have a string that I need to strip off the first 7 characters of, then my old code works just fine, except now instead of being in the Documents directory, it's in a shared app group that can be accessed by both my old code and my new watchkit extension.
Here's the code to strip off the first 7 characters (index 6 since you start with 0), you should be able to use either method...
NSString *newGroupPath = [groupPath substringFromIndex:6];
or
NSString *newGroupPath = [groupPath substringWithRange:NSMakeRange(6, [str length]-6)];
This just removes the "file://" from the absoluteString that was made from the NSURL and gives you back your older string path the starts "/Users/YourName/Library/Developer/yada yada yada"
Hope that helps you, I have spent 4 hours figuring it out.
It seems to work for me on the simulator, I haven't tried it on the Watch yet. But at least my app is now working the way it was before, just saving the data in a shared app group. (I have a singleton that manages all of my data throughout my app, and I want that same singleton to provide data to my watch app).

Is it possible to use NSFileManager's `copyItemAtURL:toURL:error:` method when the app is in the background?

I'm working on an iOS app, uploading videos from the Camera Roll, using NSURLSession with a background configuration. The user can queue up multiple videos for upload (the queue is executed serially).
A single upload consists of:
Getting an AVURLAsset reference to the PHAsset using PHImageManager's requestAVAssetForVideo method.
Copying the resource to a temp directory (because you cannot upload straight from the AVURLAsset's URL).
Uploading the resource using an NSURLSessionUploadTask
I can queue up multiple videos and the process works well in the foreground. They complete one after another.
But if I queue up several videos and then background the app. As soon as execution reaches the copyItemAtURL:toURL:error: stage it stalls until I foreground the app again. (I know this because I'm posting debug statements in local notifications, visible on the lock screen).
Is it possible to use copyItemAtURL:toURL:error: when the app is backgrounded?
If not, is it possible to use an AVAssetExportSession instead?
Edit 1
I've tested this while connected to the debugger and while not, the app never executes the copy command. But does so only when the app is foregrounded again.
Edit 2
To clarify, execution stalls at the copy command. It doesn't yield an error and continue execution. And implementing the fileManager:shouldCopyItemAtURL:toURL: delegate method doesn't change things. The docs for that method also say:
Prior to copying each item, the file manager asks its delegate if it
should actually do so. It does this by calling the
fileManager:shouldCopyItemAtURL:toURL: method; if that method is not
implemented (or the process is running in OS X 10.5 or earlier) it
calls the fileManager:shouldCopyItemAtPath:toPath: method instead. If
the delegate method returns YES, or if the delegate does not implement
the appropriate methods, the file manager proceeds to copy the file or
directory.
What kind of URL are you getting for the AVURLAsset? There are two, and the distinction is always relevant (take the URL generated by UIImagePickerController versus the one used by AVAssetReader/Writer:
I can demonstrate with this:
[[PHImageManager defaultManager] requestAVAssetForVideo:phAsset options:nil resultHandler:^(AVAsset *avAsset, AVAudioMix *audioMix, NSDictionary *info) {
NSURL *url = (NSURL *)[[(AVURLAsset *)avAsset URL] fileReferenceURL];
NSLog(#"url = %#", [url absoluteString]);
NSLog(#"url = %#", [url relativePath]);
}];
Whereas phAsset is the PHAsset object, and avAsset is the resulting AVAsset object generated by PHImageManager, the output to the console from the above code will produce, for example:
2016-04-16 01:15:40.155 ChromaEpsilon[3423:933358] url = file:///.file/id=16777218.8262005
2016-04-16 01:15:40.155 ChromaEpsilon[3423:933358] url = /private/var/mobile/Media/DCIM/108APPLE/IMG_8421.MOV
There's more than just these two, I believe, but start here.

Stream video while downloading iOS

I am using iOS 7 and I have a .mp4 video that I need to download in my app. The video is large (~ 1 GB) which is why it is not included as part of the app. I want the user to be able to start watching the video as soon as is starts downloading. I also want the video to be able to be cached on the iOS device so the user doesn't need to download it again later. Both the normal methods of playing videos (progressive download and live streaming) don't seem to let you cache the video, so I have made my own web service that chunks up my video file and streams the bytes down to the client. I start the streaming HTTP call using NSURLConnection:
self.request = [[NSMutableURLRequest alloc] initWithURL:self.url];
[self.request setTimeoutInterval:10]; // Expect data at least every 10 seconds
[self.request setHTTPMethod:#"GET"];
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:YES];
When I receive a data chunk, I append it to the end of the local copy of the file:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:[self videoFilePath]];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:data];
}
If I let the device run, the file is downloaded successfully and I can play it using MPMoviePlayerViewController:
NSURL *url=[NSURL fileURLWithPath:self.videoFilePath];
MPMoviePlayerViewController *controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
controller.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
[self presentMoviePlayerViewControllerAnimated:controller];
However, if I start the player before the file is completely downloaded, the video starts playing just fine. It even has the correct video length displayed at the top scrubber bar. But when the user gets to the position in the video that I had completed downloading before the video started, the video just hangs. If I close and reopen the MPMoviePlayerViewController, then the video plays until it gets to whatever location I was then at when I launched the MPMoviePlayerViewController again. If I wait until the entire video is downloaded, then the video plays without a problem.
I am not getting any events fired, or error messages printed to the console when this happens (MPMoviePlayerPlaybackStateDidChangeNotification and MPMoviePlayerPlaybackDidFinishNotification are never sent after the video starts). It seems like there is something else that is telling the controller what the length of the video is other than what the scrubber is using...
Does anyone know what could be causing this issue? I am not bound to using MPMoviePlayerViewController, so if a different video playback method would work in this situation I am all for it.
Related Unresolved Questions:
AVPlayer and Progressive Video Downloads with AVURLAssets
Progressive Video Download on iOS
How to play an in downloading progress video file in IOS
UPDATE 1
I have found that the video stall is indeed because of the file size when the video starts playing. I can get around this issue by creating a zero-ed out file before I start the download and over overwrite it as I go. Since I have control over the video streaming server, I added a custom header so I know the size of the file being streamed (default file size header for a streaming file is -1). I am creating the file in my didReceiveResponse method as follows:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// Retrieve the size of the file being streamed.
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSDictionary *headers = httpResponse.allHeaderFields;
NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
self.streamingFileSize = [formatter numberFromString:[headers objectForKey:#"StreamingFileSize"]];
// Check if we need to initialize the download file
if (![[NSFileManager defaultManager] fileExistsAtPath:self.path])
{
// Create the file being downloaded
[[NSData data] writeToFile:self.path atomically:YES];
// Allocate the size of the file we are going to download.
const char *cString = [self.path cStringUsingEncoding:NSASCIIStringEncoding];
int success = truncate(cString, self.streamingFileSize.longLongValue);
if (success != 0)
{
/* TODO: handle errors here. Probably not enough space... See 'man truncate' */
}
}
}
This works great, except that truncate causes the app to hang for about 10 seconds while it creates the ~1GB file on disk (on the simulator it is instant, only a real device has this problem). This is where I am stuck now - does anyone know of a way to allocate a file more efficiently, or a different way to get the video player to recognize the size of the file without needing to actually allocate it? I know some filesystems support "file size" and "size on disk" as two different properties... not sure if iOS has something like that?
I figured out how to do this, and it is much simpler than my original idea.
First, since my video is in .mp4, the MPMoviePlayerViewController or AVPlayer class can play it directly from a web server - I don't need to implement anything special and they can still seek to any point in the video. This must be part of how the .mp4 encoding works with the movie players. So, I just have the raw file available on the server - no special headers required.
Next, when the user decides to play the video I immediately start playing the video from the server URL:
NSURL *url=[NSURL fileURLWithPath:serverVidelFileURLString];
controller = [[MPMoviePlayerViewController alloc] initWithContentURL:url];
controller.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
[self presentMoviePlayerViewControllerAnimated:controller];
This makes it so the user can watch the video and seek to any location they want. Then, I start downloading the file manually using NSURLConnection like I had been doing above, except now I am not streaming the file, I just download it directly. This way I don't need the custom header since the file size is included in the HTTP response.
When my background download completes, I switch the playing item from the server URL to the local file. This is important for network performance because the movie players only download a few seconds ahead of what the user is watching. Being able to switch to the local file as soon as possible is key to avoid downloading too much duplicate data:
NSTimeInterval currentPlaybackTime = videoController.moviePlayer.currentPlaybackTime;
[controller.moviePlayer setContentURL:url];
[controller.moviePlayer setCurrentPlaybackTime:currentPlaybackTime];
[controller.moviePlayer play];
This method does have the user downloading two video files at the same time initially, but initial testing on the network speeds my users will be using shows it only increases the download time by a few seconds. Works for me!
You gotta create an internal webserver that acts like a proxy! Then set your player to play the movie from the localhost.
When using HTTP protocol to play a video with MPMoviePlayerViewController, the first thing the player does is to ask for the byte-range 0-1 (first 2 bytes) just to obtain the file length. Then, the player asks for "chunks" of the video using the "byte-range" HTTP command (the purpose is to save some battery).
What you have to do is to implement this internal server that delivers the video to the player, but your "proxy" must consider the length of your video as the full length of the file, even if the actual file hasn't been completely downloaded from the internet.
Then you you set your player to play a movie from " http:// localhost : someport "
I've done this before... it works perfectly!
Good luck!
I can only assume that the MPMoviePlayerViewController caches the file length of the file when you started it.
The way to fix (just) this issue is to first determine how large the file is. Then create a file of that length. Keeping an offset pointer, as the file downloads, you can overwrite the "null" values in the file with the real data.
So you get to a specific point in the download, start the MPMoviePlayerViewController, and let it run. I'd also suggest you use the "F_NOCACHE" flag (with fcntl()) so you bypass the file block cache (which means you will lower your memory footprint).
The downside to this architecture is that if you get stalled, and the movie player gets ahead of you, well, the user is going to have a pretty bad experience. Not sure if there is any way for you to monitor and take preemptive action.
EDIT: its quite possible that the video is not read sequentially, but certain information requires the player to essentially look ahead for something. If so, then this is doomed to fail. The only other possible solution is to use some software tool to sequentially order the file (I'm no video expert so cannot comment from experience on any of the above).
To test this out, you can construct a "damaged" video of varying lengths, and test that to see what works and what does not. For instance, suppose you have a 100Meg file. Write a little utility program, and over write the last 50Megs of data with zeros. Now play this video. Its should fail 1/2 through. If it fails right away, well, you now know that its seeking in the file.
If non sequential, its possible that its looking at the last 1000 bytes or so, in which case if you don't overwrite that things work as you want. If you get lucky and this is the case, you would eventually download the last 1000 bytes, then then start from the front of the file.
It really gets down to finding some way before introducing real networking into the picture, to play a partial file. You will surely find it easier to artificially introduce the networking conditions without really doing it real time.

Resources