Асинхронная загрузка AVURLAsset в AVQueuePlayer с нарушением порядка — iOS

Мне нужно воспроизвести несколько аудиозаписей подряд, поэтому для этой цели я использую AVQueuePlayer. Я загружаю в него аудио как:

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

Теперь проблема в том, что, поскольку я загружаю ресурсы с URL-адреса через Async (см. условие else в коде выше), порядок аудиофайлов в моем AVQueuePlayer не совпадает с URL-адресами в _messagesArray

e.g : _messagesArray = audioURL1 , audioURL2, audioURL3

затем из-за асинхронности, в зависимости от того, что загружается первым, оно сначала переходит в AVQueuePlayer

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

Пожалуйста, предложите решение для поддержания порядка в моем плеере.

Спасибо.


person Hyder    schedule 12.06.2016    source источник
comment
Когда вы вставляете, вы можете проверить по URL-адресу элемента, где он находится в соответствии с _messageArray?   -  person Larme    schedule 12.06.2016
comment
@Larme, можешь немного уточнить? не понял   -  person Hyder    schedule 12.06.2016
comment
Пожалуйста, помогите кто-нибудь...   -  person Hyder    schedule 13.06.2016


Ответы (2)


Я знаю, что вы пишете приложение на 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 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)
    }
}

Просто инициализируйте класс обратным вызовом и передайте вызов метода loadAssetAsync со словарем NSIndexPath и AVURLAsset каждый раз, когда вы хотите загрузить некоторые активы.

person Samer Murad    schedule 29.12.2016

Я застрял в той же проблеме, что и вы, но с помощью этого ответа: https://stackoverflow.com/a/34897956/12234693 и выполнив вызовы для создания активов в отдельной DispatchGroup(), я смог решить проблему. Код, если вы или кто-либо застрял в одной и той же проблеме, выглядит следующим образом:

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)
    }
person Tarek El-Sayed    schedule 22.04.2020