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.
243 lines
7.9 KiB
243 lines
7.9 KiB
import Foundation
|
|
import CoreBluetooth
|
|
import AVFoundation
|
|
import UIKit
|
|
|
|
/// 蓝牙事件处理协议
|
|
@objc protocol BluetoothEventHandler: AnyObject {
|
|
func sendEvent(_ event: [String: Any])
|
|
}
|
|
|
|
@objc class ClassicBluetoothHelper: NSObject, CBCentralManagerDelegate {
|
|
// MARK: - 属性
|
|
|
|
// 音频会话管理器
|
|
private let audioSessionManager = AudioSessionManager.shared
|
|
|
|
// 蓝牙管理器
|
|
private var centralManager: CBCentralManager?
|
|
|
|
// 事件处理器
|
|
private weak var eventHandler: BluetoothEventHandler?
|
|
|
|
// MARK: - 初始化与设置
|
|
|
|
override init() {
|
|
super.init()
|
|
}
|
|
|
|
// 设置事件处理器
|
|
@objc func setEventHandler(_ handler: BluetoothEventHandler) {
|
|
eventHandler = handler
|
|
}
|
|
|
|
|
|
// MARK: - CBCentralManagerDelegate
|
|
|
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
let stateString = getBluetoothStateString(state: central.state)
|
|
sendBluetoothStateEvent(state: stateString)
|
|
}
|
|
|
|
// MARK: - 设备监控
|
|
|
|
// 获取蓝牙状态字符串
|
|
private func getBluetoothStateString(state: CBManagerState) -> String {
|
|
switch state {
|
|
case .poweredOn:
|
|
return "on"
|
|
case .poweredOff:
|
|
return "off"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// 发送蓝牙状态事件
|
|
private func sendBluetoothStateEvent(state: String) {
|
|
let event: [String: Any] = [
|
|
"type": "bluetoothStateChanged",
|
|
"state": state,
|
|
"timestamp": Int(Date().timeIntervalSince1970 * 1000)
|
|
]
|
|
eventHandler?.sendEvent(event)
|
|
}
|
|
|
|
// 启动蓝牙设备监控
|
|
@objc func initialize() {
|
|
if centralManager == nil {
|
|
centralManager = CBCentralManager(delegate: self, queue: nil)
|
|
}
|
|
|
|
|
|
|
|
// 发送当前蓝牙状态
|
|
if let manager = centralManager {
|
|
let stateString = getBluetoothStateString(state: manager.state)
|
|
sendBluetoothStateEvent(state: stateString)
|
|
}
|
|
// 移除之前的监听器
|
|
NotificationCenter.default.removeObserver(self)
|
|
// 添加新的监听器
|
|
audioSessionManager.addRouteChangeListener(self, selector: #selector(handleRouteChange(_:)))
|
|
BluetoothMediaButtonHelper.shared.startButtonListening()
|
|
}
|
|
|
|
// 处理音频路由变化
|
|
@objc private func handleRouteChange(_ notification: Notification) {
|
|
guard let userInfo = notification.userInfo,
|
|
let reasonValue = userInfo[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
|
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else {
|
|
return
|
|
}
|
|
|
|
// 获取当前设备
|
|
let currentDevices = getConnectedAudioDevices()
|
|
|
|
// 检查原因
|
|
switch reason {
|
|
case .newDeviceAvailable:
|
|
// 新设备已连接
|
|
for device in currentDevices {
|
|
// 发送设备连接事件
|
|
NSLog("发送设备连接事件 - 设备: \(device["name"] ?? "未知设备") [\(device["address"] ?? "未知地址")]")
|
|
sendDeviceConnectedEvent(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)
|
|
]
|
|
// 发送设备断开事件
|
|
sendDeviceDisconnectedEvent(device: device)
|
|
}
|
|
}
|
|
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
// 检查是否为蓝牙输出
|
|
private func isBluetoothOutput(_ portType: AVAudioSession.Port) -> Bool {
|
|
return portType == .bluetoothA2DP || portType == .bluetoothHFP || portType == .bluetoothLE
|
|
}
|
|
|
|
// MARK: - 设备事件
|
|
|
|
// 发送设备连接事件
|
|
private func sendDeviceConnectedEvent(device: [String: String]) {
|
|
guard let name = device["name"], let address = device["address"] else { return }
|
|
|
|
let deviceMap: [String: Any] = [
|
|
"name": name,
|
|
"address": address,
|
|
"type": "headset"
|
|
]
|
|
|
|
let event: [String: Any] = [
|
|
"type": "deviceConnected",
|
|
"device": deviceMap,
|
|
"timestamp": Int(Date().timeIntervalSince1970 * 1000)
|
|
]
|
|
|
|
eventHandler?.sendEvent(event)
|
|
}
|
|
|
|
// 发送设备断开事件
|
|
private func sendDeviceDisconnectedEvent(device: [String: String]) {
|
|
guard let name = device["name"], let address = device["address"] else { return }
|
|
|
|
let deviceMap: [String: Any] = [
|
|
"name": name,
|
|
"address": address,
|
|
"type": "headset"
|
|
]
|
|
|
|
let event: [String: Any] = [
|
|
"type": "deviceDisconnected",
|
|
"device": deviceMap,
|
|
"timestamp": Int(Date().timeIntervalSince1970 * 1000)
|
|
]
|
|
|
|
eventHandler?.sendEvent(event)
|
|
}
|
|
|
|
// MARK: - 公共API
|
|
|
|
// 获取当前连接的耳机设备
|
|
@objc func getConnectedHeadsetDevices(_ completion: @escaping ([[String: String]]?, String?) -> Void) {
|
|
// 检查蓝牙状态
|
|
if centralManager?.state != .poweredOn {
|
|
completion(nil, "蓝牙未启用")
|
|
return
|
|
}
|
|
|
|
do {
|
|
// 获取设备
|
|
let devices = getConnectedAudioDevices()
|
|
completion(devices, nil)
|
|
} catch {
|
|
completion(nil, "获取设备失败: \(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
// 获取当前连接的音频设备
|
|
private func getConnectedAudioDevices() -> [[String: String]] {
|
|
var result: [[String: String]] = []
|
|
|
|
// 获取当前音频路由
|
|
let outputs = audioSessionManager.getCurrentRoute().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
|
|
}
|
|
|
|
// 释放资源
|
|
@objc func dispose() {
|
|
audioSessionManager.removeRouteChangeListener(self)
|
|
eventHandler = nil
|
|
}
|
|
|
|
|
|
|
|
// 检查蓝牙是否启用
|
|
@objc func isBluetoothEnabled() -> Bool {
|
|
return centralManager?.state == .poweredOn
|
|
}
|
|
|
|
// 清理设备地址,移除后缀
|
|
private func cleanDeviceAddress(_ address: String) -> String {
|
|
// 检查是否包含常见分隔符
|
|
if let range = address.range(of: "-") ?? address.range(of: ":") ?? address.range(of: "_") {
|
|
// 只保留分隔符之前的部分
|
|
return String(address[..<range.lowerBound])
|
|
}
|
|
|
|
// iOS设备UID有时会包含额外信息,尝试只保留标准MAC地址格式部分
|
|
// MAC地址通常是12个十六进制字符,可能带有分隔符
|
|
let hexPattern = "[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}|[0-9A-Fa-f]{12}"
|
|
if let regex = try? NSRegularExpression(pattern: hexPattern, options: []),
|
|
let match = regex.firstMatch(in: address, options: [], range: NSRange(location: 0, length: address.utf16.count)) {
|
|
let matchRange = match.range
|
|
if let range = Range(matchRange, in: address) {
|
|
return String(address[range])
|
|
}
|
|
}
|
|
|
|
return address
|
|
}
|
|
}
|
|
|