fix:增加p2p消息协议代码、优化对讲时应用卡死、调整StreamController处理数据时的逻辑、调整在寻找图片帧时的代码逻辑

This commit is contained in:
liyi 2025-01-14 13:43:12 +08:00
parent 641ff4a2b6
commit 7a0e8f9e28
14 changed files with 413 additions and 232 deletions

View File

@ -2745,7 +2745,9 @@ class ApiProvider extends BaseProvider {
jsonEncode({ jsonEncode({
'deviceType': deviceType, 'deviceType': deviceType,
'deviceMac': deviceMac, 'deviceMac': deviceMac,
})); }),
isShowNetworkErrorMsg: false,
isUnShowLoading: true);
} }
extension ExtensionString on String { extension ExtensionString on String {

View File

@ -2739,4 +2739,7 @@ class ApiRepository {
); );
return DeviceNetwork.fromJson(res.body); return DeviceNetwork.fromJson(res.body);
} }
} }

View File

@ -42,12 +42,10 @@ class UdpBlePassThroughHandler extends ScpMessageBaseHandle
@override @override
void handleResp(ScpMessage scpMessage) async { void handleResp(ScpMessage scpMessage) async {
final BleResp bleResp = scpMessage.Payload; final BleResp bleResp = scpMessage.Payload;
AppLog.log('收到蓝牙消息回复:${bleResp.structData}');
// List<int>
String hexString = bleResp.structData String hexString = bleResp.structData
.map((byte) => byte.toRadixString(16).padLeft(2, '0')) .map((byte) => byte.toRadixString(16).padLeft(2, '0'))
.join(' '); .join(' ');
AppLog.log('蓝牙透传回复:$hexString'); AppLog.log('收到蓝牙透传回复:$hexString');
await CommandReciverManager.appDataReceive(bleResp.structData); await CommandReciverManager.appDataReceive(bleResp.structData);
@ -57,8 +55,6 @@ class UdpBlePassThroughHandler extends ScpMessageBaseHandle
AppLog.log('收到开门请求命令回复'); AppLog.log('收到开门请求命令回复');
_replyOpenLock(reply); _replyOpenLock(reply);
} }
}); });
} }
@ -146,7 +142,7 @@ class UdpBlePassThroughHandler extends ScpMessageBaseHandle
lockDetailLogic.lockReportLockSuccessfullyUploadData(); lockDetailLogic.lockReportLockSuccessfullyUploadData();
lockDetailLogic.resetOpenDoorState(); lockDetailLogic.resetOpenDoorState();
lockDetailState.animationController!.stop(); lockDetailState.animationController?.stop();
// //
lockDetailLogic.getLockRecordLastUploadDataTime(); lockDetailLogic.getLockRecordLastUploadDataTime();

View File

@ -3,7 +3,7 @@ import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/startChart/entity/scp_message.dart'; import 'package:star_lock/talk/startChart/entity/scp_message.dart';
import 'package:star_lock/talk/startChart/handle/scp_message_base_handle.dart'; import 'package:star_lock/talk/startChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart'; import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/startChart/p2p/p2p_manage.dart';
import 'package:star_lock/talk/startChart/proto/generic.pb.dart'; import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart'; import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_request.pb.dart'; import 'package:star_lock/talk/startChart/proto/talk_request.pb.dart';
@ -68,7 +68,7 @@ class UdpRbcuInfoHandler extends ScpMessageBaseHandle
/// rbcuInfo消息 /// rbcuInfo消息
void _handleResultRbcuInfo(RbcuInfo rbcuInfo) { void _handleResultRbcuInfo(RbcuInfo rbcuInfo) {
P2pManage().communicationObjectRbcuInfo = rbcuInfo; startChartManage.rbcuInfo = rbcuInfo;
startChartManage.stopSendingRbcuInfoMessages(); startChartManage.stopSendingRbcuInfoMessages();
} }
} }

View File

@ -0,0 +1,57 @@
import 'package:star_lock/login/selectCountryRegion/common/index.dart';
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
import 'package:star_lock/talk/startChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_request.pb.dart';
class UdpRbcuProBeHandler extends ScpMessageBaseHandle
implements ScpMessageHandler {
@override
deserializePayload(
{required int payloadType,
required int messageType,
required List<int> byte,
int? offset,
int? PayloadLength,
int? spTotal,
int? spIndex,
int? messageId}) {
if (messageType == MessageTypeConstant.Resp) {
final GenericResp genericResp = GenericResp();
genericResp.mergeFromBuffer(byte);
return genericResp;
} else if (messageType == MessageTypeConstant.Req) {
final RbcuProbe rbcuProbe = RbcuProbe();
rbcuProbe.mergeFromBuffer(byte);
return rbcuProbe;
} else {
String payload = utf8.decode(byte);
return payload;
}
}
@override
void handleInvalidReq(ScpMessage scpMessage) {
// TODO: implement handleInvalidReq
}
@override
void handleRealTimeData(ScpMessage scpMessage) {
// TODO: implement handleRealTimeData
}
@override
void handleReq(ScpMessage scpMessage) {
// rbcuprobe
final RbcuProbe rbcuProbe = scpMessage.Payload;
//
startChartManage.rbcuProbe = rbcuProbe;
}
@override
void handleResp(ScpMessage scpMessage) {}
}

