简体   繁体   English

在 AVQueuePlayer 中异步加载 AVURLAsset 扰乱顺序 - iOS

[英]Loading AVURLAsset asynchronously in AVQueuePlayer disturbing the order - iOS

I need to play some audios in a row, and therefore using AVQueuePlayer for that purpose.我需要连续播放一些音频,因此为此使用AVQueuePlayer I'm loading audios in it as :我在其中加载音频:

for (NSInteger i = row ; i<_messagesArray.count ; i++)
    {
        _urlString = ((PFFile*)(_messagesArray[i][@"messageFile"])).url;

        AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL URLWithString:_urlString] options:nil];
        NSArray *keys = @[@"playable"];


        AVPlayerItem *item = [AVPlayerItem playerItemWithAsset:asset];

        // Subscribe to the AVPlayerItem's DidPlayToEndTime notification.
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemDidFinishPlaying:) name:AVPlayerItemDidPlayToEndTimeNotification object:item];



        if (_queuePlayer == nil)
        {
            _queuePlayer = [[AVQueuePlayer alloc] initWithPlayerItem:item];

        }
        else
        {   /*This loads Async*/
            [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
                [_queuePlayer insertItem:item afterItem:nil];
            }];
        }

    }

[_queuePlayer play];

Now the problem is that since I'm loading the assets from URL via Async (See else condition in the code above), the order of audio files in my AVQueuePlayer is not same as the URLs in the _messagesArray现在的问题是,由于我通过Async从 URL 加载资产(请参阅上面代码中的else条件),我的AVQueuePlayer中音频文件的顺序与_messagesArray的 URL 不同

eg : _messagesArray = audioURL1 , audioURL2, audioURL3例如: _messagesArray = audioURL1 , audioURL2, audioURL3

then due to async, whichever loads first, goes into AVQueuePlayer first然后由于异步,以先加载者为准,先进入AVQueuePlayer

AVQueuePlayer = audio2 (from URL2) , audio1 (from URL1) , audio3 (from URL3)

Please suggest a solution to maintain the order in my player.请提出一个解决方案来维持我的播放器中的顺序。

Thanks.谢谢。

I know you are writing an App in Objective-c, but here is a code I wrote doing exactly what you need in Swift, hope it helps you and anyone looking for the same solution.我知道您正在使用 Objective-c 编写应用程序,但这里是我编写的代码,完全符合您在 Swift 中的需求,希望它可以帮助您和任何正在寻找相同解决方案的人。

import Foundation
import AVFoundation
class AssetsLoader : NSObject {
    public typealias AssetsLoaderCallback = ((IndexPath,AVPlayerItem)->Void)
    private var callback: AssetsLoaderCallback
    private(set) var loadedAssets: [IndexPath:AVURLAsset] = [:]
    private(set) var pendingAssets: [IndexPath:AVURLAsset] = [:]
    static var assetKeysRequiredToPlay = [
        "playable",
        "tracks",
        "duration"
    ]
    init(callback:@escaping AssetsLoaderCallback) {
        self.callback = callback
    }

    func loadAssetsAsync(assets:[IndexPath:AVURLAsset]) {
        loadedAssets = [:]
        pendingAssets = [:]
        for (key, value) in assets {
            loadAssetAsync(index: key, asset: value)
        }
    }

    private func loadAssetAsync(index:IndexPath,asset:AVURLAsset) {
        asset.loadValuesAsynchronously(forKeys: AssetsLoader.assetKeysRequiredToPlay) { [weak self] in
            for key in AssetsLoader.assetKeysRequiredToPlay {
                var error : NSError?
                if asset.statusOfValue(forKey: key, error: &error) == .failed {
                    //FIXME: Asset Could not load
                    print("Asset Could not load")
                }
            }
            if !asset.isPlayable {
                print("Cant play, move to next asset?")
            } else {
                // Asset Ready, Check if
                self?.prepareAssetForCallback(index: index, asset: asset)
            }
        }
    }
    private func prepareAssetForCallback(index:IndexPath,asset:AVURLAsset) {
        if index.row == 0 {
            /// First Asset
            loadedAssets[index] = asset
            self.sendReadyAsset(index: index)
            if let nextInline = pendingAssets[index.rowSuccessor()] {
                self.freePendingAsset(index: index.rowSuccessor(), asset: nextInline)
            }
        } else {
            self.freePendingAsset(index: index, asset: asset)
        }
    }

    private func freePendingAsset(index:IndexPath,asset:AVURLAsset) {
        if loadedAssets[index.rowPredecessor()] != nil && loadedAssets[index] == nil {
            loadedAssets[index] = asset
            self.sendReadyAsset(index: index)
            if let nextInline = pendingAssets[index.rowSuccessor()] {
                self.freePendingAsset(index: index.rowSuccessor(), asset: nextInline)
            }
        } else {
            if pendingAssets[index] == nil {
                pendingAssets[index] = asset
            }
        }
    }

    private func sendReadyAsset(index:IndexPath) {
        self.callback(index, AVPlayerItem(asset:self.loadedAssets[index]!))
    }
}

NSIndexPath extension: NSIndexPath 扩展:

extension IndexPath {

    func rowPredecessor() -> IndexPath {
        assert(self.row != 0, "-1 is an illegal index row")
        return IndexPath(row: self.row - 1, section: self.section)
    }

    func rowSuccessor() -> IndexPath {
        return IndexPath(row: self.row + 1, section: self.section)
    }
}

Just Initialize the class with a callback, and pass call the method loadAssetAsync with a dictionary of an NSIndexPath and AVURLAsset each time you want to load some assets.只需使用回调初始化类,并在每次要加载一些资产时使用NSIndexPathAVURLAsset的字典传递调用方法loadAssetAsync

I have been stuck on this same problem as you have been but by using this answer : https://stackoverflow.com/a/34897956/12234693 and making the calls to create assets in a separate DispatchGroup() I was able to solve the problem.我和你一样遇到了同样的问题,但是通过使用这个答案: https : //stackoverflow.com/a/34897956/12234693并调用在单独的 DispatchGroup() 中创建资产我能够解决问题。 The code if you or anyone stuck at the same problem is as follows :如果您或任何人遇到同样的问题,代码如下:

func makeItem(url: String) {
        let avAsset = AVURLAsset(url: URL(string: url)!)
        let group = DispatchGroup()
        group.enter()
        avAsset.loadValuesAsynchronously(forKeys: ["playable", "tracks", "duration"]) {

        }
        group.leave()
        group.notify(queue: .main) {
                self.enqueue(avAsset: avAsset)
        }
    }
func enqueue(avAsset: AVURLAsset) {
        let item = AVPlayerItem(asset: avAsset)
        self.player2!.insert(item, after: nil)
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM