Skip to content

Instantly share code, notes, and snippets.

@samsonjs
Last active June 24, 2024 15:20
Show Gist options
  • Save samsonjs/62bf98437627d67d63b5eff99eb6bd77 to your computer and use it in GitHub Desktop.
Save samsonjs/62bf98437627d67d63b5eff99eb6bd77 to your computer and use it in GitHub Desktop.
FB14035001: Tracks loaded from an AVAsset should be marked `sending` to be used from actor-isolated contexts
import AVFoundation
/*
* In Swift 6 it’s not currently possible to directly load tracks from an AVAsset using a method like
* AVAsset.loadTracks(withMediaType:) from an actor-isolated context, because AVAssetTrack isn’t Sendable. Given
* that many AVFoundation types are not sendable / thread-safe it’s tempting to use an actor to work with
* compositions and that currently requires some work-arounds that shouldn’t be necessary and may not be obvious
* to everyone.
*/
// Non-isolated contexts work fine but if this class were building an AVComposition then it’s preferable to enforce
// isolation / serialization of access to the components of that composition.
class NonisolatedTrackLoader {
func loadTracks() async throws {
// ✅ Works in nonisolated contexts
try await AVAsset().loadTracks(withMediaType: .video)
}
}
// Since many AVFoundation types aren’t Sendable it’s going to be common to reach for actors to work with them.
// However this is currently a warning in Swift 5 and an error in Swift 6.
actor IsolatedTrackLoader {
func loadTracks() async throws {
// ❌ Non-sendable type '[AVAssetTrack]' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary. Class 'AVAssetTrack' does not conform to the 'Sendable' protocol
try await AVAsset().loadTracks(withMediaType: .video)
}
// Using the extension method below we can load tracks because they’re marked as being sent, which should be the default
func loadTracksWithWorkaround() async throws {
// ✅ Works when tracks are sent
_ = try await AVAsset().sendTracks(withMediaType: .video)
}
}
extension AVAsset {
// This works around the issue but we shouldn't need to write this ourselves. The built-in methods for loading
// tracks should send them to the caller.
func sendTracks(withMediaType mediaType: AVMediaType) async throws -> sending [AVAssetTrack] {
try await loadTracks(withMediaType: mediaType)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment