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