From c865db7a9f17da25f28463af2dcb2a6a0c25014a Mon Sep 17 00:00:00 2001 From: liyi Date: Sat, 28 Dec 2024 14:58:01 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E5=A2=9E=E5=8A=A0=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E9=80=BB=E8=BE=91=E3=80=81=E8=B0=83=E6=95=B4?= =?UTF-8?q?proto=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lan/lan_en.json | 1 + lan/lan_keys.json | 1 + lan/lan_zh.json | 1 + .../handle/impl/udp_talk_accept_handler.dart | 3 + .../handle/impl/udp_talk_hangup_handler.dart | 4 + .../handle/impl/udp_talk_reject_handler.dart | 16 +- .../handle/impl/udp_talk_request_handler.dart | 10 + .../handle/scp_message_base_handle.dart | 5 + lib/talk/startChart/start_chart_manage.dart | 13 +- .../views/talkView/talk_view_logic.dart | 189 +++++++++++++++--- .../views/talkView/talk_view_page.dart | 121 ++++++----- .../views/talkView/talk_view_state.dart | 31 ++- pubspec.yaml | 4 + 13 files changed, 302 insertions(+), 97 deletions(-) diff --git a/lan/lan_en.json b/lan/lan_en.json index e203f2c4..e5aeb2fa 100644 --- a/lan/lan_en.json +++ b/lan/lan_en.json @@ -1011,6 +1011,7 @@ "请在锁设置中开启远程开锁": "Please enable remote unlocking in the lock settings", "接听": "Answer", "截图已保存到相册": "Screenshot saved to album", + "录屏已保存到相册": "Screen recording file saved to album", "添加遥控": "Add remote control", "已连接到锁,请按遥控": "Connected to the lock, please press the remote control", "遥控号": "Remote control number", diff --git a/lan/lan_keys.json b/lan/lan_keys.json index 1656967f..e5047118 100755 --- a/lan/lan_keys.json +++ b/lan/lan_keys.json @@ -1014,6 +1014,7 @@ "请在锁设置中开启远程开锁": "请在锁设置中开启远程开锁", "接听": "接听", "截图已保存到相册": "截图已保存到相册", + "录屏已保存到相册": "录屏已保存到相册", "添加遥控": "添加遥控", "已连接到锁,请按遥控": "已连接到锁,请按遥控", "遥控号": "遥控号", diff --git a/lan/lan_zh.json b/lan/lan_zh.json index 522b21da..ccaae2c0 100755 --- a/lan/lan_zh.json +++ b/lan/lan_zh.json @@ -1013,6 +1013,7 @@ "请在锁设置中开启远程开锁": "请在锁设置中开启远程开锁", "接听": "接听", "截图已保存到相册": "截图已保存到相册", + "录屏已保存到相册": "录屏已保存到相册", "添加遥控": "添加遥控", "已连接到锁,请按遥控": "已连接到锁,请按遥控", "遥控号": "遥控号", diff --git a/lib/talk/startChart/handle/impl/udp_talk_accept_handler.dart b/lib/talk/startChart/handle/impl/udp_talk_accept_handler.dart index f6e932e1..eca0b059 100644 --- a/lib/talk/startChart/handle/impl/udp_talk_accept_handler.dart +++ b/lib/talk/startChart/handle/impl/udp_talk_accept_handler.dart @@ -39,6 +39,9 @@ class UdpTalkAcceptHandler extends ScpMessageBaseHandle stopRingtone(); // 设置状态为接听成功 talkStatus.setAnsweredSuccessfully(); + // 同意接听之后,停止对讲请求超时监听定时器 + talkeRequestOverTimeTimerManager.receiveMessage(); + talkeRequestOverTimeTimerManager.dispose(); } } diff --git a/lib/talk/startChart/handle/impl/udp_talk_hangup_handler.dart b/lib/talk/startChart/handle/impl/udp_talk_hangup_handler.dart index f7e39e2a..32c267a9 100644 --- a/lib/talk/startChart/handle/impl/udp_talk_hangup_handler.dart +++ b/lib/talk/startChart/handle/impl/udp_talk_hangup_handler.dart @@ -39,6 +39,10 @@ class UdpTalkHangUpHandler extends ScpMessageBaseHandle startChartManage.stopTalkExpectMessageTimer(); talkStatus.setHangingUpDuring(); stopRingtone(); + + // 拒绝接听之后,停止对讲请求超时监听定时器 + talkeRequestOverTimeTimerManager.receiveMessage(); + talkeRequestOverTimeTimerManager.dispose(); } @override diff --git a/lib/talk/startChart/handle/impl/udp_talk_reject_handler.dart b/lib/talk/startChart/handle/impl/udp_talk_reject_handler.dart index 07dfe4f6..0eb85fa4 100644 --- a/lib/talk/startChart/handle/impl/udp_talk_reject_handler.dart +++ b/lib/talk/startChart/handle/impl/udp_talk_reject_handler.dart @@ -18,16 +18,13 @@ class UdpTalkRejectHandler extends ScpMessageBaseHandle @override void handleReq(ScpMessage scpMessage) { // 收到接听拒绝请求 - startChartManage.sendGenericRespSuccessMessage( - ToPeerId: scpMessage.FromPeerId!, - FromPeerId: scpMessage.ToPeerId!, - PayloadType: scpMessage.PayloadType!, - ); - startChartManage.stopTalkPingMessageTimer(); - startChartManage.stopTalkExpectMessageTimer(); + // 回复成功消息 + replySuccessMessage(scpMessage); + // 停止铃声 stopRingtone(); // 收到接听拒绝回复 talkStatus.setRejected(); + } @override @@ -35,6 +32,11 @@ class UdpTalkRejectHandler extends ScpMessageBaseHandle startChartManage.stopTalkPingMessageTimer(); startChartManage.stopTalkExpectMessageTimer(); stopRingtone(); + + + // 拒绝接听之后,停止对讲请求超时监听定时器 + talkeRequestOverTimeTimerManager.receiveMessage(); + talkeRequestOverTimeTimerManager.dispose(); } @override diff --git a/lib/talk/startChart/handle/impl/udp_talk_request_handler.dart b/lib/talk/startChart/handle/impl/udp_talk_request_handler.dart index 20e649fa..1e3ddb54 100644 --- a/lib/talk/startChart/handle/impl/udp_talk_request_handler.dart +++ b/lib/talk/startChart/handle/impl/udp_talk_request_handler.dart @@ -34,6 +34,16 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle startChartManage.ToPeerId = scpMessage.FromPeerId!; // 处理收到接听请求后的事件 _talkRequestEvent(talkObjectName: talkReq.callerName); + + // 启动对讲请求超时定时器 + talkeRequestOverTimeTimerManager.startTimer(); + talkeRequestOverTimeTimerManager.setOnTimeout(() { + if (talkStatus.status == TalkStatus.waitingAnswer) { + // 超时未接听,发送挂断请求 + startChartManage.sendTalkRejectMessage(); + Get.back(); + } + }); } @override diff --git a/lib/talk/startChart/handle/scp_message_base_handle.dart b/lib/talk/startChart/handle/scp_message_base_handle.dart index 63e639dd..5a0aabcd 100644 --- a/lib/talk/startChart/handle/scp_message_base_handle.dart +++ b/lib/talk/startChart/handle/scp_message_base_handle.dart @@ -36,6 +36,11 @@ class ScpMessageBaseHandle { final audioManager = AudioPlayerManager(); + // 通话请求超时未处理监听定时器管理 + final talkeRequestOverTimeTimerManager = OverTimeTimerManager( + timeoutInSeconds: 30, + ); + // 通话保持超时监听定时器管理 final talkePingOverTimeTimerManager = OverTimeTimerManager( timeoutInSeconds: 260, diff --git a/lib/talk/startChart/start_chart_manage.dart b/lib/talk/startChart/start_chart_manage.dart index 83485db6..e5087f6d 100644 --- a/lib/talk/startChart/start_chart_manage.dart +++ b/lib/talk/startChart/start_chart_manage.dart @@ -86,7 +86,7 @@ class StartChartManage { final int _maxPayloadSize = 8 * 1024; // 分包大小 // 默认通话的期望数据格式 - TalkExpectReq defaultTalkExpect = TalkExpectReq( + TalkExpectReq _defaultTalkExpect = TalkExpectReq( videoType: [VideoTypeE.IMAGE], audioType: [AudioTypeE.G711], ); @@ -857,7 +857,7 @@ class StartChartManage { (Timer timer) { // 发送期望接受消息 sendTalkExpectMessage( - talkExpect: defaultTalkExpect, + talkExpect: _defaultTalkExpect, ); }, ); @@ -971,9 +971,16 @@ class StartChartManage { talkExpectTimer = null; // 清除定时器引用 } + // 重新发送预期数据 + void reStartTalkExpectMessageTimer() { + stopTalkExpectMessageTimer(); + startTalkExpectTimer(); + } + /// 修改预期接收到的数据 void changeTalkExpectDataType({required TalkExpectReq talkExpect}) { - defaultTalkExpect = talkExpect; + _defaultTalkExpect = talkExpect; + reStartTalkExpectMessageTimer(); } /// 销毁资源 diff --git a/lib/talk/startChart/views/talkView/talk_view_logic.dart b/lib/talk/startChart/views/talkView/talk_view_logic.dart index 264374bb..edd759ea 100644 --- a/lib/talk/startChart/views/talkView/talk_view_logic.dart +++ b/lib/talk/startChart/views/talkView/talk_view_logic.dart @@ -1,34 +1,36 @@ import 'dart:async'; import 'dart:io'; -import 'dart:math'; +import 'dart:ui' as ui; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_pcm_sound/flutter_pcm_sound.dart'; -import 'package:flutter_voice_processor/flutter_voice_processor.dart'; +import 'package:flutter_screen_recording/flutter_screen_recording.dart'; +import 'package:gallery_saver/gallery_saver.dart'; + import 'package:get/get.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:star_lock/app_settings/app_settings.dart'; -import 'package:star_lock/blue/io_tool/manager_event_bus.dart'; -import 'package:star_lock/talk/call/callTalk.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.pbenum.dart'; +import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart'; import 'package:star_lock/talk/startChart/start_chart_manage.dart'; -import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; + import 'package:star_lock/talk/startChart/views/talkView/talk_view_state.dart'; -import '../../../../talk/call/g711.dart'; -import '../../../../talk/udp/udp_manage.dart'; -import '../../../../talk/udp/udp_senderManage.dart'; import '../../../../tools/baseGetXController.dart'; -import '../../../../tools/eventBusEventManage.dart'; class TalkViewLogic extends BaseGetXController { final TalkViewState state = TalkViewState(); Timer? _syncTimer; int _startTime = 0; - final int bufferSize = 22; // 缓冲区大小(以帧为单位) + final int bufferSize = 20; // 缓冲区大小(以帧为单位) final List frameTimestamps = []; int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS) int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS) @@ -82,22 +84,27 @@ class TalkViewLogic extends BaseGetXController { void _startListenTalkData() { state.talkDataRepository.talkDataStream.listen((talkData) { final contentType = talkData.contentType; + final currentTimestamp = DateTime.now().millisecondsSinceEpoch; + + /// 如果不是通话中的状态不处理对讲数据 + if (state.startChartTalkStatus.status != TalkStatus.duringCall) { + return; + } + // 判断数据类型,进行分发处理 switch (contentType) { case TalkData_ContentTypeE.G711: - // state.audioBuffer.add(talkData); - if (state.audioBuffer.length < 60) { - // 假设缓冲区大小为60帧 + if (state.audioBuffer.length < bufferSize) { state.audioBuffer.add(talkData); } break; case TalkData_ContentTypeE.Image: - // state.videoBuffer.add(talkData); - // 增加视频缓冲区大小 - if (state.videoBuffer.length < 60) { - // 假设缓冲区大小为60帧 + if (state.videoBuffer.length < bufferSize) { state.videoBuffer.add(talkData); } + + /// 更新网络状态 + updateNetworkStatus(currentTimestamp); break; } }); @@ -136,6 +143,7 @@ class TalkViewLogic extends BaseGetXController { state.listData.value = Uint8List.fromList(talkData.content); } + /// 启动播放 void _startPlayback() { int frameIntervalMs = 45; // 初始帧间隔设置为45毫秒(约22FPS) @@ -154,7 +162,15 @@ class TalkViewLogic extends BaseGetXController { // 播放合适的音频帧 if (state.audioBuffer.isNotEmpty && state.audioBuffer.first.durationMs <= elapsedTime) { - _playAudioData(state.audioBuffer.removeAt(0)); + // 判断音频开关是否打开 + if (state.isOpenVoice.value) { + _playAudioData(state.audioBuffer.removeAt(0)); + } else { + // 如果不播放音频,只从缓冲区中读取数据,但不移除 + // 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长 + // 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的 + state.audioBuffer.removeAt(0); + } } // 播放合适的视频帧 @@ -165,10 +181,10 @@ class TalkViewLogic extends BaseGetXController { if (state.videoBuffer.length > 1) { state.videoBuffer.removeAt(0); } else { - // 记录当前时间戳 - frameTimestamps.add(DateTime.now().millisecondsSinceEpoch); - // 计算并更新 FPS - _updateFps(frameTimestamps); + // // 记录当前时间戳 + // frameTimestamps.add(DateTime.now().millisecondsSinceEpoch); + // // 计算并更新 FPS + // _updateFps(frameTimestamps); _playVideoData(state.videoBuffer.removeAt(0)); } } @@ -195,7 +211,15 @@ class TalkViewLogic extends BaseGetXController { // 播放合适的音频帧 if (state.audioBuffer.isNotEmpty && state.audioBuffer.first.durationMs <= elapsedTime) { - _playAudioData(state.audioBuffer.removeAt(0)); + // 判断音频开关是否打开 + if (state.isOpenVoice.value) { + _playAudioData(state.audioBuffer.removeAt(0)); + } else { + // 如果不播放音频,只从缓冲区中读取数据,但不移除 + // 你可以根据需要调整此处逻辑,例如保留缓冲区的最大长度,防止无限增长 + // 仅移除缓冲区数据但不播放音频,确保音频也是实时更新的 + state.audioBuffer.removeAt(0); + } } // 播放合适的视频帧 @@ -206,19 +230,47 @@ class TalkViewLogic extends BaseGetXController { if (state.videoBuffer.length > 1) { state.videoBuffer.removeAt(0); } else { - // 记录当前时间戳 - frameTimestamps.add(DateTime.now().millisecondsSinceEpoch); - // 计算并更新 FPS - _updateFps(frameTimestamps); + // // 记录当前时间戳 + // frameTimestamps.add(DateTime.now().millisecondsSinceEpoch); + // // 计算并更新 FPS + // _updateFps(frameTimestamps); _playVideoData(state.videoBuffer.removeAt(0)); } } }); } + /// 修改网络状态 + void updateNetworkStatus(int currentTimestamp) { + if (state.lastFrameTimestamp.value != 0) { + final frameInterval = currentTimestamp - state.lastFrameTimestamp.value; + if (frameInterval > 500 && frameInterval <= 1000) { + // 判断帧间隔是否在500毫秒到1秒之间 + state.networkStatus.value = NetworkStatus.lagging; + showNetworkStatus("Network is lagging"); + } else if (frameInterval > 1000) { + // 判断帧间隔是否超过1秒 + state.networkStatus.value = NetworkStatus.delayed; + showNetworkStatus("Network is delayed"); + } else { + state.networkStatus.value = NetworkStatus.normal; + state.alertCount.value = 0; // 重置计数器 + EasyLoading.dismiss(); // 网络恢复正常时关闭提示 + } + } + state.lastFrameTimestamp.value = currentTimestamp; + } + + /// 提示网络状态 + void showNetworkStatus(String message) { + if (state.alertCount.value < 3 && !EasyLoading.isShow) { + showToast(message); + state.alertCount++; + } + } + /// 停止播放音频 void _stopPlayG711Data() async { - print('停止播放'); await FlutterPcmSound.pause(); await FlutterPcmSound.stop(); await FlutterPcmSound.clear(); @@ -288,4 +340,85 @@ class TalkViewLogic extends BaseGetXController { // 状态错误,返回页面 Get.back(); } + + /// 更新发送预期数据 + void updateTalkExpect() { + TalkExpectReq talkExpectReq = TalkExpectReq(); + if (state.isOpenVoice.value) { + talkExpectReq = TalkExpectReq( + videoType: [VideoTypeE.IMAGE], + audioType: [AudioTypeE.G711], + ); + } else { + talkExpectReq = TalkExpectReq( + videoType: [VideoTypeE.IMAGE], + audioType: [], + ); + } + + /// 修改发送预期数据 + StartChartManage().changeTalkExpectDataType(talkExpect: talkExpectReq); + state.isOpenVoice.value = !state.isOpenVoice.value; + } + + /// 截图并保存到相册 + Future captureAndSavePng() async { + try { + if (state.globalKey.currentContext == null) { + AppLog.log('截图失败: 未找到当前上下文'); + return; + } + final RenderRepaintBoundary boundary = state.globalKey.currentContext! + .findRenderObject()! as RenderRepaintBoundary; + final ui.Image image = await boundary.toImage(); + final ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.png); + + if (byteData == null) { + AppLog.log('截图失败: 图像数据为空'); + return; + } + final Uint8List pngBytes = byteData.buffer.asUint8List(); + + // 获取应用程序的文档目录 + final Directory directory = await getApplicationDocumentsDirectory(); + final String imagePath = '${directory.path}/screenshot.png'; + + // 将截图保存为文件 + final File imgFile = File(imagePath); + await imgFile.writeAsBytes(pngBytes); + + // 将截图保存到相册 + await ImageGallerySaver.saveFile(imagePath); + + AppLog.log('截图保存路径: $imagePath'); + showToast('截图已保存到相册'.tr); + } catch (e) { + AppLog.log('截图失败: $e'); + } + } + + /// 开始录屏 + Future startRecording() async { + getPermissionStatus(); + bool started = + await FlutterScreenRecording.startRecordScreenAndAudio("Recording"); + if (started) { + state.isRecording.value = true; + } + } + + /// 停止录屏 + Future stopRecording() async { + String path = await FlutterScreenRecording.stopRecordScreen; + if (path != null) { + state.isRecording.value = false; + // 保存录制的视频到相册 + await GallerySaver.saveVideo(path).then((bool? success) {}); + showToast('录屏已保存到相册'.tr); + } else { + state.isRecording.value = false; + print("Recording failed"); + } + } } diff --git a/lib/talk/startChart/views/talkView/talk_view_page.dart b/lib/talk/startChart/views/talkView/talk_view_page.dart index db49b6ba..879dcb0a 100644 --- a/lib/talk/startChart/views/talkView/talk_view_page.dart +++ b/lib/talk/startChart/views/talkView/talk_view_page.dart @@ -1,10 +1,16 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_screen_recording/flutter_screen_recording.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:gallery_saver/gallery_saver.dart'; import 'package:get/get.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/main/lockDetail/realTimePicture/realTimePicture_state.dart'; import 'package:star_lock/talk/call/callTalk.dart'; @@ -43,7 +49,6 @@ class _TalkViewPageState extends State state.animationController.repeat(); //动画开始、结束、向前移动或向后移动时会调用StatusListener state.animationController.addStatusListener((AnimationStatus status) { - // AppLog.log("AnimationStatus:$status"); if (status == AnimationStatus.completed) { state.animationController.reset(); state.animationController.forward(); @@ -70,23 +75,29 @@ class _TalkViewPageState extends State height: ScreenUtil().screenHeight, fit: BoxFit.cover, ) - : Image.memory( - state.listData.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); - }, + : PopScope( + canPop: false, + child: RepaintBoundary( + key: state.globalKey, + child: Image.memory( + state.listData.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); + }, + ), + ), ), ), - Obx(() => state.listData.value.isEmpty + Obx(() => state.talkStatus.value == TalkStatus.answeredSuccessfully ? Positioned( bottom: 300.h, child: Text( @@ -114,17 +125,20 @@ class _TalkViewPageState extends State ), ), ), - Positioned( - top: 100.h, - left: 10.w, - child: Obx( - () => Text( - 'FPS:${state.fps.value}', - style: TextStyle(fontSize: 30.sp, color: Colors.orange,fontWeight: FontWeight.bold), - ), - ), - ), - Obx(() => state.listData.value.isEmpty + // Positioned( + // top: 100.h, + // left: 10.w, + // child: Obx( + // () => Text( + // 'FPS:${state.fps.value}', + // style: TextStyle( + // fontSize: 30.sp, + // color: Colors.orange, + // fontWeight: FontWeight.bold), + // ), + // ), + // ), + Obx(() => state.talkStatus.value == TalkStatus.answeredSuccessfully ? buildRotationTransition() : Container()) ], @@ -137,7 +151,7 @@ class _TalkViewPageState extends State // 打开关闭声音 GestureDetector( onTap: () { - state.isOpenVoice.value = !state.isOpenVoice.value; + logic.updateTalkExpect(); }, child: Container( width: 50.w, @@ -148,16 +162,16 @@ class _TalkViewPageState extends State height: 40.w, image: state.isOpenVoice.value ? const AssetImage( - 'images/main/icon_lockDetail_monitoringCloseVoice.png') + 'images/main/icon_lockDetail_monitoringOpenVoice.png') : const AssetImage( - 'images/main/icon_lockDetail_monitoringOpenVoice.png'))), + 'images/main/icon_lockDetail_monitoringCloseVoice.png'))), ), ), SizedBox(width: 50.w), // 截图 GestureDetector( - onTap: () { - // Get.toNamed(Routers.monitoringRealTimeScreenPage); + onTap: () async { + await logic.captureAndSavePng(); }, child: Container( width: 50.w, @@ -173,32 +187,38 @@ class _TalkViewPageState extends State SizedBox(width: 50.w), // 录制 GestureDetector( - onTap: () { - // Get.toNamed(Routers.monitoringRealTimeScreenPage); + onTap: () async { + if (state.isRecording.value) { + await logic.stopRecording(); + } else { + await logic.startRecording(); + } }, child: Container( width: 50.w, height: 50.w, padding: EdgeInsets.all(5.w), child: Image( - width: 40.w, - height: 40.w, - fit: BoxFit.fill, - image: const AssetImage( - 'images/main/icon_lockDetail_monitoringScreenRecording.png')), + width: 40.w, + height: 40.w, + fit: BoxFit.fill, + image: const AssetImage( + 'images/main/icon_lockDetail_monitoringScreenRecording.png'), + ), ), ), SizedBox(width: 50.w), GestureDetector( - onTap: () { - logic.showToast('功能暂未开放'.tr); - }, - child: Image( - width: 28.w, - height: 28.w, - fit: BoxFit.fill, - image: const AssetImage( - 'images/main/icon_lockDetail_rectangle.png'))) + onTap: () { + logic.showToast('功能暂未开放'.tr); + }, + child: Image( + width: 28.w, + height: 28.w, + fit: BoxFit.fill, + image: const AssetImage('images/main/icon_lockDetail_rectangle.png'), + ), + ), ]); } @@ -212,7 +232,10 @@ class _TalkViewPageState extends State getAnswerBtnImg(), getAnswerBtnName(), Colors.white, - longPress: () async {}, + longPress: () async { + if (state.talkStatus.value == TalkStatus.answeredSuccessfully || + state.talkStatus.value == TalkStatus.duringCall) {} + }, longPressUp: () async {}, onClick: () async { if (state.talkStatus.value == TalkStatus.waitingAnswer) { diff --git a/lib/talk/startChart/views/talkView/talk_view_state.dart b/lib/talk/startChart/views/talkView/talk_view_state.dart index 403ede77..896cbfa4 100644 --- a/lib/talk/startChart/views/talkView/talk_view_state.dart +++ b/lib/talk/startChart/views/talkView/talk_view_state.dart @@ -13,8 +13,15 @@ import 'package:star_lock/talk/startChart/start_chart_talk_status.dart'; import '../../../../tools/storage.dart'; +enum NetworkStatus { + normal, // 0 + lagging, // 1 + delayed, // 2 + packetLoss // 3 +} + class TalkViewState { - RxBool isOpenVoice = false.obs; + int udpSendDataFrameNumber = 0; // 帧序号 // var isSenderAudioData = false.obs;// 是否要发送音频数据 @@ -27,7 +34,7 @@ class TalkViewState { Rx listData = Uint8List(0).obs; //得到的视频流字节数据 RxList listAudioData = [].obs; //得到的音频流字节数据 - + GlobalKey globalKey = GlobalKey(); late final VoiceProcessor? voiceProcessor; late Timer oneMinuteTimeTimer = @@ -40,8 +47,6 @@ class TalkViewState { late Timer openDoorTimer; late AnimationController animationController; - RxDouble fps = 0.0.obs; // 添加 FPS 计数 - late Timer autoBackTimer = Timer(const Duration(seconds: 1), () {}); //发送30秒监视后自动返回 @@ -50,19 +55,25 @@ class TalkViewState { RxInt elapsedSeconds = 0.obs; + + + // 星图对讲相关状态 List audioBuffer = [].obs; List videoBuffer = [].obs; - - RxBool isPlaying = false.obs; // 是否开始播放 - - Rx talkStatus = TalkStatus.none.obs; //星图对讲状态 - // 获取 startChartTalkStatus 的唯一实例 final StartChartTalkStatus startChartTalkStatus = StartChartTalkStatus.instance; - // 通话数据流的单例流数据处理类 final TalkDataRepository talkDataRepository = TalkDataRepository.instance; + RxInt lastFrameTimestamp = 0.obs; // 上一帧的时间戳,用来判断网络环境 + Rx networkStatus = + NetworkStatus.normal.obs; // 网络状态:0-正常 1-网络卡顿 2-网络延迟 3-网络丢包 + RxInt alertCount = 0.obs; // 网络状态提示计数器 + RxInt maxAlertNumber = 3.obs; // 网络状态提示最大提示次数 + RxBool isOpenVoice = true.obs; // 是否打开声音 + RxBool isRecording = true.obs; // 是否录屏中 + RxDouble fps = 0.0.obs; // 添加 FPS 计数 + } diff --git a/pubspec.yaml b/pubspec.yaml index d1426507..0691f5ca 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -256,6 +256,10 @@ dependencies: asn1lib: ^1.0.0 fast_rsa: ^3.6.6 protobuf: ^3.1.0 + #录屏 + flutter_screen_recording: 2.0.16 + #图库保存 + gallery_saver: ^2.3.2