【问题标题】:Phasset + AfNetworking Upload Multiple VideosPhasset + AfNetworking 上传多个视频
【发布时间】:2016-01-21 14:02:42
【问题描述】:

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

  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 嗨。它正在主线程中上传..因为它是通过第三方库完成的。我不确定这是否重要。谢谢你的意见:)
  • 我们如何发送实时照片和慢动作视频。这些是 2 个或更多文件的组合。所以它们有多个文件 URL,

标签: ios objective-c iphone afnetworking phasset


【解决方案1】:

为每个视频创建 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];

【讨论】:

  • 嗨。正如我在问题中所描述的那样。我无法通过设备上的本地文件 url(在 PHAsset 的情况下)来做到这一点。但它在模拟器上运行良好。我已经更新了我的问题!
  • Emulator 工作正常,因为您的计算机的 RAM 比 iPhone 大,它可以将文件的数据复制到内存中,我可以看到您正在获取 GetAssetData ,使用 Assets URL 并且不要使用它创建 dataWithContentsOfURL,直接上传使用 URL 而不创建 NSData
  • 你的方法(这也是我的,我以前试过)在真实设备上不起作用。正如我所指出的,它失败并出现错误,即每当文件通过本地路径上传时,它无法从文件系统中读取(请注意文件路径是通过 PHAsset 请求的!)
  • 如果你能够像这样创建 NSData NSData *videoData= [NSData dataWithContentsOfURL:localVideoUrl];,那么这意味着你能够打开文件并且你应该能够重用这个 URL 来上传
  • 这是真的。但是当涉及到上传时,它并没有像想象的那样工作。由于我没有设计,我将编写一个测试..并为此使用第三方测试服务。如果它有效。我会接受你的回答并告诉我的客户他们的 iPhone 有问题
【解决方案2】:

上面提出的解决方案只是部分正确(之前是我自己发现的)。由于系统不允许读取沙箱之外的文件,因此文件无法通过文件路径访问(读/写)而只能复制。在 iOS 9 及以上版本中,Photos Framework 提供了 API(无法通过 NSFileManager 完成,只能使用 Photos 框架 api)将文件复制到您 App 的沙箱目录中。这是我在挖掘文档和头文件后使用的代码。

首先复制一个文件到应用沙箱目录。

// 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 您应该先将 Phasset 写入(复制)到您的内部应用存储,然后使用本地文件路径上传。
  • 这很好。您对在上传之前如何检索视频的元数据有什么建议吗?
【解决方案3】:

请参阅我对您的其他问题的回答 here。在这里似乎相关,因为主题是链接的。

在那个答案中,我描述了根据我的经验,Apple 不会让我们直接访问与 PHAsset 关联的视频源文件。或者一个 ALAsset 。

为了访问和上传视频文件,您必须先使用AVAssetExportSession 创建一个副本。或者使用 iOS9+ PHAssetResourceManager API。

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

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

在我的原始答案中,我提到了VimeoUpload。它是一个处理将视频文件上传到 Vimeo 的库,但它的核心可以重新用于处理并发背景视频文件上传到任何目的地。全面披露:我是图书馆的作者之一。

【讨论】:

  • 感谢您的意见。这正是我所做的。然而。我花了一段时间才弄清楚如何在后台使用 NSURLSession 进行长时间上传:由于某种原因,我的参数在后台上传模式下丢失了。经过长时间的研究,我发现某个地方必须将参数写入(遵循特定格式)到文件中,而不是作为参数。
  • @ProblemSlover 您可以分享您的 NSURLSession 代码以进行上传吗?我也无法在后台上传视频。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-19
  • 1970-01-01
  • 1970-01-01
  • 2014-04-29
  • 1970-01-01
  • 2014-02-10
  • 2015-08-31
相关资源
最近更新 更多