iosvideoavassetphotosframework

Convert PHAsset (video) to AVAsset, synchronously


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.


Solution

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