fix:增加p2p消息协议代码、优化对讲时应用卡死、调整StreamController处理数据时的逻辑、调整在寻找图片帧时的代码逻辑
This commit is contained in:
parent
641ff4a2b6
commit
7a0e8f9e28
@ -2745,7 +2745,9 @@ class ApiProvider extends BaseProvider {
|
|||||||
jsonEncode({
|
jsonEncode({
|
||||||
'deviceType': deviceType,
|
'deviceType': deviceType,
|
||||||
'deviceMac': deviceMac,
|
'deviceMac': deviceMac,
|
||||||
}));
|
}),
|
||||||
|
isShowNetworkErrorMsg: false,
|
||||||
|
isUnShowLoading: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ExtensionString on String {
|
extension ExtensionString on String {
|
||||||
|
|||||||
@ -2739,4 +2739,7 @@ class ApiRepository {
|
|||||||
);
|
);
|
||||||
return DeviceNetwork.fromJson(res.body);
|
return DeviceNetwork.fromJson(res.body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,12 +42,10 @@ class UdpBlePassThroughHandler extends ScpMessageBaseHandle
|
|||||||
@override
|
@override
|
||||||
void handleResp(ScpMessage scpMessage) async {
|
void handleResp(ScpMessage scpMessage) async {
|
||||||
final BleResp bleResp = scpMessage.Payload;
|
final BleResp bleResp = scpMessage.Payload;
|
||||||
AppLog.log('收到蓝牙消息回复:${bleResp.structData}');
|
|
||||||
// 将 List<int> 转换为十六进制字符串
|
|
||||||
String hexString = bleResp.structData
|
String hexString = bleResp.structData
|
||||||
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
|
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
|
||||||
.join(' ');
|
.join(' ');
|
||||||
AppLog.log('蓝牙透传回复:$hexString');
|
AppLog.log('收到蓝牙透传回复:$hexString');
|
||||||
|
|
||||||
await CommandReciverManager.appDataReceive(bleResp.structData);
|
await CommandReciverManager.appDataReceive(bleResp.structData);
|
||||||
|
|
||||||
@ -57,8 +55,6 @@ class UdpBlePassThroughHandler extends ScpMessageBaseHandle
|
|||||||
AppLog.log('收到开门请求命令回复');
|
AppLog.log('收到开门请求命令回复');
|
||||||
_replyOpenLock(reply);
|
_replyOpenLock(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +142,7 @@ class UdpBlePassThroughHandler extends ScpMessageBaseHandle
|
|||||||
lockDetailLogic.lockReportLockSuccessfullyUploadData();
|
lockDetailLogic.lockReportLockSuccessfullyUploadData();
|
||||||
|
|
||||||
lockDetailLogic.resetOpenDoorState();
|
lockDetailLogic.resetOpenDoorState();
|
||||||
lockDetailState.animationController!.stop();
|
lockDetailState.animationController?.stop();
|
||||||
|
|
||||||
//锁数据更新
|
//锁数据更新
|
||||||
lockDetailLogic.getLockRecordLastUploadDataTime();
|
lockDetailLogic.getLockRecordLastUploadDataTime();
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
|
|||||||
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
||||||
import 'package:star_lock/talk/startChart/handle/scp_message_base_handle.dart';
|
import 'package:star_lock/talk/startChart/handle/scp_message_base_handle.dart';
|
||||||
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
|
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
|
||||||
import 'package:star_lock/talk/startChart/p2p/p2p_manage.dart';
|
|
||||||
import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_request.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/talk_request.pb.dart';
|
||||||
@ -68,7 +68,7 @@ class UdpRbcuInfoHandler extends ScpMessageBaseHandle
|
|||||||
|
|
||||||
/// 处理回复的rbcuInfo消息
|
/// 处理回复的rbcuInfo消息
|
||||||
void _handleResultRbcuInfo(RbcuInfo rbcuInfo) {
|
void _handleResultRbcuInfo(RbcuInfo rbcuInfo) {
|
||||||
P2pManage().communicationObjectRbcuInfo = rbcuInfo;
|
startChartManage.rbcuInfo = rbcuInfo;
|
||||||
startChartManage.stopSendingRbcuInfoMessages();
|
startChartManage.stopSendingRbcuInfoMessages();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
57
lib/talk/startChart/handle/impl/udp_rbcuProBe_handler.dart
Normal file
57
lib/talk/startChart/handle/impl/udp_rbcuProBe_handler.dart
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import 'package:star_lock/login/selectCountryRegion/common/index.dart';
|
||||||
|
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
|
||||||
|
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
||||||
|
import 'package:star_lock/talk/startChart/handle/scp_message_base_handle.dart';
|
||||||
|
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
|
||||||
|
|
||||||
|
import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
|
||||||
|
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
||||||
|
import 'package:star_lock/talk/startChart/proto/talk_request.pb.dart';
|
||||||
|
|
||||||
|
class UdpRbcuProBeHandler extends ScpMessageBaseHandle
|
||||||
|
implements ScpMessageHandler {
|
||||||
|
@override
|
||||||
|
deserializePayload(
|
||||||
|
{required int payloadType,
|
||||||
|
required int messageType,
|
||||||
|
required List<int> byte,
|
||||||
|
int? offset,
|
||||||
|
int? PayloadLength,
|
||||||
|
int? spTotal,
|
||||||
|
int? spIndex,
|
||||||
|
int? messageId}) {
|
||||||
|
if (messageType == MessageTypeConstant.Resp) {
|
||||||
|
final GenericResp genericResp = GenericResp();
|
||||||
|
genericResp.mergeFromBuffer(byte);
|
||||||
|
return genericResp;
|
||||||
|
} else if (messageType == MessageTypeConstant.Req) {
|
||||||
|
final RbcuProbe rbcuProbe = RbcuProbe();
|
||||||
|
rbcuProbe.mergeFromBuffer(byte);
|
||||||
|
return rbcuProbe;
|
||||||
|
} else {
|
||||||
|
String payload = utf8.decode(byte);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleInvalidReq(ScpMessage scpMessage) {
|
||||||
|
// TODO: implement handleInvalidReq
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleRealTimeData(ScpMessage scpMessage) {
|
||||||
|
// TODO: implement handleRealTimeData
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleReq(ScpMessage scpMessage) {
|
||||||
|
// 收到rbcuprobe
|
||||||
|
final RbcuProbe rbcuProbe = scpMessage.Payload;
|
||||||
|
// 发送确认包
|
||||||
|
startChartManage.rbcuProbe = rbcuProbe;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleResp(ScpMessage scpMessage) {}
|
||||||
|
}
|
||||||
@ -128,10 +128,10 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
|||||||
/// 处理g711音频数据
|
/// 处理g711音频数据
|
||||||
void _handleVideoG711(TalkData talkData) {
|
void _handleVideoG711(TalkData talkData) {
|
||||||
try {
|
try {
|
||||||
final g711Data = talkData.content;
|
// final g711Data = talkData.content;
|
||||||
// 转pcm数据
|
// // 转pcm数据
|
||||||
List<int> pcmBytes = G711().convertList(g711Data);
|
// List<int> pcmBytes = G711().convertList(g711Data);
|
||||||
talkData.content = pcmBytes;
|
// talkData.content = pcmBytes;
|
||||||
talkDataRepository.addTalkData(talkData);
|
talkDataRepository.addTalkData(talkData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error decoding G.711 to PCM: $e');
|
print('Error decoding G.711 to PCM: $e');
|
||||||
@ -142,34 +142,69 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
|||||||
// 存储找到的所有完整帧
|
// 存储找到的所有完整帧
|
||||||
List<Uint8List> frames = [];
|
List<Uint8List> frames = [];
|
||||||
|
|
||||||
// 寻找完整帧 (0xFFD8 开始, 0xFFD9 结束)
|
// 手动遍历 payload
|
||||||
int startIdx = 0;
|
int i = 0;
|
||||||
while (startIdx < payload.length - 1) {
|
while (i < payload.length - 1) {
|
||||||
// 找到帧的起始标志 0xFFD8
|
// 寻找起始标志 0xFFD8
|
||||||
startIdx = payload.indexOf(0xFF, startIdx);
|
if (payload[i] == 0xFF && payload[i + 1] == 0xD8) {
|
||||||
if (startIdx == -1 || startIdx + 1 >= payload.length) break;
|
int startIdx = i;
|
||||||
if (payload[startIdx + 1] == 0xD8) {
|
i += 2; // 跳过起始标志
|
||||||
// 找到帧的起始标志 0xFFD8
|
|
||||||
int endIdx = startIdx + 2;
|
// 寻找结束标志 0xFFD9
|
||||||
while (endIdx < payload.length - 1) {
|
while (i < payload.length - 1) {
|
||||||
endIdx = payload.indexOf(0xFF, endIdx);
|
if (payload[i] == 0xFF && payload[i + 1] == 0xD9) {
|
||||||
if (endIdx == -1 || endIdx + 1 >= payload.length) break;
|
// 找到结束标志 0xFFD9
|
||||||
if (payload[endIdx + 1] == 0xD9) {
|
int endIdx = i + 2;
|
||||||
// 找到帧的结束标志 0xFFD9
|
// 使用 Uint8List.view 创建视图,避免内存分配
|
||||||
Uint8List frame = payload.sublist(startIdx, endIdx + 2);
|
frames.add(
|
||||||
frames.add(frame);
|
Uint8List.view(payload.buffer, startIdx, endIdx - startIdx));
|
||||||
startIdx = endIdx + 2; // 继续寻找下一个帧
|
i = endIdx; // 继续寻找下一个帧
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
endIdx += 1; // 继续寻找结束标志
|
i += 1; // 继续寻找结束标志
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
startIdx += 1; // 继续寻找下一个起始标志
|
i += 1; // 继续寻找起始标志
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回找到的所有完整帧
|
// 返回找到的所有完整帧
|
||||||
return frames;
|
return frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Future<List<Uint8List>> _processCompletePayload(Uint8List payload) async {
|
||||||
|
// // 存储找到的所有完整帧
|
||||||
|
// List<Uint8List> frames = [];
|
||||||
|
//
|
||||||
|
// // 寻找完整帧 (0xFFD8 开始, 0xFFD9 结束)
|
||||||
|
// int startIdx = 0;
|
||||||
|
// while (startIdx < payload.length - 1) {
|
||||||
|
// // 找到帧的起始标志 0xFFD8
|
||||||
|
// startIdx = payload.indexOf(0xFF, startIdx);
|
||||||
|
// if (startIdx == -1 || startIdx + 1 >= payload.length) break;
|
||||||
|
// if (payload[startIdx + 1] == 0xD8) {
|
||||||
|
// // 找到帧的起始标志 0xFFD8
|
||||||
|
// int endIdx = startIdx + 2;
|
||||||
|
// while (endIdx < payload.length - 1) {
|
||||||
|
// endIdx = payload.indexOf(0xFF, endIdx);
|
||||||
|
// if (endIdx == -1 || endIdx + 1 >= payload.length) break;
|
||||||
|
// if (payload[endIdx + 1] == 0xD9) {
|
||||||
|
// // 找到帧的结束标志 0xFFD9
|
||||||
|
// Uint8List frame = payload.sublist(startIdx, endIdx + 2);
|
||||||
|
// frames.add(frame);
|
||||||
|
// startIdx = endIdx + 2; // 继续寻找下一个帧
|
||||||
|
// break;
|
||||||
|
// } else {
|
||||||
|
// endIdx += 1; // 继续寻找结束标志
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// startIdx += 1; // 继续寻找下一个起始标志
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // 返回找到的所有完整帧
|
||||||
|
// return frames;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||||
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
|
import 'package:star_lock/talk/startChart/constant/message_type_constant.dart';
|
||||||
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
||||||
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
|
||||||
@ -31,6 +32,8 @@ class UdpTalkHangUpHandler extends ScpMessageBaseHandle
|
|||||||
talkeRequestOverTimeTimerManager.cancel();
|
talkeRequestOverTimeTimerManager.cancel();
|
||||||
talkePingOverTimeTimerManager.cancel();
|
talkePingOverTimeTimerManager.cancel();
|
||||||
talkDataOverTimeTimerManager.cancel();
|
talkDataOverTimeTimerManager.cancel();
|
||||||
|
|
||||||
|
EasyLoading.showToast('已挂断');
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@ -4,7 +4,17 @@ import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
|
|||||||
|
|
||||||
class TalkDataRepository {
|
class TalkDataRepository {
|
||||||
// 创建一个私有的构造函数,防止外部创建实例
|
// 创建一个私有的构造函数,防止外部创建实例
|
||||||
TalkDataRepository._();
|
TalkDataRepository._() {
|
||||||
|
_talkDataStreamController = StreamController<TalkData>.broadcast(
|
||||||
|
onListen: () {
|
||||||
|
_isListening = true;
|
||||||
|
},
|
||||||
|
onCancel: () {
|
||||||
|
_isListening = false;
|
||||||
|
},
|
||||||
|
sync: false, // 异步模式
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 使用 _instance 来保存单例对象
|
// 使用 _instance 来保存单例对象
|
||||||
static final TalkDataRepository _instance = TalkDataRepository._();
|
static final TalkDataRepository _instance = TalkDataRepository._();
|
||||||
@ -13,18 +23,37 @@ class TalkDataRepository {
|
|||||||
static TalkDataRepository get instance => _instance;
|
static TalkDataRepository get instance => _instance;
|
||||||
|
|
||||||
// 创建一个 StreamController
|
// 创建一个 StreamController
|
||||||
final StreamController<TalkData> _talkDataStreamController = StreamController<TalkData>.broadcast();
|
late final StreamController<TalkData> _talkDataStreamController;
|
||||||
|
|
||||||
|
bool _isListening = false;
|
||||||
|
|
||||||
// 提供一个方法来获取 Stream
|
// 提供一个方法来获取 Stream
|
||||||
Stream<TalkData> get talkDataStream => _talkDataStreamController.stream;
|
Stream<TalkData> get talkDataStream =>
|
||||||
|
_talkDataStreamController.stream.transform(
|
||||||
|
StreamTransformer<TalkData, TalkData>.fromHandlers(
|
||||||
|
handleData: (TalkData data, EventSink<TalkData> sink) {
|
||||||
|
// 限制缓冲区大小为 100
|
||||||
|
if (_buffer.length >= 100) {
|
||||||
|
_buffer.removeAt(0); // 丢弃最旧的数据
|
||||||
|
}
|
||||||
|
_buffer.add(data);
|
||||||
|
sink.add(data);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final List<TalkData> _buffer = []; // 用于存储数据的缓冲区
|
||||||
|
|
||||||
// 提供一个方法来添加 TalkData 到 Stream
|
// 提供一个方法来添加 TalkData 到 Stream
|
||||||
void addTalkData(TalkData talkData) async {
|
void addTalkData(TalkData talkData) async {
|
||||||
_talkDataStreamController.add(talkData);
|
if (_isListening) {
|
||||||
|
Future.microtask(() {
|
||||||
|
_talkDataStreamController.add(talkData);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提供一个方法来关闭 StreamController
|
// 提供一个方法来关闭 StreamController
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_talkDataStreamController.close();
|
_talkDataStreamController.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,6 +57,8 @@ class ScpMessageHandlerFactory {
|
|||||||
return UdpTalkHangUpHandler();
|
return UdpTalkHangUpHandler();
|
||||||
case PayloadTypeConstant.RbcuInfo:
|
case PayloadTypeConstant.RbcuInfo:
|
||||||
return UdpRbcuInfoHandler();
|
return UdpRbcuInfoHandler();
|
||||||
|
case PayloadTypeConstant.RbcuProbe:
|
||||||
|
return UdpRbcuInfoHandler();
|
||||||
default:
|
default:
|
||||||
return UnKnowPayloadTypeHandler();
|
return UnKnowPayloadTypeHandler();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,108 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
|
||||||
|
|
||||||
class P2pManage {
|
|
||||||
// 常量定义
|
|
||||||
static const String testMessage = 'Hello'; // 测试消息
|
|
||||||
static const int localPort = 0; // 绑定本地端口(0 表示由系统分配)
|
|
||||||
static const Duration connectionTimeout = Duration(seconds: 5); // 连接超时时间
|
|
||||||
static const List<String> localNetworkPrefixes = [
|
|
||||||
'192.168.',
|
|
||||||
'10.',
|
|
||||||
'172.'
|
|
||||||
]; // 局域网 IP 前缀
|
|
||||||
|
|
||||||
RbcuInfo? communicationObjectRbcuInfo;
|
|
||||||
|
|
||||||
void init() {
|
|
||||||
// 初始化逻辑
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析 address 属性,提取对方的 IP 和端口
|
|
||||||
List<Map<String, String>> parseRemoteAddresses() {
|
|
||||||
final addresses = communicationObjectRbcuInfo?.address ?? [];
|
|
||||||
return addresses.map((addr) {
|
|
||||||
final parts = addr.split(':');
|
|
||||||
return {'ip': parts[0], 'port': parts[1]};
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否为局域网 IP
|
|
||||||
bool _isLocalNetworkIp(String ip) {
|
|
||||||
for (final prefix in localNetworkPrefixes) {
|
|
||||||
if (ip.startsWith(prefix)) {
|
|
||||||
// 处理 172.16.x.x 到 172.31.x.x
|
|
||||||
if (prefix == '172.') {
|
|
||||||
final secondOctet = int.tryParse(ip.split('.')[1]) ?? 0;
|
|
||||||
if (secondOctet >= 16 && secondOctet <= 31) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 UDP 连接
|
|
||||||
Future<void> createUdpConnection() async {
|
|
||||||
final addresses = parseRemoteAddresses();
|
|
||||||
|
|
||||||
// 优先使用局域网 IP
|
|
||||||
addresses.sort((a, b) {
|
|
||||||
final isALocal = _isLocalNetworkIp(a['ip']!);
|
|
||||||
final isBLocal = _isLocalNetworkIp(b['ip']!);
|
|
||||||
if (isALocal && !isBLocal) return -1; // a 是局域网 IP,优先级更高
|
|
||||||
if (!isALocal && isBLocal) return 1; // b 是局域网 IP,优先级更高
|
|
||||||
return 0; // 优先级相同
|
|
||||||
});
|
|
||||||
|
|
||||||
// 尝试连接
|
|
||||||
for (final addr in addresses) {
|
|
||||||
final ip = addr['ip']!;
|
|
||||||
final port = int.parse(addr['port']!);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final socket = await RawDatagramSocket.bind(
|
|
||||||
InternetAddress.anyIPv4, localPort); // 绑定本地端口
|
|
||||||
|
|
||||||
// 发送测试数据
|
|
||||||
final testData = testMessage.codeUnits;
|
|
||||||
socket.send(testData, InternetAddress(ip), port);
|
|
||||||
|
|
||||||
// 设置超时时间
|
|
||||||
final completer = Completer<void>();
|
|
||||||
|
|
||||||
// 监听响应
|
|
||||||
socket.listen((event) {
|
|
||||||
if (event == RawSocketEvent.read) {
|
|
||||||
final datagram = socket.receive();
|
|
||||||
if (datagram != null) {
|
|
||||||
final response = String.fromCharCodes(datagram.data);
|
|
||||||
print('收到来自 $ip:$port 的响应: $response');
|
|
||||||
completer.complete(); // 收到响应,完成 Future
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 等待响应或超时
|
|
||||||
try {
|
|
||||||
await completer.future.timeout(connectionTimeout);
|
|
||||||
print('成功连接到 $ip:$port');
|
|
||||||
return; // 连接成功,退出循环
|
|
||||||
} on TimeoutException {
|
|
||||||
print('连接到 $ip:$port 超时');
|
|
||||||
} finally {
|
|
||||||
socket.close(); // 关闭 socket
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print('连接到 $ip:$port 失败: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
print('无法连接到任何地址');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -29,6 +31,7 @@ import 'package:star_lock/talk/startChart/handle/other/talke_request_over_time_t
|
|||||||
import 'package:star_lock/talk/startChart/handle/scp_message_handle.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/handle/scp_message_handler_factory.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/rbcu.pb.dart';
|
||||||
|
import 'package:star_lock/talk/startChart/proto/rbcu.pbserver.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_data.pb.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/proto/talk_expect.pb.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_expect.pbserver.dart';
|
import 'package:star_lock/talk/startChart/proto/talk_expect.pbserver.dart';
|
||||||
@ -86,9 +89,14 @@ class StartChartManage {
|
|||||||
int _defaultIntervalTime = 1; // 默认定时发送间隔(s)
|
int _defaultIntervalTime = 1; // 默认定时发送间隔(s)
|
||||||
Timer? talkDataTimer; // 发送通话数据消息定时器
|
Timer? talkDataTimer; // 发送通话数据消息定时器
|
||||||
Timer? rbcuInfoTimer; // p2p地址交换定时器
|
Timer? rbcuInfoTimer; // p2p地址交换定时器
|
||||||
|
Timer? rbcuProbeTimer; // p2p打洞包
|
||||||
|
Timer? rbcuConfirmTimer; // p2p打洞确认包
|
||||||
|
String _rbcuSessionId = ''; // p2p SessionId
|
||||||
Timer? talkRequestTimer; // 对讲请求定时器
|
Timer? talkRequestTimer; // 对讲请求定时器
|
||||||
final int maxAttempts = 15; // 最大执行次数
|
final int maxAttempts = 15; // 最大执行次数
|
||||||
|
RbcuInfo? rbcuInfo;
|
||||||
|
RbcuProbe? rbcuProbe;
|
||||||
|
RbcuConfirm? rbcuConfirm;
|
||||||
final int _maxPayloadSize = 8 * 1024; // 分包大小
|
final int _maxPayloadSize = 8 * 1024; // 分包大小
|
||||||
|
|
||||||
// 默认通话的期望数据格式
|
// 默认通话的期望数据格式
|
||||||
@ -254,6 +262,7 @@ class StartChartManage {
|
|||||||
) // 去除 "udp://" 前缀
|
) // 去除 "udp://" 前缀
|
||||||
.cast<
|
.cast<
|
||||||
String>(); // 转换为 Iterable<String>// 将 Iterable<String?> 转换为 Iterable<String>
|
String>(); // 转换为 Iterable<String>// 将 Iterable<String?> 转换为 Iterable<String>
|
||||||
|
_rbcuSessionId = uuid;
|
||||||
final RbcuInfo rbcuInfo = RbcuInfo(
|
final RbcuInfo rbcuInfo = RbcuInfo(
|
||||||
sessionId: uuid,
|
sessionId: uuid,
|
||||||
name: uuid,
|
name: uuid,
|
||||||
@ -261,6 +270,7 @@ class StartChartManage {
|
|||||||
time: int64Timestamp,
|
time: int64Timestamp,
|
||||||
isResp: isResp,
|
isResp: isResp,
|
||||||
);
|
);
|
||||||
|
|
||||||
final message = MessageCommand.genericRbcuInfoMessage(
|
final message = MessageCommand.genericRbcuInfoMessage(
|
||||||
ToPeerId: ToPeerId,
|
ToPeerId: ToPeerId,
|
||||||
FromPeerId: FromPeerId,
|
FromPeerId: FromPeerId,
|
||||||
@ -270,6 +280,44 @@ class StartChartManage {
|
|||||||
_sendMessage(message: message);
|
_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))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final info = rbcuInfo = RbcuInfo(address: ['119.137.55.161:62289']);
|
||||||
|
if (info != null && info.address != null && info.address.length > 0) {
|
||||||
|
info.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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送RbcuConfirm 打洞确认
|
||||||
|
void _sendRbcuConfirmMessage() async {
|
||||||
|
RbcuConfirm(
|
||||||
|
sessionId: _rbcuSessionId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 启动定时任务
|
// 启动定时任务
|
||||||
void startSendingRbcuInfoMessages({required String ToPeerId}) {
|
void startSendingRbcuInfoMessages({required String ToPeerId}) {
|
||||||
// 每隔 1 秒执行一次 _sendRbcuInfoMessage
|
// 每隔 1 秒执行一次 _sendRbcuInfoMessage
|
||||||
@ -280,12 +328,43 @@ class StartChartManage {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送打洞包
|
||||||
|
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() {
|
void stopSendingRbcuInfoMessages() {
|
||||||
rbcuInfoTimer?.cancel(); // 取消定时器
|
rbcuInfoTimer?.cancel(); // 取消定时器
|
||||||
rbcuInfoTimer = null;
|
rbcuInfoTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 停止定时任务
|
||||||
|
void stopSendingRbcuProBeMessages() {
|
||||||
|
rbcuProbeTimer?.cancel(); // 取消定时器
|
||||||
|
rbcuProbeTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止定时任务
|
||||||
|
void stopSendingRbcuConfirmMessages() {
|
||||||
|
rbcuConfirmTimer?.cancel(); // 取消定时器
|
||||||
|
rbcuConfirmTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
// 回复RbcuInfo
|
// 回复RbcuInfo
|
||||||
void replyRbcuInfoMessage({required String ToPeerId}) {
|
void replyRbcuInfoMessage({required String ToPeerId}) {
|
||||||
_sendRbcuInfoMessage(ToPeerId: ToPeerId, isResp: true);
|
_sendRbcuInfoMessage(ToPeerId: ToPeerId, isResp: true);
|
||||||
@ -345,7 +424,9 @@ class StartChartManage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 发送对讲数据
|
// 发送对讲数据
|
||||||
|
// 现在的场景只有给锁板发送音频数据
|
||||||
Future<void> sendTalkDataMessage({required TalkData talkData}) async {
|
Future<void> sendTalkDataMessage({required TalkData talkData}) async {
|
||||||
|
String toPeerId = lockPeerId;
|
||||||
final List<int> payload = talkData.content;
|
final List<int> payload = talkData.content;
|
||||||
// 计算需要分多少个包发送
|
// 计算需要分多少个包发送
|
||||||
final int totalPackets = (payload.length / _maxPayloadSize).ceil();
|
final int totalPackets = (payload.length / _maxPayloadSize).ceil();
|
||||||
@ -361,10 +442,10 @@ class StartChartManage {
|
|||||||
|
|
||||||
// 分包数据不递增messageID
|
// 分包数据不递增messageID
|
||||||
final messageId =
|
final messageId =
|
||||||
MessageCommand.getNextMessageId(ToPeerId, increment: false);
|
MessageCommand.getNextMessageId(toPeerId, increment: false);
|
||||||
// 组装分包数据
|
// 组装分包数据
|
||||||
final message = MessageCommand.talkDataMessage(
|
final message = MessageCommand.talkDataMessage(
|
||||||
ToPeerId: 'D78Fo4CjNzUXz8DxuUhLtcRpnGFXhSzhzs191XzhJttS',
|
ToPeerId: toPeerId,
|
||||||
FromPeerId: FromPeerId,
|
FromPeerId: FromPeerId,
|
||||||
payload: packet,
|
payload: packet,
|
||||||
SpTotal: totalPackets,
|
SpTotal: totalPackets,
|
||||||
@ -375,7 +456,7 @@ class StartChartManage {
|
|||||||
await _sendMessage(message: message);
|
await _sendMessage(message: message);
|
||||||
}
|
}
|
||||||
// 分包发送完了递增一下id
|
// 分包发送完了递增一下id
|
||||||
MessageCommand.getNextMessageId(ToPeerId);
|
MessageCommand.getNextMessageId(toPeerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送心跳包消息
|
// 发送心跳包消息
|
||||||
@ -603,6 +684,20 @@ class StartChartManage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发送消息
|
||||||
|
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 {
|
Future<StarChartRegisterNodeEntity> _requestStarChartRegisterNode() async {
|
||||||
// 获取设备信息
|
// 获取设备信息
|
||||||
@ -960,6 +1055,8 @@ class StartChartManage {
|
|||||||
stopReStartOnlineStartChartServer();
|
stopReStartOnlineStartChartServer();
|
||||||
stopTalkDataTimer();
|
stopTalkDataTimer();
|
||||||
stopSendingRbcuInfoMessages();
|
stopSendingRbcuInfoMessages();
|
||||||
|
stopSendingRbcuProBeMessages();
|
||||||
|
stopSendingRbcuConfirmMessages();
|
||||||
_resetData();
|
_resetData();
|
||||||
await Storage.removerRelayInfo();
|
await Storage.removerRelayInfo();
|
||||||
await Storage.removerStarChartRegisterNodeInfo();
|
await Storage.removerStarChartRegisterNodeInfo();
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
@ -23,6 +24,7 @@ import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart';
|
|||||||
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart';
|
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart';
|
||||||
import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart';
|
import 'package:star_lock/main/lockDetail/lockDetail/lockNetToken_entity.dart';
|
||||||
import 'package:star_lock/network/api_repository.dart';
|
import 'package:star_lock/network/api_repository.dart';
|
||||||
|
import 'package:star_lock/talk/call/g711.dart';
|
||||||
|
|
||||||
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
||||||
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
|
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
|
||||||
@ -43,18 +45,18 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
||||||
Timer? _syncTimer; // 音视频播放刷新率定时器
|
Timer? _syncTimer; // 音视频播放刷新率定时器
|
||||||
int _startTime = 0; // 开始播放时间戳,用于判断帧数据中的时间戳位置
|
int _startTime = 0; // 开始播放时间戳,用于判断帧数据中的时间戳位置
|
||||||
final int bufferSize = 20; // 缓冲区大小(以帧为单位)
|
int bufferSize = 20; // 缓冲区大小(以帧为单位)
|
||||||
final List<int> frameTimestamps = []; // 帧时间戳用于计算 FPS
|
final List<int> frameTimestamps = []; // 帧时间戳用于计算 FPS
|
||||||
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||||
int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS)
|
int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS)
|
||||||
int maxFrameIntervalMs = 500; // 最大帧间隔(约1 FPS)
|
int maxFrameIntervalMs = 100; // 最大帧间隔(约1 FPS)
|
||||||
// int maxFrameIntervalMs = 100; // 最大帧间隔(约10 FPS)
|
// int maxFrameIntervalMs = 100; // 最大帧间隔(约10 FPS)
|
||||||
|
|
||||||
/// 初始化音频播放器
|
/// 初始化音频播放器
|
||||||
void _initFlutterPcmSound() {
|
void _initFlutterPcmSound() {
|
||||||
const int sampleRate = 44100;
|
const int sampleRate = 8000;
|
||||||
FlutterPcmSound.setLogLevel(LogLevel.verbose);
|
FlutterPcmSound.setLogLevel(LogLevel.none);
|
||||||
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 2);
|
FlutterPcmSound.setup(sampleRate: sampleRate, channelCount: 1);
|
||||||
// 设置 feed 阈值
|
// 设置 feed 阈值
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
FlutterPcmSound.setFeedThreshold(-1); // Android 平台的特殊处理
|
FlutterPcmSound.setFeedThreshold(-1); // Android 平台的特殊处理
|
||||||
@ -82,24 +84,22 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
// 监听音视频数据流
|
// 监听音视频数据流
|
||||||
void _startListenTalkData() {
|
void _startListenTalkData() {
|
||||||
state.talkDataRepository.talkDataStream.listen((TalkData talkData) {
|
state.talkDataRepository.talkDataStream.listen((TalkData talkData) async {
|
||||||
final contentType = talkData.contentType;
|
final contentType = talkData.contentType;
|
||||||
|
int currentTimestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
// 判断数据类型,进行分发处理
|
// 判断数据类型,进行分发处理
|
||||||
switch (contentType) {
|
switch (contentType) {
|
||||||
case TalkData_ContentTypeE.G711:
|
case TalkData_ContentTypeE.G711:
|
||||||
if (state.audioBuffer.length < bufferSize) {
|
if (state.audioBuffer.length >= bufferSize) {
|
||||||
state.audioBuffer.add(talkData);
|
state.audioBuffer.removeAt(0); // 丢弃最旧的数据
|
||||||
}
|
}
|
||||||
// print('收到音频数据');
|
state.audioBuffer.add(talkData); // 添加新数据
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TalkData_ContentTypeE.Image:
|
case TalkData_ContentTypeE.Image:
|
||||||
if (state.videoBuffer.length < bufferSize) {
|
if (state.videoBuffer.length >= bufferSize) {
|
||||||
state.videoBuffer.add(talkData);
|
state.videoBuffer.removeAt(0); // 丢弃最旧的数据
|
||||||
}
|
}
|
||||||
// print('talkData durationMs-->:${talkData.durationMs}');
|
state.videoBuffer.add(talkData); // 添加新数据
|
||||||
|
|
||||||
/// 更新网络状态
|
/// 更新网络状态
|
||||||
// updateNetworkStatus(currentTimestamp);
|
// updateNetworkStatus(currentTimestamp);
|
||||||
break;
|
break;
|
||||||
@ -127,9 +127,11 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 播放音频数据
|
/// 播放音频数据
|
||||||
void _playAudioData(TalkData talkData) {
|
void _playAudioData(TalkData talkData) async {
|
||||||
|
final list = G711().decodeAndDenoise(talkData.content, true, 8000, 100, 50);
|
||||||
|
|
||||||
// 将 PCM 数据转换为 PcmArrayInt16
|
// 将 PCM 数据转换为 PcmArrayInt16
|
||||||
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(talkData.content);
|
final PcmArrayInt16 fromList = PcmArrayInt16.fromList(list);
|
||||||
FlutterPcmSound.feed(fromList);
|
FlutterPcmSound.feed(fromList);
|
||||||
if (!state.isPlaying.value) {
|
if (!state.isPlaying.value) {
|
||||||
FlutterPcmSound.play();
|
FlutterPcmSound.play();
|
||||||
@ -138,7 +140,7 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 播放视频数据
|
/// 播放视频数据
|
||||||
void _playVideoData(TalkData talkData) {
|
void _playVideoData(TalkData talkData) async {
|
||||||
state.listData.value = Uint8List.fromList(talkData.content);
|
state.listData.value = Uint8List.fromList(talkData.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,8 +155,6 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
final elapsedTime = currentTime - _startTime;
|
final elapsedTime = currentTime - _startTime;
|
||||||
|
|
||||||
// 根据 elapsedTime 同步音频和视频
|
|
||||||
// AppLog.log('Elapsed Time: $elapsedTime ms');
|
|
||||||
// 动态调整帧间隔
|
// 动态调整帧间隔
|
||||||
_adjustFrameInterval();
|
_adjustFrameInterval();
|
||||||
|
|
||||||
@ -163,6 +163,7 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
state.audioBuffer.first.durationMs <= elapsedTime) {
|
state.audioBuffer.first.durationMs <= elapsedTime) {
|
||||||
// 判断音频开关是否打开
|
// 判断音频开关是否打开
|
||||||
if (state.isOpenVoice.value) {
|
if (state.isOpenVoice.value) {
|
||||||
|
AppLog.log('播放音频:${state.audioBuffer[0]}');
|
||||||
_playAudioData(state.audioBuffer.removeAt(0));
|
_playAudioData(state.audioBuffer.removeAt(0));
|
||||||
} else {
|
} else {
|
||||||
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
||||||
@ -189,6 +190,13 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
/// 动态调整帧间隔
|
/// 动态调整帧间隔
|
||||||
void _adjustFrameInterval() {
|
void _adjustFrameInterval() {
|
||||||
|
int newFrameIntervalMs = frameIntervalMs;
|
||||||
|
if (state.networkStatus.value == NetworkStatus.lagging) {
|
||||||
|
bufferSize = 30; // 增大缓冲区
|
||||||
|
} else {
|
||||||
|
bufferSize = 20; // 恢复默认缓冲区大小
|
||||||
|
}
|
||||||
|
|
||||||
if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
|
if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
|
||||||
// 如果缓冲区较小且帧间隔小于最大值,则增加帧间隔
|
// 如果缓冲区较小且帧间隔小于最大值,则增加帧间隔
|
||||||
frameIntervalMs += 5;
|
frameIntervalMs += 5;
|
||||||
@ -197,38 +205,47 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// 如果缓冲区较大且帧间隔大于最小值,则减少帧间隔
|
// 如果缓冲区较大且帧间隔大于最小值,则减少帧间隔
|
||||||
frameIntervalMs -= 5;
|
frameIntervalMs -= 5;
|
||||||
}
|
}
|
||||||
_syncTimer?.cancel();
|
// 只有在帧间隔发生变化时才重建定时器
|
||||||
_syncTimer =
|
if (newFrameIntervalMs != frameIntervalMs) {
|
||||||
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
frameIntervalMs = newFrameIntervalMs;
|
||||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
// 取消旧的定时器
|
||||||
final elapsedTime = currentTime - _startTime;
|
_syncTimer?.cancel();
|
||||||
|
_syncTimer =
|
||||||
|
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
|
||||||
|
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final elapsedTime = currentTime - _startTime;
|
||||||
|
|
||||||
// 播放合适的音频帧
|
// 播放合适的音频帧
|
||||||
if (state.audioBuffer.isNotEmpty &&
|
if (state.audioBuffer.isNotEmpty &&
|
||||||
state.audioBuffer.first.durationMs <= elapsedTime) {
|
state.audioBuffer.first.durationMs <= elapsedTime) {
|
||||||
// 判断音频开关是否打开
|
// 判断音频开关是否打开
|
||||||
if (state.isOpenVoice.value) {
|
if (state.isOpenVoice.value) {
|
||||||
_playAudioData(state.audioBuffer.removeAt(0));
|
_playAudioData(state.audioBuffer.removeAt(0));
|
||||||
} else {
|
} else {
|
||||||
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
// 如果不播放音频,只从缓冲区中读取数据,但不移除
|
||||||
// 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长
|
// 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长
|
||||||
// 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的
|
// 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的
|
||||||
state.audioBuffer.removeAt(0);
|
state.audioBuffer.removeAt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 播放合适的视频帧
|
// 播放合适的视频帧
|
||||||
// 跳帧策略:如果缓冲区中有多个帧,且它们的时间戳都在当前时间之前,则播放最新的帧
|
// 跳帧策略:如果缓冲区中有多个帧,且它们的时间戳都在当前时间之前,则播放最新的帧
|
||||||
while (state.videoBuffer.isNotEmpty &&
|
int maxFramesToProcess = 5; // 每次最多处理 5 帧
|
||||||
state.videoBuffer.first.durationMs <= elapsedTime) {
|
int processedFrames = 0;
|
||||||
// 如果有多个帧,移除旧的帧,保持最新的帧
|
|
||||||
if (state.videoBuffer.length > 1) {
|
while (state.videoBuffer.isNotEmpty &&
|
||||||
state.videoBuffer.removeAt(0);
|
state.videoBuffer.first.durationMs <= elapsedTime &&
|
||||||
} else {
|
processedFrames < maxFramesToProcess) {
|
||||||
_playVideoData(state.videoBuffer.removeAt(0));
|
if (state.videoBuffer.length > 1) {
|
||||||
|
state.videoBuffer.removeAt(0);
|
||||||
|
} else {
|
||||||
|
_playVideoData(state.videoBuffer.removeAt(0));
|
||||||
|
}
|
||||||
|
processedFrames++;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 修改网络状态
|
/// 修改网络状态
|
||||||
@ -238,11 +255,11 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
if (frameInterval > 500 && frameInterval <= 1000) {
|
if (frameInterval > 500 && frameInterval <= 1000) {
|
||||||
// 判断帧间隔是否在500毫秒到1秒之间
|
// 判断帧间隔是否在500毫秒到1秒之间
|
||||||
state.networkStatus.value = NetworkStatus.lagging;
|
state.networkStatus.value = NetworkStatus.lagging;
|
||||||
showNetworkStatus("Network is lagging");
|
// showNetworkStatus("Network is lagging");
|
||||||
} else if (frameInterval > 1000) {
|
} else if (frameInterval > 1000) {
|
||||||
// 判断帧间隔是否超过1秒
|
// 判断帧间隔是否超过1秒
|
||||||
state.networkStatus.value = NetworkStatus.delayed;
|
state.networkStatus.value = NetworkStatus.delayed;
|
||||||
showNetworkStatus("Network is delayed");
|
// showNetworkStatus("Network is delayed");
|
||||||
} else {
|
} else {
|
||||||
state.networkStatus.value = NetworkStatus.normal;
|
state.networkStatus.value = NetworkStatus.normal;
|
||||||
state.alertCount.value = 0; // 重置计数器
|
state.alertCount.value = 0; // 重置计数器
|
||||||
@ -436,12 +453,13 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
_stopPlayG711Data();
|
_stopPlayG711Data(); // 停止播放音频
|
||||||
state.listData.value = Uint8List(0);
|
state.listData.value = Uint8List(0); // 清空视频数据
|
||||||
state.audioBuffer.clear();
|
state.audioBuffer.clear(); // 清空音频缓冲区
|
||||||
state.videoBuffer.clear();
|
state.videoBuffer.clear(); // 清空视频缓冲区
|
||||||
_syncTimer?.cancel();
|
_syncTimer?.cancel(); // 取消定时器
|
||||||
_syncTimer = null;
|
_syncTimer = null; // 释放定时器引用
|
||||||
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 处理无效通话状态
|
/// 处理无效通话状态
|
||||||
@ -563,7 +581,7 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
// _concatenateFrames(state.recordingAudioAllFrames); // 连接所有帧
|
// _concatenateFrames(state.recordingAudioAllFrames); // 连接所有帧
|
||||||
final List<int> pcmBytes = _listLinearToULaw(frame);
|
final List<int> pcmBytes = _listLinearToULaw(frame);
|
||||||
// 发送音频数据
|
// 发送音频数据
|
||||||
StartChartManage().sendTalkDataMessage(
|
await StartChartManage().sendTalkDataMessage(
|
||||||
talkData: TalkData(
|
talkData: TalkData(
|
||||||
content: pcmBytes,
|
content: pcmBytes,
|
||||||
contentType: TalkData_ContentTypeE.G711,
|
contentType: TalkData_ContentTypeE.G711,
|
||||||
@ -571,6 +589,7 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
state.startRecordingAudioTime.value.millisecondsSinceEpoch,
|
state.startRecordingAudioTime.value.millisecondsSinceEpoch,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
AppLog.log('发送音频数据');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onError(VoiceProcessorException error) {
|
void _onError(VoiceProcessorException error) {
|
||||||
|
|||||||
@ -168,7 +168,36 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
// ),
|
// ),
|
||||||
Obx(() => state.listData.value.isEmpty
|
Obx(() => state.listData.value.isEmpty
|
||||||
? buildRotationTransition()
|
? buildRotationTransition()
|
||||||
: Container())
|
: Container()),
|
||||||
|
|
||||||
|
Obx(() => state.isLongPressing.value
|
||||||
|
? Positioned(
|
||||||
|
top: 80.h,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(10.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.7),
|
||||||
|
borderRadius: BorderRadius.circular(10.w),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(Icons.mic, color: Colors.white, size: 24.w),
|
||||||
|
SizedBox(width: 10.w),
|
||||||
|
Text(
|
||||||
|
'正在说话...',
|
||||||
|
style:
|
||||||
|
TextStyle(fontSize: 20.sp, color: Colors.white),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Container()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -273,11 +302,13 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
if (state.talkStatus.value == TalkStatus.answeredSuccessfully) {
|
||||||
print('开始录音');
|
print('开始录音');
|
||||||
logic.startProcessingAudio();
|
logic.startProcessingAudio();
|
||||||
|
state.isLongPressing.value = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
longPressUp: () async {
|
longPressUp: () async {
|
||||||
print('停止录音');
|
print('停止录音');
|
||||||
logic.stopProcessingAudio();
|
logic.stopProcessingAudio();
|
||||||
|
state.isLongPressing.value = false;
|
||||||
},
|
},
|
||||||
onClick: () async {
|
onClick: () async {
|
||||||
if (state.talkStatus.value ==
|
if (state.talkStatus.value ==
|
||||||
@ -300,8 +331,8 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
AppColors.mainColor,
|
AppColors.mainColor,
|
||||||
onClick: () {
|
onClick: () {
|
||||||
// if (UDPManage().remoteUnlock == 1) {
|
// if (UDPManage().remoteUnlock == 1) {
|
||||||
logic.udpOpenDoorAction();
|
logic.udpOpenDoorAction();
|
||||||
// showDeletPasswordAlertDialog(context);
|
// showDeletPasswordAlertDialog(context);
|
||||||
// } else {
|
// } else {
|
||||||
// logic.showToast('请在锁设置中开启远程开锁'.tr);
|
// logic.showToast('请在锁设置中开启远程开锁'.tr);
|
||||||
// }
|
// }
|
||||||
@ -334,36 +365,43 @@ class _TalkViewPageState extends State<TalkViewPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget bottomBtnItemWidget(String iconUrl, String name, Color backgroundColor,
|
Widget bottomBtnItemWidget(
|
||||||
{required Function() onClick,
|
String iconUrl,
|
||||||
Function()? longPress,
|
String name,
|
||||||
Function()? longPressUp}) {
|
Color backgroundColor, {
|
||||||
|
required Function() onClick,
|
||||||
|
Function()? longPress,
|
||||||
|
Function()? longPressUp,
|
||||||
|
}) {
|
||||||
double wh = 80.w;
|
double wh = 80.w;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onClick,
|
onTap: onClick,
|
||||||
onLongPress: longPress,
|
onLongPress: longPress,
|
||||||
onLongPressUp: longPressUp,
|
onLongPressUp: longPressUp,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 140.h,
|
height: 140.h,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
width: wh,
|
width: wh,
|
||||||
height: wh,
|
height: wh,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: backgroundColor,
|
color: backgroundColor,
|
||||||
borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2)),
|
borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2),
|
||||||
padding: EdgeInsets.all(20.w),
|
|
||||||
child: Image.asset(iconUrl, fit: BoxFit.fitWidth),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 20.w),
|
padding: EdgeInsets.all(20.w),
|
||||||
Expanded(
|
child: Image.asset(iconUrl, fit: BoxFit.fitWidth),
|
||||||
child: Text(name,
|
),
|
||||||
style: TextStyle(fontSize: 20.sp, color: Colors.white),
|
SizedBox(height: 20.w),
|
||||||
textAlign: TextAlign.center))
|
Expanded(
|
||||||
],
|
child: Text(name,
|
||||||
)),
|
style: TextStyle(fontSize: 20.sp, color: Colors.white),
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import 'package:flutter_voice_processor/flutter_voice_processor.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:get/get_rx/get_rx.dart';
|
import 'package:get/get_rx/get_rx.dart';
|
||||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||||
|
import 'package:get/state_manager.dart';
|
||||||
import 'package:network_info_plus/network_info_plus.dart';
|
import 'package:network_info_plus/network_info_plus.dart';
|
||||||
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
import 'package:star_lock/talk/startChart/constant/talk_status.dart';
|
||||||
import 'package:star_lock/talk/startChart/handle/other/talk_data_repository.dart';
|
import 'package:star_lock/talk/startChart/handle/other/talk_data_repository.dart';
|
||||||
@ -54,7 +55,13 @@ class TalkViewState {
|
|||||||
|
|
||||||
// 星图对讲相关状态
|
// 星图对讲相关状态
|
||||||
List<TalkData> audioBuffer = <TalkData>[].obs;
|
List<TalkData> audioBuffer = <TalkData>[].obs;
|
||||||
|
List<TalkData> audioBuffer2 = <TalkData>[].obs;
|
||||||
|
List<TalkData> activeAudioBuffer =<TalkData>[].obs;
|
||||||
|
List<TalkData> activeVideoBuffer =<TalkData>[].obs;
|
||||||
|
|
||||||
|
|
||||||
List<TalkData> videoBuffer = <TalkData>[].obs;
|
List<TalkData> videoBuffer = <TalkData>[].obs;
|
||||||
|
List<TalkData> videoBuffer2 = <TalkData>[].obs;
|
||||||
RxBool isPlaying = false.obs; // 是否开始播放
|
RxBool isPlaying = false.obs; // 是否开始播放
|
||||||
Rx<TalkStatus> talkStatus = TalkStatus.none.obs; //星图对讲状态
|
Rx<TalkStatus> talkStatus = TalkStatus.none.obs; //星图对讲状态
|
||||||
// 获取 startChartTalkStatus 的唯一实例
|
// 获取 startChartTalkStatus 的唯一实例
|
||||||
@ -80,4 +87,5 @@ class TalkViewState {
|
|||||||
final int sampleRate = 8000; //录音频采样率为8000
|
final int sampleRate = 8000; //录音频采样率为8000
|
||||||
List<List<int>> recordingAudioAllFrames = <List<int>>[]; // 录制音频的所有帧
|
List<List<int>> recordingAudioAllFrames = <List<int>>[]; // 录制音频的所有帧
|
||||||
RxInt rotateAngle = 0.obs; // 旋转角度(以弧度为单位)
|
RxInt rotateAngle = 0.obs; // 旋转角度(以弧度为单位)
|
||||||
|
RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user