import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:convert/convert.dart'; import 'package:fast_rsa/fast_rsa.dart' as fastRsa; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:pointycastle/export.dart' as pc; import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/flavors.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/network/start_chart_api.dart'; import 'package:star_lock/talk/startChart/command/message_command.dart'; import 'package:star_lock/talk/startChart/constant/ip_constant.dart'; import 'package:star_lock/talk/startChart/constant/listen_addr_type_constant.dart'; import 'package:star_lock/talk/startChart/constant/message_type_constant.dart'; import 'package:star_lock/talk/startChart/constant/payload_type_constant.dart'; import 'package:star_lock/talk/startChart/entity/relay_info_entity.dart'; import 'package:star_lock/talk/startChart/entity/report_information_data.dart'; import 'package:star_lock/talk/startChart/entity/scp_message.dart'; import 'package:star_lock/talk/startChart/entity/star_chart_register_node_entity.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/proto/talk_data.pb.dart'; import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; import 'package:star_lock/tools/baseGetXController.dart'; import 'package:star_lock/tools/deviceInfo_utils.dart'; import 'package:star_lock/tools/storage.dart'; import 'package:uuid/uuid.dart'; import 'dart:convert'; import 'package:asn1lib/asn1lib.dart' as asn1lib; // Prefix for asn1lib class StartChartManage { // 私有构造函数,防止外部直接new对象 StartChartManage._internal(); // 单例对象 static final StartChartManage _instance = StartChartManage._internal(); // 工厂构造函数,返回单例对象 factory StartChartManage() { return _instance; } // 产品昵称 final String _productName = F.navTitle; RawDatagramSocket? _udpSocket; final Map> _completers = {}; // 发送消息的请求是否完成 final Uuid _uuid = Uuid(); // 用于区分发送的消息的唯一id int _messageMaxTimeout = 5; // 消息最大超时时间(s) late String remoteHost = ''; // 远程主机地址(服务器返回) late int remotePort = 0; // 远程主机端口(服务器返回) final int localPort = 62289; // 本地端口 String localPublicHost = ''; // 本地公网ip地址 int heartbeatIntervalTime = 1; // 心跳包间隔时间(s) Timer? _heartBeatTimer; // 心跳包定时器 bool _heartBeatTimerRunning = false; // 心跳包定时任务发送状态 String ToPeerId = ''; // 对端ID String FromPeerId = ''; // 我的ID // echo测试peer对端 final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH'; bool isOnlineStartChartServer = false; // 星图是否上线成功 int reStartOnlineStartChartServerIntervalTime = 1; // 重新上线星图服务任务间隔(s) Timer? reStartOnlineStartChartServerTimer; // 重新上线定时器 int talkPingIntervalTime = 1; // 发送通话保持消息间隔(s) Timer? talkPingTimer; // 发送通话保持消息定时器 int talkExpectIntervalTime = 1; // 发送通话预期数据的消息间隔(s) Timer? talkExpectTimer; // 发送通话预期消息定时器 int talkDataIntervalTime = 10; // 通话数据的消息间隔(ms) Timer? talkDataTimer; // 发送通话数据消息定时器 final int _maxPayloadSize = 8 * 1024; // 分包大小 // 默认通话的期望数据格式 TalkExpect defaultTalkExpect = TalkExpect( videoType: [TalkExpect_VideoTypeE.IMAGE], audioType: [TalkExpect_AudioTypeE.G711], ); // 默认通话数据 TalkData defaultTalkData = TalkData(); String relayPeerId = ''; // 中继peerId // 获取 StartChartTalkStatus 的唯一实例 StartChartTalkStatus talkStatus = StartChartTalkStatus.instance; // 星图服务初始化 Future init() async { if (isOnlineStartChartServer && _udpSocket != null) { // 如果已经上线就不进行初始化 return; } // 节点注册 await _clientRegister(); // 中继查询 await _relayQuery(); // 初始化udp服务 await _onlineRelayService(); // 上报 await reportInformation(); } /// 客户端注册 Future _clientRegister() async { final StarChartRegisterNodeEntity? registerNodeEntity = await Storage.getStarChartRegisterNodeInfo(); if (registerNodeEntity != null && registerNodeEntity.peer?.id != null) { _log(text: '获取到星图注册节点信息:$registerNodeEntity'); FromPeerId = registerNodeEntity.peer!.id ?? ''; } else { _log(text: '开始注册客户端'); final StarChartRegisterNodeEntity requestStarChartRegisterNode = await _requestStarChartRegisterNode(); await _saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode); FromPeerId = requestStarChartRegisterNode.peer!.id ?? ''; bindUserStarchart(); } } //绑定星图配置 Future bindUserStarchart() async { try { final StarChartRegisterNodeEntity? registerNodeEntity = await Storage.getStarChartRegisterNodeInfo(); final LoginEntity entity = await ApiRepository.to.bindUserStarchart( starchartId: registerNodeEntity?.peer?.id ?? '', starchartPeerPublicKey: registerNodeEntity?.peer?.publicKey ?? '', starchartPeerPrivateKey: registerNodeEntity?.peer?.privateKey ?? '', ); if (entity.errorCode!.codeIsSuccessful) { AppLog.log('绑定成功'); } else { AppLog.log('绑定失败'); } } catch (e) { AppLog.log('Error bindUserStarchart: $e'); } } // 中继查询 Future _relayQuery() async { final RelayInfoEntity relayInfoEntity = await StartChartApi.to.relayQueryInfo(); _saveRelayInfoEntityToStorage(relayInfoEntity); if (relayInfoEntity.client_addr != null) { localPublicHost = relayInfoEntity.client_addr!; } if (relayInfoEntity.relay_list != null && relayInfoEntity.relay_list!.length > 0) { for (int i = 0; i <= relayInfoEntity.relay_list!.length; i++) { final data = relayInfoEntity.relay_list?[i]; if (data?.peerID != FromPeerId) { final parseUdpUrl = _parseUdpUrl(data?.listenAddr ?? ''); remoteHost = parseUdpUrl['host'] ?? ''; remotePort = parseUdpUrl['port'] ?? ''; relayPeerId = data?.peerID ?? ''; ToPeerId = relayPeerId; _log(text: '中继信息----》${relayInfoEntity}'); break; } } } else { _log(text: '未查询到中继信息----》'); } } void closeUdpSocket() { if (_udpSocket != null) { _udpSocket?.close(); } } // 初始化udp Future _onlineRelayService() async { var addressIListenFrom = InternetAddress.anyIPv4; RawDatagramSocket.bind(addressIListenFrom, localPort) .then((RawDatagramSocket socket) { _udpSocket = socket; /// 广播功能 _udpSocket!.broadcastEnabled = true; /// 设置数据接收回调 _onReceiveData(_udpSocket!); }).catchError((error) { _log(text: 'Failed to bind UDP socket: $error'); }); } // 上报信息至发现服务 Future reportInformation() async { _log(text: '上报信息至发现服务'); // 构建参数 ReportInformationData data = await _makeReportInformationData(); final response = await StartChartApi.to.reportInformation( reportInformationData: data, ); if (response.statusCode == 200) { talkStatus.setInitializationCompleted(); // 发送心跳消息 _sendHeartbeatMessage(); // 发送送上线消息 await reStartOnlineStartChartServer(); } } // 发送上线消息 Future _sendOnlineMessage() async { if (isOnlineStartChartServer) { _log(text: '星图已上线,请勿重复发送上线消息'); return; } // 组装上线消息 final message = MessageCommand.goOnlineRelay( FromPeerId: FromPeerId, ToPeerId: ToPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 发送对讲请求消息 Future sendCallRequestMessage({required String ToPeerId}) async { if (talkStatus.status == TalkStatus.duringCall) { _log(text: '已经在通话中,请勿重复发送对讲请求'); return; } // 组装上线消息 final message = MessageCommand.talkRequestMessage( FromPeerId: FromPeerId, ToPeerId: ToPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 发送对讲数据 Future sendTalkDataMessage({required TalkData talkData}) async { // 组装上线消息 final message = MessageCommand.talkDataMessage( FromPeerId: FromPeerId, ToPeerId: ToPeerId, talkData: talkData, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 发送心跳包消息 void _sendHeartbeatMessage() async { if (_heartBeatTimerRunning) { _log(text: '心跳已经开始了. 请勿重复发送心跳包消息'); return; } _heartBeatTimer ??= Timer.periodic( Duration( seconds: heartbeatIntervalTime, ), (Timer timer) async { final List message = MessageCommand.heartbeatMessage( FromPeerId: FromPeerId, ToPeerId: relayPeerId, MessageId: MessageCommand.getNextMessageId(relayPeerId, increment: true), ); await _sendMessage(message: message); }, ); _heartBeatTimerRunning = true; } // 发送回声测试消息 void sendEchoMessage( {required List payload, required String toPeerId}) async { // 计算需要分多少个包发送 final int totalPackets = (payload.length / _maxPayloadSize).ceil(); // 循环遍历 for (int i = 0; i < totalPackets; i++) { int start = i * _maxPayloadSize; int end = (i + 1) * _maxPayloadSize; if (end > payload.length) { end = payload.length; } // 取到分包数据 List packet = payload.sublist(start, end); // 分包数据不递增messageID final messageId = MessageCommand.getNextMessageId(toPeerId, increment: false); // 组装分包数据 final message = MessageCommand.echoMessage( ToPeerId: toPeerId, FromPeerId: FromPeerId, payload: packet, SpTotal: totalPackets, SpIndex: i + 1, MessageId: messageId, ); // 发送消息 await _sendMessage(message: message); } // 分包发送完了递增一下id MessageCommand.getNextMessageId(toPeerId); } // 发送网关初始化消息 void sendGatewayResetMessage( {required String ToPeerId, required int gatewayId}) async { final message = MessageCommand.gatewayResetMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, gatewayId: gatewayId, time: DateTime.now().millisecondsSinceEpoch ~/ 1000, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 发送同意接听消息 void sendTalkAcceptMessage() async { final message = MessageCommand.talkAcceptMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); talkStatus.setWaitingAnswer(); } // 发送拒绝接听消息 void sendTalkRejectMessage() async { final message = MessageCommand.talkRejectMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 发送期望接受消息 void sendTalkExpectMessage({required TalkExpect talkExpect}) async { final message = MessageCommand.talkExpectMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, talkExpect: talkExpect, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 回复成功消息 void sendGenericRespSuccessMessage( {required String ToPeerId, required String FromPeerId, required int PayloadType}) async { final message = MessageCommand.genericRespSuccessMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, PayloadType: PayloadType, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: false), ); await _sendMessage(message: message); } // 回复失败消息 void sendGenericRespErrorMessage( {required String ToPeerId, required String FromPeerId, required int PayloadType}) async { final message = MessageCommand.genericRespErrorMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, PayloadType: PayloadType, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: false), ); await _sendMessage(message: message); } // 发送通话保持消息 Future sendTalkPingMessage( {required String ToPeerId, required String FromPeerId}) async { final message = MessageCommand.talkPingMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 发送通话中挂断消息 Future sendTalkHangupMessage() async { final message = MessageCommand.talkHangupMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 重新上线 Future reStartOnlineStartChartServer() async { if (isOnlineStartChartServer) { _log(text: '星图已上线,请勿重复发送上线消息'); return; } reStartOnlineStartChartServerTimer ??= Timer.periodic( Duration( seconds: reStartOnlineStartChartServerIntervalTime, ), (Timer timer) async { // 重新发送上线消息 await _sendOnlineMessage(); }, ); } // 重新发送心跳 void reStartHeartBeat() { stopHeartbeat(); _sendHeartbeatMessage(); } // 停止定时发送心跳包 void stopHeartbeat() { _heartBeatTimer?.cancel(); _heartBeatTimer = null; // 清除定时器引用 _heartBeatTimerRunning = false; } // 停止重新上线 void stopReStartOnlineStartChartServer() { reStartOnlineStartChartServerTimer?.cancel(); reStartOnlineStartChartServerTimer = null; // 清除定时器引用 } // 发送消息 Future _sendMessage({required List message}) async { var result = await _udpSocket?.send( message, InternetAddress(remoteHost), remotePort); if (result != message.length) { AppLog.log('❌Udp send data error----> $result ${message.length}'); // _udpSocket = null; } } // 请求注册节点 Future _requestStarChartRegisterNode() async { // 获取设备信息 final Map deviceInfo = await _getDeviceInfo(); // 发送注册节点请求 final StarChartRegisterNodeEntity response = await StartChartApi.to.starChartRegisterNode( product: _productName, model: '${deviceInfo['brand']}_${deviceInfo['model']}', name: '${deviceInfo['id']}', unique: deviceInfo['id'] ?? Uuid().v1(), ); return response; } // 保存星图注册节点信息至缓存 Future _saveStarChartRegisterNodeToStorage( StarChartRegisterNodeEntity starChartRegisterNodeEntity) async { if (starChartRegisterNodeEntity != null) { await Storage.saveStarChartRegisterNodeInfo(starChartRegisterNodeEntity); _log(text: '注册成功'); } } // 保存星图中继服务器信息至缓存 Future _saveRelayInfoEntityToStorage( RelayInfoEntity relayInfoEntity) async { if (relayInfoEntity != null) { await Storage.saveRelayInfo(relayInfoEntity); } } // 构造上报信息数据参数 Future _makeReportInformationData() async { // 从缓存中获取中继信息 final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); // 获取公钥 final publicKey = await getPublicKey(); // 获取私钥 final privateKey = await getPrivateKey(); // 生成签名 final sign = await _generateSign( currentTimestamp: relayInfoEntity!.time ?? 0, privateKeyHex: privateKey, ); // 获取本机所有ip地址和中继返回的外网地址 final List listenAddrDataList = await _makeListenAddrDataList(); // final RelayServiceData relayServiceData = RelayServiceData( name: relayInfoEntity?.relay_list?[0].name ?? '', listen_addr: relayInfoEntity?.relay_list?[0].listenAddr ?? '', peers_max: relayInfoEntity?.relay_list?[0].peerMax ?? 0, peers_current: relayInfoEntity?.relay_list?[0].peerCurrent ?? 0, ); ReportInformationData data = ReportInformationData( id: FromPeerId, public_key: publicKey, listen_addr: listenAddrDataList, relay_service: relayServiceData, time: relayInfoEntity.time ?? 0, sign: sign, ); return data; } // 解析对端数据 Future analyzeInformationOtherEnd() async { await StartChartApi.to.analyzeInformationOtherEnd(peerId: ToPeerId); } // 获取本机所有ip地址和中继返回的外网地址 Future> _makeListenAddrDataList() async { final List listenAddrDataList = []; final List localIp = await _getAllIpAddresses(); // 从缓存中获取中继信息,取出返回的客户端ip地址 final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); if (relayInfoEntity != null && relayInfoEntity.client_addr != null) { listenAddrDataList.add( ListenAddrData( type: ListenAddrTypeConstant.relay, address: relayInfoEntity.relay_list!.last!.listenAddr!, ), ); } localIp.forEach((element) { listenAddrDataList.add( ListenAddrData( type: ListenAddrTypeConstant.local, address: IpConstant.udpUrl + element + ':' + localPort.toString(), ), ); }); return listenAddrDataList ?? []; } /// 获取本机所有 IP 地址 Future> _getAllIpAddresses() async { final List ipAddresses = []; try { final List interfaces = await NetworkInterface.list( includeLoopback: true, type: InternetAddressType.any, ); for (final interface in interfaces) { for (final address in interface.addresses) { // 获取原始 IP 地址 String ipAddress = address.address; // 移除 IPv6 地址中的接口标识符(如果有) if (ipAddress.contains('%')) { ipAddress = ipAddress.split('%').first; } // 确保 IP 地址不为空且不在排除列表中 if (ipAddress.isNotEmpty && !IpConstant.reportExcludeIp.contains(ipAddress)) { ipAddresses.add(ipAddress); } } } } catch (e) { _log(text: '❌--->获取本机IP时出现错误: $e'); } return ipAddresses; // 注意:这里不需要 `?? []`,因为 `ipAddresses` 已经初始化为一个空列表。 } void _log({required String text}) { AppLog.log('$_productName=====${text}'); } /// 获取设备信息 Future> _getDeviceInfo() async { final Map deviceInfo = await DeviceInfoUtils.getDeviceInfo(); return deviceInfo; } /// 解析 UDP URL 并提取 IP 地址和端口号 Map _parseUdpUrl(String url) { // 使用正则表达式匹配 IP 地址和端口号 final regex = RegExp(r'udp://(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)') .firstMatch(url); if (regex != null) { final ip = regex.group(1); final portStr = regex.group(2); final port = int.tryParse(portStr ?? ''); if (ip != null && port != null) { return {'host': ip, 'port': port}; } } throw FormatException('无法解析 URL 格式: $url'); } String bytesToHex(List bytes) { return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); } // 生成签名sing Future _generateSign({ required int currentTimestamp, required String privateKeyHex, }) async { String resultSign = ''; try { // 1. 将 32 位时间戳以小端字节序编码为二进制数据 Uint8List signData = encodeTimestampToLittleEndianBytes(currentTimestamp); // 2.将十六进制字符串转换为字节数组 List privateKeyBytes = hexToBytes(privateKeyHex); // 3.将私钥转换为 PEM 格式 final pemPrivateKey = convertToPemPrivateKey(privateKeyBytes, isPKCS8: true); // 4.签名 var result = await fastRsa.RSA .signPKCS1v15Bytes(signData, fastRsa.Hash.SHA256, pemPrivateKey); resultSign = hex.encode(result); } catch (e) { _log(text: '❌--->上报信息生成签名时出现错误: $e'); e.printError(); } return resultSign ?? ''; } // 将 32 位时间戳以小端字节序编码为二进制数据 Uint8List encodeTimestampToLittleEndianBytes(int timestamp) { // 创建一个 4 字节的 ByteData 对象 ByteData byteData = ByteData(4); // 将 32 位时间戳写入 ByteData,使用小端字节序 byteData.setUint32(0, timestamp, Endian.little); // 将 ByteData 转换为 Uint8List Uint8List bytes = byteData.buffer.asUint8List(); return bytes; } /// 转换私钥格式 String convertToPemPrivateKey(List privateKeyBytes, {bool isPKCS8 = true}) { // 将字节数组转换为Base64编码的字符串 String base64PrivateKey = base64Encode(privateKeyBytes); // 添加PEM格式的头尾标签 String pemHeader; String pemFooter; if (isPKCS8) { pemHeader = "-----BEGIN PRIVATE KEY-----"; pemFooter = "-----END PRIVATE KEY-----"; } else { pemHeader = "-----BEGIN RSA PRIVATE KEY-----"; pemFooter = "-----END RSA PRIVATE KEY-----"; } // 将Base64字符串分行为每行64个字符 const lineLength = 64; List lines = []; // 用于存储每一行 for (int i = 0; i < base64PrivateKey.length; i += lineLength) { int end = (i + lineLength < base64PrivateKey.length) ? i + lineLength : base64PrivateKey.length; lines.add(base64PrivateKey.substring(i, end)); } // 组合成完整的PEM格式字符串 return "$pemHeader\n${lines.join('\n')}\n$pemFooter"; } /// 自定义 PEM 格式的 RSA 私钥解析器 pc.RSAPrivateKey loadPrivateKey(String privateKeyHex) { // 将十六进制字符串转换为字节数组 final uint8list = Uint8List.fromList(hexToBytes(privateKeyHex)); try { // 使用 asn1lib 的 ASN1Parser 解析 final asn1Parser = asn1lib.ASN1Parser(uint8list); final topLevelSeq = asn1Parser.nextObject() as asn1lib.ASN1Sequence; final modulus = bytesToBigInt( (topLevelSeq.elements[1] as asn1lib.ASN1Integer).valueBytes()); final privateExponent = bytesToBigInt( (topLevelSeq.elements[3] as asn1lib.ASN1Integer).valueBytes()); final p = bytesToBigInt( (topLevelSeq.elements[4] as asn1lib.ASN1Integer).valueBytes()); final q = bytesToBigInt( (topLevelSeq.elements[5] as asn1lib.ASN1Integer).valueBytes()); return pc.RSAPrivateKey(modulus, privateExponent, p, q); } catch (e) { // 如果发生解码错误,打印错误信息 print("Error decoding private key: $e"); rethrow; } } // 将十六进制字符串转换为字节数组 List hexToBytes(String hex) { return List.generate(hex.length ~/ 2, (i) => int.parse(hex.substring(i * 2, i * 2 + 2), radix: 16)); } BigInt bytesToBigInt(Uint8List bytes) { return BigInt.parse( bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(), radix: 16, ); } /// 获取公钥 Future getPublicKey() async { // 从缓存中获取星图注册节点信息 final StarChartRegisterNodeEntity? starChartRegisterNodeInfo = await Storage.getStarChartRegisterNodeInfo(); return starChartRegisterNodeInfo?.peer?.publicKey ?? ''; } /// 获取私钥 Future getPrivateKey() async { // 从缓存中获取星图注册节点信息 final StarChartRegisterNodeEntity? starChartRegisterNodeInfo = await Storage.getStarChartRegisterNodeInfo(); return starChartRegisterNodeInfo?.peer?.privateKey ?? ''; } // 接收返回的数据 void _onReceiveData(RawDatagramSocket socket) { socket.listen((RawSocketEvent event) { if (event == RawSocketEvent.read) { Datagram? dg = socket.receive(); try { if (dg?.data != null) { final deserialize = ScpMessage.deserialize(dg!.data); if (deserialize != null) { // 处理返回数据 _handleUdpResultData(deserialize); } if (deserialize.PayloadType != PayloadTypeConstant.heartbeat) { if (deserialize.Payload != null) { _log(text: 'Udp收到结构体数据---》$deserialize'); } // _log(text: 'text---》${utf8.decode(deserialize.Payload)}'); } } } catch (e, stackTrace) { _log(text: '❌ Udp result data error ----> $e'); _log(text: '堆栈跟踪:\n$stackTrace'); } } }); } // 处理udp返回的数据 void _handleUdpResultData(ScpMessage scpMessage) { final int payloadType = scpMessage.PayloadType ?? 0; final int messageType = scpMessage.MessageType ?? 0; try { final ScpMessageHandler handler = ScpMessageHandlerFactory.createHandler(payloadType); if (messageType == MessageTypeConstant.Req) { handler.handleReq(scpMessage); } else if (messageType == MessageTypeConstant.Resp) { handler.handleResp(scpMessage); } else if (messageType == MessageTypeConstant.RealTimeData) { handler.handleRealTimeData(scpMessage); } else { handler.handleInvalidReq(scpMessage); } } catch (e, stackTrace) { _log(text: '❌ 处理udp返回数据时遇到错误---> $e\n,$stackTrace'); } } /// 通话保持定时器 void startTalkPingMessageTimer() { talkPingTimer ??= Timer.periodic( Duration( seconds: talkPingIntervalTime, ), (Timer timer) async { await sendTalkPingMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, ); }, ); } // 停止通话保持 void stopTalkPingMessageTimer() { talkPingTimer?.cancel(); talkPingTimer = null; // 清除定时器引用 } /// 通话期望数据定时器 void startTalkExpectTimer() { talkExpectTimer ??= Timer.periodic( Duration( seconds: talkExpectIntervalTime, ), (Timer timer) { // 发送期望接受消息 sendTalkExpectMessage( talkExpect: defaultTalkExpect, ); }, ); } /// 通话数据定时器 void startTalkDataTimer() async { // 如果已经启动了就不运行 if (talkDataTimer != null) return; // 读取 assets 文件 final ByteData data = await rootBundle.load('assets/talk.h264'); final List byteData = data.buffer.asUint8List(); int current = 0; int start = 0; int end = 0; final List chunks = extractChunks(byteData); talkDataTimer ??= Timer.periodic( Duration( milliseconds: talkDataIntervalTime, ), (Timer timer) { if (current >= chunks.length) { print('数据已经发完'); start = 0; end = 0; current = 0; timer.cancel(); return; } // 提取 NALU 边界并生成 chunks end = chunks[current]; current++; List frameData = byteData.sublist(start, end); if (frameData.length == 0) timer.cancel(); defaultTalkData = TalkData( content: frameData, contentType: TalkData_ContentTypeE.H264, ); start = end; // 发送童话数据 sendTalkDataMessage(talkData: defaultTalkData); }, ); } List extractChunks(List byteData) { int i = 0; int length = byteData.length; int naluCount = 0; int value; int state = 0; int lastIndex = 0; List result = []; const minNaluPerChunk = 22; // 每个数据块包含的最小NALU数量 while (i < length) { value = byteData[i++]; // finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01) switch (state) { case 0: if (value == 0) { state = 1; } break; case 1: if (value == 0) { state = 2; } else { state = 0; } break; case 2: case 3: if (value == 0) { state = 3; } else if (value == 1 && i < length) { if (lastIndex > 0) { naluCount++; } if (naluCount >= minNaluPerChunk) { result.add(lastIndex - state - 1); naluCount = 0; } state = 0; lastIndex = i; } else { state = 0; } break; default: break; } } if (naluCount > 0) { result.add(lastIndex); } return result; } // 停止发送通话数据 void stopTalkDataTimer() { talkDataTimer?.cancel(); talkDataTimer = null; // 清除定时器引用 } // 停止发送通话期望数据 void stopTalkExpectMessageTimer() { talkExpectTimer?.cancel(); talkExpectTimer = null; // 清除定时器引用 } /// 修改预期接收到的数据 void changeTalkExpectDataType({required TalkExpect talkExpect}) { defaultTalkExpect = talkExpect; } /// 销毁资源 void destruction() async { isOnlineStartChartServer = false; stopTalkExpectMessageTimer(); stopTalkPingMessageTimer(); stopHeartbeat(); stopReStartOnlineStartChartServer(); stopTalkDataTimer(); // await Storage.removerRelayInfo(); // await Storage.removerStarChartRegisterNodeInfo(); } }