Convert PHAsset (video) to AVAsset, synchronously
Asked Answered
D

6

14

I need to use the AVAsset object, in order to play it using AVPlayer and AVPlayerLayer. I started using the Photos framework since AssetsLibrary is deprecated. Now I got to the point where I have an array of PHAsset objects and I need to convert them to AVAsset. I tried enumerating through the PHFetchResult and allocation a new AVAsset using the PHAsset's localized description, but it does not seem to show any video when I play it.

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

I assume the localized description is not the absolute url of the video.

I also stumbled upon the PHImageManager and the requestAVAssetForVideo, however, the options parameter when it comes down to video does not have an isSynchrounous property, which is the case with the image options parameter.

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

Is there a synchronous way to do this?

Thanks.

Disagreeable answered 28/9, 2015 at 16:19 Comment(0)
S
27

No, there isn't. But you can build a synchronous version:

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

However if you do this on the main thread and requestAVAssetForVideo: takes too long, you risk locking up your UI or even being terminated by the iOS watchdog.

It's probably safer to rework your app to work with the asynchronous callback version. Something like this:

__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];
    });
}];
Sibell answered 28/9, 2015 at 22:31 Comment(3)
Works like a charm!! But what of the two options do you recommend? I'm using the second one, and also, What is __weak __typeof(self) weakSelf = self; ?Grantham
The second option is more flexible (it can be called on the main thread without fear) so I would choose that. The weakSelf idiom is a way of avoiding retain cycles: https://mcmap.net/q/108425/-always-pass-weak-reference-of-self-into-block-in-arc/22147Sibell
A better experience would probably a more responsive app. Use the asynchronous process and let the user know the work is being done. On older devices, could take a longer time so I don't think you should rely on a synchronous process.Unasked
D
6

For Swift 2, you can easily play the video with PHAsset using this method below,

Import File

import AVKit

From 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()
                }
            })
        })
    }
Digestif answered 20/9, 2016 at 21:26 Comment(0)
A
1

Import

import AVKit

Swift 5

let phAsset = info[UIImagePickerControllerPHAsset] as? PHAsset
    
PHCachingImageManager().requestAVAsset(forVideo: phAsset, options: nil) { (avAsset, _, _) in
    print(avAsset)
}
Astrionics answered 19/6, 2019 at 14:55 Comment(2)
This is asynchronous methodCardiovascular
I think it's haven't for sync methodAstrionics
C
0

You can try this trick but it is handy when you have 3,4 or maybe 5 phassets that you want to convert to 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...
       }
        }

So basically,you can call this method again when you have converted 1 phasset to AVAsset.I know this might not be an efficient code but it should not be forbidden for little purposes.

Condyle answered 29/8, 2016 at 7:51 Comment(0)
J
0

The following is a Swift 4 implementation that relies on a semaphore to make the request synchronously.

The code is commented to explain the various steps.

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
}
Jochbed answered 4/2, 2018 at 5:46 Comment(1)
This use is not correct. avAsset return nil to always. Because requestAVAsset is a async func. You can use like that: https://mcmap.net/q/392854/-convert-phasset-video-to-avasset-synchronouslyAstrionics
C
0

Those who are coming here for asynchronous approach.

Swift version :

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
    }

Croze answered 13/9, 2021 at 10:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.