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.
 
 
 
 
 
 

156 lines
4.5 KiB

import 'dart:async';
import 'package:flutter/services.dart';
/// 经典蓝牙插件
class ClassicBluetooth {
static const MethodChannel _methodChannel =
MethodChannel('com.yunqiinnovation.classic_bluetooth/methods');
static const EventChannel _eventChannel =
EventChannel('com.yunqiinnovation.classic_bluetooth/events');
static Stream<BluetoothEvent>? _eventStream;
/// 获取已连接的蓝牙设备
static Future<List<BluetoothDevice>> getConnectedDevices() async {
final List<dynamic> devices =
await _methodChannel.invokeMethod('getConnectedDevices');
return devices.map((device) => BluetoothDevice.fromJson(device)).toList();
}
/// 检查蓝牙是否已启用
static Future<bool> isBluetoothEnabled() async {
return await _methodChannel.invokeMethod('isBluetoothEnabled');
}
/// 发起经典蓝牙配对(A2DP 出声的前置动作)。**2026-09-11 为车载香薰新增。**
///
/// 返回原生层的状态串:
/// - Android:`already_bonded` / `bonding_in_progress` / `bonding_started`
/// (Android 没有"直接连某个 profile"的公开 API,配对成功后系统自己接 A2DP)
/// - iOS:恒为 `unsupported_ios` —— Apple 不提供发起经典蓝牙配对的接口,
/// 只能引导用户去「设置 → 蓝牙」手动配一次
///
/// 失败抛 [PlatformException](`INVALID_MAC` / `PERMISSION_DENIED` /
/// `BT_DISABLED` / `NO_ADAPTER` / `BOND_FAILED`)。
static Future<String?> connectDevice(String macAddress) async {
return await _methodChannel.invokeMethod<String>(
'connectDevice',
{'macAddress': macAddress},
);
}
/// 这台 MAC 当前是不是已经接上音频通路(A2DP)。**2026-09-11 新增。**
///
/// Android 查 `BluetoothProfile.A2DP` 的已连接列表;
/// iOS 查 `AVAudioSession` 的当前输出(`uid` 去掉后缀就是经典蓝牙 MAC)。
/// 拿不到一律 false,不抛。
static Future<bool> isA2dpConnected(String macAddress) async {
try {
return await _methodChannel.invokeMethod<bool>(
'isA2dpConnected',
{'macAddress': macAddress},
) ??
false;
} catch (_) {
return false;
}
}
/// 监听蓝牙事件(状态变化、设备连接/断开)
static Stream<BluetoothEvent> get events {
_eventStream ??= _eventChannel
.receiveBroadcastStream()
.map((dynamic event) => BluetoothEvent.fromJson(event))
.handleError((error) {
print('[ClassicBluetooth] 事件流错误: $error');
});
return _eventStream!;
}
}
/// 蓝牙设备模型
class BluetoothDevice {
final String name;
final String address;
final bool isActive;
BluetoothDevice({
required this.name,
required this.address,
this.isActive = false,
});
factory BluetoothDevice.fromJson(Map<dynamic, dynamic> json) {
return BluetoothDevice(
name: json['name'] as String,
address: json['address'] as String,
isActive: json['isActive'] == 'true',
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'address': address,
'isActive': isActive,
};
}
@override
String toString() =>
'BluetoothDevice(name: $name, address: $address, isActive: $isActive)';
}
/// 简化的蓝牙事件类
class BluetoothEvent {
/// 事件类型:stateChanged, deviceConnected, deviceDisconnected
final String type;
/// 蓝牙状态: on, off, turningOn, turningOff, unknown
final String? state;
/// 相关的蓝牙设备
final BluetoothDevice? device;
/// 事件发生时间
final DateTime timestamp;
BluetoothEvent({
required this.type,
this.state,
this.device,
required this.timestamp,
});
factory BluetoothEvent.fromJson(Map<dynamic, dynamic> json) {
final String type = json['type'] as String;
final timestamp =
DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int);
// 处理设备相关事件
BluetoothDevice? device;
if (json.containsKey('device')) {
device = BluetoothDevice.fromJson(json['device']);
}
return BluetoothEvent(
type: type,
state: json['state'] as String?,
device: device,
timestamp: timestamp,
);
}
@override
String toString() {
if (type == 'stateChanged') {
return 'BluetoothEvent(type: $type, state: $state)';
} else if (type == 'deviceConnected' || type == 'deviceDisconnected') {
return 'BluetoothEvent(type: $type, device: $device)';
} else {
return 'BluetoothEvent(type: $type)';
}
}
}