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选取器导出文件作为ALAssetALAsset提供了一个允许您从存储中读取文件的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到任何目的地。 充分披露:我是图书馆的作者之一。