简体   繁体   English

如何使用Swift Concurrency加载AVQueuePlayer?

[英]How to use Swift Concurrency to load AVQueuePlayer?

I have about 200 audio assets about 5 seconds each that are loaded into an AVQueuePlayer .我有大约 200 个音频资产,每个资产大约 5 秒,它们被加载到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 .我只支持 iOS 16+,所以我正在尝试使用AVFoundation中可用的新 Swift 并发 API。 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 .由于您似乎正试图直接影响itemsplayer ,所以我会使用actor To keep everything together and synchronized.将所有内容保持在一起并保持同步。

To bypass captured var 'item' in concurrently-executing code you can use .enumerated()要绕过captured var 'item' in concurrently-executing code您可以使用.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然后只需使用TaskTask.detached调用loadItems

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

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

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