View File

@ -128,10 +128,10 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
/// g711音频数据 /// g711音频数据
void _handleVideoG711(TalkData talkData) { void _handleVideoG711(TalkData talkData) {
try { try {
final g711Data = talkData.content; // final g711Data = talkData.content;
// pcm数据 // // pcm数据
List<int> pcmBytes = G711().convertList(g711Data); // List<int> pcmBytes = G711().convertList(g711Data);
talkData.content = pcmBytes; // talkData.content = pcmBytes;
talkDataRepository.addTalkData(talkData); talkDataRepository.addTalkData(talkData);
} catch (e) { } catch (e) {
print('Error decoding G.711 to PCM: $e'); print('Error decoding G.711 to PCM: $e');
@ -142,34 +142,69 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
// //
List<Uint8List> frames = []; List<Uint8List> frames = [];
// (0xFFD8 , 0xFFD9 ) // payload
int startIdx = 0; int i = 0;
while (startIdx < payload.length - 1) { while (i < payload.length - 1) {
// 0xFFD8 // 0xFFD8
startIdx = payload.indexOf(0xFF, startIdx); if (payload[i] == 0xFF && payload[i + 1] == 0xD8) {
if (startIdx == -1 || startIdx + 1 >= payload.length) break; int startIdx = i;
if (payload[startIdx + 1] == 0xD8) { i += 2; //
// 0xFFD8
int endIdx = startIdx + 2; // 0xFFD9
while (endIdx < payload.length - 1) { while (i < payload.length - 1) {
endIdx = payload.indexOf(0xFF, endIdx); if (payload[i] == 0xFF && payload[i + 1] == 0xD9) {
if (endIdx == -1 || endIdx + 1 >= payload.length) break; // 0xFFD9
if (payload[endIdx + 1] == 0xD9) { int endIdx = i + 2;
// 0xFFD9 // 使 Uint8List.view
Uint8List frame = payload.sublist(startIdx, endIdx + 2); frames.add(
frames.add(frame); Uint8List.view(payload.buffer, startIdx, endIdx - startIdx));
startIdx = endIdx + 2; // i = endIdx; //
break; break;
} else { } else {
endIdx += 1; // i += 1; //
} }
} }
} else { } else {
startIdx += 1; // i += 1; //
} }
} }
// //
return frames; return frames;
} }
// Future<List<Uint8List>> _processCompletePayload(Uint8List payload) async {
// //
// List<Uint8List> frames = [];
//
// // (0xFFD8 , 0xFFD9 )
// int startIdx = 0;
// while (startIdx < payload.length - 1) {
// // 0xFFD8
// startIdx = payload.indexOf(0xFF, startIdx);
// if (startIdx == -1 || startIdx + 1 >= payload.length) break;
// if (payload[startIdx + 1] == 0xD8) {
// // 0xFFD8
// int endIdx = startIdx + 2;
// while (endIdx < payload.length - 1) {
// endIdx = payload.indexOf(0xFF, endIdx);
// if (endIdx == -1 || endIdx + 1 >= payload.length) break;
// if (payload[endIdx + 1] == 0xD9) {
// // 0xFFD9
// Uint8List frame = payload.sublist(startIdx, endIdx + 2);
// frames.add(frame);
// startIdx = endIdx + 2; //
// break;
// } else {
// endIdx += 1; //
// }
// }
// } else {
// startIdx += 1; //
// }
// }
//
// //
// return frames;
// }
} }

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart'; import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
import 'package:star_lock/talk/startChart/constant/talk_status.dart'; import 'package:star_lock/talk/startChart/constant/talk_status.dart';
import 'package:star_lock/talk/startChart/entity/scp_message.dart'; import 'package:star_lock/talk/startChart/entity/scp_message.dart';
@ -31,6 +32,8 @@ class UdpTalkHangUpHandler extends ScpMessageBaseHandle
talkeRequestOverTimeTimerManager.cancel(); talkeRequestOverTimeTimerManager.cancel();
talkePingOverTimeTimerManager.cancel(); talkePingOverTimeTimerManager.cancel();
talkDataOverTimeTimerManager.cancel(); talkDataOverTimeTimerManager.cancel();
EasyLoading.showToast('已挂断');
} }
@override @override

