Преобразование PHAsset (видео) в AVAsset, синхронно
Мне нужно использовать объект AVAsset, чтобы воспроизвести его с помощью AVPlayer и AVPlayerLayer. Я начал использовать фреймворк Photos, так как AssetsLibrary устарела. Теперь я дошел до того, что у меня есть массив объектов PHAsset, и мне нужно преобразовать их в AVAsset. Я попытался перечислить через PHFetchResult и выделить новый AVAsset, используя локализованное описание PHAsset, но, похоже, при воспроизведении он не показывает никакого видео.
PHAssetCollection *assetColl = [self scaryVideosAlbum];
PHFetchResult *getVideos = [PHAsset fetchAssetsInAssetCollection:assetColl options:nil];
[getVideos enumerateObjectsUsingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
NSURL *videoUrl = [NSURL URLWithString:asset.localizedDescription];
AVAsset *avasset = [AVAsset assetWithURL:videoUrl];
[tempArr addObject:avasset];
}];
Я предполагаю, что локализованное описание не является абсолютным URL видео.
Я также наткнулся на PHImageManager и requestAVAssetForVideo, однако параметр options, когда дело касается видео, не имеет свойства isSynchrounous, как в случае с параметром параметров изображения.
PHVideoRequestOptions *option = [PHVideoRequestOptions new];
[[PHImageManager defaultManager] requestAVAssetForVideo:videoAsset options:option resultHandler:^(AVAsset * _Nullable avasset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
Есть ли синхронный способ сделать это?
Благодарю.
6 ответов
Нет, нет Но вы можете построить синхронную версию:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
PHVideoRequestOptions *option = [PHVideoRequestOptions new];
__block AVAsset *resultAsset;
[[PHImageManager defaultManager] requestAVAssetForVideo:videoAsset options:option resultHandler:^(AVAsset * avasset, AVAudioMix * audioMix, NSDictionary * info) {
resultAsset = avasset;
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// yay, we synchronously have the asset
[self doSomethingWithAsset:resultAsset];
Однако, если вы делаете это в главном потоке и requestAVAssetForVideo:
Это занимает слишком много времени, вы рискуете заблокировать свой пользовательский интерфейс или даже быть остановленным сторожевым таймером iOS.
Вероятно, безопаснее переработать ваше приложение для работы с версией асинхронного обратного вызова. Что-то вроде этого:
__weak __typeof(self) weakSelf = self;
[[PHImageManager defaultManager] requestAVAssetForVideo:videoAsset options:option resultHandler:^(AVAsset * avasset, AVAudioMix * audioMix, NSDictionary * info) {
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf doSomethingWithAsset:avasset];
});
}];
Для Swift 2 вы можете легко воспроизводить видео с PHAsset
используя этот метод ниже,
Импортировать файл
import AVKit
От PHAsset
static func playVideo (view:UIViewController, asset:PHAsset) {
guard (asset.mediaType == PHAssetMediaType.Video)
else {
print("Not a valid video media type")
return
}
PHCachingImageManager().requestAVAssetForVideo(asset, options: nil, resultHandler: {(asset: AVAsset?, audioMix: AVAudioMix?, info: [NSObject : AnyObject]?) in
let asset = asset as! AVURLAsset
dispatch_async(dispatch_get_main_queue(), {
let player = AVPlayer(URL: asset.URL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
view.presentViewController(playerViewController, animated: true) {
playerViewController.player!.play()
}
})
})
}
Вы можете использовать это:
let asset = info[UIImagePickerControllerPHAsset] as? PHAsset
PHCachingImageManager().requestAVAsset(forVideo: phAsset, options: nil) { (avAsset, _, _) in
if let avAsset = avAsset {
print(avAsset)
}
}
Ниже приведена реализация Swift 4, в которой для выполнения запроса синхронно используется семафор.
Код комментируется для объяснения различных шагов.
func requestAVAsset(asset: PHAsset) -> AVAsset? {
// We only want videos here
guard asset.mediaType == .video else { return nil }
// Create your semaphore and allow only one thread to access it
let semaphore = DispatchSemaphore.init(value: 1)
let imageManager = PHImageManager()
var avAsset: AVAsset?
// Lock the thread with the wait() command
semaphore.wait()
// Now go fetch the AVAsset for the given PHAsset
imageManager.requestAVAsset(forVideo: asset, options: nil) { (asset, _, _) in
// Save your asset to the earlier place holder
avAsset = asset
// We're done, let the semaphore know it can unlock now
semaphore.signal()
}
return avAsset
}
Вы можете попробовать этот трюк, но он удобен, когда у вас есть 3,4 или, может быть, 5 phassets, которые вы хотите преобразовать в AVAsset:
[[PHImageManager defaultManager] requestAVAssetForVideo:assetsArray[0] options:option resultHandler:^(AVAsset * avasset, AVAudioMix * audioMix, NSDictionary * info) {
//do something with this asset
[[PHImageManager defaultManager] requestAVAssetForVideo:assetsArray[1] options:option resultHandler:^(AVAsset * avasset, AVAudioMix * audioMix, NSDictionary * info) {
//so on...
}
}
Таким образом, в принципе, вы можете вызвать этот метод еще раз, когда вы конвертировали 1 фазу в AVAsset. Я знаю, что это может быть неэффективный код, но его не следует запрещать для небольших целей.
Тех, кто сюда приходит за асинхронным подходом.
Быстрая версия:
func requestAVAsset(asset: PHAsset)-> AVAsset? {
guard asset.mediaType == .video else { return nil }
let phVideoOptions = PHVideoRequestOptions()
phVideoOptions.version = .original
let group = DispatchGroup()
let imageManager = PHImageManager.default()
var avAsset: AVAsset?
group.enter()
imageManager.requestAVAsset(forVideo: asset, options: phVideoOptions) { (asset, _, _) in
avAsset = asset
group.leave()
}
group.wait()
return avAsset
}