import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:provider/provider.dart'; import 'package:star_lock/appRouters.dart'; import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/flavors.dart'; import 'package:star_lock/login/login/entity/LoginData.dart'; import 'package:star_lock/login/login/entity/LoginEntity.dart'; import 'package:star_lock/main/lockDetail/lockDetail/device_network_info.dart'; import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/network/start_chart_api.dart'; import 'package:star_lock/talk/other/audio_player_manager.dart'; import 'package:star_lock/talk/starChart/command/message_command.dart'; import 'package:star_lock/talk/starChart/constant/ip_constant.dart'; import 'package:star_lock/talk/starChart/constant/listen_addr_type_constant.dart'; import 'package:star_lock/talk/starChart/constant/message_type_constant.dart'; import 'package:star_lock/talk/starChart/constant/payload_type_constant.dart'; import 'package:star_lock/talk/starChart/constant/talk_constant.dart'; import 'package:star_lock/talk/starChart/constant/talk_status.dart'; import 'package:star_lock/talk/starChart/entity/relay_info_entity.dart'; import 'package:star_lock/talk/starChart/entity/report_information_data.dart'; import 'package:star_lock/talk/starChart/entity/scp_message.dart'; import 'package:star_lock/talk/starChart/entity/star_chart_register_node_entity.dart'; import 'package:star_lock/talk/starChart/exception/start_chart_message_exception.dart'; import 'package:star_lock/talk/starChart/handle/impl/debug_Info_model.dart'; import 'package:star_lock/talk/starChart/handle/impl/udp_talk_data_handler.dart'; import 'package:star_lock/talk/starChart/handle/other/do_sign.dart'; import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart'; import 'package:star_lock/talk/starChart/handle/other/talke_data_over_time_timer_manager.dart'; import 'package:star_lock/talk/starChart/handle/other/talke_ping_over_time_timer_manager.dart'; import 'package:star_lock/talk/starChart/handle/other/talke_request_over_time_timer_manager.dart'; import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart'; import 'package:star_lock/talk/starChart/handle/scp_message_handler_factory.dart'; import 'package:star_lock/talk/starChart/proto/rbcu.pb.dart'; import 'package:star_lock/talk/starChart/proto/rbcu.pbserver.dart'; import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/starChart/proto/talk_expect.pbserver.dart'; import 'package:star_lock/talk/starChart/status/star_chart_talk_status.dart'; import 'package:star_lock/tools/baseGetXController.dart'; import 'package:star_lock/tools/commonDataManage.dart'; import 'package:star_lock/tools/deviceInfo_utils.dart'; import 'package:star_lock/tools/storage.dart'; import 'package:uuid/uuid.dart'; class StartChartManage { // 私有构造函数,防止外部直接new对象 StartChartManage._internal(); // 单例对象 static final StartChartManage _instance = StartChartManage._internal(); final TalkeRequestOverTimeTimerManager talkeRequestOverTimeTimerManager = TalkeRequestOverTimeTimerManager(); final TalkePingOverTimeTimerManager talkePingOverTimeTimerManager = TalkePingOverTimeTimerManager(); final TalkDataOverTimeTimerManager talkDataOverTimeTimerManager = TalkDataOverTimeTimerManager(); // 工厂构造函数,返回单例对象 factory StartChartManage() { return _instance; } // 产品昵称 final String _productName = F.navTitle; RawDatagramSocket? _udpSocket; final Uuid _uuid = Uuid(); // 随机UUID,用于匹配接下来的打洞会话 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 String lockPeerId = ''; // 锁peerId DeviceNetworkInfo lockNetworkInfo = DeviceNetworkInfo(); // 锁网络信息 List lockListPeerId = []; // 锁列表peerId // echo测试peer对端 final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH'; bool isOnlineStarChartServer = false; // 星图是否上线成功 Timer? reStartOnlineStartChartServerTimer; // 重新上线定时器 Timer? talkPingTimer; // 发送通话保持消息定时器 Timer? talkExpectTimer; // 发送通话预期消息定时器 Timer? talkAcceptTimer; // 重发同意接听消息定时器 int talkDataIntervalTime = 10; // 发送通话数据的消息间隔(ms) int _defaultIntervalTime = 1; // 默认定时发送间隔(s) Timer? talkDataTimer; // 发送通话数据消息定时器 Timer? rbcuInfoTimer; // p2p地址交换定时器 Timer? rbcuProbeTimer; // p2p打洞包 Timer? rbcuConfirmTimer; // p2p打洞确认包 Timer? talkHangupTimer; // 添加挂断消息定时器 Timer? talkRejectTimer; // 添加拒绝接听定时器 String _rbcuSessionId = ''; // p2p SessionId Timer? talkRequestTimer; // 对讲请求定时器 final int maxAttempts = 15; // 最大执行次数 RbcuInfo? rbcuInfo; RbcuProbe? rbcuProbe; RbcuConfirm? rbcuConfirm; final int _maxPayloadSize = 8 * 1024; // 分包大小 int rotateAngle = 0; // 视频旋转角度 int videoWidth = 0; // 视频宽度 int videoHeight = 0; // 视频高度 // 默认通话的期望数据格式 TalkExpectReq _defaultTalkExpect = TalkConstant.H264Expect; String relayPeerId = ''; // 中继peerId // 获取 StartChartTalkStatus 的唯一实例 StartChartTalkStatus talkStatus = StartChartTalkStatus.instance; // 星图服务初始化 Future init() async { if (F.isXHJ) { return; } // 判断是否登录账户 final loginData = await Storage.getLoginData(); if ((isOnlineStarChartServer && _udpSocket != null) || loginData == null) { // 如果已经上线就不进行初始化 return; } // 节点注册 await _clientRegister(loginData); // 中继查询 await _relayQuery(); // 初始化udp服务 await _onlineRelayService(); // 上报 await reportInformation(); } /// 客户端注册 Future _clientRegister(LoginData? loginData) async { if (loginData?.starchart?.starchartId != null) { _log(text: '获取到星图注册节点信息:${loginData?.starchart}'); FromPeerId = loginData?.starchart?.starchartId ?? ''; } else { _log(text: '开始注册客户端'); final StarChartRegisterNodeEntity requestStarChartRegisterNode = await _requestStarChartRegisterNode(); await _saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode); FromPeerId = requestStarChartRegisterNode.peer!.id ?? ''; bindUserStarchart(requestStarChartRegisterNode); } } //绑定星图配置 Future bindUserStarchart( StarChartRegisterNodeEntity requestStarChartRegisterNode) async { try { final LoginEntity entity = await ApiRepository.to.bindUserStarchart( starchartId: requestStarChartRegisterNode.peer?.id ?? '', starchartPeerPublicKey: requestStarChartRegisterNode.peer?.publicKey ?? '', starchartPeerPrivateKey: requestStarChartRegisterNode.peer?.privateKey ?? '', ); requestStarChartRegisterNode.peer?.id = entity.data?.starchart?.starchartId; 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 = await _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(); _udpSocket = null; } } // 初始化udp Future _onlineRelayService() async { var addressIListenFrom = InternetAddress.anyIPv4; RawDatagramSocket.bind(addressIListenFrom, localPort) .then((RawDatagramSocket socket) { _udpSocket = socket; /// 广播功能 _udpSocket!.broadcastEnabled = true; /// 设置数据接收回调 _onReceiveData(_udpSocket!, Get.context!); }).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(); } } // 发送RbcuInfo 地址交换消息 void _sendRbcuInfoMessage( {required String ToPeerId, bool isResp = false}) async { final uuid = _uuid.v1(); final int timestamp = DateTime.now().millisecondsSinceEpoch; final Int64 int64Timestamp = Int64(timestamp); // 使用构造函数 // 获取本机所有ip地址和中继返回的外网地址 final List listenAddrDataList = await _makeListenAddrDataList(); listenAddrDataList.insert( 0, // 插入到头部 ListenAddrData( type: ListenAddrTypeConstant.local, address: localPublicHost + ':' + localPort.toString(), ), ); final address = listenAddrDataList .where((element) => element.type == ListenAddrTypeConstant.local) // 过滤出本地地址 .map((e) => e.address) // 转换为 List .where((addr) => addr != null) // 过滤掉 null 值 .map( (addr) => addr!.replaceAll(IpConstant.udpUrl, ''), ) // 去除 "udp://" 前缀 .cast< String>(); // 转换为 Iterable// 将 Iterable 转换为 Iterable _rbcuSessionId = uuid; final RbcuInfo rbcuInfo = RbcuInfo( sessionId: uuid, name: uuid, address: address, time: int64Timestamp, isResp: isResp, ); final message = MessageCommand.genericRbcuInfoMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), rbcuInfo: rbcuInfo, ); _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))), ); } if (rbcuInfo != null && rbcuInfo!.address != null && rbcuInfo!.address.length > 0) { rbcuInfo!.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); }); } } // 发送打洞确认包 void _sendRbcuConfirmMessage() async { RbcuConfirm( sessionId: _rbcuSessionId, ); } // 启动定时任务 void startSendingRbcuInfoMessages({required String ToPeerId}) { // 每隔 1 秒执行一次 _sendRbcuInfoMessage rbcuInfoTimer ??= Timer.periodic(Duration(seconds: _defaultIntervalTime), (timer) { // 发送RbcuInfo 地址交换消息 _log(text: '发送RbcuInfo 地址交换消息'); _sendRbcuInfoMessage(ToPeerId: ToPeerId); }); } // 发送打洞包 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() { rbcuInfoTimer?.cancel(); // 取消定时器 rbcuInfoTimer = null; } // 停止定时任务 void stopSendingRbcuProBeMessages() { rbcuProbeTimer?.cancel(); // 取消定时器 rbcuProbeTimer = null; } // 停止定时任务 void stopSendingRbcuConfirmMessages() { rbcuConfirmTimer?.cancel(); // 取消定时器 rbcuConfirmTimer = null; } // 回复RbcuInfo void replyRbcuInfoMessage({required String ToPeerId}) { _sendRbcuInfoMessage(ToPeerId: ToPeerId, isResp: true); } // 发送上线消息 Future _sendOnlineMessage() async { if (isOnlineStarChartServer) { _log(text: '星图已上线,请勿重复发送上线消息'); return; } // 组装上线消息 final message = MessageCommand.goOnlineRelay( FromPeerId: FromPeerId, ToPeerId: ToPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } /// 启动持续发送对讲请求 void startCallRequestMessageTimer({required String ToPeerId}) async { // 如果已经处于等待接听状态就不发送 // if (talkStatus.status != TalkStatus.proactivelyCallWaitingAnswer) { // // 如果是h264则跳转至webview // if (_defaultTalkExpect.videoType.contains(VideoTypeE.H264)) { // Get.toNamed( // Routers.h264WebView, // ); // } else { // Get.toNamed( // Routers.starChartTalkView, // ); // } // } final LockListInfoItemEntity currentKeyInfo = CommonDataManage().currentKeyInfo; final isH264 = currentKeyInfo.lockFeature?.isH264 == 1; final isMJpeg = currentKeyInfo.lockFeature?.isMJpeg == 1; // 优先使用H264,其次是MJPEG if (isH264) { Get.toNamed( Routers.h264View, ); } else if (isMJpeg) { Get.toNamed( Routers.starChartTalkView, ); } else { Get.toNamed( Routers.starChartTalkView, ); } // 启动定时器持续发送对讲请求 talkRequestTimer ??= Timer.periodic( Duration( seconds: _defaultIntervalTime, ), (Timer timer) async { await sendCallRequestMessage(ToPeerId: ToPeerId); }, ); talkStatus.setProactivelyCallWaitingAnswer(); // 启动对讲请求应答超时判断 talkeRequestOverTimeTimerManager.start(); } /// 停止持续发送对讲请求 void stopCallRequestMessageTimer() async { talkRequestTimer?.cancel(); talkRequestTimer = null; } // 发送对讲请求消息 Future sendCallRequestMessage({required String ToPeerId}) async { // 组装上线消息 final message = MessageCommand.talkRequestMessage( FromPeerId: FromPeerId, ToPeerId: ToPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 发送对讲数据 // 现在的场景只有给锁板发送音频数据 Future sendTalkDataMessage({required TalkData talkData}) async { String toPeerId = ToPeerId; final List payload = talkData.content; // 计算需要分多少个包发送 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 packetTalkData = payload.sublist(start, end); // 分包数据不递增messageID final messageId = MessageCommand.getNextMessageId(toPeerId, increment: false); // 组装分包数据 final message = MessageCommand.talkDataMessage( ToPeerId: toPeerId, FromPeerId: FromPeerId, talkData: TalkData( contentType: talkData.contentType, content: packetTalkData, durationMs: talkData.durationMs, ), SpTotal: totalPackets, SpIndex: i + 1, MessageId: messageId, ); // 发送消息 await _sendMessage(message: message); } // 分包发送完了递增一下id MessageCommand.getNextMessageId(toPeerId); } // 发送心跳包消息 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); stopTalkExpectMessageTimer(); } void _sendTalkRejectMessage() { final message = MessageCommand.talkRejectMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); _sendMessage(message: message); } // 发送拒绝接听消息 void startTalkRejectMessageTimer() async { try { int count = 0; final int maxCount = 3; // 最大执行次数为10秒 talkRejectTimer ??= Timer.periodic( Duration(seconds: _defaultIntervalTime), (Timer timer) async { _sendTalkRejectMessage(); count++; if (count >= maxCount) { timer.cancel(); talkRejectTimer = null; } }, ); } catch (e) { AppLog.log("startTalkRejectMessageTimer e:${e}"); } finally { // 设置状态为拒绝 StartChartTalkStatus.instance.setRejected(); // 停止播放铃声 AudioPlayerManager().stopRingtone(); // 停止发送通话保持消息、通话预期数据请求 stopTalkExpectMessageTimer(); stopTalkPingMessageTimer(); stopCallRequestMessageTimer(); stopSendingRbcuInfoMessages(); stopSendingRbcuProBeMessages(); stopTalkAcceptTimer(); stopCallRequestMessageTimer(); // 取消定时器 talkePingOverTimeTimerManager.cancel(); talkDataOverTimeTimerManager.cancel(); } } // 发送期望接受消息 void sendTalkExpectMessage({required TalkExpectReq talkExpect}) async { final message = MessageCommand.talkExpectMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, talkExpect: talkExpect, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); // _log(text: '发送预期数据:${talkExpect}'); } // 回复成功消息 void sendGenericRespSuccessMessage({ required String ToPeerId, required String FromPeerId, required int PayloadType, required int messageId, }) async { if (messageId == null) { messageId = MessageCommand.getNextMessageId(ToPeerId, increment: false); } final message = MessageCommand.genericRespSuccessMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, PayloadType: PayloadType, MessageId: messageId, ); await _sendMessage(message: message); } // 回复失败消息 void sendGenericRespErrorMessage({ required String ToPeerId, required String FromPeerId, required int PayloadType, required int messageId, }) async { if (messageId == null) { messageId = MessageCommand.getNextMessageId(ToPeerId, increment: false); } 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); // _log(text: '发送通话保持'); } void _sendTalkHangupMessage() async { final message = MessageCommand.talkHangupMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), ); await _sendMessage(message: message); } // 发送通话中挂断消息 // 启动定时发送挂断消息 void startTalkHangupMessageTimer() { talkHangupTimer ??= Timer.periodic( Duration(seconds: _defaultIntervalTime), (Timer timer) async { _sendTalkHangupMessage(); }, ); // 设置状态为通话中挂断 StartChartTalkStatus.instance.setHangingUpDuring(); // 停止播放铃声 AudioPlayerManager().stopRingtone(); // 停止发送通话保持消息、通话预期数据请求 stopTalkExpectMessageTimer(); stopTalkPingMessageTimer(); stopCallRequestMessageTimer(); stopSendingRbcuInfoMessages(); stopSendingRbcuProBeMessages(); stopTalkAcceptTimer(); stopCallRequestMessageTimer(); // 取消定时器 talkePingOverTimeTimerManager.cancel(); talkDataOverTimeTimerManager.cancel(); } // 停止发送挂断消息 void stopTalkHangupMessageTimer() { talkHangupTimer?.cancel(); talkHangupTimer = null; } // 停止发送挂断消息 void stopTalkRejectMessageTimer() { talkRejectTimer?.cancel(); talkRejectTimer = null; } // 重新上线 Future reStartOnlineStartChartServer() async { if (isOnlineStarChartServer) { _log(text: '星图已上线,请勿重复发送上线消息'); return; } reStartOnlineStartChartServerTimer ??= Timer.periodic( Duration( seconds: _defaultIntervalTime, ), (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) { throw StartChartMessageException( '❌Udp send data error----> $result ${message.length}'); // _udpSocket = null; } //ToDo: 增加对讲调试、正式可删除 // UdpTalkDataHandler().updateSendDataRate(message.length); // // // 更新调试信息 // Provider.of(Get.context!, listen: false).updateDebugInfo( // UdpTalkDataHandler().getLastRecvDataRate() ~/ 1024, // 转换为KB // UdpTalkDataHandler().getLastRecvPacketCount(), // UdpTalkDataHandler().getLastSendDataRate() ~/ 1024, // 转换为KB // UdpTalkDataHandler().getLastSendPacketCount(), // ); } // 发送消息 Future _sendNatMessage( {required List 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 _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); final LoginData? loginData = await Storage.getLoginData(); loginData?.updateStarchart(starChartRegisterNodeEntity); Storage.saveLoginData(loginData); } } // 保存星图中继服务器信息至缓存 Future _saveRelayInfoEntityToStorage( RelayInfoEntity relayInfoEntity) async { if (relayInfoEntity != null) { await Storage.saveRelayInfo(relayInfoEntity); } } // 构造上报信息数据参数 Future _makeReportInformationData() async { // 从缓存中获取中继信息 final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); // 获取公钥 final String publicKey = await getPublicKey(); // 获取私钥 final String privateKey = await getPrivateKey(); // 生成签名 final sign = await DoSign().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 地址和端口号 /// 解析 UDP URL 并提取 IP 地址和端口号,同时处理域名解析 Future> _parseUdpUrl(String url) async { final regex = RegExp(r'udp://([a-zA-Z0-9.-]+):(\d+)').firstMatch(url); if (regex != null) { final host = regex.group(1); final portStr = regex.group(2); final port = int.tryParse(portStr ?? ''); if (host != null && port != null) { try { // 尝试进行 DNS 解析 final List addresses = await InternetAddress.lookup(host); if (addresses.isEmpty) { throw FormatException('DNS resolution failed for $host'); } // 使用解析后的第一个 IP 地址 final String resolvedIp = addresses.first.address; return {'host': resolvedIp, 'port': port}; } catch (e) { throw FormatException('DNS resolution error for $host: $e'); } } } throw FormatException('无法解析 URL 格式: $url'); } String bytesToHex(List bytes) { return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); } /// 获取公钥 Future getPublicKey() async { final loginData = await Storage.getLoginData(); return loginData?.starchart?.starchartPeerPublicKey ?? ''; } /// 获取私钥 Future getPrivateKey() async { final loginData = await Storage.getLoginData(); return loginData?.starchart?.starchartPeerPrivateKey ?? ''; } // 接收返回的数据 void _onReceiveData(RawDatagramSocket socket, BuildContext context) { socket.listen((RawSocketEvent event) { if (event == RawSocketEvent.read) { Datagram? dg = socket.receive(); try { if (dg?.data != null) { final deserialize = ScpMessage.deserialize(dg!.data); // //ToDo: 增加对讲调试、正式可删除 // UdpTalkDataHandler().updateRecvDataRate(dg.data.length); // // 更新调试信息 // Provider.of(context, listen: false).updateDebugInfo( // UdpTalkDataHandler().getLastRecvDataRate() ~/ 1024, // 转换为KB // UdpTalkDataHandler().getLastRecvPacketCount(), // UdpTalkDataHandler().getLastSendDataRate() ~/ 1024, // 转换为KB // UdpTalkDataHandler().getLastSendPacketCount(), // ); 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) { throw StartChartMessageException('$e\n,$stackTrace'); } } }); } // 处理udp返回的数据 void _handleUdpResultData(ScpMessage scpMessage) { final int payloadType = scpMessage.PayloadType ?? 0; final int messageType = scpMessage.MessageType ?? 0; try { // 记录分包数据用于统计丢包率 if (scpMessage.SpIndex != null && scpMessage.SpTotal != null && scpMessage.MessageId != null) { PacketLossStatistics().recordPacket( scpMessage.MessageId!, scpMessage.SpIndex!, scpMessage.SpTotal!); } 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) { throw StartChartMessageException('❌ 处理udp返回数据时遇到错误---> $e\n,$stackTrace'); } } /// 通话保持定时器 void startTalkPingMessageTimer() { talkPingTimer ??= Timer.periodic( Duration( seconds: _defaultIntervalTime, ), (Timer timer) async { await sendTalkPingMessage( ToPeerId: ToPeerId, FromPeerId: FromPeerId, ); }, ); } // 停止通话保持 void stopTalkPingMessageTimer() { talkPingTimer?.cancel(); talkPingTimer = null; // 清除定时器引用 } /// 通话期望数据定时器 void startTalkExpectTimer() { talkExpectTimer ??= Timer.periodic( Duration( seconds: _defaultIntervalTime, ), (Timer timer) { // 发送期望接受消息 sendTalkExpectMessage( talkExpect: _defaultTalkExpect, ); }, ); } /// 通话期望数据定时器 void startTalkAcceptTimer() { talkAcceptTimer ??= Timer.periodic( Duration( seconds: _defaultIntervalTime, ), (Timer timer) { sendTalkAcceptMessage(); }, ); } void stopTalkAcceptTimer() { talkAcceptTimer?.cancel(); talkAcceptTimer = null; // 清除定时器引用 } // 停止发送通话数据 void stopTalkDataTimer() { talkDataTimer?.cancel(); talkDataTimer = null; // 清除定时器引用 } // 停止发送通话期望数据 void stopTalkExpectMessageTimer() { talkExpectTimer?.cancel(); talkExpectTimer = null; // 清除定时器引用 } // 重新发送预期数据 void reStartTalkExpectMessageTimer() { stopTalkExpectMessageTimer(); startTalkExpectTimer(); } /// 修改预期接收到的数据 void changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( {required TalkExpectReq talkExpect}) { _defaultTalkExpect = talkExpect; reStartTalkExpectMessageTimer(); } void reSetDefaultTalkExpect() { _defaultTalkExpect = TalkConstant.H264Expect; } TalkExpectReq getDefaultTalkExpect() { return _defaultTalkExpect; } /// 修改预期接收到的数据 void sendOnlyImageVideoTalkExpectData() { final talkExpectReq = TalkExpectReq( videoType: [VideoTypeE.IMAGE], audioType: [], ); changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( talkExpect: talkExpectReq); } /// 修改预期接收到的数据 void sendOnlyH264VideoTalkExpectData() { final talkExpectReq = TalkExpectReq( videoType: [VideoTypeE.H264], audioType: [], ); changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( talkExpect: talkExpectReq); } /// 修改预期接收到的数据 void sendImageVideoAndG711AudioTalkExpectData() { changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( talkExpect: TalkConstant.ImageExpect); } /// 修改预期接收到的数据 void sendH264VideoAndG711AudioTalkExpectData() { changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer( talkExpect: TalkConstant.H264Expect); } /// 发送远程开锁 void sendRemoteUnLockMessage({ required String bluetoothDeviceName, required List openLockCommand, }) { sendBleMessage( bluetoothDeviceName: bluetoothDeviceName, bleStructData: openLockCommand, ); } /// 发送蓝牙透传消息 void sendBleMessage({ required String bluetoothDeviceName, required List bleStructData, }) async { // 组装上线消息 final message = MessageCommand.bleMessage( FromPeerId: FromPeerId, ToPeerId: lockPeerId, MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true), bluetoothDeviceName: bluetoothDeviceName, bleStructData: bleStructData, ); await _sendMessage(message: message); } /// 销毁资源 void destruction() async { // 先挂断 final status = talkStatus.status; if (status == TalkStatus.passiveCallWaitingAnswer || status == TalkStatus.proactivelyCallWaitingAnswer || status == TalkStatus.answeredSuccessfully || status == TalkStatus.uninitialized) { startTalkRejectMessageTimer(); startTalkHangupMessageTimer(); await Future.delayed(Duration(seconds: 1)); } isOnlineStarChartServer = false; // 停止发送心跳消息 stopHeartbeat(); // 取消发送期望数据 stopTalkExpectMessageTimer(); // 取消发送拒绝定时器 stopTalkRejectMessageTimer(); // 取消发送挂断定时器 stopTalkHangupMessageTimer(); // 取消发送通话保持消息 stopTalkPingMessageTimer(); // 取消发送上线消息 stopReStartOnlineStartChartServer(); // 取消发送通话数据 stopTalkDataTimer(); // 取消p2p打洞 stopSendingRbcuInfoMessages(); stopSendingRbcuProBeMessages(); stopSendingRbcuConfirmMessages(); // 重置数据 _resetData(); // 删除中继缓存信息 await Storage.removerRelayInfo(); // 删除注册节点信息 await Storage.removerStarChartRegisterNodeInfo(); // 关闭udp服务 closeUdpSocket(); } /// 重置数据 void _resetData() { reSetDefaultTalkExpect(); isOnlineStarChartServer = false; talkStatus.setUninitialized(); } }