diff --git a/lib/login/login/starLock_login_page.dart b/lib/login/login/starLock_login_page.dart index 45ff45cb..59cc0750 100755 --- a/lib/login/login/starLock_login_page.dart +++ b/lib/login/login/starLock_login_page.dart @@ -236,9 +236,9 @@ class _StarLockLoginPageState extends State { } : null)), SubmitBtn( - btnName: '跳转至html', + btnName: '跳转至通话', onClick: () { - Get.toNamed(Routers.LocalHtmlPage); + Get.toNamed(Routers.lockMonitoringPage); }, ), SizedBox(height: 50.w), diff --git a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart index b640ed92..03588c94 100755 --- a/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart +++ b/lib/main/lockDetail/monitoring/monitoring/lockMonitoring_page.dart @@ -16,6 +16,7 @@ import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/main/lockDetail/monitoring/monitoring/star_chart_logic.dart'; import 'package:star_lock/talk/call/callTalk.dart'; import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; +import 'package:star_lock/talk/startChart/webView/h264_web_view.dart'; import 'package:star_lock/talk/udp/udp_manage.dart'; import 'package:star_lock/tools/eventBusEventManage.dart'; import 'package:star_lock/tools/showTFView.dart'; @@ -56,62 +57,7 @@ class _LockMonitoringPageState extends State { width: 1.sw, height: 1.sh, color: Colors.transparent, - child: Stack( - children: [ - Image.memory( - state.listPhotoData.value, - gaplessPlayback: true, - width: 1.sw, - height: 1.sh, - fit: BoxFit.cover, - filterQuality: FilterQuality.high, - errorBuilder: (BuildContext context, Object error, - StackTrace? stackTrace) { - return Container(color: Colors.transparent); - }, - ), - Positioned( - top: ScreenUtil().statusBarHeight + 30.h, - width: 1.sw, - child: Obx(() { - final String sec = (state.oneMinuteTime.value % 60) - .toString() - .padLeft(2, '0'); - final String min = (state.oneMinuteTime.value ~/ 60) - .toString() - .padLeft(2, '0'); - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('$min:$sec', - style: - TextStyle(fontSize: 26.sp, color: Colors.white)), - ], - ); - }), - ), - Positioned( - bottom: 10.w, - child: Container( - width: 1.sw - 30.w * 2, - margin: EdgeInsets.all(30.w), - decoration: BoxDecoration( - color: const Color(0xC83C3F41), - borderRadius: BorderRadius.circular(20.h), - ), - child: Column( - children: [ - SizedBox(height: 20.h), - buildTopButtons(), - SizedBox(height: 20.h), - buildBottomButtons(), - SizedBox(height: 20.h), - ], - ), - ), - ), - ], - ), + child: _buildTalkView(isMpeg4: false), ), ), ); @@ -356,4 +302,109 @@ class _LockMonitoringPageState extends State { logic.stopProcessing(); state.getTVDataRefreshUIEvent!.cancel(); } + + Widget _buildTalkView({required bool isMpeg4}) { + return isMpeg4 ? _buildMpeg4TalkView() : _buildH264TalkView(); + } + + Widget _buildMpeg4TalkView() { + return Stack( + children: [ + Image.memory( + state.listPhotoData.value, + gaplessPlayback: true, + width: 1.sw, + height: 1.sh, + fit: BoxFit.cover, + filterQuality: FilterQuality.high, + errorBuilder: + (BuildContext context, Object error, StackTrace? stackTrace) { + return Container(color: Colors.transparent); + }, + ), + Positioned( + top: ScreenUtil().statusBarHeight + 30.h, + width: 1.sw, + child: Obx(() { + final String sec = + (state.oneMinuteTime.value % 60).toString().padLeft(2, '0'); + final String min = + (state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0'); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('$min:$sec', + style: TextStyle(fontSize: 26.sp, color: Colors.white)), + ], + ); + }), + ), + Positioned( + bottom: 10.w, + child: Container( + width: 1.sw - 30.w * 2, + margin: EdgeInsets.all(30.w), + decoration: BoxDecoration( + color: const Color(0xC83C3F41), + borderRadius: BorderRadius.circular(20.h), + ), + child: Column( + children: [ + SizedBox(height: 20.h), + buildTopButtons(), + SizedBox(height: 20.h), + buildBottomButtons(), + SizedBox(height: 20.h), + ], + ), + ), + ), + ], + ); + } + + Widget _buildH264TalkView() { + return Stack( + children: [ + H264WebView(), + Positioned( + top: ScreenUtil().statusBarHeight + 30.h, + width: 1.sw, + child: Obx(() { + final String sec = + (state.oneMinuteTime.value % 60).toString().padLeft(2, '0'); + final String min = + (state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0'); + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('$min:$sec', + style: TextStyle(fontSize: 26.sp, color: Colors.white)), + ], + ); + }), + ), + Positioned( + bottom: 10.w, + child: Container( + width: 1.sw - 30.w * 2, + margin: EdgeInsets.all(30.w), + decoration: BoxDecoration( + color: const Color(0xC83C3F41), + borderRadius: BorderRadius.circular(20.h), + ), + child: Column( + children: [ + SizedBox(height: 20.h), + buildTopButtons(), + SizedBox(height: 20.h), + buildBottomButtons(), + SizedBox(height: 20.h), + ], + ), + ), + ), + ], + ); + } } diff --git a/lib/talk/startChart/handle/impl/udp_talk_data_handler.dart b/lib/talk/startChart/handle/impl/udp_talk_data_handler.dart index c88c1817..5bea2f1e 100644 --- a/lib/talk/startChart/handle/impl/udp_talk_data_handler.dart +++ b/lib/talk/startChart/handle/impl/udp_talk_data_handler.dart @@ -10,7 +10,6 @@ import 'package:star_lock/talk/startChart/proto/generic.pb.dart'; import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart'; import 'package:star_lock/talk/startChart/proto/talk_data.pbserver.dart'; - class UdpTalkDataHandler extends ScpMessageBaseHandle implements ScpMessageHandler { @override @@ -24,11 +23,10 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle @override void handleRealTimeData(ScpMessage scpMessage) { - // print('收到音视频数据:$scpMessage'); if (scpMessage.Payload != null) { final TalkData talkData = scpMessage.Payload; // 处理音视频数据 - // _handleTalkData(talkData: talkData); + _handleTalkData(talkData: talkData); // 收到数据后调用更新,防止定时器超时 talkDataOverTimeTimerManager.receiveMessage(); } @@ -55,86 +53,10 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle void _handleVideoH264(TalkData talkData) { final List content = talkData.content; - // 解析 H.264 NALU - _parseH264Nalus(content); + talkDataRepository.addTalkData(content); } void _handleVideoImage(TalkData talkData) {} void _handleVideoG711(TalkData talkData) {} - - // 解析 H.264 NALU - void _parseH264Nalus(List h264Stream) { - print('开始解析 H.264 NALU...'); - - int index = 0; - while (index < h264Stream.length) { - // 查找下一个 NALU 的起始码 - int nextStartCodeIndex = findNextStartCode(h264Stream, index); - - if (nextStartCodeIndex == -1) { - // 如果没有找到更多的起始码,结束解析 - break; - } - - // 提取当前 NALU - Uint8List naluData = - Uint8List.fromList(h264Stream.sublist(index, nextStartCodeIndex)); - - // 解析 NALU 头部 - int naluType = naluData[0] & 0x1F; // NALU 类型位于第一个字节的低 5 位 - print('找到 NALU,类型: $naluType,长度: ${naluData.length}'); - - // 根据 NALU 类型进行处理 - handleNalu(naluType, naluData); - - // 更新索引到下一个 NALU 的起始位置 - index = nextStartCodeIndex; - } - } - -// 查找下一个 NALU 的起始码 - int findNextStartCode(List data, int startIndex) { - for (int i = startIndex; i < data.length - 3; i++) { - // 检查 3 字节起始码 - if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01) { - return i; - } - - // 检查 4 字节起始码 - if (i < data.length - 4 && - data[i] == 0x00 && - data[i + 1] == 0x00 && - data[i + 2] == 0x00 && - data[i + 3] == 0x01) { - return i; - } - } - - // 如果没有找到更多的起始码,返回 -1 - return -1; - } - - // 处理 NALU - void handleNalu(int naluType, Uint8List naluData) { - switch (naluType) { - case 5: - print('IDR 帧 (关键帧)'); - break; - case 1: - print('非 IDR 帧 (P 帧)'); - break; - case 7: - print('SPS (序列参数集)'); - break; - case 8: - print('PPS (图像参数集)'); - break; - default: - print('其他 NALU 类型: $naluType'); - } - - // 你可以在这里根据 NALU 类型进行进一步处理 - // 例如,将 SPS 和 PPS 传递给解码器,或将视频帧渲染到屏幕上 - } } diff --git a/lib/talk/startChart/handle/impl/udp_talk_expect_handler.dart b/lib/talk/startChart/handle/impl/udp_talk_expect_handler.dart index f948b7f2..f7fc3c85 100644 --- a/lib/talk/startChart/handle/impl/udp_talk_expect_handler.dart +++ b/lib/talk/startChart/handle/impl/udp_talk_expect_handler.dart @@ -1,3 +1,6 @@ +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:star_lock/talk/startChart/entity/scp_message.dart'; @@ -5,6 +8,7 @@ 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/gateway_reset.pb.dart'; import 'package:star_lock/talk/startChart/proto/generic.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 '../../start_chart_manage.dart'; @@ -16,6 +20,12 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle // 收到预期音视频数据请求 final TalkExpect talkExpect = scpMessage.Payload; print('收到预期音视频数据请求:$talkExpect'); + + // 回复请求 + replySuccessMessage(scpMessage); + + // 启动发送通话数据 + // startChartManage.startTalkDataTimer(); } @override @@ -28,12 +38,8 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle } @override - void handleInvalidReq(ScpMessage scpMessage) { - - } + void handleInvalidReq(ScpMessage scpMessage) {} @override - void handleRealTimeData(ScpMessage scpMessage) { - - } + void handleRealTimeData(ScpMessage scpMessage) {} } diff --git a/lib/talk/startChart/handle/other/talk_data_repository.dart b/lib/talk/startChart/handle/other/talk_data_repository.dart new file mode 100644 index 00000000..c84d58b1 --- /dev/null +++ b/lib/talk/startChart/handle/other/talk_data_repository.dart @@ -0,0 +1,30 @@ +import 'dart:async'; + +import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart'; + +class TalkDataRepository { + // 创建一个私有的构造函数,防止外部创建实例 + TalkDataRepository._(); + + // 使用 _instance 来保存单例对象 + static final TalkDataRepository _instance = TalkDataRepository._(); + + // 提供一个静态方法来获取单例实例 + static TalkDataRepository get instance => _instance; + + // 创建一个 StreamController + final StreamController> _talkDataStreamController = StreamController>.broadcast(); + + // 提供一个方法来获取 Stream + Stream> get talkDataStream => _talkDataStreamController.stream; + + // 提供一个方法来添加 TalkData 到 Stream + void addTalkData(List talkData) { + _talkDataStreamController.add(talkData); + } + + // 提供一个方法来关闭 StreamController + void dispose() { + _talkDataStreamController.close(); + } +} \ No newline at end of file diff --git a/lib/talk/startChart/handle/scp_message_base_handle.dart b/lib/talk/startChart/handle/scp_message_base_handle.dart index 5575ebcd..06597f71 100644 --- a/lib/talk/startChart/handle/scp_message_base_handle.dart +++ b/lib/talk/startChart/handle/scp_message_base_handle.dart @@ -4,7 +4,9 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/talk/other/audio_player_manager.dart'; import 'package:star_lock/talk/startChart/constant/udp_constant.dart'; +import 'package:star_lock/talk/startChart/entity/scp_message.dart'; import 'package:star_lock/talk/startChart/handle/other/overtime_timer_manger.dart'; +import 'package:star_lock/talk/startChart/handle/other/talk_data_repository.dart'; import 'package:star_lock/talk/startChart/proto/generic.pb.dart'; import 'package:star_lock/talk/startChart/start_chart_manage.dart'; @@ -13,6 +15,9 @@ import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; class ScpMessageBaseHandle { final startChartManage = StartChartManage(); + // 通话数据流的单例流数据处理类 + final TalkDataRepository talkDataRepository = TalkDataRepository.instance; + final audioManager = AudioPlayerManager(); // 通话保持超时监听定时器管理 @@ -25,12 +30,18 @@ class ScpMessageBaseHandle { timeoutInSeconds: 3, ); + // 回复成功消息 + void replySuccessMessage(ScpMessage scpMessage){ + startChartManage.sendGenericRespSuccessMessage( + ToPeerId: scpMessage.FromPeerId!, + FromPeerId: scpMessage.ToPeerId!, + PayloadType: scpMessage.PayloadType!, + ); + } // 获取 StartChartTalkStatus 的唯一实例 StartChartTalkStatus talkStatus = StartChartTalkStatus.instance; - - bool checkGenericRespSuccess(GenericResp genericResp) { if (genericResp == null) return false; final code = genericResp.code; diff --git a/lib/talk/startChart/start_chart_manage.dart b/lib/talk/startChart/start_chart_manage.dart index cab22d7a..a238a9b6 100644 --- a/lib/talk/startChart/start_chart_manage.dart +++ b/lib/talk/startChart/start_chart_manage.dart @@ -4,6 +4,7 @@ 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'; @@ -22,6 +23,7 @@ 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'; @@ -70,6 +72,8 @@ class StartChartManage { Timer? talkPingTimer; // 发送通话保持消息定时器 int talkExpectIntervalTime = 1; // 发送通话预期数据的消息间隔(s) Timer? talkExpectTimer; // 发送通话预期消息定时器 + int talkDataIntervalTime = 10; // 通话数据的消息间隔(ms) + Timer? talkDataTimer; // 发送通话数据消息定时器 // 默认通话的期望数据格式 TalkExpect defaultTalkExpect = TalkExpect( @@ -77,6 +81,9 @@ class StartChartManage { audioType: [TalkExpect_AudioTypeE.G711], ); + // 默认通话数据 + TalkData defaultTalkData = TalkData(); + String relayPeerId = ''; // 中继peerId // 获取 StartChartTalkStatus 的唯一实例 @@ -228,6 +235,17 @@ class StartChartManage { await _sendMessage(message: message); } + // 发送对讲数据 + Future sendTalkDataMessage({required TalkData talkData}) async { + // 组装上线消息 + final message = MessageCommand.talkDataMessage( + FromPeerId: FromPeerId, + ToPeerId: ToPeerId, + talkData: talkData, + ); + await _sendMessage(message: message); + } + // 发送心跳包消息 void _sendHeartbeatMessage() async { if (_heartBeatTimerRunning) { @@ -764,6 +782,108 @@ class StartChartManage { ); } + /// 通话数据定时器 + void startTalkDataTimer() async { + // 如果已经启动了就不运行 + if (talkDataTimer != null) return; + // 读取 assets 文件 + final ByteData data = await rootBundle.load('assets/talk.h264'); + final List byteData = data.buffer.asUint8List(); + int current = 0; + int start = 0; + int end = 0; + final List 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 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 extractChunks(List byteData) { + int i = 0; + int length = byteData.length; + int naluCount = 0; + int value; + int state = 0; + int lastIndex = 0; + List 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(); @@ -781,6 +901,7 @@ class StartChartManage { stopTalkPingMessageTimer(); stopHeartbeat(); stopReStartOnlineStartChartServer(); + stopTalkDataTimer(); await Storage.removerRelayInfo(); await Storage.removerStarChartRegisterNodeInfo(); }