1292 lines
42 KiB
Dart
1292 lines
42 KiB
Dart
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();
|
||
}
|
||
}
|