This is a great service, especially with the 200GB video size limit, it allows for the Visit NYC apps to host and deliver videos through Apple’s Infrastructure. What a great offering, we hope this greatly improves the download experience for our users.
From a development side … the Apple Hosted Background Assets I would say is still in Beta because it just crashes in Xcode if you try to use it. I grasp there is an issue with the hosted asset versioning and whats live, it’s a complex issue. Maybe a solution like the StoreKit snapshot that can be stored for testing is applicable here with a gitignore? Looking forward to that.
However in the meantime, production needs require immediate attention and improving video delivery is top of mind. The development workflow for Apple Hosted Background Assets has been pushing to TestFlight and testing on device. The build numbers stack up since all testing requires pushing to TestFlight, which is to say I haven’t debugged code like this since IE6 where you have to write your own console if you want debugging! Firebug anyone?
These learnings are from an implementation that uses multiple asset packs. Our app implementation allows for the user to pick and choose videos to download. This story would be a bit different if there was only one asset pack to download as many of the concurrency issues would never surface.
Here are some learnings listed in most pain. These have all been observed on Vision OS 26.2
- AssetPackManager.shared.status concurrency
- Status updates? Prefer statusUpdates() with no args
- avoid AssetPackManager.shared.statusUpdates(forAssetPackWithID:)
- Remember to await remove()
First
First one is the .status() call with concurrency. We used an actor and a queue to make sure it gets called one at a time … otherwise, crash.
actor SnapshotWorker {
func status(packId: String) async throws -> AssetPack.Status {
#if targetEnvironment(simulator)
return [] // no packs in simulator
#else
return try await AssetPackManager.shared.status(ofAssetPackWithID: packId)
#endif
}
func throttle() async {
try? await Task.sleep(nanoseconds: 25_000_000)
}
}
.status() gets called one a time, and the result of the status is cached so that the multiple places that need access to this information do not hit the Apple method, and instead read from a cache. Yes cache management is involved here. Will you rise to the challenge?
Also note that .status() calls take a second … upwards of multiple seconds, we have 18 videos, sometimes on TestFlight upwards of 30 – 45 seconds just waiting for the video status to be updated. If videos are downloaded, this also causes contention.
Second
Multiple downloads? Multiple running statusUpdates(forAssetPackId:) goes to an error. Instead, prefer to listen to statusUpdate() that returns status updates for all asset packs, and then route that updated status into the cached asset pack ModelData to update the app state.
All of this is revolving around, treat the AssetPackManager as heavy DB call and don’t request it unless you definitely really want to. Otherwise, it’ll cause chaos if you don’t handle for how finicky the DB is.
Third
Simple one which is remove() takes a second, forget to await it and error. In testing after you remove it, you try downloading it … and well, error. Because the remove was still running as ensureLocalAvailability() is called and that’ll give an error.
You may notice if targetEnvironment(simulator) used, that may be the easiest way to simplify testing on simulator, especially for other things like StoreKit and whatever else may be in the app that needs testing other than the video playback. For simulator testing, we fall back to the streamURL and provide that to AVPlayer and things continue to function.
Overall, a great new feature from Apple. 200GB, they handle distribution. What a nice service, especially when the cloud bills for large VR files come with a monthly sticker shock.