Phasset + AfNetworking Загрузить несколько видео

В настоящее время я использую следующий код для загрузки видео:

  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. Невозможно загрузить файлы на сервер на реальном устройстве

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

Обновление: я изменил свой код, чтобы проверить подход загрузки файла по локальному пути на новом 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);

        }



    }];
}

Таким образом, проблема остается той же - файлы, загруженные на сервер, пусты

3 ответа

Решение

Предложенное выше решение является верным лишь частично (и оно было найдено мной ранее). Поскольку система не позволяет читать файлы вне песочницы, поэтому к файлам нельзя получить доступ (чтение / запись) по пути к файлу и просто скопировать. В версии iOS 9 и выше Photos Framework предоставляет API(это не может быть сделано через NSFileManager, но только с использованием API Framework Photos Framework) для копирования файла в каталог песочницы вашего приложения. Вот код, который я использовал после копания в документах и ​​файлах заголовков.

Прежде всего скопируйте файл в каталог песочницы приложения.

// 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. В случае изображений Вы можете получить NSDATA от PHAsset как показано в коде моего вопроса.. и загрузить его. или запишите его в хранилище изолированной программной среды вашего приложения перед загрузкой. Этот подход неприменим в случае больших файлов. В качестве альтернативы вы можете использовать файлы экспорта изображений / видео в качестве ALAsset, ALAsset предоставляет API, который позволяет вам читать файл из хранилища. Но вы должны записать его в хранилище песочницы перед загрузкой.

Создание NSData для каждого видео может быть плохим, потому что видео (или любые другие файлы) могут быть намного больше, чем ОЗУ устройства, я бы рекомендовал загружать как "файл", а не как "данные", если вы добавляете файл, он отправит данные с диска по частям и не будет пытаться прочитать весь файл сразу, попробуйте использовать

- (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];

Смотрите мой ответ здесь на ваш другой вопрос. Кажется уместным здесь, поскольку предмет связан.

В этом ответе я описываю, что по моему опыту Apple не дает нам прямой доступ к исходным видео-файлам, связанным с PHAsset. Или ALAsset в этом отношении.

Чтобы получить доступ к видеофайлам и загрузить их, вы должны сначала создать копию, используя AVAssetExportSession, Или используя API iOS9 + PHAssetResourceManager.

Вы не должны использовать какие-либо методы, которые загружают данные в память, поскольку вы быстро столкнетесь с исключениями OOM. И это, вероятно, не очень хорошая идея, чтобы использовать requestAVAssetForVideo(_:options:resultHandler:) метод, как указано выше, потому что вы будете иногда получать AVComposition в отличие от AVAsset (и вы не можете получить NSURL напрямую от AVComposition).

Вы также, вероятно, не хотите использовать какой-либо метод загрузки, который использует AFHTTPRequestOperation или связанные API, потому что они основаны на устаревшем API NSURLConnection, а не на более современных API NSURLSession. NSURLSession позволит вам осуществлять длительную загрузку видео в фоновом режиме, позволяя вашим пользователям покидать ваше приложение и быть уверенным, что загрузка будет завершена в любом случае.

В своем первоначальном ответе я упоминаю VimeoUpload. Это библиотека, которая обрабатывает загрузку видеофайлов в Vimeo, но ее ядро ​​можно переназначить для обработки одновременных фоновых загрузок видеофайлов в любое место назначения. Полное раскрытие: я один из авторов библиотеки.

Другие вопросы по тегам