import Foundation import CoreBluetooth import os.log import AVFoundation import AccessorySetupKit import UIKit class BleAccessorySetup: NSObject { // 单例实现 static let shared = BleAccessorySetup() // 基本属性 private var bleService: BleService? private var session: ASAccessorySession? private var isSessionActivated = false private var isInitializing = false private let logger = OSLog(subsystem: "com.yunqiinnovation.ble_service", category: "BleAccessorySetup") private var onSessionActivated: (() -> Void)? typealias DeviceDiscoveredCallback = (UUID) -> Void private var onDeviceDiscovered: DeviceDiscoveredCallback? private override init() { super.init() } // 设置BLE服务实例 func setBleService(_ service: Any) { self.bleService = service as? BleService } // 设置设备发现回调 func setDeviceDiscoveredCallback(_ callback: @escaping (UUID) -> Void) { self.onDeviceDiscovered = callback } // 初始化设备发现会话 func initializeDiscoverySession() { os_log("开始初始化设备发现会话", log: logger, type: .info) if isInitializing || isSessionActivated { os_log("会话已在初始化或已激活,跳过初始化", log: logger, type: .debug) return } isInitializing = true do { os_log("创建ASAccessorySession", log: logger, type: .debug) session = try ASAccessorySession() os_log("激活会话", log: logger, type: .debug) session?.activate(on: .main) { [weak self] event in guard let self = self else { return } os_log("收到会话事件: %{public}@", log: self.logger, type: .debug, String(describing: event.eventType)) switch event.eventType { case .activated: os_log("会话已激活", log: self.logger, type: .info) self.isSessionActivated = true DispatchQueue.main.async(execute: DispatchWorkItem(block: { self.onSessionActivated?() self.onSessionActivated = nil })) case .accessoryAdded: if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier { os_log("发现设备: %{public}@", log: self.logger, type: .info, uuid.uuidString) // 记录设备详细信息 let name = "设备_\(uuid.uuidString.prefix(8))" os_log("设备名称: %{public}@", log: self.logger, type: .debug, name) // 直接获取公司ID无需使用可选链 let descriptor = accessory.descriptor let companyIdentifier = descriptor.bluetoothCompanyIdentifier.rawValue os_log("设备公司ID: 0x%{public}x", log: self.logger, type: .debug, companyIdentifier) self.onDeviceDiscovered?(uuid) } else { os_log("发现设备但缺少UUID信息", log: self.logger, type: .error) if let accessory = event.accessory { os_log("设备信息: %{public}@", log: self.logger, type: .debug, String(describing: accessory)) } } case .accessoryRemoved: os_log("设备已移除", log: self.logger, type: .debug) if let accessory = event.accessory, let uuid = accessory.bluetoothIdentifier { os_log("移除设备UUID: %{public}@", log: self.logger, type: .debug, uuid.uuidString) } case .accessoryChanged: os_log("设备已变更", log: self.logger, type: .debug) case .pickerSetupFailed: os_log("选择器设置失败: %{public}@", log: self.logger, type: .error, event.error?.localizedDescription ?? "未知错误") default: os_log("未处理的事件类型: %{public}@", log: self.logger, type: .debug, String(describing: event.eventType)) break } } // 处理现有授权设备 if let accessories = session?.accessories { os_log("检查当前已授权设备:共%d个", log: logger, type: .debug, accessories.count) for accessory in accessories { if let uuid = accessory.bluetoothIdentifier { os_log("处理已授权设备:%@", log: logger, type: .info, uuid.uuidString) let name = "设备_\(uuid.uuidString.prefix(8))" os_log("已授权设备名称:%@", log: logger, type: .debug, name) self.onDeviceDiscovered?(uuid) } else { os_log("发现已授权设备但无UUID", log: logger, type: .error) } } } else { os_log("无已授权设备", log: logger, type: .debug) } } catch { // 出错时重置状态 os_log("初始化会话失败:%@", log: logger, type: .error, error.localizedDescription) isInitializing = false session = nil DispatchQueue.main.async(execute: DispatchWorkItem(block: { self.onSessionActivated?() self.onSessionActivated = nil })) } isInitializing = false os_log("设备发现会话初始化完成", log: logger, type: .debug) } // 初始化会话并在完成后执行回调 func initializeSessionAndRun(completion: @escaping () -> Void) { // 如果会话已存在且已激活 if session != nil && isSessionActivated { completion() return } // 如果会话存在但未激活,设置回调 if session != nil && !isSessionActivated { self.onSessionActivated = completion return } // 需要从头创建会话 self.onSessionActivated = completion initializeDiscoverySession() } // 重置会话状态 func resetSession() { // 取消可能挂起的回调 onSessionActivated = nil // 清理现有会话 session?.invalidate() // 重置状态 session = nil isSessionActivated = false isInitializing = false } // 安全显示设备选择器 func safeShowDevicePicker() { resetSession() initializeSessionAndRun { [weak self] in guard let self = self else { return } self.showDevicePicker() } } // 显示设备选择器 func showDevicePicker() { os_log("尝试显示设备选择器", log: logger, type: .info) // 检查会话是否已初始化 if session == nil { os_log("会话未初始化,重新安全初始化", log: logger, type: .debug) safeShowDevicePicker() return } // 检查会话是否已激活 if !isSessionActivated { os_log("会话尚未激活,等待激活后再显示选择器", log: logger, type: .debug) onSessionActivated = { [weak self] in self?.showDevicePicker() } return } // 创建设备描述符和显示项 os_log("创建设备描述符", log: logger, type: .debug) let descriptor = ASDiscoveryDescriptor() descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB) os_log("设置公司标识符:0x%x", log: logger, type: .debug, 0xEFAB) // 检查和记录发现设置 if let session = self.session { os_log("当前会话设置已激活: %{public}@", log: logger, type: .debug, String(describing: self.isSessionActivated)) let currentAccessories = session.accessories os_log("当前会话有%d个已知设备", log: logger, type: .debug, currentAccessories.count) } // 创建默认图像 let deviceImage = UIImage(systemName: "headphones") ?? UIImage() // 创建显示项 let displayItem = ASPickerDisplayItem( name: "语音助理耳机", productImage: deviceImage, descriptor: descriptor ) os_log("创建显示项:语音助理耳机", log: logger, type: .debug) // 显示选择器 os_log("即将显示设备选择器", log: logger, type: .debug) DispatchQueue.main.async(execute: DispatchWorkItem(block: { self.session?.showPicker(for: [displayItem]) { error in if let error = error { os_log("设备选择器错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) } else { os_log("设备选择器已显示", log: self.logger, type: .debug) } } })) } // 处理发现的设备 func handleDiscoveredAccessory(uuid: UUID) { guard let service = bleService else { return } service.connectToAccessory(uuid: uuid) } // 从指定的视图控制器显示设备选择器 func showDevicePickerFromViewController(_ viewController: UIViewController) { safeShowDevicePickerWithUI(presentingVC: viewController) } // 带UI上下文的安全显示选择器 private func safeShowDevicePickerWithUI(presentingVC: UIViewController) { os_log("准备从视图控制器显示设备选择器", log: logger, type: .info) resetSession() initializeSessionAndRun { [weak self, weak presentingVC] in guard let self = self, let presentingVC = presentingVC else { os_log("视图控制器或self已被释放", log: self?.logger ?? OSLog.default, type: .error) return } if self.session != nil && self.isSessionActivated { os_log("会话已激活,准备显示设备选择器UI", log: self.logger, type: .debug) DispatchQueue.main.async { let descriptor = ASDiscoveryDescriptor() descriptor.bluetoothCompanyIdentifier = ASBluetoothCompanyIdentifier(0xEFAB) os_log("设置公司标识符:0x%x", log: self.logger, type: .debug, 0xEFAB) os_log("描述符配置: %{public}@", log: self.logger, type: .debug, String(describing: descriptor)) let deviceImage = UIImage(systemName: "headphones") ?? UIImage() let displayItem = ASPickerDisplayItem( name: "语音助理耳机", productImage: deviceImage, descriptor: descriptor ) os_log("显示设备选择器UI", log: self.logger, type: .debug) self.session?.showPicker(for: [displayItem]) { error in os_log("设备选择器UI回调", log: self.logger, type: .debug) if let error = error { os_log("设备选择器返回错误: %{public}@", log: self.logger, type: .error, error.localizedDescription) let nsError = error as NSError os_log("错误域: %{public}@, 错误码: %d", log: self.logger, type: .error, nsError.domain, nsError.code) } } } } else { os_log("会话未激活或为nil,无法显示设备选择器", log: self.logger, type: .error) } } } // 检查蓝牙是否可用 func isBluetoothAvailable() -> Bool { // 日志输出 os_log("检查蓝牙可用性", log: logger, type: .debug) // 默认返回true,适用于iOS 18环境 if bleService == nil { os_log("BleService未设置,默认返回true", log: logger, type: .info) return true } // 尝试类型转换 guard let _ = bleService as? BleService else { os_log("BleService类型转换失败", log: logger, type: .debug) return true } // 使用AVAudioSession检查蓝牙连接 do { let audioSession = AVAudioSession.sharedInstance() let outputs = audioSession.currentRoute.outputs // 检查蓝牙音频输出设备 for output in outputs { if output.portType == .bluetoothA2DP || output.portType == .bluetoothHFP || output.portType == .bluetoothLE { os_log("发现蓝牙音频输出设备", log: logger, type: .debug) return true } } // 未找到蓝牙设备 os_log("未找到蓝牙音频设备", log: logger, type: .debug) } catch { os_log("检查音频会话出错: %{public}@", log: logger, type: .error, error.localizedDescription) } // 默认返回true,避免阻止功能 return true } }