app-starlock/lib/talk/startChart/start_chart_manage.dart

961 lines
31 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: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<String, Completer<void>> _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<void> init() async {
if (isOnlineStartChartServer && _udpSocket != null) {
// 如果已经上线就不进行初始化
return;
}
// 节点注册
await _clientRegister();
// 中继查询
await _relayQuery();
// 初始化udp服务
await _onlineRelayService();
// 上报
await reportInformation();
}
/// 客户端注册
Future<void> _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<void> 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<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 = _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<void> _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<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();
}
}
// 发送上线消息
Future<void> _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<void> 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<void> 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<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);
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<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);
}
// 发送通话中挂断消息
Future<void> sendTalkHangupMessage() async {
final message = MessageCommand.talkHangupMessage(
ToPeerId: ToPeerId,
FromPeerId: FromPeerId,
MessageId: MessageCommand.getNextMessageId(ToPeerId, increment: true),
);
await _sendMessage(message: message);
}
// 重新上线
Future<void> 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<void> _sendMessage({required List<int> 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<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);
_log(text: '注册成功');
}
}
// 保存星图中继服务器信息至缓存
Future<void> _saveRelayInfoEntityToStorage(
RelayInfoEntity relayInfoEntity) async {
if (relayInfoEntity != null) {
await Storage.saveRelayInfo(relayInfoEntity);
}
}
// 构造上报信息数据参数
Future<ReportInformationData> _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<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 地址和端口号
Map<String, dynamic> _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<int> bytes) {
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join('');
}
// 生成签名sing
Future<String> _generateSign({
required int currentTimestamp,
required String privateKeyHex,
}) async {
String resultSign = '';
try {
// 1. 将 32 位时间戳以小端字节序编码为二进制数据
Uint8List signData = encodeTimestampToLittleEndianBytes(currentTimestamp);
// 2.将十六进制字符串转换为字节数组
List<int> 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<int> 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<String> 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<int> hexToBytes(String hex) {
return List<int>.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<String> getPublicKey() async {
// 从缓存中获取星图注册节点信息
final StarChartRegisterNodeEntity? starChartRegisterNodeInfo =
await Storage.getStarChartRegisterNodeInfo();
return starChartRegisterNodeInfo?.peer?.publicKey ?? '';
}
/// 获取私钥
Future<String> 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<int> byteData = data.buffer.asUint8List();
int current = 0;
int start = 0;
int end = 0;
final List<int> 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<int> 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<int> extractChunks(List<int> byteData) {
int i = 0;
int length = byteData.length;
int naluCount = 0;
int value;
int state = 0;
int lastIndex = 0;
List<int> 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();
}
}