You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

344 lines
14 KiB

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
}
}