/** * 蓝牙录音设备管理工具类 * 支持蓝牙设备搜索、连接、数据接收等功能 */ class BluetoothRecorder { constructor() { this.adapterState = false // 蓝牙适配器状态 this.isScanning = false // 是否正在扫描 this.connectedDeviceId = null // 已连接的设备ID this.deviceList = [] // 发现的设备列表 this.services = [] // 蓝牙服务列表 this.characteristics = [] // 特征值列表 this.notifyCharacteristicId = null // 用于接收数据的特征值ID this.writeCharacteristicId = null // 用于发送数据的特征值ID // 音频数据缓冲区 this.audioBuffer = [] this.isReceiving = false // 回调函数 this.onDeviceFound = null // 发现设备回调 this.onDeviceConnected = null // 设备连接回调 this.onDeviceDisconnected = null // 设备断开回调 this.onAudioDataReceived = null // 音频数据接收回调 this.onError = null // 错误回调 } /** * 初始化蓝牙适配器 */ async initBluetoothAdapter() { return new Promise((resolve, reject) => { uni.openBluetoothAdapter({ success: (res) => { console.log('蓝牙适配器初始化成功', res) this.adapterState = true this._listenAdapterState() resolve(res) }, fail: (err) => { console.error('蓝牙适配器初始化失败', err) this.adapterState = false this._handleError('蓝牙适配器初始化失败', err) reject(err) } }) }) } /** * 监听蓝牙适配器状态变化 */ _listenAdapterState() { uni.onBluetoothAdapterStateChange((res) => { console.log('蓝牙适配器状态变化', res) this.adapterState = res.available if (!res.available) { this.connectedDeviceId = null this._handleError('蓝牙适配器不可用', res) } }) } /** * 开始搜索蓝牙设备 * @param {Object} options - 搜索选项 * @param {Array} options.services - 服务UUID列表(可选) * @param {number} options.interval - 上报间隔(毫秒) * @param {boolean} options.allowDuplicatesKey - 是否允许重复上报 */ async startScan(options = {}) { if (this.isScanning) { console.warn('已经在扫描中') return } if (!this.adapterState) { await this.initBluetoothAdapter() } return new Promise((resolve, reject) => { // 监听设备发现 uni.onBluetoothDeviceFound((res) => { const devices = res.devices || [] devices.forEach(device => { // 过滤已存在的设备 const exists = this.deviceList.find(d => d.deviceId === device.deviceId) if (!exists) { this.deviceList.push({ deviceId: device.deviceId, name: device.name || '未知设备', RSSI: device.RSSI, advertisData: device.advertisData, advertisServiceUUIDs: device.advertisServiceUUIDs, localName: device.localName }) // 触发设备发现回调 if (this.onDeviceFound) { this.onDeviceFound(device) } } }) }) // 开始搜索 uni.startBluetoothDevicesDiscovery({ services: options.services || [], allowDuplicatesKey: options.allowDuplicatesKey || false, interval: options.interval || 0, success: (res) => { console.log('开始搜索蓝牙设备', res) this.isScanning = true resolve(res) }, fail: (err) => { console.error('搜索蓝牙设备失败', err) this.isScanning = false this._handleError('搜索蓝牙设备失败', err) reject(err) } }) }) } /** * 停止搜索蓝牙设备 */ async stopScan() { if (!this.isScanning) { return } return new Promise((resolve, reject) => { uni.stopBluetoothDevicesDiscovery({ success: (res) => { console.log('停止搜索蓝牙设备', res) this.isScanning = false resolve(res) }, fail: (err) => { console.error('停止搜索失败', err) this._handleError('停止搜索失败', err) reject(err) } }) }) } /** * 连接蓝牙设备 * @param {string} deviceId - 设备ID */ async connectDevice(deviceId) { if (this.connectedDeviceId === deviceId) { console.warn('设备已连接') return } return new Promise((resolve, reject) => { uni.createBLEConnection({ deviceId: deviceId, success: async (res) => { console.log('蓝牙设备连接成功', res) this.connectedDeviceId = deviceId // 监听连接状态 this._listenConnectionState(deviceId) try { // 获取服务列表 await this.getServices(deviceId) // 触发连接成功回调 if (this.onDeviceConnected) { this.onDeviceConnected(deviceId) } resolve(res) } catch (err) { reject(err) } }, fail: (err) => { console.error('蓝牙设备连接失败', err) this._handleError('蓝牙设备连接失败', err) reject(err) } }) }) } /** * 监听连接状态 */ _listenConnectionState(deviceId) { uni.onBLEConnectionStateChange((res) => { console.log('蓝牙连接状态变化', res) if (res.deviceId === deviceId) { if (!res.connected) { // 连接断开 this.connectedDeviceId = null this.isReceiving = false if (this.onDeviceDisconnected) { this.onDeviceDisconnected(deviceId) } } } }) } /** * 断开蓝牙设备连接 */ async disconnectDevice() { if (!this.connectedDeviceId) { return } return new Promise((resolve, reject) => { uni.closeBLEConnection({ deviceId: this.connectedDeviceId, success: (res) => { console.log('蓝牙设备断开成功', res) this.connectedDeviceId = null this.isReceiving = false this.audioBuffer = [] resolve(res) }, fail: (err) => { console.error('蓝牙设备断开失败', err) this._handleError('蓝牙设备断开失败', err) reject(err) } }) }) } /** * 获取蓝牙设备服务列表 * @param {string} deviceId - 设备ID */ async getServices(deviceId) { return new Promise((resolve, reject) => { uni.getBLEDeviceServices({ deviceId: deviceId, success: async (res) => { console.log('获取服务列表成功', res) this.services = res.services || [] // 遍历服务,获取特征值 for (const service of this.services) { try { await this.getCharacteristics(deviceId, service.uuid) } catch (err) { console.warn('获取特征值失败', service.uuid, err) } } resolve(res) }, fail: (err) => { console.error('获取服务列表失败', err) this._handleError('获取服务列表失败', err) reject(err) } }) }) } /** * 获取特征值列表 * @param {string} deviceId - 设备ID * @param {string} serviceId - 服务UUID */ async getCharacteristics(deviceId, serviceId) { return new Promise((resolve, reject) => { uni.getBLEDeviceCharacteristics({ deviceId: deviceId, serviceId: serviceId, success: (res) => { console.log('获取特征值成功', serviceId, res) const chars = res.characteristics || [] this.characteristics.push(...chars) // 查找可用于通知和写入的特征值 chars.forEach(char => { // 查找可通知的特征值(用于接收音频数据) if (char.properties.notify || char.properties.indicate) { if (!this.notifyCharacteristicId) { this.notifyCharacteristicId = { deviceId: deviceId, serviceId: serviceId, characteristicId: char.uuid } // 启用通知 this.enableNotify(deviceId, serviceId, char.uuid) } } // 查找可写入的特征值(用于发送命令) if (char.properties.write || char.properties.writeNoResponse) { if (!this.writeCharacteristicId) { this.writeCharacteristicId = { deviceId: deviceId, serviceId: serviceId, characteristicId: char.uuid } } } }) resolve(res) }, fail: (err) => { console.error('获取特征值失败', err) reject(err) } }) }) } /** * 启用特征值通知(用于接收音频数据) * @param {string} deviceId - 设备ID * @param {string} serviceId - 服务UUID * @param {string} characteristicId - 特征值UUID */ async enableNotify(deviceId, serviceId, characteristicId) { return new Promise((resolve, reject) => { uni.notifyBLECharacteristicValueChange({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, state: true, // 启用通知 success: (res) => { console.log('启用通知成功', res) // 监听特征值变化(接收音频数据) uni.onBLECharacteristicValueChange((res) => { this._handleAudioData(res.value) }) this.isReceiving = true resolve(res) }, fail: (err) => { console.error('启用通知失败', err) this._handleError('启用通知失败', err) reject(err) } }) }) } /** * 处理接收到的音频数据 * @param {ArrayBuffer} data - 音频数据 */ _handleAudioData(data) { // 将 ArrayBuffer 转换为 Base64 或保存到缓冲区 const base64 = uni.arrayBufferToBase64(data) // 添加到缓冲区 this.audioBuffer.push({ data: base64, timestamp: Date.now() }) // 触发回调 if (this.onAudioDataReceived) { this.onAudioDataReceived({ data: base64, arrayBuffer: data, timestamp: Date.now() }) } } /** * 读取特征值 * @param {string} deviceId - 设备ID * @param {string} serviceId - 服务UUID * @param {string} characteristicId - 特征值UUID */ async readCharacteristic(deviceId, serviceId, characteristicId) { return new Promise((resolve, reject) => { uni.readBLECharacteristicValue({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, success: (res) => { console.log('读取特征值成功', res) resolve(res) }, fail: (err) => { console.error('读取特征值失败', err) this._handleError('读取特征值失败', err) reject(err) } }) }) } /** * 写入特征值(发送命令到设备) * @param {ArrayBuffer} value - 要写入的数据 */ async writeCharacteristic(value) { if (!this.writeCharacteristicId) { throw new Error('未找到可写入的特征值') } const { deviceId, serviceId, characteristicId } = this.writeCharacteristicId return new Promise((resolve, reject) => { uni.writeBLECharacteristicValue({ deviceId: deviceId, serviceId: serviceId, characteristicId: characteristicId, value: value, success: (res) => { console.log('写入特征值成功', res) resolve(res) }, fail: (err) => { console.error('写入特征值失败', err) this._handleError('写入特征值失败', err) reject(err) } }) }) } /** * 获取已缓存的音频数据 */ getAudioBuffer() { return this.audioBuffer } /** * 清空音频缓冲区 */ clearAudioBuffer() { this.audioBuffer = [] } /** * 合并音频数据为完整文件(Base64格式) */ mergeAudioData() { return this.audioBuffer.map(item => item.data).join('') } /** * 处理错误 */ _handleError(message, error) { console.error(`[BluetoothRecorder] ${message}`, error) if (this.onError) { this.onError({ message, error }) } } /** * 关闭蓝牙适配器 */ async closeBluetoothAdapter() { // 先断开连接 if (this.connectedDeviceId) { await this.disconnectDevice() } // 停止扫描 if (this.isScanning) { await this.stopScan() } return new Promise((resolve, reject) => { uni.closeBluetoothAdapter({ success: (res) => { console.log('关闭蓝牙适配器成功', res) this.adapterState = false resolve(res) }, fail: (err) => { console.error('关闭蓝牙适配器失败', err) reject(err) } }) }) } /** * 获取设备列表 */ getDeviceList() { return this.deviceList } /** * 清空设备列表 */ clearDeviceList() { this.deviceList = [] } } // 导出单例 export default new BluetoothRecorder()