将 PHAsset(视频)同步转换为 AVAsset

13

我需要使用AVAsset对象,以便使用AVPlayer和AVPlayerLayer进行播放。由于AssetsLibrary已被弃用,因此我开始使用Photos框架。现在我已经获得了一组PHAsset对象,我需要将它们转换为AVAsset。我尝试通过遍历PHFetchResult并使用PHAsset的本地描述来分配新的AVAsset,但当我播放它时似乎没有显示任何视频。

    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,但是,当涉及到视频时,选项参数没有isSynchrounous属性,而图像选项参数则有。

        PHVideoRequestOptions *option = [PHVideoRequestOptions new];
        [[PHImageManager defaultManager] requestAVAssetForVideo:videoAsset options:option resultHandler:^(AVAsset * _Nullable avasset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {

有没有同步的方法可以做到这一点?

谢谢。

6个回答

27
没有,但是你可以构建一个同步版本:
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:花费的时间太长,您可能会冻结您的UI甚至被iOS watchdog终止。
重新设计您的应用程序以使用异步回调版本可能更安全。类似于这样:
__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];
    });
}];

运行得非常好!但是你推荐哪个选项?我正在使用第二个选项,还有,“__weak __typeof(self) weakSelf = self;”是什么意思? - CGR
1
第二个选项更加灵活(可以在主线程上调用而不必担心),所以我会选择它。weakSelf习惯用法是避免保留循环的一种方式:https://dev59.com/1GIj5IYBdhLWcg3wpmoH - Rhythmic Fistman
更好的用户体验可能需要一个响应更快的应用程序。使用异步进程并让用户知道正在进行的工作。在旧设备上,可能需要更长时间,因此我认为您不应依赖同步进程。 - Swift Rabbit

6

对于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()
                }
            })
        })
    }

1

导入

import AVKit

Swift 5

let phAsset = info[UIImagePickerControllerPHAsset] as? PHAsset
    
PHCachingImageManager().requestAVAsset(forVideo: phAsset, options: nil) { (avAsset, _, _) in
    print(avAsset)
}

这是异步方法。 - Raman Shyniauski
1
我认为同步方法还没有完成。 - Yunus T.

0
以下是一个基于信号量实现的 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
}

1
这种用法是不正确的。avAsset总是返回nil,因为requestAVAsset是一个异步函数。你可以像这样使用:https://dev59.com/sFwY5IYBdhLWcg3wQ12k#56670647 - Yunus T.

0

你可以尝试这个技巧,但当你想要将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个PHAsset转换为AVAsset时,可以再次调用此方法。我知道这可能不是一种高效的代码,但对于小目的而言,它不应该被禁止。


0
那些来这里寻求异步方法的人。
Swift 版本:
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
    }


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接