Phasset + AfNetworking上传多个video
目前我正在使用以下代码上传video:
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { [UploadModel getAssetData:entity.asset resultHandler:^(NSData *filedata) { NSString *mimeType =[FileHelper mimeTypeForFileAtUrl:entity.fileUrl]; // NSError *fileappenderror; [formData appendPartWithFileData:filedata name:@"data" fileName: entity.filename mimeType:mimeType]; }]; } error:&urlRequestError];
GetAssetData方法
+(void)getAssetData: (PHAsset*)mPhasset resultHandler:(void(^)(NSData *imageData))dataResponse{ PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init]; options.version = PHVideoRequestOptionsVersionOriginal; [[PHImageManager defaultManager] requestAVAssetForVideo:mPhasset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) { if ([asset isKindOfClass:[AVURLAsset class]]) { NSURL *localVideoUrl = [(AVURLAsset *)asset URL]; NSData *videoData= [NSData dataWithContentsOfURL:localVideoUrl]; dataResponse(videoData); } }]; }
这种方法存在的问题是,只要上传大量/多个video文件,应用程序就会耗尽内存。 我想这是因为要求NSDATA(aka filedata
)上传文件(参见上面的方法)。 我试图请求使用方法appendPartWithFileURL
appendPartWithFileData
它在模拟器上的文件path。 并在真正的设备上出现错误,导致无法通过指定的path读取文件。 我在这里描述了这个问题PHAsset + AFNetworking。 无法将file upload到真实设备上的服务器
=======================================
更新:我已经修改了我的代码,以便通过本地path在新的iPhone 6s +上testing上传文件的方法,如下所示
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { NSString *mimeType =[FileHelper mimeTypeForFileAtUrl:entity.fileUrl]; NSError *fileappenderror; [formData appendPartWithFileURL:entity.fileUrl name:@"data" fileName:entity.filename mimeType:mimeType error:&fileappenderror]; if (fileappenderror) { [Sys MyLog: [NSString stringWithFormat:@"Failed to append: %@", [fileappenderror localizedDescription] ] ]; } } error:&urlRequestError];
在iPhone 6s +上进行testing会给出更加清晰的日志警告。由于调用appendPartWithFileURL
<Warning>: my_log: Failed to append file: The operation couldn't be completed. File URL not reachable. deny(1) file-read-metadata /private/var/mobile/Media/DCIM/100APPLE/IMG_0008.MOV 15:41:25 iPhone-6s kernel[0] <Notice>: Sandbox: My_App(396) deny(1) file-read-metadata /private/var/mobile/Media/DCIM/100APPLE/IMG_0008.MOV 15:41:25 iPhone-6s My_App[396] <Warning>: my_log: Failed to append file: The file “IMG_0008.MOV” couldn't be opened because you don't have permission to view it.
这里是用于从PHAsset获取本地文件path的代码
if (mPhasset.mediaType == PHAssetMediaTypeImage) { PHContentEditingInputRequestOptions * options = [[PHContentEditingInputRequestOptions alloc]init]; options.canHandleAdjustmentData = ^BOOL(PHAdjustmentData *adjustmeta){ return YES; }; [mPhasset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput * _Nullable contentEditingInput, NSDictionary * _Nonnull info) { dataResponse(contentEditingInput.fullSizeImageURL); }]; }else if(mPhasset.mediaType == PHAssetMediaTypeVideo){ PHVideoRequestOptions *options = [[PHVideoRequestOptions alloc] init]; options.version = PHVideoRequestOptionsVersionOriginal; [[PHImageManager defaultManager] requestAVAssetForVideo:mPhasset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) { if ([asset isKindOfClass:[AVURLAsset class]]) { NSURL *localVideoUrl = [(AVURLAsset *)asset URL]; dataResponse(localVideoUrl); } }]; }
所以问题保持不变 – 上传到服务器的文件是空的
上面提出的解决scheme只是部分正确的(之前是我自己发现的)。 由于系统不允许读取沙盒以外的文件,因此无法通过文件path访问(读取/写入)文件,只是复制文件。 在iOS 9及以上版本中,Photos Framework提供的API(不能通过NSFileManager完成,但只能使用Photos框架api)将文件复制到应用程序的沙箱目录中。 这是我在挖掘文档和头文件后使用的代码。
首先将文件复制到应用程序沙箱目录。
// Assuming PHAsset has only one resource file. PHAssetResource * resource = [[PHAssetResource assetResourcesForAsset:myPhasset] firstObject]; +(void)writeResourceToTmp: (PHAssetResource*)resource pathCallback: (void(^)(NSURL*localUrl))pathCallback { // Get Asset Resource. Take first resource object. since it's only the one image. NSString *filename = resource.originalFilename; NSString *pathToWrite = [NSTemporaryDirectory() stringByAppendingString:filename]; NSURL *localpath = [NSURL fileURLWithPath:pathToWrite]; PHAssetResourceRequestOptions *options = [PHAssetResourceRequestOptions new]; options.networkAccessAllowed = YES; [[PHAssetResourceManager defaultManager] writeDataForAssetResource:resource toFile:localpath options:options completionHandler:^(NSError * _Nullable error) { if (error) { [Sys MyLog: [NSString stringWithFormat:@"Failed to write a resource: %@",[error localizedDescription]]]; } pathCallback(localpath); }]; } // Write Resource into Tmp
上传任务本身
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { // Assuming PHAsset has only one resource file. PHAssetResource * resource = [[PHAssetResource assetResourcesForAsset:myPhasset] firstObject]; [FileHelper writeResourceToTmp:resource pathCallback:^(NSURL *localUrl) { [formData appendPartWithFileURL: localUrl name:@"data" fileName:entity.filename mimeType:mimeType error:&fileappenderror]; }]; // writeResourceToTmp }// End Url Request AFHTTPRequestOperation * operation = [[AFHTTPRequestOperation alloc ] initWithRequest:urlRequest]; //..... // Further steps are described in the AFNetworking Docs
这种上传方法有一个显着的缺点..如果设备进入“睡眠模式”,你是拧了。因此,这里推荐的上传方法是使用方法。 uploadTaskWithRequest:fromFile:progress:completionHandler
AFURLSessionManager.
对于iOS 9以下的版本..在图像的情况下您可以从PHAsset
获取NSDATA
,如我的问题的代码所示..并上传它。 或者在上传之前将其首先写入您的应用沙箱存储。 这种方法在大文件的情况下是不可用的。 或者,您可能希望使用图像/video选取器导出文件作为ALAsset
。 ALAsset
提供了一个允许您从存储中读取文件的API,但是您必须在上传之前将其写入沙箱存储。
为每个video创buildNSData可能是不好的,因为video(或任何其他文件)可以比设备的RAM大得多,我build议上传为“文件”,而不是“数据”,如果你追加文件,它会逐块发送数据,不会立即尝试读取整个文件,请尝试使用
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError * __autoreleasing *)error
也看看https://github.com/AFNetworking/AFNetworking/issues/828
在你的情况下,像这样使用它
NSURLRequest *urlRequest = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:[[entity uploadUrl]absoluteString] parameters:entity.params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) { [formData appendPartWithFileURL:entity.fileUrl name:entity.filename error:&fileappenderror]; if(fileappenderror) { NSLog(@"%@",fileappenderror); } } error:&urlRequestError];
在这里看到我的答案你的其他问题。 在这里似乎相关的主题是相关的。
在这个答案中,我描述了根据我的经验,苹果公司并没有直接访问与PHAsset相关的video源文件。 或者这个问题的ALAsset。
为了访问video文件并上传它们,您必须先使用AVAssetExportSession
创build一个副本。 或者使用iOS9 + PHAssetResourceManager API。
您不应该使用任何将数据加载到内存中的方法,因为您将很快遇到OOMexception。 如上所述,使用requestAVAssetForVideo(_:options:resultHandler:)
方法可能不是一个好主意,因为您有时会获得AVComposition而不是AVAsset(并且无法直接从AVComposition获取NSURL) 。
您也可能不希望使用任何利用AFHTTPRequestOperation
或相关API的上传方法,因为它们基于弃用的NSURLConnection API而不是更现代的NSURLSession API。 NSURLSession将允许您在后台进行长时间运行的video上传,允许用户离开您的应用程序,并确信上传将完成。
在我原来的答案我提到VimeoUpload 。 这是一个处理videofile upload到Vimeo的库,但它的核心可以重新用于处理并发的背景videofile upload到任何目的地。 充分披露:我是图书馆的作者之一。