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
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)';
|
|
}
|
|
}
|
|
}
|
|
|