View File

@ -4,7 +4,17 @@ import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
class TalkDataRepository { class TalkDataRepository {
// //
TalkDataRepository._(); TalkDataRepository._() {
_talkDataStreamController = StreamController<TalkData>.broadcast(
onListen: () {
_isListening = true;
},
onCancel: () {
_isListening = false;
},
sync: false, //
);
}
// 使 _instance // 使 _instance
static final TalkDataRepository _instance = TalkDataRepository._(); static final TalkDataRepository _instance = TalkDataRepository._();
@ -13,18 +23,37 @@ class TalkDataRepository {
static TalkDataRepository get instance => _instance; static TalkDataRepository get instance => _instance;
// StreamController // StreamController
final StreamController<TalkData> _talkDataStreamController = StreamController<TalkData>.broadcast(); late final StreamController<TalkData> _talkDataStreamController;
bool _isListening = false;
// Stream // Stream
Stream<TalkData> get talkDataStream => _talkDataStreamController.stream; Stream<TalkData> get talkDataStream =>
_talkDataStreamController.stream.transform(
StreamTransformer<TalkData, TalkData>.fromHandlers(
handleData: (TalkData data, EventSink<TalkData> sink) {
// 100
if (_buffer.length >= 100) {
_buffer.removeAt(0); //
}
_buffer.add(data);
sink.add(data);
},
),
);
final List<TalkData> _buffer = []; //
// TalkData Stream // TalkData Stream
void addTalkData(TalkData talkData) async { void addTalkData(TalkData talkData) async {
_talkDataStreamController.add(talkData); if (_isListening) {
Future.microtask(() {
_talkDataStreamController.add(talkData);
});
}
} }
// StreamController // StreamController
void dispose() { void dispose() {
_talkDataStreamController.close(); _talkDataStreamController.close();
} }
} }

View File

@ -57,6 +57,8 @@ class ScpMessageHandlerFactory {
return UdpTalkHangUpHandler(); return UdpTalkHangUpHandler();
case PayloadTypeConstant.RbcuInfo: case PayloadTypeConstant.RbcuInfo:
return UdpRbcuInfoHandler(); return UdpRbcuInfoHandler();
case PayloadTypeConstant.RbcuProbe:
return UdpRbcuInfoHandler();
default: default:
return UnKnowPayloadTypeHandler(); return UnKnowPayloadTypeHandler();
} }

View File

@ -1,108 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
class P2pManage {
//
static const String testMessage = 'Hello'; //
static const int localPort = 0; // 0
static const Duration connectionTimeout = Duration(seconds: 5); //
static const List<String> localNetworkPrefixes = [
'192.168.',
'10.',
'172.'
]; // IP
RbcuInfo? communicationObjectRbcuInfo;
void init() {
//
}
// address IP
List<Map<String, String>> parseRemoteAddresses() {
final addresses = communicationObjectRbcuInfo?.address ?? [];
return addresses.map((addr) {
final parts = addr.split(':');
return {'ip': parts[0], 'port': parts[1]};
}).toList();
}
// IP
bool _isLocalNetworkIp(String ip) {
for (final prefix in localNetworkPrefixes) {
if (ip.startsWith(prefix)) {
// 172.16.x.x 172.31.x.x
if (prefix == '172.') {
final secondOctet = int.tryParse(ip.split('.')[1]) ?? 0;
if (secondOctet >= 16 && secondOctet <= 31) {
return true;
}
} else {
return true;
}
}
}
return false;
}
// UDP
Future<void> createUdpConnection() async {
final addresses = parseRemoteAddresses();
// 使 IP
addresses.sort((a, b) {
final isALocal = _isLocalNetworkIp(a['ip']!);
final isBLocal = _isLocalNetworkIp(b['ip']!);
if (isALocal && !isBLocal) return -1; // a IP
if (!isALocal && isBLocal) return 1; // b IP
return 0; //
});
//
for (final addr in addresses) {
final ip = addr['ip']!;
final port = int.parse(addr['port']!);
try {
final socket = await RawDatagramSocket.bind(
InternetAddress.anyIPv4, localPort); //
//
final testData = testMessage.codeUnits;
socket.send(testData, InternetAddress(ip), port);
//
final completer = Completer<void>();
//
socket.listen((event) {
if (event == RawSocketEvent.read) {
final datagram = socket.receive();
if (datagram != null) {
final response = String.fromCharCodes(datagram.data);
print('收到来自 $ip:$port 的响应: $response');
completer.complete(); // Future
}
}
});
//
try {
await completer.future.timeout(connectionTimeout);
print('成功连接到 $ip:$port');
return; // 退
} on TimeoutException {
print('连接到 $ip:$port 超时');
} finally {
socket.close(); // socket
}
} catch (e) {
print('连接到 $ip:$port 失败: $e');
}
}
print('无法连接到任何地址');
}
}

