I have some problems with using NSURLSession to upload photos from Asset Library to the server.
At first NSURLSession doesn't support streaming upload. I got an exception when trying to using that:
#property (nonatomic, strong) NSURLSession *uploadSession;
...
_uploadSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration
backgroundSessionConfiguration:kUploadBackgroundURLSessionIdentifier] delegate:self delegateQueue:nil];
...
NSURLSessionUploadTask *task = [self.uploadSession uploadTaskWithStreamedRequest:URLRequest];
This is an exception:
Terminating app due to uncaught exception 'NSGenericException', reason: 'Upload tasks in background sessions must be from a file'
That's really strange because Apple's manual doesn't contain any information about using only uploadTaskWithRequest:fromFile: for background session. What if I would like to upload really huge video file from Asset Library? Should I save it previously to my tmp directory?
Looks like the only reason is to use uploadTaskWithRequest:fromFile: anyway, right? But then I have a question how server get to know what's part of the file is uploading right now if uploading process was interrupted and started to upload next part in background?
Should I manage something for that? Previously I used Content-Range for that in URL request if I wanted to continue upload part of the file which was started previously. Now I can't do that - I have to create an URL Request before creating upload task and looks like NSURLSession have to do something like that automatically for me?
Does anyone do something like that already? Thanks
Convert to NSData and copy and write in app folder
ALAsset *asset = [cameraRollUploadImages objectAtIndex:startCount];
ALAssetRepresentation *representation = [asset defaultRepresentation];
// create a buffer to hold the data for the asset's image
uint8_t *buffer = (Byte *)malloc(representation.size);// copy the data from the asset into the buffer
NSUInteger length = [representation getBytes:buffer
fromOffset:0
length:representation.size
error:nil];
// convert the buffer into a NSData object, free the buffer after
NSData *image = [[NSData alloc] initWithBytesNoCopy:buffer
length:representation.size
freeWhenDone:YES];
Right now, there is no way otherthan saving the picture to local file system or temp directory.
Following code make sure your data won't be lost with exif tags. (ALAsset => NSData)
ALAssetRepresentation *assetRepresentation = [(ALAsset *)assetBeingUploaded defaultRepresentation];
uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t)*[assetRepresentation size]);
NSUInteger buffered = 0;
if (buffer != NULL)
buffered = [assetRepresentation getBytes:buffer fromOffset:0.0 length:assetRepresentation.size error:nil];
self.imageBeingUploaded = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
The upload task in background session doesn't support completion handler. We should go for.,
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL;
I doubt how do we get the response headers or body in case if we are using background session & uploadtask with request using file?
A clean workaround will be to create a NSOperation which will copy the file from the asset library to your temporary folder using NSStream, so you won't get a crash in case of a huge file, when the operation completes you schedule a upload of that temporary file, when the upload finishes you delete it.
In my case, i need to send the file in multipart format so the creation of the temporary file is necessary but i encounter a problem in uploading large files, more then 2 Gb, example movies over 20 minutes.
You can't upload NSData in background you need to upload file format. You can create it by directory path
Related
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.
I got a problem , How to know the file is accessible ???
For example , the file is writing , but not finish yet.
And I check the file again ,
I use
[[NSFileManager defaultManager] fileExistsAtPath:filePath];
It return true ... but the file is not accessible .
How to determine the file is ready ?
Thanks
Webber
EDIT
Maybe I'm not telling my problem clearly . I mean if you start transport a video file . before transport finish . the video file is not accessible , but you still can get a part of transporting file.
Write Data in async way using GCD concept and once it is completed, the completion handler will be executed.(Where completion of a writing process shall be detected)
The dispatch I/O convenience API lets you perform asynchronous read and write operations on file descriptors. This API supports stream-based semantics for accessing the contents of the file-descriptor.
Method:
void dispatch_write ( dispatch_fd_t fd, dispatch_data_t data, dispatch_queue_t queue, void (^handler)(dispatch_data_t data, int error) );
Ref: https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_write
Basically you need to check the attributes of the file and particullary the NSFileBusy attribute:
if ([fileManager fileExistsAtPath:filePath]) {
NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil];
NSLog(#"File Busy %#", [attributes objectForKey:NSFileBusy]);
}
NSFileCoordinator and NSFilePresenter are created just for that. You may find interesting Advanced iCloud Document Storage video from wwdc that covers the usage of this classes. Building a Document-based App will be great to watch too.
Try to fetch data of file by using following code :
NSURL *url = [NSURL fileURLWithPath:filePath];
NSData *readData = [NSData dataWithContentsOfURL:url];
I have created a download task. I have cancelled the task in my application with cancelByProducingResumeData and saved to disk. When user relaunch application and click on resume button I have again started the download task by self.downloadTask = [session downloadTaskWithResumeData:resumableData];. But this gives error.
Invalid resume data for background download. Background downloads must use http or https and must download to an accessible file
EDIT: I have verified the tmp directory on simulator contains the downloadData but not able to access that.
After restarting the application link on the tmp file in resuminData is not valid. Change the field-"NSURLSessionResumeInfoLocalPath" in resuminData, to the correct path of the tmp file.
use:
NSMutableDictionary* resumeDictionary = [NSPropertyListSerialization propertyListWithData:resumingData options:NSPropertyListMutableContainers format:NULL error:&error];
...
NSString* badFilePath = [resumeDictionary objectForKey:#"NSURLSessionResumeInfoLocalPath"];
...
[resumeDictionary setObject:trueTmpfile forKey:#"NSURLSessionResumeInfoLocalPath"];
...
NSData* newResumingData = [NSPropertyListSerialization dataFromPropertyList:resumeDictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:&errorDesc];
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).
This is my first real project. I have an app that captures several seconds of video using AVFoundation, outputs this to a file in the documents directory and lets the user preview the video before they upload it using HTTP and a PHP script on my website.
All the video capture and preview work perfectly but I am stuck on uploading the video file.
I learnt a lot from this simpleSDK video which shows how to achieve the desired effect using a video file stored in the apps main bundle.
The code from the tutorial that set up videoData ready to upload originally looked like this:
NSData *videoData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Movie" ofType:#"mov"]];
NSString *urlString = #"http://www.iphonedevnation.com/video-tutorial/upload.php";
The filename of the video file that I need to upload is always unique and generated using CFUUIDCreateString. I join this string to the path for the documents directory, add ".mov" to the end of it and save it into a text file for retrieving later.
This all works as I am able to retrieve the filename from the file and use it to preview the movie clip elsewhere in the app.
My path is in an NSString, that I have tried converting to NSURL and removing the file suffix to get it to work with the NSData *videoData.........line but it doesn't compile, I get an "No known class method for selector 'dataWithContentsOfFile:ofType.' error. I am targeting iOS 5 and using Xcode 4.3 with ARC and Storyboards.
I've been at this for best part of 5 hours now so hopefully someone can help. My code, which included tips from elsewhere on converting from a NSString to NSURL follows:
NSString *content = [[NSString alloc] initWithContentsOfFile:lastSavedTalentFilenamePath
usedEncoding:nil
error:nil];
NSLog(#"content=%#",content);
//Need to now remove the '.mov' file type identifier
NSString *shortContent= [content substringToIndex:[content length]-4];
NSLog(#"***************shortContent***************%#", shortContent);
NSURL *convertedContent = [NSURL fileURLWithPath:shortContent];
NSLog(#"***************convertedContent***********%#",convertedContent);
NSData *videoData = [NSData dataWithContentsOfFile:convertedContent ofType:#"mov"];];
There is no NSData method called dataWithContentsOfFile:ofType:
The methods available are:
+ dataWithContentsOfFile:
+ dataWithContentsOfFile:options:error:
both of which take the file location as an NSString so there's not need to convert to an NSURL