app-starlock/lib/talk/starChart/star_chart_manage.dart
2025-06-23 15:28:43 +08:00

1292 lines
42 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:io';
import 'dart:isolate';
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';
// Socket选项常量
const int SO_RCVBUF = 8; // 接收缓冲区
const int SO_SNDBUF = 7; // 发送缓冲区
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<LockListInfoItemEntity> 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;
// 音视频帧级丢包统计变量
final Map<int, Set<int>> _avFrameParts = {};
int _avFrameTotal = 0;
int _avFrameLost = 0;
// 查询音视频帧丢包率
double getAvFrameLossRate() {
if (_avFrameTotal == 0) return 0.0;
return _avFrameLost / _avFrameTotal;
}
// 星图服务初始化
Future<void> 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<void> _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<void> 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<void> _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<void> _onlineRelayService() async {
var addressIListenFrom = InternetAddress.anyIPv4;
RawDatagramSocket.bind(addressIListenFrom, localPort)
.then((RawDatagramSocket socket) {
// 设置接收缓冲区大小 (SO_RCVBUF = 8)
socket.setRawOption(
RawSocketOption.fromInt(
RawSocketOption.levelSocket,
8, // SO_RCVBUF for Android/iOS
2 * 1024 * 1024, // 2MB receive buffer
),
);
// 设置发送缓冲区大小 (SO_SNDBUF = 7)
socket.setRawOption(
RawSocketOption.fromInt(
RawSocketOption.levelSocket,
7, // SO_SNDBUF for Android/iOS
2 * 1024 * 1024, // 2MB send buffer
),
);
_udpSocket = socket;
/// 广播功能
_udpSocket!.broadcastEnabled = true;
/// 设置数据接收回调
_onReceiveData(_udpSocket!, Get.context!);
}).catchError((error) {
_log(text: 'Failed to bind UDP socket: $error');
});
}
// 上报信息至发现服务
Future<void> 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<ListenAddrData> 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<String?>
.where((addr) => addr != null) // 过滤掉 null 值
.map(
(addr) => addr!.replaceAll(IpConstant.udpUrl, ''),
) // 去除 "udp://" 前缀
.cast<
String>(); // 转换为 Iterable<String>// 将 Iterable<String?> 转换为 Iterable<String>
_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<void> _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<void> sendCallRequestMessage({required String ToPeerId}) async {
// 组装上线消息
final message = MessageCommand.talkRequestMessage(
FromPeerId: FromPeerId,
ToPeerId: ToPeerId,
MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true),
);
await _sendMessage(message: message);
}
// 发送对讲数据
// 现在的场景只有给锁板发送音频数据
Future<void> sendTalkDataMessage({required TalkData talkData}) async {
String toPeerId = ToPeerId;
final List<int> 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<int> 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<int> message = MessageCommand.heartbeatMessage(
FromPeerId: FromPeerId,
ToPeerId: relayPeerId,
MessageId:
MessageCommand.getNextMessageId(relayPeerId, increment: true),
);
await _sendMessage(message: message);
},
);
_heartBeatTimerRunning = true;
}
// 发送回声测试消息
void sendEchoMessage(
{required List<int> 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<int> 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<void> 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<void> 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<void> _sendMessage({required List<int> 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<DebugInfoModel>(Get.context!, listen: false).updateDebugInfo(
// UdpTalkDataHandler().getLastRecvDataRate() ~/ 1024, // 转换为KB
// UdpTalkDataHandler().getLastRecvPacketCount(),
// UdpTalkDataHandler().getLastSendDataRate() ~/ 1024, // 转换为KB
// UdpTalkDataHandler().getLastSendPacketCount(),
// );
}
// 发送消息
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 {
// 获取设备信息
final Map<String, String> 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<void> _saveStarChartRegisterNodeToStorage(
StarChartRegisterNodeEntity starChartRegisterNodeEntity) async {
if (starChartRegisterNodeEntity != null) {
await Storage.saveStarChartRegisterNodeInfo(starChartRegisterNodeEntity);
final LoginData? loginData = await Storage.getLoginData();
loginData?.updateStarchart(starChartRegisterNodeEntity);
Storage.saveLoginData(loginData);
}
}
// 保存星图中继服务器信息至缓存
Future<void> _saveRelayInfoEntityToStorage(
RelayInfoEntity relayInfoEntity) async {
if (relayInfoEntity != null) {
await Storage.saveRelayInfo(relayInfoEntity);
}
}
// 构造上报信息数据参数
Future<ReportInformationData> _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<ListenAddrData> 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<void> analyzeInformationOtherEnd() async {
await StartChartApi.to.analyzeInformationOtherEnd(peerId: ToPeerId);
}
// 获取本机所有ip地址和中继返回的外网地址
Future<List<ListenAddrData>> _makeListenAddrDataList() async {
final List<ListenAddrData> listenAddrDataList = [];
final List<String> 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<List<String>> _getAllIpAddresses() async {
final List<String> ipAddresses = [];
try {
final List<NetworkInterface> 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<Map<String, String>> _getDeviceInfo() async {
final Map<String, String> deviceInfo =
await DeviceInfoUtils.getDeviceInfo();
return deviceInfo;
}
/// 解析 UDP URL 并提取 IP 地址和端口号
/// 解析 UDP URL 并提取 IP 地址和端口号,同时处理域名解析
Future<Map<String, dynamic>> _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<InternetAddress> 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<int> bytes) {
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
}
/// 获取公钥
Future<String> getPublicKey() async {
final loginData = await Storage.getLoginData();
return loginData?.starchart?.starchartPeerPublicKey ?? '';
}
/// 获取私钥
Future<String> 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;
while ((dg = socket.receive()) != null) {
try {
if (dg?.data != null) {
// Fallback: 主线程处理(极端情况)
// 更好的做法:批量处理
final deserialize = ScpMessage.deserialize(dg!.data);
// if (deserialize.PayloadType == PayloadTypeConstant.talkData) {
// _log(
// text: 'mesaageId:${deserialize.MessageId},'
// 'SpTotal:${deserialize.SpTotal},SpIndex:${deserialize.SpIndex}');
// }
if (deserialize != null) {
_handleUdpResultData(deserialize);
}
}
} 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 {
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<int> openLockCommand,
}) {
sendBleMessage(
bluetoothDeviceName: bluetoothDeviceName,
bleStructData: openLockCommand,
);
}
/// 发送蓝牙透传消息
void sendBleMessage({
required String bluetoothDeviceName,
required List<int> 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();
}
}