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.
283 lines
7.6 KiB
283 lines
7.6 KiB
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
/// 设备类,代表一个BLE设备
|
|
class BleDevice {
|
|
final String id;
|
|
final String name;
|
|
final int rssi;
|
|
|
|
BleDevice({required this.id, required this.name, required this.rssi});
|
|
|
|
factory BleDevice.fromJson(Map<String, dynamic> json) {
|
|
return BleDevice(
|
|
id: json['id'] as String,
|
|
name: json['name'] as String? ?? 'Unknown Device',
|
|
rssi: json['rssi'] as int? ?? 0,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'id': id,
|
|
'name': name,
|
|
'rssi': rssi,
|
|
};
|
|
}
|
|
|
|
@override
|
|
String toString() => 'BleDevice(id: $id, name: $name, rssi: $rssi)';
|
|
}
|
|
|
|
/// 连接状态
|
|
enum ConnectionState {
|
|
disconnected,
|
|
connecting,
|
|
connected,
|
|
disconnecting,
|
|
error
|
|
}
|
|
|
|
/// 事件类型
|
|
class EventType {
|
|
static const String audioData = 'audio_data';
|
|
static const String connectionState = 'connection_state';
|
|
static const String scanResult = 'scan_result';
|
|
static const String wakeupSignal = 'wakeup_signal';
|
|
static const String commandResult = 'command_result';
|
|
}
|
|
|
|
/// 杰理BLE插件主类
|
|
class Jieli {
|
|
static const MethodChannel _methodChannel =
|
|
MethodChannel('com.yunqiinnovation.jieli');
|
|
static const EventChannel _eventChannel =
|
|
EventChannel('com.yunqiinnovation.jieli/events');
|
|
|
|
/// 扫描结果流
|
|
static final StreamController<List<BleDevice>> _scanController =
|
|
StreamController<List<BleDevice>>.broadcast();
|
|
Stream<List<BleDevice>> get scanResults => _scanController.stream;
|
|
|
|
/// 连接状态变化流
|
|
static final StreamController<ConnectionState> _connectionController =
|
|
StreamController<ConnectionState>.broadcast();
|
|
Stream<ConnectionState> get connectionState => _connectionController.stream;
|
|
|
|
/// 音频数据流
|
|
static final StreamController<Uint8List> _audioController =
|
|
StreamController<Uint8List>.broadcast();
|
|
Stream<Uint8List> get audioDataStream => _audioController.stream;
|
|
|
|
/// 唤醒信号流
|
|
static final StreamController<bool> _wakeupController =
|
|
StreamController<bool>.broadcast();
|
|
Stream<bool> get wakeupStream => _wakeupController.stream;
|
|
|
|
/// 命令结果流
|
|
static final StreamController<CommandResult> _commandResultController =
|
|
StreamController<CommandResult>.broadcast();
|
|
Stream<CommandResult> get commandResultStream =>
|
|
_commandResultController.stream;
|
|
|
|
/// 原始事件流
|
|
Stream<Map<String, dynamic>>? _eventStream;
|
|
|
|
/// 事件流订阅
|
|
StreamSubscription? _eventSubscription;
|
|
|
|
/// 命令类型常量
|
|
static const int commandTypeVolume = 1;
|
|
|
|
/// 构造函数,初始化平台通道和事件监听
|
|
Jieli() {
|
|
_setupEventListener();
|
|
}
|
|
|
|
/// 设置事件监听器
|
|
void _setupEventListener() {
|
|
_eventSubscription =
|
|
_eventChannel.receiveBroadcastStream().listen(_handleEvent);
|
|
}
|
|
|
|
/// 处理来自原生平台的事件
|
|
void _handleEvent(dynamic event) {
|
|
if (event is! Map<dynamic, dynamic>) return;
|
|
|
|
final eventData = Map<String, dynamic>.from(event);
|
|
final String eventType = eventData['type'] as String;
|
|
final dynamic data = eventData['data'];
|
|
|
|
switch (eventType) {
|
|
case EventType.scanResult:
|
|
if (data is List<dynamic>) {
|
|
final deviceList = data
|
|
.map(
|
|
(item) => BleDevice.fromJson(Map<String, dynamic>.from(item)))
|
|
.toList();
|
|
_scanController.add(deviceList);
|
|
}
|
|
break;
|
|
case EventType.connectionState:
|
|
if (data is int && data >= 0 && data < ConnectionState.values.length) {
|
|
_connectionController.add(ConnectionState.values[data]);
|
|
}
|
|
break;
|
|
case EventType.audioData:
|
|
if (data is Uint8List) {
|
|
print('收到ble通道消息: $data');
|
|
_audioController.add(data);
|
|
}
|
|
break;
|
|
case EventType.wakeupSignal:
|
|
if (data is bool) {
|
|
_wakeupController.add(data);
|
|
}
|
|
break;
|
|
case EventType.commandResult:
|
|
if (data is Map<dynamic, dynamic>) {
|
|
final commandType = data['commandType'] as int;
|
|
final success = data['success'] as bool;
|
|
_commandResultController.add(CommandResult(
|
|
commandType: commandType,
|
|
success: success,
|
|
));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// 开始扫描BLE设备
|
|
Future<bool> startScan({int scanTimeout = 10000}) async {
|
|
final result = await _methodChannel.invokeMethod<bool>(
|
|
'startScan',
|
|
{'scanTimeout': scanTimeout},
|
|
);
|
|
return result ?? false;
|
|
}
|
|
|
|
/// 停止扫描BLE设备
|
|
Future<bool> stopScan() async {
|
|
final result = await _methodChannel.invokeMethod<bool>('stopScan');
|
|
return result ?? false;
|
|
}
|
|
|
|
/// 连接到指定设备
|
|
Future<bool> connect(String deviceId) async {
|
|
final result = await _methodChannel.invokeMethod<bool>(
|
|
'connect',
|
|
{'deviceId': deviceId},
|
|
);
|
|
return result ?? false;
|
|
}
|
|
|
|
/// 断开连接
|
|
Future<bool> disconnect() async {
|
|
final result = await _methodChannel.invokeMethod<bool>('disconnect');
|
|
return result ?? false;
|
|
}
|
|
|
|
/// 发送开始录音命令
|
|
Future<bool> sendStartRecordingCommand() async {
|
|
final result =
|
|
await _methodChannel.invokeMethod<bool>('sendStartRecordingCommand');
|
|
return result ?? false;
|
|
}
|
|
|
|
/// 发送停止录音命令
|
|
Future<bool> sendStopRecordingCommand() async {
|
|
final result =
|
|
await _methodChannel.invokeMethod<bool>('sendStopRecordingCommand');
|
|
return result ?? false;
|
|
}
|
|
|
|
/// 关闭插件,释放资源
|
|
Future<void> dispose() async {
|
|
await stopScan();
|
|
// await disconnect();
|
|
// _eventSubscription?.cancel();
|
|
// _scanController.close();
|
|
// _connectionController.close();
|
|
// _audioController.close();
|
|
// _wakeupController.close();
|
|
// _commandResultController.close();
|
|
}
|
|
|
|
/// 检查蓝牙是否已启用
|
|
Future<bool> isBluetoothEnabled() async {
|
|
final result =
|
|
await _methodChannel.invokeMethod<bool>('isBluetoothEnabled');
|
|
return result ?? false;
|
|
}
|
|
|
|
/// 请求打开蓝牙
|
|
Future<bool> requestBluetoothEnable() async {
|
|
final result =
|
|
await _methodChannel.invokeMethod<bool>('requestBluetoothEnable');
|
|
return result ?? false;
|
|
}
|
|
|
|
/// 调整音量
|
|
/// [volume] 音量值,范围0-16
|
|
Future<bool> adjustVolume(int volume) async {
|
|
try {
|
|
final result = await _methodChannel.invokeMethod<bool>(
|
|
'adjustVolume',
|
|
{'volume': volume},
|
|
);
|
|
return result ?? false;
|
|
} catch (e) {
|
|
print('调整音量失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 增加音量
|
|
Future<bool> increaseVolume() async {
|
|
try {
|
|
final result = await _methodChannel.invokeMethod<bool>('increaseVolume');
|
|
return result ?? false;
|
|
} catch (e) {
|
|
print('增加音量失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 减小音量
|
|
Future<bool> decreaseVolume() async {
|
|
try {
|
|
final result = await _methodChannel.invokeMethod<bool>('decreaseVolume');
|
|
return result ?? false;
|
|
} catch (e) {
|
|
print('减小音量失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 命令结果回调
|
|
Stream<CommandResult> get onCommandResult => commandResultStream
|
|
.where((event) => event.commandType == commandTypeVolume)
|
|
.map((event) => event);
|
|
}
|
|
|
|
/// 蓝牙设备模型
|
|
class BluetoothDevice {
|
|
// ... existing code ...
|
|
}
|
|
|
|
/// 命令结果
|
|
class CommandResult {
|
|
final int commandType;
|
|
final bool success;
|
|
|
|
CommandResult({
|
|
required this.commandType,
|
|
required this.success,
|
|
});
|
|
|
|
@override
|
|
String toString() =>
|
|
'CommandResult{commandType: $commandType, success: $success}';
|
|
}
|
|
|