#if canImport(AVFoundation) import AVFoundation import Foundation enum AudioSession { typealias Token = any NSObjectProtocol #if os(iOS) || os(watchOS) static var outputLatency: TimeInterval { AVAudioSession.sharedInstance().outputLatency } static func activate() async throws { let session = AVAudioSession.sharedInstance() #if os(watchOS) try session.setCategory(.playback, mode: .default, policy: .longFormAudio) _ = try await session.activate() #else try session.setCategory(.playback, mode: .default) try session.setActive(true) #endif } static func observe( interrupted: @escaping @Sendable (Bool) -> Void, routeLost: @escaping @Sendable () -> Void ) -> [Token] { let center = NotificationCenter.default let session = AVAudioSession.sharedInstance() let interruption = center.addObserver(forName: AVAudioSession.interruptionNotification, object: session, queue: nil) { guard let raw = $0.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt, let type = AVAudioSession.InterruptionType(rawValue: raw) else { return } switch type { case .began: interrupted(true) case .ended: let options = $0.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0 if AVAudioSession.InterruptionOptions(rawValue: options).contains(.shouldResume) { interrupted(false) } @unknown default: break } } let route = center.addObserver(forName: AVAudioSession.routeChangeNotification, object: session, queue: nil) { let raw = $0.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt if raw.flatMap(AVAudioSession.RouteChangeReason.init) == .oldDeviceUnavailable { routeLost() } } return [interruption, route] } #else static var outputLatency: TimeInterval { 0 } static func activate() async throws {} static func observe( interrupted _: @escaping @Sendable (Bool) -> Void, routeLost _: @escaping @Sendable () -> Void ) -> [Token] { [] } #endif } #endif