Phasset + AfNetworking 上传多个视频

9

目前我正在使用以下代码上传视频:

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

                }
            }];
    }

这种方法的问题在于当上传大型/多个视频文件时,应用程序会简单地耗尽内存。我想这是由于请求文件(在上面的方法中查看)的NSDATA(又名filedata)上传。我已经尝试使用appendPartWithFileURL方法请求文件路径,而不是 appendPartWithFileData ,在模拟器上可以运行。但在真实设备上失败,显示指定路径无法读取该文件的错误。我在这里描述了这个问题: PHAsset + AFNetworking. Unable to upload files to the server on a real device 更新:为了测试在新的iPhone 6s+上通过本地路径上传文件的方法,我已经修改了我的代码如下。
 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+上进行测试时,日志警告更加明显。这是调用方法 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中获取本地文件路径的代码:

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

        }



    }];
}

因此问题仍然存在-上传到服务器的文件为空。

你是在主线程上传还是创建了一个独立的线程? - Muzammil
@Muzammil 你好。它正在通过主线程上传...因为它是通过第三方库完成的。我不确定这是否重要。谢谢你的建议 :) - ProblemSlover
我们如何发送实时照片和慢动作视频。这些是由2个或更多文件组合而成的。因此,它们有多个文件URL。 - Utsav Dusad
3个回答

12

上述提议的解决方案部分正确(此前我自己也发现了这一点)。由于系统不允许在沙盒之外读取文件,因此文件路径无法访问(读取/写入),只能复制。在iOS 9及以上版本中,照片框架提供API(不能通过NSFileManager完成,而是只能使用照片框架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

这种上传方法有一个显著的缺点,如果设备进入“睡眠模式”,你就会出现问题。因此,在这里推荐使用AFURLSessionManager中的uploadTaskWithRequest:fromFile:progress:completionHandler 方法来上传。

对于iOS 9以下的版本,如果是图片,你可以像我的问题代码中展示的那样从PHAsset获取NSDATA并上传它,或者在上传之前先写入到应用程序沙盒存储中。但是这种方法对于大型文件不可行。或者您可以使用图像/视频选择器导出文件作为ALAssetALAsset提供了允许您从存储中读取文件的API,但您必须在上传之前将其写入沙箱存储。


@problemSolver,您的答案在同一屏幕上运行得很好,但是当我们必须在下一个屏幕上进行上传部分时,该如何实现呢?我已经在集合视图中显示了从相册中获取的所有视频,并且在选择单元格时,我必须在下一个视图控制器中上传所选的视频。我正在将从PHAssetResource提取的URL传递到下一个屏幕。但是它没有上传文件,显示响应为(null)。 - sarita
@sarita 你应该先将Phasset复制到你的内部应用存储中,然后使用本地文件路径进行上传。 - ProblemSlover
这很好。你有关于如何在上传前检索视频元数据的建议吗? - Curious101

6

为每个视频创建NSData可能不好,因为视频(或任何其他文件)可能比设备的RAM大得多, 我建议上传为“文件”而不是“数据”,如果您附加文件,它将逐块从磁盘发送数据,并且不会尝试一次读取整个文件,请尝试使用

- (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),我无法通过本地文件URL进行操作,但在模拟器上却可以正常工作。我已更新我的问题! - ProblemSlover
模拟器工作正常,因为您的计算机具有比 iPhone 更大的 RAM,并且它可以将文件数据复制到内存中。我可以看到您正在获取 GetAssetData,请使用 Assets URL,不要使用 dataWithContentsOfURL 创建它,直接使用 URL 上传而不创建 NSData。 - ogres
没错。但是当涉及到上传时,它并没有按照预期的那样工作。由于我没有设备,我将编写一个测试,并使用第三方测试服务进行测试。如果它能正常工作,我会接受你的答案,并告诉我的客户,他们的iPhone可能出了些问题。 - ProblemSlover
请确保您可以访问资产的URL(如果您可以打开它,那么您应该已经拥有了它),但如果访问是临时的,您可以先将其复制到文档或其他地方(在仍然具有访问权限的情况下),然后上传。 附言:接受答案并不重要,但我也想了解背后的原因。 - ogres
由于没有其他人给出答案,因此奖励归您。测试了我的方法(通过URL上传,正如您建议的),在旧的iPhone 5S上使用第三方服务进行了测试。它可以工作,但是客户告诉我在新设备上无法工作。因此,请继续寻找解决方法。 - ProblemSlover
显示剩余2条评论

3

看到你的其他问题,我在这里回答了类似的问题,因为主题相关。

在那个答案中,我描述了我的经验是苹果不直接提供与PHAsset关联的视频源文件访问权限。同样对于ALAsset也是如此。

为了访问视频文件并上传它们,您必须首先使用AVAssetExportSession或使用iOS 9+ PHAssetResourceManager API创建副本。

您不应该使用任何将数据加载到内存中的方法,因为您很快就会遇到OOM异常。也不应该像上面所述使用requestAVAssetForVideo(_:options:resultHandler:)方法,因为有时你会得到一个AVComposition而不是AVAsset(你不能直接从AVComposition获取NSURL)。

您也可能不想使用任何基于已弃用的NSURLConnection API的AFHTTPRequestOperation或相关API的上传方法,而是使用更现代的NSURLSession API。NSURLSession将允许您在后台进程中进行长时间运行的视频上传,使您的用户可以离开您的应用程序,并确信上传将完成。

在我最初的答案中,我提到了VimeoUpload。它是处理视频文件上传到Vimeo的库,但它的核心可以重新编写以处理并发后台视频文件上传到任何目的地。全面披露:我是该库的作者之一。


感谢您的输入。这正是我所做的。然而,我花了一些时间才弄清楚如何在后台使用NSURLSession进行长时间上传:由于某种原因,在后台上传模式下我的参数丢失了。经过长时间的研究,我发现参数必须以特定格式写入文件而不是作为参数。 - ProblemSlover
@ProblemSolver 你可以分享一下你的NSURLSession上传代码吗?我在后台上传视频方面也遇到了一些问题。 - nyus2006

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接