447 lines
14 KiB
JavaScript
447 lines
14 KiB
JavaScript
"use strict";
|
||
const common_vendor = require("./vendor.js");
|
||
class BluetoothRecorder {
|
||
constructor() {
|
||
this.adapterState = false;
|
||
this.isScanning = false;
|
||
this.connectedDeviceId = null;
|
||
this.deviceList = [];
|
||
this.services = [];
|
||
this.characteristics = [];
|
||
this.notifyCharacteristicId = null;
|
||
this.writeCharacteristicId = null;
|
||
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) => {
|
||
common_vendor.index.openBluetoothAdapter({
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:36", "蓝牙适配器初始化成功", res);
|
||
this.adapterState = true;
|
||
this._listenAdapterState();
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:42", "蓝牙适配器初始化失败", err);
|
||
this.adapterState = false;
|
||
this._handleError("蓝牙适配器初始化失败", err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 监听蓝牙适配器状态变化
|
||
*/
|
||
_listenAdapterState() {
|
||
common_vendor.index.onBluetoothAdapterStateChange((res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:56", "蓝牙适配器状态变化", 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) {
|
||
common_vendor.index.__f__("warn", "at common/bluetooth.js:74", "已经在扫描中");
|
||
return;
|
||
}
|
||
if (!this.adapterState) {
|
||
await this.initBluetoothAdapter();
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
common_vendor.index.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);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
common_vendor.index.startBluetoothDevicesDiscovery({
|
||
services: options.services || [],
|
||
allowDuplicatesKey: options.allowDuplicatesKey || false,
|
||
interval: options.interval || 0,
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:113", "开始搜索蓝牙设备", res);
|
||
this.isScanning = true;
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:118", "搜索蓝牙设备失败", err);
|
||
this.isScanning = false;
|
||
this._handleError("搜索蓝牙设备失败", err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 停止搜索蓝牙设备
|
||
*/
|
||
async stopScan() {
|
||
if (!this.isScanning) {
|
||
return;
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
common_vendor.index.stopBluetoothDevicesDiscovery({
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:138", "停止搜索蓝牙设备", res);
|
||
this.isScanning = false;
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:143", "停止搜索失败", err);
|
||
this._handleError("停止搜索失败", err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 连接蓝牙设备
|
||
* @param {string} deviceId - 设备ID
|
||
*/
|
||
async connectDevice(deviceId) {
|
||
if (this.connectedDeviceId === deviceId) {
|
||
common_vendor.index.__f__("warn", "at common/bluetooth.js:157", "设备已连接");
|
||
return;
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
common_vendor.index.createBLEConnection({
|
||
deviceId,
|
||
success: async (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:165", "蓝牙设备连接成功", 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) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:186", "蓝牙设备连接失败", err);
|
||
this._handleError("蓝牙设备连接失败", err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 监听连接状态
|
||
*/
|
||
_listenConnectionState(deviceId) {
|
||
common_vendor.index.onBLEConnectionStateChange((res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:199", "蓝牙连接状态变化", 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) => {
|
||
common_vendor.index.closeBLEConnection({
|
||
deviceId: this.connectedDeviceId,
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:225", "蓝牙设备断开成功", res);
|
||
this.connectedDeviceId = null;
|
||
this.isReceiving = false;
|
||
this.audioBuffer = [];
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:232", "蓝牙设备断开失败", err);
|
||
this._handleError("蓝牙设备断开失败", err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 获取蓝牙设备服务列表
|
||
* @param {string} deviceId - 设备ID
|
||
*/
|
||
async getServices(deviceId) {
|
||
return new Promise((resolve, reject) => {
|
||
common_vendor.index.getBLEDeviceServices({
|
||
deviceId,
|
||
success: async (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:249", "获取服务列表成功", res);
|
||
this.services = res.services || [];
|
||
for (const service of this.services) {
|
||
try {
|
||
await this.getCharacteristics(deviceId, service.uuid);
|
||
} catch (err) {
|
||
common_vendor.index.__f__("warn", "at common/bluetooth.js:257", "获取特征值失败", service.uuid, err);
|
||
}
|
||
}
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:264", "获取服务列表失败", err);
|
||
this._handleError("获取服务列表失败", err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 获取特征值列表
|
||
* @param {string} deviceId - 设备ID
|
||
* @param {string} serviceId - 服务UUID
|
||
*/
|
||
async getCharacteristics(deviceId, serviceId) {
|
||
return new Promise((resolve, reject) => {
|
||
common_vendor.index.getBLEDeviceCharacteristics({
|
||
deviceId,
|
||
serviceId,
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:283", "获取特征值成功", 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,
|
||
serviceId,
|
||
characteristicId: char.uuid
|
||
};
|
||
this.enableNotify(deviceId, serviceId, char.uuid);
|
||
}
|
||
}
|
||
if (char.properties.write || char.properties.writeNoResponse) {
|
||
if (!this.writeCharacteristicId) {
|
||
this.writeCharacteristicId = {
|
||
deviceId,
|
||
serviceId,
|
||
characteristicId: char.uuid
|
||
};
|
||
}
|
||
}
|
||
});
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:317", "获取特征值失败", 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) => {
|
||
common_vendor.index.notifyBLECharacteristicValueChange({
|
||
deviceId,
|
||
serviceId,
|
||
characteristicId,
|
||
state: true,
|
||
// 启用通知
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:338", "启用通知成功", res);
|
||
common_vendor.index.onBLECharacteristicValueChange((res2) => {
|
||
this._handleAudioData(res2.value);
|
||
});
|
||
this.isReceiving = true;
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:349", "启用通知失败", err);
|
||
this._handleError("启用通知失败", err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 处理接收到的音频数据
|
||
* @param {ArrayBuffer} data - 音频数据
|
||
*/
|
||
_handleAudioData(data) {
|
||
const base64 = common_vendor.index.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) => {
|
||
common_vendor.index.readBLECharacteristicValue({
|
||
deviceId,
|
||
serviceId,
|
||
characteristicId,
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:394", "读取特征值成功", res);
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:398", "读取特征值失败", 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) => {
|
||
common_vendor.index.writeBLECharacteristicValue({
|
||
deviceId,
|
||
serviceId,
|
||
characteristicId,
|
||
value,
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:424", "写入特征值成功", res);
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:428", "写入特征值失败", 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) {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:461", `[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) => {
|
||
common_vendor.index.closeBluetoothAdapter({
|
||
success: (res) => {
|
||
common_vendor.index.__f__("log", "at common/bluetooth.js:487", "关闭蓝牙适配器成功", res);
|
||
this.adapterState = false;
|
||
resolve(res);
|
||
},
|
||
fail: (err) => {
|
||
common_vendor.index.__f__("error", "at common/bluetooth.js:492", "关闭蓝牙适配器失败", err);
|
||
reject(err);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
/**
|
||
* 获取设备列表
|
||
*/
|
||
getDeviceList() {
|
||
return this.deviceList;
|
||
}
|
||
/**
|
||
* 清空设备列表
|
||
*/
|
||
clearDeviceList() {
|
||
this.deviceList = [];
|
||
}
|
||
}
|
||
const bluetoothRecorder = new BluetoothRecorder();
|
||
exports.bluetoothRecorder = bluetoothRecorder;
|
||
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/bluetooth.js.map
|