I'm trying to do a simple implementation of AVAssetResourceLoaderDelegate on OSX/iOS as a sort of sanity check before continuing to implement a more complex version. I figured I would start by just making it load the full video file in one big read. To my surprise I haven't been able to get this to work.
Here's my code:
- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(nonnull AVAssetResourceLoadingRequest *)loadingRequest
{
NSString* path = #"/Users/andrew/Projects/work/videotextureplugin-streaming/unityProject/Assets/StreamingAssets/BigBuckBunny_640x360.m4v";
AVAssetResourceLoadingDataRequest* dataRequest = loadingRequest.dataRequest;
AVAssetResourceLoadingContentInformationRequest* contentRequest = loadingRequest.contentInformationRequest;
// handle content request
if (contentRequest)
{
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:NULL];
unsigned long long fileSize = [attributes fileSize]; // in bytes
NSLog(#"File Size: %llu", fileSize);
contentRequest.contentType = #"com.apple.m4v-video";
contentRequest.contentLength = fileSize;
contentRequest.byteRangeAccessSupported = NO;
}
// handle data request
if (dataRequest)
{
NSFileHandle* file = [NSFileHandle fileHandleForReadingAtPath:path];
NSData* requestedData = [file readDataToEndOfFile];
NSLog(#"Requested data length: %lu", (unsigned long)[requestedData length]);
[loadingRequest.dataRequest respondWithData:requestedData];
[loadingRequest finishLoading];
}
return YES;
}
The file size and data length values are correct when they print. It just says "AVPlayer ready to play but has zero video tracks" in the log. I've tried a few different video files, as well as various contentType settings with no luck. All of the files I've tested work fine if I open them via a URL like "file:///" followed by the same path used above.
Thanks in advance for any assistance or advice on debugging this.
Related
I'm developing an app that can receive messages/files while it's on the background via webRTC.
When a file is received, I write it to disk. But when trying to access it later (even between app launches) that file doesn't exist.
User's document folder has NSFileProtectionCompleteUntilFirstUserAuthentication attribute.
I've tried to create the file on disk using NSData's [writeToURL:options:error:], [writeToFile:options:error:]; NSFileManager's [createFileAtPath:contents:attributes:] and also NSFileHandle's methods.
All of them successfully create the file at the designated path/url. Right after creation I check whether file exists with NSFileManager's [attributesOfItemAtPath:error:] which shows me the following:
attributes: {
NSFileCreationDate = "2018-05-07 18:47:50 +0000";
NSFileExtensionHidden = 0;
NSFileGroupOwnerAccountID = 501;
NSFileGroupOwnerAccountName = mobile;
NSFileModificationDate = "2018-05-07 18:47:50 +0000";
NSFileOwnerAccountID = 501;
NSFileOwnerAccountName = mobile;
NSFilePosixPermissions = 420;
NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication;
NSFileReferenceCount = 1;
NSFileSize = 92156;
NSFileSystemFileNumber = 695101;
NSFileSystemNumber = 16777219;
NSFileType = NSFileTypeRegular;
}
[[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]] also shows me that the file exists after write.
Considering it could be a threading problem, I've also tried to write that file putting it on a main thread block, but result is the same. First file seems to be written but, when trying to access it afterwards it's like it never was.
Is there anything I could be missing?
edit: Added function I use to write.
- (void) saveFileData:(NSData *)fileData completionHandler:(void(^)(BOOL success))completionHandler {
NSURL *fileURL = [self fileURL];
NSError *error = nil;
[fileData writeToURL:fileURL options:NSDataWritingAtomic error:&error];
if (error) {
ZLogError(ZLogTypeFile, #"[%#] could not be saved: %#", self.fileKey, error);
completionHandler(NO);
return;
}
ZLogDebug(ZLogTypeFile, #"<file: %#> exists after write:%d", fileURL, [[NSFileManager defaultManager] fileExistsAtPath:[fileURL path]]);
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:&error];
ZLogDebug(ZLogTypeFile, #"attributes: %#", attributes);
completionHandler(YES);
}
output comes as (where Documents is the users NSDocumentDirectory in the app)
[file: /Documents/57/Downloads/Images/9d1687ab5f4374a2c00429a24316b5ccd3fb0a67.png] exists after write:1
and getting the file (an image):
- (UIImage *) imageFromURL:(NSURL *)imageURL {
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[imageURL path]];
if (!fileExists) {
ZLogDebug(ZLogTypeFile, #"[file: %#] exists: %d", imageURL, fileExists);
return nil;
}
return [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
}
in log (note I've taken out the long path before Documents directory just here):
[file: /Documents/57/Downloads/Images/9d1687ab5f4374a2c00429a24316b5ccd3fb0a67.png] exists: 1
I'm currently using the Black Raccoon library in my iOS app to upload small files to an FTP server using the following suggested code:
- (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]; }
- (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; }
What do I need to do to upload large files (greater than 50MB) without needing to write the entire file to be uploaded into memory?
I'm working on iOS project that supports iTunes file sharing feature. The goal is realtime tracking incoming/changed data's.
I'm using (kinda modified) DirectoryWatcher class from Apple's sample code
and also tried this source code.
The data is NSBundle (*.bundle) and some bundles are in 100-500 MB ranges, depends on its content, some video/audio stuff. The bundles has xml based descriptor file in it.
The problem is any of these codes above fires notification or whatever else when the data just started copying and but not when the copy/change/remove process finished completely.
Tried next:
checking file attributes:
NSDictionary *fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:[contURL path] error:nil];
BOOL fileBusy = [[fileAttrs objectForKey:NSFileBusy] boolValue];
looking for the fileSize changes:
dispatch_async(_checkQueue, ^{
for (NSURL *contURL in tempBundleURLs) {
NSInteger lastSize = 0;
NSDictionary *fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:[contURL path] error:nil];
NSInteger fileSize = [[fileAttrs objectForKey:NSFileSize] intValue];
do {
lastSize = fileSize;
[NSThread sleepForTimeInterval:1];
fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:[contURL path] error:nil];
fileSize = [[fileAttrs objectForKey:NSFileSize] intValue];
NSLog(#"doing job");
} while (lastSize != fileSize);
NSLog(#"next job");
}
);
any other solutions?
The solution above works great for bin files, but not for .bundle (as .bundle files are directory actually). In order to make it work with .bundle, you should iterate each file inside .bundle
You can use GCD's dispatch sources mechanism - using it you can observe particular system events (in your case, this is vnode type events, since you're working with file system).
To setup observer for particular directory, i used code like this:
- (dispatch_source_t) fileSystemDispatchSourceAtPath:(NSString*) path
{
int fileDescr = open([path fileSystemRepresentation], O_EVTONLY);// observe file system events for particular path - you can pass here Documents directory path
//observer queue is my private dispatch_queue_t object
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fileDescr, DISPATCH_VNODE_ATTRIB| DISPATCH_VNODE_WRITE|DISPATCH_VNODE_LINK|DISPATCH_VNODE_EXTEND, observerQueue);// create dispatch_source object to observe vnode events
dispatch_source_set_registration_handler(source, ^{
NSLog(#"registered for observation");
//event handler is called each time file system event of selected type (DISPATCH_VNODE_*) has occurred
dispatch_source_set_event_handler(source, ^{
dispatch_source_vnode_flags_t flags = dispatch_source_get_data(source);//obtain flags
NSLog(#"%lu",flags);
if(flags & DISPATCH_VNODE_WRITE)//flag is set to DISPATCH_VNODE_WRITE every time data is appended to file
{
NSLog(#"DISPATCH_VNODE_WRITE");
NSDictionary* dict = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
float size = [[dict valueForKey:NSFileSize] floatValue];
NSLog(#"%f",size);
}
if(flags & DISPATCH_VNODE_ATTRIB)//this flag is passed when file is completely written.
{
NSLog(#"DISPATCH_VNODE_ATTRIB");
dispatch_source_cancel(source);
}
if(flags & DISPATCH_VNODE_LINK)
{
NSLog(#"DISPATCH_VNODE_LINK");
}
if(flags & DISPATCH_VNODE_EXTEND)
{
NSLog(#"DISPATCH_VNODE_EXTEND");
}
NSLog(#"file = %#",path);
NSLog(#"\n\n");
});
dispatch_source_set_cancel_handler(source, ^{
close(fileDescr);
});
});
//we have to resume dispatch_objects
dispatch_resume(source);
return source;
}
I found two rather reliable (i.e. not 100% reliable but reliable enough for my needs) approaches, which only work in conjunction with polling the contents of the directory:
Check NSURLContentModificationDateKey. While the file is being transferred, this value is set to the current date. After transfer has finished, it is set to the value the original file had: BOOL busy = (-1.0 * [modDate timeintervalSinceNow]) < pollInterval;
Check NSURLThumbnailDictionaryKey. While the file is being transferred, this value is nil, afterwards it cointains a thumbnail, but probably only for file types from which the system can produce a thumbnail. Not a problem for me cause I only care about images and videos, but maybe for you. While this is more reliable than solution 1, it hammers the CPU quite a bit and may even cause your app to get killed if you have a lot of files in the import directory.
Dispatch sources and polling can be combined, i.e. when a dispatch source detects a change, start polling until no busy files are left.
I am having the problem of writing large file (>200 MB) in iOS device (iPad) but in the simulator it works perfect.
I am using NSFileManager to create file and NSFileData to write file.
I think there is no problem in my code as it runs fine in the simulator.
Does anyone having the same problem?
To elaborate my situation:
I am saving chunk of files (3MB each) in my device which works fine. That means for a 300 MB file I have 100 chunks. Now, from the 100 chunks I want to create the actual file. So I am using the NSFileManager to create file in first iteration and then using NSFileData to write the 3MB data at the end of the file. While running the program it crashes after 61 chunks. I am guessing there might have some memory related issues in the iPad.
I am saving the chunk of files in fileDir in the format data-0, data-1, data-2...
I am applying the decrypt operation on data but for simplicity I have removed that portion.
// List of chunk files
NSArray *filelist= [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileDir error:err];
for(int i = 0; i < [filelist count]; i++) {
// Read the chunk of file
fileName = [[NSString alloc] initWithFormat:#"data-%d", i];
filePath = [fileDir stringByAppendingPathComponent:fileName];
fileReadHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
// Write in tempFile
if(offset == 0){
if([[NSFileManager defaultManager] createFileAtPath:tempFile contents:data attributes:nil]){
fileWriteHandle = [NSFileHandle fileHandleForWritingAtPath:tempFile];
NSLog(#"File was created!");
} else {
NSLog(#"File was not created.");
}
} else {
[fileWriteHandle seekToEndOfFile]; // Tried with comment out this line but same problem
// Write the decrypted data from chunk
[fileWriteHandle writeData:[[fileReadHandle readDataToEndOfFile] decryptedAES256DataUsingKey:AESEncryptionKey error:err]];
}
}
Edit (11.02.2013)
I tried with my previous code where I omitted the data decryption part.
Interestingly, the problem was in the decryption part I guess cause without the decryption it works fine. I have added the decryption code. For decryption I am using NSData+CommonCrypto library (it's non ARC) but my project is in ARC.
It could be an operating system issue because the NSFileHandle is never being closed for each chunk. I would recommend closing it.
Also, it looks like you have your variables declared outside the scope of the for loop. Unless you need those variables outside the loop, it's generally good to keep the scope of your variables as small as possible, especially if you are using ARC and are trying to think about when memory will be released.
If you think that the NSFileHandle is holding onto data in memory, try to use the -synchronizeFile method after writing each chunk to make sure in memory changes are reflected to disk.
Also, I moved the creation of the file you are writing to outside the loop, because it's easier to follow for me.
Try this adjustment:
// List of chunk files
NSArray *filelist= [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileDir error:err];
if([[NSFileManager defaultManager] createFileAtPath:tempFile contents:[NSData data] attributes:nil]){
NSLog(#"File was created!");
} else {
NSLog(#"File was not created.");
}
NSFileHandle *fileWriteHandle = [NSFileHandle fileHandleForWritingAtPath:tempFile];
for(int i = 0; i < [filelist count]; i++) {
// Read the chunk of file
NSString *fileName = [NSString stringWithFormat:#"data-%d", i];
NSString *filePath = [fileDir stringByAppendingPathComponent:fileName];
NSFileHandle *fileReadHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
NSData *data = [fileReadHandle readDataToEndOfFile];
// No longer using the file
[fileReadHandle closeFile];
// Write in tempFile
[fileWriteHandle writeData:data];
[fileWriteHandle synchronizeFile];// Flush any data in memory to disk
}
[fileWriteHandle closeFile];
Modifying the following code worked like a magic,
#autoreleasepool {
[fileWriteHandle writeData:[[fileReadHandle readDataToEndOfFile] decryptedAES256DataUsingKey:AESEncryptionKey error:err]];
}
I am looking for the library for splitting (divide) a binary file into multiple files.
If there is 20MB size of file named "test.m4v" in iOS temporary folder (NSTemporaryDirectory()),
I would like to split that to
test.m4v.000 (7MB)
test.m4v.001 (7MB)
test.m4v.002 (6MB)
Something like that (It doesn't have to be '7MB', could be 5MB like that)
like command line split command., I don't think we can call this command inside iOS app.
Is there iOS (free/paid) library to do that? I might need to just low level access and write it, but I am too lazy to do that ;)
This should work assuming the file isn't so large that it freaks out at dataWithContentsOfFile:filename. iOS might do caching in the background, but I don't know.
-(NSUInteger)splitFile:(NSString *)filename chunkSize:(NSUInteger)chunkSize {
NSUInteger chunksWritten;
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
NSData *fileData = [NSData dataWithContentsOfFile:filename];
NSString *newFileName;
NSRange dataRange;
for (chunksWritten = 0; chunksWritten * chunkSize < [fileData length]; chunksWritten++) {
newFileName = [filename stringByAppendingPathExtension:[NSString stringWithFormat:#"%03d", chunksWritten]];
dataRange = NSMakeRange(chunksWritten * chunkSize, MIN(chunkSize, [fileData length] - chunksWritten * chunkSize));
if (![fm createFileAtPath:newFileName contents:[fileData subdataWithRange:dataRange] attributes:nil]) {
NSLog(#"Error writing chunk #%d", chunksWritten);
break;
}
}
return chunksWritten;
}
The error checking obviously needs to be more robust.