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.
601 lines
19 KiB
601 lines
19 KiB
import 'dart:async';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
|
import 'package:get/get.dart';
|
|
import '../../core/utils/logger.dart';
|
|
import 'dart:io' show Platform;
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
|
|
/// 设备类型枚举
|
|
enum DeviceType {
|
|
unknown, // 未知设备类型
|
|
classicAudio, // 经典蓝牙音频设备(耳机等)
|
|
ble, // BLE设备
|
|
dual // 双模设备(同时支持经典蓝牙和BLE)
|
|
}
|
|
|
|
/// 蓝牙设备服务,用于发现和管理蓝牙设备
|
|
class BluetoothService extends GetxService {
|
|
static BluetoothService get to => Get.find<BluetoothService>();
|
|
|
|
// 内部实例
|
|
BluetoothService._internal();
|
|
static final BluetoothService _instance = BluetoothService._internal();
|
|
factory BluetoothService() => _instance;
|
|
|
|
// ====== 状态管理 ======
|
|
// 蓝牙适配器状态
|
|
final _isAvailable = false.obs;
|
|
final _isOn = false.obs;
|
|
final _isInitialized = false.obs;
|
|
|
|
// 操作状态
|
|
final _isScanning = false.obs;
|
|
final _permissionsGranted = false.obs;
|
|
|
|
// 设备管理
|
|
final _connectedDevices = <BluetoothDevice>[].obs;
|
|
final _systemPairedDevices = <BluetoothDevice>[].obs;
|
|
final _scanResults = <ScanResult>[].obs;
|
|
final _selectedDevice = Rx<BluetoothDevice?>(null);
|
|
final _deviceType = Rx<DeviceType>(DeviceType.unknown);
|
|
final _connectionState = Rx<BluetoothConnectionState>(BluetoothConnectionState.disconnected);
|
|
final _lastConnectedDeviceInfo = Rx<Map<String, dynamic>>({});
|
|
|
|
// 流订阅
|
|
StreamSubscription? _stateSubscription;
|
|
StreamSubscription? _scanSubscription;
|
|
StreamSubscription? _connectionSubscription;
|
|
|
|
// ====== Getters ======
|
|
// 基本getter
|
|
bool get isAvailable => _isAvailable.value;
|
|
bool get isOn => _isOn.value;
|
|
bool get isInitialized => _isInitialized.value;
|
|
bool get isScanning => _isScanning.value;
|
|
bool get permissionsGranted => _permissionsGranted.value;
|
|
|
|
// 设备相关getter
|
|
List<BluetoothDevice> get connectedDevices => _connectedDevices;
|
|
List<BluetoothDevice> get systemPairedDevices => _systemPairedDevices;
|
|
List<ScanResult> get scanResults => _scanResults;
|
|
BluetoothDevice? get selectedDevice => _selectedDevice.value;
|
|
DeviceType get deviceType => _deviceType.value;
|
|
BluetoothConnectionState get connectionState => _connectionState.value;
|
|
Map<String, dynamic> get lastConnectedDeviceInfo => _lastConnectedDeviceInfo.value;
|
|
|
|
// Observable getters
|
|
RxBool get isAvailableObs => _isAvailable;
|
|
RxBool get isOnObs => _isOn;
|
|
RxBool get isInitializedObs => _isInitialized;
|
|
RxBool get isScanningObs => _isScanning;
|
|
RxBool get permissionsGrantedObs => _permissionsGranted;
|
|
RxList<BluetoothDevice> get connectedDevicesObs => _connectedDevices;
|
|
RxList<ScanResult> get scanResultsObs => _scanResults;
|
|
Rx<BluetoothDevice?> get selectedDeviceObs => _selectedDevice;
|
|
Rx<DeviceType> get deviceTypeObs => _deviceType;
|
|
Rx<BluetoothConnectionState> get connectionStateObs => _connectionState;
|
|
Rx<Map<String, dynamic>> get lastConnectedDeviceInfoObs => _lastConnectedDeviceInfo;
|
|
|
|
/// 初始化蓝牙服务
|
|
Future<BluetoothService> init() async {
|
|
if (_isInitialized.value) {
|
|
Logger.info('蓝牙服务已初始化,无需重复初始化');
|
|
return this;
|
|
}
|
|
|
|
Logger.info('开始初始化蓝牙服务');
|
|
|
|
try {
|
|
// 1. 设置蓝牙状态监听
|
|
_setupBluetoothListeners();
|
|
|
|
// 2. 初始检查蓝牙状态
|
|
_checkBluetoothState();
|
|
|
|
// 3. 标记初始化完成
|
|
_isInitialized.value = true;
|
|
Logger.info('蓝牙服务初始化完成');
|
|
return this;
|
|
} catch (e) {
|
|
Logger.error('初始化蓝牙服务失败: $e');
|
|
return this;
|
|
}
|
|
}
|
|
|
|
/// 设置蓝牙状态监听
|
|
void _setupBluetoothListeners() {
|
|
// 取消旧订阅(如果有)
|
|
_stateSubscription?.cancel();
|
|
|
|
// 监听蓝牙适配器状态变化
|
|
_stateSubscription = FlutterBluePlus.adapterState.listen((state) {
|
|
_isOn.value = state == BluetoothAdapterState.on;
|
|
_isAvailable.value = state != BluetoothAdapterState.unavailable;
|
|
|
|
if (state == BluetoothAdapterState.on) {
|
|
Logger.info('蓝牙已开启');
|
|
if (_permissionsGranted.value) {
|
|
_refreshSystemDevices();
|
|
}
|
|
} else if (state == BluetoothAdapterState.off) {
|
|
Logger.info('蓝牙已关闭');
|
|
_resetDeviceState();
|
|
}
|
|
});
|
|
}
|
|
|
|
/// 初始检查蓝牙状态
|
|
Future<void> _checkBluetoothState() async {
|
|
try {
|
|
bool isOn = await FlutterBluePlus.isOn
|
|
.timeout(const Duration(seconds: 5), onTimeout: () => false);
|
|
|
|
_isOn.value = isOn;
|
|
_isAvailable.value = isOn;
|
|
Logger.info('当前蓝牙是否已开启: $isOn');
|
|
} catch (e) {
|
|
Logger.error('检查蓝牙状态失败: $e');
|
|
_isOn.value = false;
|
|
_isAvailable.value = false;
|
|
}
|
|
}
|
|
|
|
/// 重置设备状态
|
|
void _resetDeviceState() {
|
|
_connectedDevices.clear();
|
|
_systemPairedDevices.clear();
|
|
_scanResults.clear();
|
|
_selectedDevice.value = null;
|
|
_lastConnectedDeviceInfo.value = {};
|
|
_deviceType.value = DeviceType.unknown;
|
|
|
|
// 取消正在进行的操作
|
|
_scanSubscription?.cancel();
|
|
_connectionSubscription?.cancel();
|
|
_isScanning.value = false;
|
|
}
|
|
|
|
/// 检查并请求蓝牙权限
|
|
Future<bool> requestPermissions() async {
|
|
try {
|
|
// iOS无需额外权限
|
|
if (!Platform.isAndroid) {
|
|
_permissionsGranted.value = true;
|
|
return true;
|
|
}
|
|
|
|
// 请求所有必要权限
|
|
final permissions = <Permission>[
|
|
Permission.bluetoothScan,
|
|
Permission.bluetoothConnect,
|
|
Permission.location, // Android需要定位权限来扫描BLE设备
|
|
Permission.microphone
|
|
];
|
|
|
|
// 一次性请求所有权限
|
|
Map<Permission, PermissionStatus> statuses = await permissions.request();
|
|
|
|
// 检查所有权限是否已授予
|
|
bool allGranted = statuses.values.every((status) => status.isGranted);
|
|
_permissionsGranted.value = allGranted;
|
|
|
|
if (!allGranted) {
|
|
// 检查哪些权限被拒绝
|
|
List<String> deniedPermissions = [];
|
|
statuses.forEach((permission, status) {
|
|
if (!status.isGranted) {
|
|
deniedPermissions.add(permission.toString().split('.').last);
|
|
}
|
|
});
|
|
|
|
Logger.warning('权限被拒绝: ${deniedPermissions.join(", ")}');
|
|
}
|
|
|
|
return allGranted;
|
|
} catch (e) {
|
|
Logger.error('请求权限失败: $e');
|
|
_permissionsGranted.value = false;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 刷新系统蓝牙设备(已连接和已配对)
|
|
Future<void> _refreshSystemDevices() async {
|
|
if (!_permissionsGranted.value || !_isOn.value) {
|
|
Logger.warning('无法刷新系统设备:蓝牙未开启或缺少权限');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 获取已连接的系统设备
|
|
List<BluetoothDevice> devices = await FlutterBluePlus.connectedSystemDevices
|
|
.timeout(const Duration(seconds: 10), onTimeout: () => <BluetoothDevice>[]);
|
|
|
|
_connectedDevices.value = devices;
|
|
Logger.info('获取到${devices.length}个系统已连接设备');
|
|
|
|
// 更新选中设备的连接状态(如果有)
|
|
if (_selectedDevice.value != null) {
|
|
bool stillConnected = _connectedDevices.any(
|
|
(d) => d.remoteId.str == _selectedDevice.value!.remoteId.str
|
|
);
|
|
|
|
_connectionState.value = stillConnected
|
|
? BluetoothConnectionState.connected
|
|
: BluetoothConnectionState.disconnected;
|
|
|
|
if (stillConnected) {
|
|
_updateLastConnectedDeviceInfo();
|
|
}
|
|
}
|
|
|
|
// 获取系统已配对设备
|
|
List<BluetoothDevice> pairedDevices = await FlutterBluePlus.bondedDevices
|
|
.timeout(const Duration(seconds: 10), onTimeout: () => <BluetoothDevice>[]);
|
|
|
|
_systemPairedDevices.value = pairedDevices;
|
|
Logger.info('获取到${pairedDevices.length}个系统已配对设备');
|
|
} catch (e) {
|
|
Logger.error('刷新系统设备失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 开始扫描附近的蓝牙设备
|
|
Future<List<ScanResult>> startScan({Duration? timeout}) async {
|
|
// 检查条件
|
|
if (!_permissionsGranted.value) {
|
|
Logger.warning('缺少蓝牙扫描权限,无法扫描设备');
|
|
return [];
|
|
}
|
|
|
|
if (!_isOn.value) {
|
|
Logger.warning('蓝牙未开启,无法扫描设备');
|
|
return [];
|
|
}
|
|
|
|
// 如果已经在扫描,先停止
|
|
if (_isScanning.value) {
|
|
await stopScan();
|
|
}
|
|
|
|
_scanResults.clear();
|
|
Logger.info('开始扫描附近蓝牙设备...');
|
|
|
|
try {
|
|
// 设置结果监听
|
|
_scanSubscription?.cancel();
|
|
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
|
_scanResults.value = results;
|
|
});
|
|
|
|
// 开始扫描
|
|
_isScanning.value = true;
|
|
await FlutterBluePlus.startScan(
|
|
timeout: timeout ?? const Duration(seconds: 10),
|
|
androidScanMode: AndroidScanMode.lowLatency,
|
|
);
|
|
|
|
// 扫描完成
|
|
_isScanning.value = false;
|
|
Logger.info('扫描完成,发现${_scanResults.length}个设备');
|
|
return _scanResults;
|
|
} catch (e) {
|
|
_isScanning.value = false;
|
|
Logger.error('扫描设备失败: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// 停止扫描
|
|
Future<void> stopScan() async {
|
|
if (!_isScanning.value) return;
|
|
|
|
try {
|
|
await FlutterBluePlus.stopScan();
|
|
_isScanning.value = false;
|
|
Logger.info('已停止蓝牙扫描');
|
|
} catch (e) {
|
|
Logger.error('停止扫描失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 连接到指定蓝牙设备
|
|
Future<bool> connectToDevice(BluetoothDevice device) async {
|
|
// 检查条件
|
|
if (!_permissionsGranted.value) {
|
|
Logger.warning('缺少蓝牙连接权限,无法连接设备');
|
|
return false;
|
|
}
|
|
|
|
if (!_isOn.value) {
|
|
Logger.warning('蓝牙未开启,无法连接设备');
|
|
return false;
|
|
}
|
|
|
|
Logger.info('尝试连接设备: ${device.platformName}');
|
|
|
|
try {
|
|
// 确定设备类型
|
|
_determineDeviceType(device);
|
|
|
|
// 检查设备是否已连接
|
|
bool alreadyConnected = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str);
|
|
|
|
// 如果当前已选择该设备且已连接,则无需再连接
|
|
if (_selectedDevice.value == device && alreadyConnected) {
|
|
Logger.info('设备已连接,无需重复连接');
|
|
_connectionState.value = BluetoothConnectionState.connected;
|
|
_updateLastConnectedDeviceInfo();
|
|
return true;
|
|
}
|
|
|
|
// 如果已选择了其他设备,先断开
|
|
if (_selectedDevice.value != null && _selectedDevice.value != device) {
|
|
await disconnectFromDevice();
|
|
}
|
|
|
|
// 设置新的目标设备
|
|
_selectedDevice.value = device;
|
|
|
|
// 针对不同类型设备采取不同连接策略
|
|
if (_deviceType.value == DeviceType.classicAudio) {
|
|
// 经典蓝牙设备(如音频设备)依赖系统连接
|
|
return _connectToClassicAudioDevice(device);
|
|
} else {
|
|
// BLE设备直接通过库连接
|
|
return _connectToBLEDevice(device);
|
|
}
|
|
} catch (e) {
|
|
Logger.error('连接设备过程中出错: $e');
|
|
_connectionState.value = BluetoothConnectionState.disconnected;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 连接经典蓝牙音频设备
|
|
Future<bool> _connectToClassicAudioDevice(BluetoothDevice device) async {
|
|
Logger.info('尝试连接经典蓝牙音频设备');
|
|
|
|
try {
|
|
// 刷新系统设备列表
|
|
await _refreshSystemDevices();
|
|
|
|
// 检查设备是否在系统已连接列表中
|
|
bool connected = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str);
|
|
|
|
if (connected) {
|
|
Logger.info('经典蓝牙设备已在系统连接列表中');
|
|
_connectionState.value = BluetoothConnectionState.connected;
|
|
_updateLastConnectedDeviceInfo();
|
|
return true;
|
|
} else {
|
|
Logger.info('经典蓝牙设备未在系统连接列表中,需要在系统设置中进行配对');
|
|
_connectionState.value = BluetoothConnectionState.disconnected;
|
|
return false;
|
|
}
|
|
} catch (e) {
|
|
Logger.error('连接经典蓝牙设备失败: $e');
|
|
_connectionState.value = BluetoothConnectionState.disconnected;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 连接BLE设备
|
|
Future<bool> _connectToBLEDevice(BluetoothDevice device) async {
|
|
Logger.info('尝试连接BLE设备');
|
|
_connectionState.value = BluetoothConnectionState.connecting;
|
|
|
|
try {
|
|
// 设置连接状态监听
|
|
_connectionSubscription?.cancel();
|
|
_connectionSubscription = device.connectionState.listen((state) {
|
|
Logger.info('BLE连接状态变化: $state');
|
|
_connectionState.value = state;
|
|
|
|
if (state == BluetoothConnectionState.connected) {
|
|
_updateLastConnectedDeviceInfo();
|
|
}
|
|
});
|
|
|
|
// 尝试连接
|
|
await device.connect(
|
|
autoConnect: false,
|
|
timeout: const Duration(seconds: 10)
|
|
);
|
|
|
|
// 等待连接状态
|
|
final completer = Completer<bool>();
|
|
bool isCompleted = false; // 添加一个本地变量跟踪完成状态
|
|
|
|
// 检查当前状态
|
|
if (_connectionState.value == BluetoothConnectionState.connected) {
|
|
completer.complete(true);
|
|
isCompleted = true;
|
|
} else {
|
|
// 设置超时
|
|
Timer(const Duration(seconds: 12), () {
|
|
if (!isCompleted) { // 使用本地变量而不是 completer.isCompleted
|
|
Logger.warning('连接设备超时');
|
|
completer.complete(false);
|
|
isCompleted = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
bool result = await completer.future;
|
|
|
|
if (result) {
|
|
Logger.info('BLE设备连接成功');
|
|
_updateLastConnectedDeviceInfo();
|
|
} else {
|
|
Logger.warning('BLE设备连接失败');
|
|
// 尝试断开连接
|
|
await device.disconnect().catchError((_) {});
|
|
}
|
|
|
|
return result;
|
|
} catch (e) {
|
|
Logger.error('连接BLE设备失败: $e');
|
|
_connectionState.value = BluetoothConnectionState.disconnected;
|
|
|
|
// 尝试断开连接
|
|
try {
|
|
await device.disconnect();
|
|
} catch (_) {}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 断开当前设备连接
|
|
Future<bool> disconnectFromDevice() async {
|
|
// 没有已选择的设备
|
|
if (_selectedDevice.value == null) {
|
|
return true;
|
|
}
|
|
|
|
// 取消连接状态监听
|
|
_connectionSubscription?.cancel();
|
|
_connectionSubscription = null;
|
|
|
|
try {
|
|
// 对BLE设备才需要执行断开操作
|
|
if (_deviceType.value != DeviceType.classicAudio) {
|
|
await _selectedDevice.value!.disconnect();
|
|
}
|
|
|
|
// 清除设备状态
|
|
_selectedDevice.value = null;
|
|
_connectionState.value = BluetoothConnectionState.disconnected;
|
|
return true;
|
|
} catch (e) {
|
|
Logger.error('断开设备连接失败: $e');
|
|
|
|
// 即使失败,也清除选中设备
|
|
_selectedDevice.value = null;
|
|
_connectionState.value = BluetoothConnectionState.disconnected;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 查找系统已连接设备
|
|
Future<BluetoothDevice?> findConnectedSystemDevice() async {
|
|
if (!_permissionsGranted.value || !_isOn.value) {
|
|
Logger.warning('蓝牙未开启或缺少权限,无法查找系统连接设备');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
// 刷新系统设备列表
|
|
await _refreshSystemDevices();
|
|
|
|
if (_connectedDevices.isEmpty) {
|
|
return null;
|
|
}
|
|
|
|
// 优先找出可能的音频设备
|
|
BluetoothDevice device = _findAudioDevice(_connectedDevices) ?? _connectedDevices.first;
|
|
|
|
// 自动选择设备
|
|
_selectedDevice.value = device;
|
|
_determineDeviceType(device);
|
|
_connectionState.value = BluetoothConnectionState.connected;
|
|
_updateLastConnectedDeviceInfo();
|
|
|
|
return device;
|
|
} catch (e) {
|
|
Logger.error('查找系统已连接设备失败: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// 查找音频设备
|
|
BluetoothDevice? _findAudioDevice(List<BluetoothDevice> devices) {
|
|
if (devices.isEmpty) return null;
|
|
|
|
// 音频设备的可能名称关键词
|
|
final List<String> audioKeywords = [
|
|
'headphone', 'earphone', 'earbuds', 'headset',
|
|
'speaker', 'audio', '耳机', '音箱', '音响'
|
|
];
|
|
|
|
// 尝试匹配音频设备名称
|
|
for (BluetoothDevice device in devices) {
|
|
String name = device.platformName.toLowerCase();
|
|
if (audioKeywords.any((keyword) => name.contains(keyword))) {
|
|
return device;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// 确定设备类型
|
|
void _determineDeviceType(BluetoothDevice device) {
|
|
final String deviceName = device.platformName.toLowerCase();
|
|
|
|
// 音频设备关键词
|
|
final List<String> audioKeywords = [
|
|
'headphone', 'earphone', 'earbuds', 'headset',
|
|
'speaker', 'audio', '耳机', '音箱', '音响'
|
|
];
|
|
|
|
bool hasAudioKeyword = audioKeywords.any((keyword) => deviceName.contains(keyword));
|
|
bool inConnectedList = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str);
|
|
|
|
if (hasAudioKeyword || inConnectedList) {
|
|
_deviceType.value = DeviceType.classicAudio;
|
|
} else {
|
|
_deviceType.value = DeviceType.ble;
|
|
}
|
|
}
|
|
|
|
/// 获取已连接设备的信息
|
|
Map<String, dynamic> getConnectedDeviceInfo() {
|
|
if (_selectedDevice.value == null) {
|
|
return {
|
|
'connected': false,
|
|
'device': null,
|
|
'deviceType': _deviceType.value.toString().split('.').last
|
|
};
|
|
}
|
|
|
|
final deviceInfo = {
|
|
'connected': _connectionState.value == BluetoothConnectionState.connected,
|
|
'device': {
|
|
'name': _selectedDevice.value!.platformName.isEmpty ?
|
|
'未命名设备' : _selectedDevice.value!.platformName,
|
|
'id': _selectedDevice.value!.remoteId.str,
|
|
'isConnected': _connectionState.value == BluetoothConnectionState.connected,
|
|
'rssi': _scanResults
|
|
.firstWhereOrNull((r) => r.device.remoteId == _selectedDevice.value!.remoteId)
|
|
?.rssi
|
|
},
|
|
'deviceType': _deviceType.value.toString().split('.').last
|
|
};
|
|
|
|
return deviceInfo;
|
|
}
|
|
|
|
/// 更新最后连接的设备信息
|
|
void _updateLastConnectedDeviceInfo() {
|
|
if (_selectedDevice.value != null) {
|
|
_lastConnectedDeviceInfo.value = getConnectedDeviceInfo();
|
|
}
|
|
}
|
|
|
|
/// 清理资源
|
|
@override
|
|
void onClose() {
|
|
// 取消所有订阅
|
|
_stateSubscription?.cancel();
|
|
_scanSubscription?.cancel();
|
|
_connectionSubscription?.cancel();
|
|
|
|
// 断开设备连接
|
|
disconnectFromDevice();
|
|
|
|
super.onClose();
|
|
}
|
|
}
|