简体   繁体   中英

How to use Swift Concurrency to load AVQueuePlayer?

I have about 200 audio assets about 5 seconds each that are loaded into an AVQueuePlayer . I do this so it plays all the assets as one full song, but allowing the user to view or jump to a specific verse.

The issue is the main thread gets locked when I load them in like this:

let items = urls
    .map(AVAsset.init)
    .map(AVPlayerItem.init)

let player = AVQueuePlayer(items: items)

Then I discovered I must asynchronously load them into the player so tried something like this:

let items = urls
    .map(AVAsset.init)
    .map(AVPlayerItem.init)

let player = AVQueuePlayer()

// Below does NOT compile!!
await withThrowingTaskGroup(of: Void.self) { group in
    for var item in items {
        group.addTask { _ = try await item.asset.load(.duration) }
        for try await result in group {
            player?.insert(item, after: nil)
        }
    }
}

I'm only supporting iOS 16+ so I'm trying to make use of the new Swift Concurrency APIs available in AVFoundation . I tried following this document but there's a lot of gaps I can't quite get. What is the proper way of loading up that many assets into the queue player without locking up the main thread?

How about something like this?

import AVFoundation

let items = [URL(string: "https://google.com")!]

let player = AVQueuePlayer()

try await withThrowingTaskGroup(of: AVPlayerItem.self) { group in
    for item in items {
        group.addTask {
            AVPlayerItem(asset: AVAsset(url: item))
        }
    }
    
    for try await item in group {
        player.insert(item, after: nil)
        print(item)
    }
}

Since it seems that you are trying to directly affect items and player I would use actor . To keep everything together and synchronized.

To bypass captured var 'item' in concurrently-executing code you can use .enumerated()

actor PlayerLoader{
    let items = [URL(string: "https://v.ftcdn.net/01/53/82/41/700_F_153824165_dN7n9QftImnClb7z1IJIjbLgLlkHyYDS_ST.mp4")!]
        .map(AVAsset.init)
        .map(AVPlayerItem.init)
    
    let player = AVQueuePlayer()
    
    func loadItems() async throws{
        try await withThrowingTaskGroup(of: Void.self) { group in
            for (idx, _) in items.enumerated() {
                group.addTask { [weak self] in
                    _ = try await self?.items[idx].asset.load(.duration)
                }
                for try await _ in group {
                    player.insert(items[idx], after: nil)
                }
            }
        }
    }
}

Then just call loadItems with Task or Task.detached

let loader = PlayerLoader()

let task = Task{ //or Task.detached   depending on where you will be calling this line of code
    do{
        try await loader.loadItems()
        print("total number of items loaded \(loader.player.items().count)")
    }catch{
        print(error)
        throw error
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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