View File

@ -1,5 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'dart:typed_data';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -29,6 +31,7 @@ import 'package:star_lock/talk/startChart/handle/other/talke_request_over_time_t
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart'; import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/startChart/handle/scp_message_handler_factory.dart'; import 'package:star_lock/talk/startChart/handle/scp_message_handler_factory.dart';
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart'; import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
import 'package:star_lock/talk/startChart/proto/rbcu.pbserver.dart';
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart'; import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_expect.pbserver.dart'; import 'package:star_lock/talk/startChart/proto/talk_expect.pbserver.dart';
@ -86,9 +89,14 @@ class StartChartManage {
int _defaultIntervalTime = 1; // (s) int _defaultIntervalTime = 1; // (s)
Timer? talkDataTimer; // Timer? talkDataTimer; //
Timer? rbcuInfoTimer; // p2p地址交换定时器 Timer? rbcuInfoTimer; // p2p地址交换定时器
Timer? rbcuProbeTimer; // p2p打洞包
Timer? rbcuConfirmTimer; // p2p打洞确认包
String _rbcuSessionId = ''; // p2p SessionId
Timer? talkRequestTimer; // Timer? talkRequestTimer; //
final int maxAttempts = 15; // final int maxAttempts = 15; //
RbcuInfo? rbcuInfo;
RbcuProbe? rbcuProbe;
RbcuConfirm? rbcuConfirm;
final int _maxPayloadSize = 8 * 1024; // final int _maxPayloadSize = 8 * 1024; //
// //
@ -254,6 +262,7 @@ class StartChartManage {
) // "udp://" ) // "udp://"
.cast< .cast<
String>(); // Iterable<String>// Iterable<String?> Iterable<String> String>(); // Iterable<String>// Iterable<String?> Iterable<String>
_rbcuSessionId = uuid;
final RbcuInfo rbcuInfo = RbcuInfo( final RbcuInfo rbcuInfo = RbcuInfo(
sessionId: uuid, sessionId: uuid,
name: uuid, name: uuid,
@ -261,6 +270,7 @@ class StartChartManage {
time: int64Timestamp, time: int64Timestamp,
isResp: isResp, isResp: isResp,
); );
final message = MessageCommand.genericRbcuInfoMessage( final message = MessageCommand.genericRbcuInfoMessage(
ToPeerId: ToPeerId, ToPeerId: ToPeerId,
FromPeerId: FromPeerId, FromPeerId: FromPeerId,
@ -270,6 +280,44 @@ class StartChartManage {
_sendMessage(message: message); _sendMessage(message: message);
} }
// RbcuProbe
void _sendRbcuProbeMessage() async {
//
String generateRandomString(int length) {
const chars =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
final random = Random();
return String.fromCharCodes(
List.generate(
length, (index) => chars.codeUnitAt(random.nextInt(chars.length))),
);
}
final info = rbcuInfo = RbcuInfo(address: ['119.137.55.161:62289']);
if (info != null && info.address != null && info.address.length > 0) {
info.address.forEach((element) {
// element
final parts = element.split(':');
final host = parts[0]; // IP
final port = int.tryParse(parts[1]) ?? 0; // 0
final RbcuProbe rbcuProbe = RbcuProbe(
sessionId: _rbcuSessionId,
data: generateRandomString(100),
targetAddress: element);
final rbcuProBeBuffer = rbcuProbe.writeToBuffer();
_sendNatMessage(message: rbcuProBeBuffer, host: host, port: port);
});
}
}
// RbcuConfirm
void _sendRbcuConfirmMessage() async {
RbcuConfirm(
sessionId: _rbcuSessionId,
);
}
// //
void startSendingRbcuInfoMessages({required String ToPeerId}) { void startSendingRbcuInfoMessages({required String ToPeerId}) {
// 1 _sendRbcuInfoMessage // 1 _sendRbcuInfoMessage
@ -280,12 +328,43 @@ class StartChartManage {
}); });
} }
//
void startSendingRbcuProbeTMessages() {
// 1 _sendRbcuInfoMessage
rbcuProbeTimer ??=
Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
// RbcuProbe
_sendRbcuProbeMessage();
});
}
//
void startSendingRbcuConfirmTMessages() {
rbcuConfirmTimer ??=
Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) {
// RbcuProbe
_sendRbcuConfirmMessage();
});
}
// //
void stopSendingRbcuInfoMessages() { void stopSendingRbcuInfoMessages() {
rbcuInfoTimer?.cancel(); // rbcuInfoTimer?.cancel(); //
rbcuInfoTimer = null; rbcuInfoTimer = null;
} }
//
void stopSendingRbcuProBeMessages() {
rbcuProbeTimer?.cancel(); //
rbcuProbeTimer = null;
}
//
void stopSendingRbcuConfirmMessages() {
rbcuConfirmTimer?.cancel(); //
rbcuConfirmTimer = null;
}
// RbcuInfo // RbcuInfo
void replyRbcuInfoMessage({required String ToPeerId}) { void replyRbcuInfoMessage({required String ToPeerId}) {
_sendRbcuInfoMessage(ToPeerId: ToPeerId, isResp: true); _sendRbcuInfoMessage(ToPeerId: ToPeerId, isResp: true);
@ -345,7 +424,9 @@ class StartChartManage {
} }
// //
//
Future<void> sendTalkDataMessage({required TalkData talkData}) async { Future<void> sendTalkDataMessage({required TalkData talkData}) async {
String toPeerId = lockPeerId;
final List<int> payload = talkData.content; final List<int> payload = talkData.content;
// //
final int totalPackets = (payload.length / _maxPayloadSize).ceil(); final int totalPackets = (payload.length / _maxPayloadSize).ceil();
@ -361,10 +442,10 @@ class StartChartManage {
// messageID // messageID
final messageId = final messageId =
MessageCommand.getNextMessageId(ToPeerId, increment: false); MessageCommand.getNextMessageId(toPeerId, increment: false);
// //
final message = MessageCommand.talkDataMessage( final message = MessageCommand.talkDataMessage(
ToPeerId: 'D78Fo4CjNzUXz8DxuUhLtcRpnGFXhSzhzs191XzhJttS', ToPeerId: toPeerId,
FromPeerId: FromPeerId, FromPeerId: FromPeerId,
payload: packet, payload: packet,
SpTotal: totalPackets, SpTotal: totalPackets,
@ -375,7 +456,7 @@ class StartChartManage {
await _sendMessage(message: message); await _sendMessage(message: message);
} }
// id // id
MessageCommand.getNextMessageId(ToPeerId); MessageCommand.getNextMessageId(toPeerId);
} }
// //
@ -603,6 +684,20 @@ class StartChartManage {
} }
} }
//
Future<void> _sendNatMessage(
{required List<int> message,
required String host,
required int port}) async {
_log(text: '发送nat消息');
var result = await _udpSocket?.send(message, InternetAddress(host), port);
if (result != message.length) {
throw StartChartMessageException(
'❌Udp send data error----> $result ${message.length}');
// _udpSocket = null;
}
}
// //
Future<StarChartRegisterNodeEntity> _requestStarChartRegisterNode() async { Future<StarChartRegisterNodeEntity> _requestStarChartRegisterNode() async {
// //
@ -960,6 +1055,8 @@ class StartChartManage {
stopReStartOnlineStartChartServer(); stopReStartOnlineStartChartServer();
stopTalkDataTimer(); stopTalkDataTimer();
stopSendingRbcuInfoMessages(); stopSendingRbcuInfoMessages();
stopSendingRbcuProBeMessages();
stopSendingRbcuConfirmMessages();
_resetData(); _resetData();
await Storage.removerRelayInfo(); await Storage.removerRelayInfo();
await Storage.removerStarChartRegisterNodeInfo(); await Storage.removerStarChartRegisterNodeInfo();

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
@ -23,6 +24,7 @@ import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart';
import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/talk/call/g711.dart';
import 'package:star_lock/talk/startChart/constant/talk_status.dart'; import 'package:star_lock/talk/startChart/constant/talk_status.dart';
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart'; import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
@ -43,18 +45,18 @@ class TalkViewLogic extends BaseGetXController {
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state; final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
Timer? _syncTimer; // Timer? _syncTimer; //
int _startTime = 0; // int _startTime = 0; //
final int bufferSize = 20; // int bufferSize = 20; //
final List<int> frameTimestamps = []; // FPS final List<int> frameTimestamps = []; // FPS
int frameIntervalMs = 45; // 4522FPS int frameIntervalMs = 45; // 4522FPS
int minFrameIntervalMs = 30; // 33 FPS int minFrameIntervalMs = 30; // 33 FPS
int maxFrameIntervalMs = 500; // 1 FPS int maxFrameIntervalMs = 100; // 1 FPS
// int maxFrameIntervalMs = 100; // 10 FPS // int maxFrameIntervalMs = 100; // 10 FPS
/// ///
void _initFlutterPcmSound() { void _initFlutterPcmSound() {
const int sampleRate = 44100; const int sampleRate = 8000;
FlutterPcmSound.setLogLevel(LogLevel.verbose); FlutterPcmSound.setLogLevel(LogLevel.none);
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 2); FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
// feed // feed
if (Platform.isAndroid) { if (Platform.isAndroid) {
FlutterPcmSound.setFeedThreshold(-1); // Android FlutterPcmSound.setFeedThreshold(-1); // Android
@ -82,24 +84,22 @@ class TalkViewLogic extends BaseGetXController {
// //
void _startListenTalkData() { void _startListenTalkData() {
state.talkDataRepository.talkDataStream.listen((TalkData talkData) { state.talkDataRepository.talkDataStream.listen((TalkData talkData) async {
final contentType = talkData.contentType; final contentType = talkData.contentType;
int currentTimestamp = DateTime.now().millisecondsSinceEpoch;
// //
switch (contentType) { switch (contentType) {
case TalkData_ContentTypeE.G711: case TalkData_ContentTypeE.G711:
if (state.audioBuffer.length < bufferSize) { if (state.audioBuffer.length >= bufferSize) {
state.audioBuffer.add(talkData); state.audioBuffer.removeAt(0); //
} }
// print('收到音频数据'); state.audioBuffer.add(talkData); //
break; break;
case TalkData_ContentTypeE.Image: case TalkData_ContentTypeE.Image:
if (state.videoBuffer.length < bufferSize) { if (state.videoBuffer.length >= bufferSize) {
state.videoBuffer.add(talkData); state.videoBuffer.removeAt(0); //
} }
// print('talkData durationMs-->:${talkData.durationMs}'); state.videoBuffer.add(talkData); //
/// ///
// updateNetworkStatus(currentTimestamp); // updateNetworkStatus(currentTimestamp);
break; break;
@ -127,9 +127,11 @@ class TalkViewLogic extends BaseGetXController {
} }
/// ///
void _playAudioData(TalkData talkData) { void _playAudioData(TalkData talkData) async {
final list = G711().decodeAndDenoise(talkData.content, true, 8000, 100, 50);
// PCM PcmArrayInt16 // PCM PcmArrayInt16
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(talkData.content); final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
FlutterPcmSound.feed(fromList); FlutterPcmSound.feed(fromList);
if (!state.isPlaying.value) { if (!state.isPlaying.value) {
FlutterPcmSound.play(); FlutterPcmSound.play();
@ -138,7 +140,7 @@ class TalkViewLogic extends BaseGetXController {
} }
/// ///
void _playVideoData(TalkData talkData) { void _playVideoData(TalkData talkData) async {
state.listData.value = Uint8List.fromList(talkData.content); state.listData.value = Uint8List.fromList(talkData.content);
} }
@ -153,8 +155,6 @@ class TalkViewLogic extends BaseGetXController {
final currentTime = DateTime.now().millisecondsSinceEpoch; final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime; final elapsedTime = currentTime - _startTime;
// elapsedTime
// AppLog.log('Elapsed Time: $elapsedTime ms');
// //
_adjustFrameInterval(); _adjustFrameInterval();
@ -163,6 +163,7 @@ class TalkViewLogic extends BaseGetXController {
state.audioBuffer.first.durationMs <= elapsedTime) { state.audioBuffer.first.durationMs <= elapsedTime) {
// //
if (state.isOpenVoice.value) { if (state.isOpenVoice.value) {
AppLog.log('播放音频:${state.audioBuffer[0]}');
_playAudioData(state.audioBuffer.removeAt(0)); _playAudioData(state.audioBuffer.removeAt(0));
} else { } else {
// //
@ -189,6 +190,13 @@ class TalkViewLogic extends BaseGetXController {
/// ///
void _adjustFrameInterval() { void _adjustFrameInterval() {
int newFrameIntervalMs = frameIntervalMs;
if (state.networkStatus.value == NetworkStatus.lagging) {
bufferSize = 30; //
} else {
bufferSize = 20; //
}
if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) { if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
// //
frameIntervalMs += 5; frameIntervalMs += 5;
@ -197,38 +205,47 @@ class TalkViewLogic extends BaseGetXController {
// //
frameIntervalMs -= 5; frameIntervalMs -= 5;
} }
_syncTimer?.cancel(); //
_syncTimer = if (newFrameIntervalMs != frameIntervalMs) {
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) { frameIntervalMs = newFrameIntervalMs;
final currentTime = DateTime.now().millisecondsSinceEpoch; //
final elapsedTime = currentTime - _startTime; _syncTimer?.cancel();
_syncTimer =
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime;
// //
if (state.audioBuffer.isNotEmpty && if (state.audioBuffer.isNotEmpty &&
state.audioBuffer.first.durationMs <= elapsedTime) { state.audioBuffer.first.durationMs <= elapsedTime) {
// //
if (state.isOpenVoice.value) { if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0)); _playAudioData(state.audioBuffer.removeAt(0));
} else { } else {
// //
// //
// //
state.audioBuffer.removeAt(0); state.audioBuffer.removeAt(0);
}
} }
}
// //
// //
while (state.videoBuffer.isNotEmpty && int maxFramesToProcess = 5; // 5
state.videoBuffer.first.durationMs <= elapsedTime) { int processedFrames = 0;
//
if (state.videoBuffer.length > 1) { while (state.videoBuffer.isNotEmpty &&
state.videoBuffer.removeAt(0); state.videoBuffer.first.durationMs <= elapsedTime &&
} else { processedFrames < maxFramesToProcess) {
_playVideoData(state.videoBuffer.removeAt(0)); if (state.videoBuffer.length > 1) {
state.videoBuffer.removeAt(0);
} else {
_playVideoData(state.videoBuffer.removeAt(0));
}
processedFrames++;
} }
} });
}); }
} }
/// ///
@ -238,11 +255,11 @@ class TalkViewLogic extends BaseGetXController {
if (frameInterval > 500 && frameInterval <= 1000) { if (frameInterval > 500 && frameInterval <= 1000) {
// 5001 // 5001
state.networkStatus.value = NetworkStatus.lagging; state.networkStatus.value = NetworkStatus.lagging;
showNetworkStatus("Network is lagging"); // showNetworkStatus("Network is lagging");
} else if (frameInterval > 1000) { } else if (frameInterval > 1000) {
// 1 // 1
state.networkStatus.value = NetworkStatus.delayed; state.networkStatus.value = NetworkStatus.delayed;
showNetworkStatus("Network is delayed"); // showNetworkStatus("Network is delayed");
} else { } else {
state.networkStatus.value = NetworkStatus.normal; state.networkStatus.value = NetworkStatus.normal;
state.alertCount.value = 0; // state.alertCount.value = 0; //
@ -436,12 +453,13 @@ class TalkViewLogic extends BaseGetXController {
@override @override
void onClose() { void onClose() {
_stopPlayG711Data(); _stopPlayG711Data(); //
state.listData.value = Uint8List(0); state.listData.value = Uint8List(0); //
state.audioBuffer.clear(); state.audioBuffer.clear(); //
state.videoBuffer.clear(); state.videoBuffer.clear(); //
_syncTimer?.cancel(); _syncTimer?.cancel(); //
_syncTimer = null; _syncTimer = null; //
super.onClose();
} }
/// ///
@ -563,7 +581,7 @@ class TalkViewLogic extends BaseGetXController {
// _concatenateFrames(state.recordingAudioAllFrames); // // _concatenateFrames(state.recordingAudioAllFrames); //
final List<int> pcmBytes = _listLinearToULaw(frame); final List<int> pcmBytes = _listLinearToULaw(frame);
// //
StartChartManage().sendTalkDataMessage( await StartChartManage().sendTalkDataMessage(
talkData: TalkData( talkData: TalkData(
content: pcmBytes, content: pcmBytes,
contentType: TalkData_ContentTypeE.G711, contentType: TalkData_ContentTypeE.G711,
@ -571,6 +589,7 @@ class TalkViewLogic extends BaseGetXController {
state.startRecordingAudioTime.value.millisecondsSinceEpoch, state.startRecordingAudioTime.value.millisecondsSinceEpoch,
), ),
); );
AppLog.log('发送音频数据');
} }
void _onError(VoiceProcessorException error) { void _onError(VoiceProcessorException error) {

View File

@ -168,7 +168,36 @@ class _TalkViewPageState extends State<TalkViewPage>
// ), // ),
Obx(() => state.listData.value.isEmpty Obx(() => state.listData.value.isEmpty
? buildRotationTransition() ? buildRotationTransition()
: Container()) : Container()),
Obx(() => state.isLongPressing.value
? Positioned(
top: 80.h,
left: 0,
right: 0,
child: Center(
child: Container(
padding: EdgeInsets.all(10.w),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(10.w),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.mic, color: Colors.white, size: 24.w),
SizedBox(width: 10.w),
Text(
'正在说话...',
style:
TextStyle(fontSize: 20.sp, color: Colors.white),
),
],
),
),
),
)
: Container()),
], ],
), ),
); );
@ -273,11 +302,13 @@ class _TalkViewPageState extends State<TalkViewPage>
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) { if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
print('开始录音'); print('开始录音');
logic.startProcessingAudio(); logic.startProcessingAudio();
state.isLongPressing.value = true;
} }
}, },
longPressUp: () async { longPressUp: () async {
print('停止录音'); print('停止录音');
logic.stopProcessingAudio(); logic.stopProcessingAudio();
state.isLongPressing.value = false;
}, },
onClick: () async { onClick: () async {
if (state.talkStatus.value == if (state.talkStatus.value ==
@ -300,8 +331,8 @@ class _TalkViewPageState extends State<TalkViewPage>
AppColors.mainColor, AppColors.mainColor,
onClick: () { onClick: () {
// if (UDPManage().remoteUnlock == 1) { // if (UDPManage().remoteUnlock == 1) {
logic.udpOpenDoorAction(); logic.udpOpenDoorAction();
// showDeletPasswordAlertDialog(context); // showDeletPasswordAlertDialog(context);
// } else { // } else {
// logic.showToast('请在锁设置中开启远程开锁'.tr); // logic.showToast('请在锁设置中开启远程开锁'.tr);
// } // }
@ -334,36 +365,43 @@ class _TalkViewPageState extends State<TalkViewPage>
} }
} }
Widget bottomBtnItemWidget(String iconUrl, String name, Color backgroundColor, Widget bottomBtnItemWidget(
{required Function() onClick, String iconUrl,
Function()? longPress, String name,
Function()? longPressUp}) { Color backgroundColor, {
required Function() onClick,
Function()? longPress,
Function()? longPressUp,
}) {
double wh = 80.w; double wh = 80.w;
return GestureDetector( return GestureDetector(
onTap: onClick, onTap: onClick,
onLongPress: longPress, onLongPress: longPress,
onLongPressUp: longPressUp, onLongPressUp: longPressUp,
child: SizedBox( child: SizedBox(
height: 140.h, height: 140.h,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Container( Container(
width: wh, width: wh,
height: wh, height: wh,
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor, color: backgroundColor,
borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2)), borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2),
padding: EdgeInsets.all(20.w),
child: Image.asset(iconUrl, fit: BoxFit.fitWidth),
), ),
SizedBox(height: 20.w), padding: EdgeInsets.all(20.w),
Expanded( child: Image.asset(iconUrl, fit: BoxFit.fitWidth),
child: Text(name, ),
style: TextStyle(fontSize: 20.sp, color: Colors.white), SizedBox(height: 20.w),
textAlign: TextAlign.center)) Expanded(
], child: Text(name,
)), style: TextStyle(fontSize: 20.sp, color: Colors.white),
textAlign: TextAlign.center),
)
],
),
),
); );
} }

View File

@ -6,6 +6,7 @@ import 'package:flutter_voice_processor/flutter_voice_processor.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_rx/get_rx.dart'; import 'package:get/get_rx/get_rx.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/state_manager.dart';
import 'package:network_info_plus/network_info_plus.dart'; import 'package:network_info_plus/network_info_plus.dart';
import 'package:star_lock/talk/startChart/constant/talk_status.dart'; import 'package:star_lock/talk/startChart/constant/talk_status.dart';
import 'package:star_lock/talk/startChart/handle/other/talk_data_repository.dart'; import 'package:star_lock/talk/startChart/handle/other/talk_data_repository.dart';
@ -54,7 +55,13 @@ class TalkViewState {
// //
List<TalkData> audioBuffer = <TalkData>[].obs; List<TalkData> audioBuffer = <TalkData>[].obs;
List<TalkData> audioBuffer2 = <TalkData>[].obs;
List<TalkData> activeAudioBuffer =<TalkData>[].obs;
List<TalkData> activeVideoBuffer =<TalkData>[].obs;
List<TalkData> videoBuffer = <TalkData>[].obs; List<TalkData> videoBuffer = <TalkData>[].obs;
List<TalkData> videoBuffer2 = <TalkData>[].obs;
RxBool isPlaying = false.obs; // RxBool isPlaying = false.obs; //
Rx<TalkStatus> talkStatus = TalkStatus.none.obs; // Rx<TalkStatus> talkStatus = TalkStatus.none.obs; //
// startChartTalkStatus // startChartTalkStatus
@ -80,4 +87,5 @@ class TalkViewState {
final int sampleRate = 8000; //8000 final int sampleRate = 8000; //8000
List<List<int>> recordingAudioAllFrames = <List<int>>[]; // List<List<int>> recordingAudioAllFrames = <List<int>>[]; //
RxInt rotateAngle = 0.obs; // RxInt rotateAngle = 0.obs; //
RxBool isLongPressing = false.obs; //
} }