import Foundation import CoreBluetooth import AVFoundation /// 蓝牙事件类型 enum BluetoothEventType { case stateChanged case deviceConnected case deviceDisconnected } /// 蓝牙状态枚举 enum BluetoothState: String { case unknown = "unknown" case off = "off" case on = "on" case unauthorized = "unauthorized" case unsupported = "unsupported" case resetting = "resetting" } /// 简化的蓝牙助手类,专注于iOS上的蓝牙耳机检测 class ClassicBluetoothHelper: NSObject { // 状态监控相关 private var isMonitoring = false private var stateCallback: ((BluetoothEventType, [String: Any]) -> Void)? // 用于监控蓝牙系统状态,延迟初始化 private var centralManager: CBCentralManager? // 音频路由变化观察者 private var routeChangeObserver: NSObjectProtocol? override init() { super.init() // 初始化时不立即创建centralManager,延迟到实际需要时 } deinit { stopBluetoothStateMonitoring() } /// 延迟初始化CBCentralManager,只在实际需要时创建 private func initializeCentralManagerIfNeeded() { if centralManager == nil { centralManager = CBCentralManager( delegate: self, queue: DispatchQueue.main, options: [CBCentralManagerOptionShowPowerAlertKey: true] ) } } /// 检查蓝牙是否启用,此方法会触发权限请求 func isBluetoothEnabled() -> Bool { // 此方法调用会触发权限申请 initializeCentralManagerIfNeeded() guard let central = centralManager else { print("检查蓝牙状态失败:centralManager未初始化") return false } // 检查蓝牙状态并记录 let stateDescription: String let isEnabled: Bool switch central.state { case .poweredOn: stateDescription = "已开启" isEnabled = true case .poweredOff: stateDescription = "已关闭" isEnabled = false case .unauthorized: stateDescription = "未授权" isEnabled = false case .unsupported: stateDescription = "不支持" isEnabled = false case .resetting: stateDescription = "重置中" isEnabled = false case .unknown: stateDescription = "未知" isEnabled = false @unknown default: stateDescription = "未定义状态" isEnabled = false } print("蓝牙状态检查结果: \(stateDescription), isEnabled: \(isEnabled)") return isEnabled } /// 获取已连接的蓝牙耳机,此方法会触发权限请求 func getConnectedDevices(completion: @escaping ([[String: String]], Error?) -> Void) { // 确保蓝牙已初始化 initializeCentralManagerIfNeeded() // 获取音频设备(主要是蓝牙耳机) let audioDevices = getConnectedAudioDevices() // 日志输出 if !audioDevices.isEmpty { print("已找到\(audioDevices.count)个蓝牙音频设备:") for device in audioDevices { print("- \(device["name"] ?? "未知") (\(device["address"] ?? "未知地址"))") } } else { print("未找到蓝牙音频设备") } completion(audioDevices, nil) } // 获取连接的蓝牙音频设备 private func getConnectedAudioDevices() -> [[String: String]] { var result: [[String: String]] = [] // 获取当前音频路由 let outputs = AVAudioSession.sharedInstance().currentRoute.outputs // 过滤蓝牙相关设备 for output in outputs where isBluetoothOutput(output.portType) { let device: [String: String] = [ "name": output.portName, "address": cleanDeviceAddress(output.uid) ] result.append(device) } return result } // 检查是否为蓝牙输出 private func isBluetoothOutput(_ portType: AVAudioSession.Port) -> Bool { return portType == .bluetoothA2DP || portType == .bluetoothHFP || portType == .bluetoothLE } // 清理设备地址 private func cleanDeviceAddress(_ address: String) -> String { // 移除类似 "-tacl" 的后缀 if let range = address.range(of: "-", options: .backwards) { return String(address[.. Void) { self.stateCallback = callback if !isMonitoring { isMonitoring = true // 初始化蓝牙管理器 initializeCentralManagerIfNeeded() // 观察音频路由变化 let notificationCenter = NotificationCenter.default routeChangeObserver = notificationCenter.addObserver( forName: AVAudioSession.routeChangeNotification, object: nil, queue: nil ) { [weak self] notification in self?.handleAudioRouteChange(notification) } // 激活音频会话 activateAudioSession() } } // 处理音频路由变化 private func handleAudioRouteChange(_ notification: Notification) { guard let userInfo = notification.userInfo, let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt, let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return } switch reason { case .newDeviceAvailable: // 新音频设备已连接 let audioDevices = getConnectedAudioDevices() for device in audioDevices { stateCallback?(.deviceConnected, ["device": device]) } case .oldDeviceUnavailable: // 获取断开之前的路由 if let previousRoute = userInfo[AVAudioSessionRouteChangePreviousRouteKey] as? AVAudioSessionRouteDescription { for output in previousRoute.outputs where isBluetoothOutput(output.portType) { let device: [String: String] = [ "name": output.portName, "address": cleanDeviceAddress(output.uid) ] stateCallback?(.deviceDisconnected, ["device": device]) } } default: break } } // 激活音频会话 private func activateAudioSession() { do { let audioSession = AVAudioSession.sharedInstance() try audioSession.setCategory(.playback, mode: .default) try audioSession.setActive(true) } catch { print("设置音频会话失败: \(error.localizedDescription)") } } /// 停止监听蓝牙状态变化 func stopBluetoothStateMonitoring() { if isMonitoring { isMonitoring = false // 移除音频路由观察者 if let observer = routeChangeObserver { NotificationCenter.default.removeObserver(observer) routeChangeObserver = nil } // 关闭音频会话 deactivateAudioSession() } // 清除回调 stateCallback = nil } // 关闭音频会话 private func deactivateAudioSession() { do { try AVAudioSession.sharedInstance().setActive(false) } catch { print("关闭音频会话失败: \(error.localizedDescription)") } } /// 报告当前蓝牙状态 func reportCurrentBluetoothState() { // 如果centralManager未初始化,不触发状态报告 guard let centralManager = centralManager else { return } // 直接调用delegate方法保持状态处理一致性 centralManagerDidUpdateState(centralManager) } } // MARK: - CBCentralManagerDelegate extension ClassicBluetoothHelper: CBCentralManagerDelegate { func centralManagerDidUpdateState(_ central: CBCentralManager) { let state: BluetoothState // 添加详细日志输出 print("iOS蓝牙状态原始值: \(central.state.rawValue)") switch central.state { case .poweredOn: print("蓝牙状态: 已开启(.poweredOn)") state = .on case .poweredOff: print("蓝牙状态: 已关闭(.poweredOff)") state = .off case .unauthorized: print("蓝牙状态: 未授权(.unauthorized)") state = .unauthorized case .unsupported: print("蓝牙状态: 不支持(.unsupported)") state = .unsupported case .resetting: print("蓝牙状态: 重置中(.resetting)") state = .resetting case .unknown: print("蓝牙状态: 未知(.unknown)") state = .unknown @unknown default: print("蓝牙状态: 未处理的新状态(\(central.state.rawValue))") state = .unknown } print("向Flutter端报告的蓝牙状态: \(state.rawValue)") // 仅发送兼容的状态(为保持向后兼容,只发送on/off/unknown) let reportState: String switch state { case .on: reportState = BluetoothState.on.rawValue case .off, .unauthorized, .unsupported: // 这些状态对用户来说都意味着蓝牙不可用 reportState = BluetoothState.off.rawValue default: reportState = BluetoothState.unknown.rawValue } stateCallback?(.stateChanged, ["state": reportState]) } }