skip to Main Content

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?

2

Answers


  1. 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)
        }
    }
    
    Login or Signup to reply.
  2. 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
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search