import AVFoundation import Foundation enum SoundEffect: String, Sendable { case cardFlip = "card_flip" case cardPlace = "card_place" case error = "error" case victory = "victory" } @Observable final class SoundManager: @unchecked Sendable { var isEnabled: Bool = true private var players: [SoundEffect: AVAudioPlayer] = [:] private let queue = DispatchQueue(label: "com.solicards.sound") init() { preload() } func play(_ effect: SoundEffect) async { guard isEnabled else { return } queue.async { [weak self] in guard let player = self?.players[effect] else { return } player.currentTime = 0 player.play() } } private func preload() { for effect in SoundEffect.allCases { if let url = Bundle.main.url(forResource: effect.rawValue, withExtension: "caf", subdirectory: "Sounds") { do { let player = try AVAudioPlayer(contentsOf: url) player.prepareToPlay() players[effect] = player } catch { // Sound will be unavailable — not a fatal error } } } } } extension SoundEffect: CaseIterable {}