Getting byte Data from File - ios

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.

Related

Dropbox sync api large video file upload

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).

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.

NSURLSession and stream upload in background

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

Xcode - iOS - Simply upload a file to FTP Server

I'm trying to work with FTP Servers.
I have googled around for everything and everything is hard to understand for beginners like me. SimpleFTPSample is hard to understand because it is so much at a time. views, buttons, labels, textflelds, upload, download, request, list, get. Same with BlackRaccoon and everything else.
How to Simply and programily upload "test.txt" to FTP Server: "192.168.1.111" in Xcode (iPhone app) without views or button. Just code that can be in the ViewDidLoad for example.
Maybe something like this?:
NSURL* url = [NSURL URLWithString:#"ftp://username:pw#189.92.32.34"];
CFReadStreamRef stream = CFReadStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url);
stream.delegate= self;
[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[stream open];
but which file?
expand this, or write a new code. i don't know, this is new for me.
Thanks Jonathan
As the writer of Black Raccoon perhaps I'm biased (well, I KNOW I'm biased), but I've attempted to make it as simple and powerful as possible. Let's look at what you want to do, upload a file:
There are four things we need to upload a file - start up code, then four delegate methods: overwrite check, data, success and the fail. Let's assume that you read the entire file into memory (okay for small files less than 2 megs).
First, you need this in your header:
BRRequestUpload *uploadData; // Black Raccoon's upload object
NSData *uploadData; // data we plan to upload
Now for the code part:
- (IBAction) uploadFile :(id)sender
{
//----- get the file path for the item we want to upload
NSString *applicationDocumentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filepath = [NSString stringWithFormat: #"%#/%#", applicationDocumentsDir, #"file.text"];
//----- read the entire file into memory (small files only)
uploadData = [NSData dataWithContentsOfFile: filepath];
//----- create our upload object
uploadFile = [[BRRequestUpload alloc] initWithDelegate: self];
//----- for anonymous login just leave the username and password nil
uploadFile.path = #"/home/user/myfile.txt";
uploadFile.hostname = #"192.168.1.100";
uploadFile.username = #"yourusername";
uploadFile.password = #"yourpassword";
//----- we start the request
[uploadFile start];
}
The first will be asking your code if you want to overwrite an existing file.
-(BOOL) shouldOverwriteFileWithRequest: (BRRequest *) request
{
//----- set this as appropriate if you want the file to be overwritten
if (request == uploadFile)
{
//----- if uploading a file, we set it to YES (if set to NO, nothing happens)
return YES;
}
}
Next, Black Raccoon will ask you for chunks of data to send. If you have a very large file you NEVER want to try to send it all in one shot - Apple's API will choke and drop data. However, we only have one small chunk so we do this:
- (NSData *) requestDataToSend: (BRRequestUpload *) request
{
//----- returns data object or nil when complete
//----- basically, first time we return the pointer to the NSData.
//----- and BR will upload the data.
//----- Second time we return nil which means no more data to send
NSData *temp = uploadData; // this is a shallow copy of the pointer
uploadData = nil; // next time around, return nil...
return temp;
}
Remember we can ONLY do this for a small file.
Next we have our completion handler (if things worked according to plan):
-(void) requestCompleted: (BRRequest *) request
{
if (request == uploadFile)
{
NSLog(#"%# completed!", request);
uploadFile = nil;
}
}
Lastly we have our failure handler:
-(void) requestFailed:(BRRequest *) request
{
if (request == uploadFile)
{
NSLog(#"%#", request.error.message);
uploadFile = nil;
}
}
It would be WONDERFUL if it was as simple as saying [BRFtpUploadTo: dest srcfile: srcFile destfile: dstFile] but there are many reasons why you SHOULDN'T. Part of it has to do with how Apple has implemented their internal FTP. There are also the issues of blocking, errors, etc. In the end, FTP sounds like it should be trivial but ends up being a bit of a nightmare.
FTP is non-trivial which is why there are so many implementations. I'm not arguing that Black Raccoon is the best, but it is maintained with response to issues being between minutes to a couple of days.
It may look daunting at first, but Black Raccoon is, in my opinion, one of the better FTP libraries. I've spent a lot of time and effort to make it a quality product with excellent response to issues. How do I do this for free? Volume. ;)
Good luck with whatever FTP software you end up with!
Upload path is required when uploading. That is the way FTP works.
The port is the standard FTP port. I know of no way to change this without violating the API. If you figure it out, you stand a good chance of not passing Apple's check.
This code will upload/download any file.
I do not know how to make this work under secure conditions. This uses Apple's FTP protocol. There are other FTP packages that have built this from scratch and are far more intelligent. I would look into them.
BR was designed because I needed simple FTP communication. White Raccoon didn't do this for me because at the time (it has since been modernized).

iOS: sending .jpg image as base64 to tcp server

This one is going to kill me. I'm so close to getting this done except for this one stupid problem. And I am not sure I will be able to adequately describe the problem, but I'll try.
My enterprise app uses the iPhone camera to take pictures of receipts of purchases made by our field personnel. I used a real cool API for turning the jpeg data to base 64 (https://github.com/nicklockwood/Base64) to send via TCP connection to the VB 2010 server, which reads it as a text string and converts it back to a binary.
When the base64 file is created, it is first saved to disk on the phone, because there may be more images. Then when ready to send, the process will read each base64 file and send it one at a time.
The text string created by the base64 function is quite large, and at first it was only sending about 131,000 bytes, which would convert back to binary easily enough but would render about 1/4 to 1/3 of the image. I just figured that the data was being truncated because the app was trying to get ahead of itself.
So then I found a nice snippet that showed me how to use the NSStreamEventHasSpaceAvailable event to split the base64 string into several chunks and send them sequentially. (http://www.ios-developer.net/iphone-ipad-programmer/development/tcpip/tcp-client) That works great insofar as it sends the full file -- that is, the resulting file received by the server is the correct size, the same as the base64 file before it's sent.
The problem here is that at some point the file received by the server is corrupted because it seems to start all over at the beginning of the file... in other words, the data starts to repeat itself.
The odd part is that the repeating part starts at exactly the same spot in the received file every time: at position 131016. It doesn't start the repetition at the end of the file, it just interrupts the file at that point and jumps back to the beginning. And it happens that that was the size of the file that was sent before I started using the HasSpaceAvailable event. I can't figure out what the significance of the value 131,016 is. Maximum buffer size somewhere?
Using all kinds of NSLogs and breakpoints, I have pretty much determined that the data is leaving the phone that way, and not being scrambled by the server. I also wrote in an NSMailComposeViewer method that would email me the base64 file as an attachment, and it comes through perfectly.
Here is the code for when the file is read from disk and sent to the server:
int i;
for (i = 0; i < [imageList count];i++){
NSArray *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory= [documentsPath objectAtIndex:0]; //Get the docs directory
NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:imageFileName];
imageFileName = [imageList objectAtIndex:i] ;
NSLog(#"Image index: %d - image file name: %#",i,imageFileName);
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:imagePath];
if(fileExists == YES){
NSString *imageReceipt = [NSString stringWithContentsOfFile:imagePath encoding:NSASCIIStringEncoding error:nil];
int32_t imageStringLen = [imageReceipt length];
NSString *imageSize = [NSString stringWithFormat: #"%d",imageStringLen];
NSString *currentImage = [NSString stringWithFormat:#"image,%#,%#,%#",imageFileName,imageSize,imageReceipt]; //creates a CSV string with a header string with the filename and file size, and then appends the image data as the final comma-separated string.
data = [[NSMutableData alloc] initWithData:[currentImage dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
And then here is the code that uses the HasSpaceAvailable event:
case NSStreamEventHasSpaceAvailable:
if (data != nil)
{
//Send rest of the packet
int ActualOutputBytes = [outputStream write:[data bytes] maxLength:[data length]];
int totalLength = [data length];
if (ActualOutputBytes >= totalLength)
{
//It was all sent
data = nil;
}
else
{
//Only partially sent
[data replaceBytesInRange:NSMakeRange(0, ActualOutputBytes) withBytes:NULL length:0]; //Remove sent bytes from the start
}
}
break;
(I especially like this code because it would allow placing a ProgressView control on the screen.)
The network stream event handler code is in the root view controller, but the image data in base64 is being sent from another view controller. My instinct tells me that this is not a problem because it has worked fine until now, but with much shorter strings.
Now, there's one other issue that may be related -- and probably is. I can't seem to complete the transfer of the data unless I close the app. The server doesn't see it until the connection is closed, I guess. I have tried placing [outputStream close] in various places in the code to no avail. I've also tried terminating the base64 string with a linefeed or carriage return or both.
The server is programmed to save the file when it has seen the correct number of bytes, but that never happens until the app is closed. I know by using WireShark on the server that some of the data is being received, but the remaining data, as I have said, doesn't arrive until the app is closed.
I suspect that this last part (completing the transfer) is the problem, but for the life of me, I can't find anything online that addresses this, unless I am just too ignorant to know what search terms to use.... which is highly likely.
I hope I have given enough information. Can anyone help me?
EDIT
The solution to the problem appeared to be different than what was suspected in the original answer. Through the discussion in the comments the QP has been led to a solution. Here is a summary:
The transmission of data through a raw TCP socket requires thorough handling of the enqueing logic, which was not properly taken into account by the QP. I recommended to use the socket library CocoaAsyncSocket which handles this part of the task and which in turn led the QP to a working solution.
Original Answer:
I'd guess NSString is not up to the task. It's made to hold, well, strings.
Try to read the file into an NSData directly instead and sending it as binary. (Its ascii in the end, isn't it?) Besides, this will be much more resource friendly than your current code.

Resources