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 (aka filedata) 来上传文件(参见上面的方法)。我尝试使用在模拟器上工作的方法来请求文件 appendPartWithFileURL路径appendPartWithFileData 。并在真实设备上失败,并出现无法通过指定路径读取文件的错误。我已经在这里 PHAsset + AFNetworking 描述了这个问题。无法在真机上上传文件到服务器

========================================

更新:我已经修改了我的代码,以便测试在新 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);

        }



    }];
}

所以问题仍然存在 - 上传到服务器的文件是空的

4

3 回答 3

12

上面提出的解决方案只是部分正确(之前是我自己发现的)。由于系统不允许读取沙盒之外的文件,因此文件无法通过文件路径访问(读/写)而只能复制。在 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

这种上传方法有一个明显的缺点..如果设备进入“睡眠模式”,你会被搞砸的。因此,这里推荐的上传方法是使用方法。uploadTaskWithRequest:fromFile:progress:completionHandlerAFURLSessionManager.

对于iOS 9以下的版本..如果是图片,您可以NSDATAPHAsset我的问题代码中获取..并上传。或在上传之前先将其写入您的应用沙箱存储。这种方法不适用于大文件。或者,您可能希望使用图像/视频选择器将文件导出为ALAsset. ALAsset提供 api 允许您从存储中读取文件。但您必须在上传之前将其写入沙盒存储。

于 2015-11-06T02:12:44.387 回答
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];
于 2015-10-27T16:22:51.493 回答
3

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

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

为了访问视频文件并上传它们,您必须首先使用AVAssetExportSession. 或者使用 iOS9+ PHAssetResourceManager API。

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

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

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

于 2016-03-16T19:47:30.937 回答