Reputation: 36078
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?
Upvotes: 2
Views: 825
Reputation: 29271
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
}
}
Upvotes: 2
Reputation: 2102
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)
}
}
Upvotes: 1