diff --git a/lib/talk/starChart/views/native/talk_view_native_decode_page_debug.dart b/lib/talk/starChart/views/native/talk_view_native_decode_page_debug.dart new file mode 100644 index 00000000..f6f00f52 --- /dev/null +++ b/lib/talk/starChart/views/native/talk_view_native_decode_page_debug.dart @@ -0,0 +1,511 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; +import 'package:http/http.dart' as http; +import 'package:provider/provider.dart'; +import 'package:star_lock/appRouters.dart'; +import 'package:star_lock/flavors.dart'; +import 'package:star_lock/talk/call/callTalk.dart'; +import 'package:star_lock/talk/starChart/constant/talk_status.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/star_chart_manage.dart'; +import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_logic.dart'; +import 'package:star_lock/talk/starChart/views/native/talk_view_native_decode_state.dart'; +import 'package:star_lock/talk/starChart/views/talkView/talk_view_logic.dart'; +import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart'; +import 'package:video_decode_plugin/video_decode_plugin.dart'; + +import '../../../../app_settings/app_colors.dart'; +import '../../../../tools/showTFView.dart'; + +class TalkViewNativeDecodePageDebug extends StatefulWidget { + const TalkViewNativeDecodePageDebug({Key? key}) : super(key: key); + + @override + State createState() => _TalkViewNativeDecodePageDebugState(); +} + +class _TalkViewNativeDecodePageDebugState extends State with TickerProviderStateMixin { + final TalkViewNativeDecodeLogic logic = Get.put(TalkViewNativeDecodeLogic()); + final TalkViewNativeDecodeState state = Get.find().state; + final startChartManage = StartChartManage(); + + @override + void initState() { + super.initState(); + + state.animationController = AnimationController( + vsync: this, // 确保使用的TickerProvider是当前Widget + duration: const Duration(seconds: 1), + ); + + state.animationController.repeat(); + //动画开始、结束、向前移动或向后移动时会调用StatusListener + state.animationController.addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + state.animationController.reset(); + state.animationController.forward(); + } else if (status == AnimationStatus.dismissed) { + state.animationController.reset(); + state.animationController.forward(); + } + }); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () async { + // 返回 false 表示禁止退出 + return false; + }, + child: Container( + width: 1.sw, + height: 1.sh, + color: Colors.blue, + child: Stack( + alignment: Alignment.center, + children: [ + // 悬浮帧率统计信息条 + Obx( + () { + final double screenWidth = MediaQuery.of(context).size.width; + final double screenHeight = MediaQuery.of(context).size.height; + + // 防御性处理:只要loading中或textureId为null,优先渲染loading/占位 + if (state.isLoading.isTrue || state.textureId.value == null) { + return Image.asset( + 'images/main/monitorBg.png', + width: screenWidth, + height: screenHeight, + fit: BoxFit.cover, + ); + } else { + return PopScope( + canPop: false, + child: RepaintBoundary( + key: state.globalKey, + child: SizedBox( + width: StartChartManage().videoHeight.w, + height: StartChartManage().videoWidth.h, + child: RotatedBox( + // 解码器不支持硬件旋转,使用RotatedBox + quarterTurns: startChartManage.rotateAngle ~/ 90, + child: Platform.isIOS + ? Transform.scale( + scale: 1.008, // 轻微放大,消除iOS白边 + child: Texture( + textureId: state.textureId.value!, + filterQuality: FilterQuality.medium, + ), + ) + : Texture( + textureId: state.textureId.value!, + filterQuality: FilterQuality.medium, + ), + ), + ), + ), + ); + } + }, + ), + + Obx(() => state.isLoading.isTrue + ? Positioned( + bottom: 310.h, + child: Text( + '正在创建安全连接...'.tr, + style: TextStyle(color: Colors.black, fontSize: 26.sp), + )) + : Container()), + Obx(() => state.isLoading.isFalse && state.oneMinuteTime.value > 0 + ? Positioned( + top: ScreenUtil().statusBarHeight + 75.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), + ), + ], + ); + }, + ), + ) + : Container()), + Positioned( + bottom: 10.w, + child: Container( + width: 1.sw - 30.w * 2, + // height: 300.h, + margin: EdgeInsets.all(30.w), + decoration: + BoxDecoration(color: Colors.black.withOpacity(0.2), borderRadius: BorderRadius.circular(20.h)), + child: Column( + children: [ + SizedBox(height: 20.h), + bottomTopBtnWidget(), + SizedBox(height: 20.h), + bottomBottomBtnWidget(), + SizedBox(height: 20.h), + ], + ), + ), + ), + Obx(() => state.isLoading.isTrue ? buildRotationTransition() : 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( + '正在说话...'.tr, + style: TextStyle(fontSize: 20.sp, color: Colors.white), + ), + ], + ), + ), + ), + ) + : Container()), + ], + ), + ), + ); + } + + Widget bottomTopBtnWidget() { + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + // 打开关闭声音 + GestureDetector( + onTap: () { + if (state.talkStatus.value == TalkStatus.answeredSuccessfully) { + // 打开关闭声音 + logic.updateTalkExpect(); + } + }, + child: Container( + width: 50.w, + height: 50.w, + padding: EdgeInsets.all(5.w), + child: Obx(() => Image( + width: 40.w, + height: 40.w, + image: state.isOpenVoice.value + ? const AssetImage('images/main/icon_lockDetail_monitoringOpenVoice.png') + : const AssetImage('images/main/icon_lockDetail_monitoringCloseVoice.png'))), + ), + ), + SizedBox(width: 50.w), + // 截图 + GestureDetector( + onTap: () async { + if (state.talkStatus.value == TalkStatus.answeredSuccessfully) { + await logic.captureAndSavePng(); + } + }, + child: Container( + width: 50.w, + height: 50.w, + padding: EdgeInsets.all(5.w), + child: Image( + width: 40.w, + height: 40.w, + image: const AssetImage('images/main/icon_lockDetail_monitoringScreenshot.png')), + ), + ), + SizedBox(width: 50.w), + // 录制 + GestureDetector( + onTap: () async { + logic.showToast('功能暂未开放'.tr); + // if ( + // state.talkStatus.value == TalkStatus.answeredSuccessfully) { + // if (state.isRecordingScreen.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'), + ), + ), + ), + SizedBox(width: 50.w), + // 清晰度切换按钮 + GestureDetector( + onTap: () async { + // 弹出底部弹出层,选择清晰度 + showModalBottomSheet( + context: context, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20.w)), + ), + builder: (BuildContext context) { + final List qualities = ['高清', '标清']; + return SafeArea( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: qualities.map((q) { + return Obx(() => InkWell( + onTap: () { + Navigator.of(context).pop(); + logic.onQualityChanged(q); + }, + child: Container( + padding: EdgeInsets.symmetric(vertical: 18.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + q, + style: TextStyle( + color: state.currentQuality.value == q ? AppColors.mainColor : Colors.black, + fontWeight: state.currentQuality.value == q ? FontWeight.bold : FontWeight.normal, + fontSize: 28.sp, + ), + ), + ], + ), + ), + )); + }).toList(), + ), + ), + ); + }, + ); + }, + child: Container( + child: Icon(Icons.high_quality_outlined, color: Colors.white, size: 38.w), + ), + ), + Visibility( + visible: state.currentLanguage == 'zh_CN' && Platform.isAndroid, + child: SizedBox(width: 38.w), + ), + Visibility( + visible: state.currentLanguage == 'zh_CN' && Platform.isAndroid, + child: IconButton( + icon: Icon( + Icons.notification_add_sharp, + size: 32.w, + color: Colors.white, + ), + onPressed: () { + Get.toNamed(Routers.permissionGuidancePage); + }, + ), + ) + ]); + } + + Widget bottomBottomBtnWidget() { + return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + // 接听 + Obx( + () => bottomBtnItemWidget( + getAnswerBtnImg(), + getAnswerBtnName(), + Colors.white, + longPress: () async { + if (state.talkStatus.value == TalkStatus.answeredSuccessfully) { + // 启动录音 + logic.startProcessingAudio(); + state.isLongPressing.value = true; + } + }, + longPressUp: () async { + // 停止录音 + logic.stopProcessingAudio(); + state.isLongPressing.value = false; + }, + onClick: () async { + if (state.talkStatus.value == TalkStatus.passiveCallWaitingAnswer) { + // 接听 + logic.initiateAnswerCommand(); + } + }, + ), + ), + bottomBtnItemWidget('images/main/icon_lockDetail_hangUp.png', '挂断'.tr, Colors.red, onClick: () { + // 挂断 + logic.udpHangUpAction(); + }), + bottomBtnItemWidget( + 'images/main/icon_lockDetail_monitoringUnlock.png', + '开锁'.tr, + AppColors.mainColor, + onClick: () { + // if (state.talkStatus.value == TalkStatus.answeredSuccessfully && + // state.listData.value.length > 0) { + // logic.udpOpenDoorAction(); + // } + // if (UDPManage().remoteUnlock == 1) { + // logic.udpOpenDoorAction(); + // showDeletPasswordAlertDialog(context); + // } else { + // logic.showToast('请在锁设置中开启远程开锁'.tr); + // } + logic.remoteOpenLock(); + }, + ) + ]); + } + + String getAnswerBtnImg() { + switch (state.talkStatus.value) { + case TalkStatus.passiveCallWaitingAnswer: + return 'images/main/icon_lockDetail_monitoringAnswerCalls.png'; + case TalkStatus.answeredSuccessfully: + case TalkStatus.proactivelyCallWaitingAnswer: + return 'images/main/icon_lockDetail_monitoringUnTalkback.png'; + default: + return 'images/main/icon_lockDetail_monitoringAnswerCalls.png'; + } + } + + String getAnswerBtnName() { + switch (state.talkStatus.value) { + case TalkStatus.passiveCallWaitingAnswer: + return '接听'.tr; + case TalkStatus.proactivelyCallWaitingAnswer: + case TalkStatus.answeredSuccessfully: + return '长按说话'.tr; + default: + return '接听'.tr; + } + } + + Widget bottomBtnItemWidget( + String iconUrl, + String name, + Color backgroundColor, { + required Function() onClick, + Function()? longPress, + Function()? longPressUp, + }) { + double wh = 80.w; + return GestureDetector( + onTap: onClick, + onLongPress: longPress, + onLongPressUp: longPressUp, + child: SizedBox( + height: 160.w, + width: 140.w, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: wh, + height: wh, + constraints: BoxConstraints( + minWidth: wh, + ), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular((wh + 10.w * 2) / 2), + ), + padding: EdgeInsets.all(20.w), + child: Image.asset(iconUrl, fit: BoxFit.fitWidth), + ), + SizedBox(height: 20.w), + Text( + name, + style: TextStyle(fontSize: 20.sp, color: Colors.white), + textAlign: TextAlign.center, // 当文本超出指定行数时,使用省略号表示 + maxLines: 2, // 设置最大行数为1 + ) + ], + ), + ), + ); + } + + // 根据丢包率返回对应的颜色 + Color _getPacketLossColor(double lossRate) { + if (lossRate < 1.0) { + return Colors.green; // 丢包率低于1%显示绿色 + } else if (lossRate < 5.0) { + return Colors.yellow; // 丢包率1%-5%显示黄色 + } else if (lossRate < 10.0) { + return Colors.orange; // 丢包率5%-10%显示橙色 + } else { + return Colors.red; // 丢包率高于10%显示红色 + } + } + + //旋转动画 + Widget buildRotationTransition() { + return Positioned( + left: ScreenUtil().screenWidth / 2 - 220.w / 2, + top: ScreenUtil().screenHeight / 2 - 220.w / 2 - 150.h, + child: GestureDetector( + child: RotationTransition( + //设置动画的旋转中心 + alignment: Alignment.center, + //动画控制器 + turns: state.animationController, + //将要执行动画的子view + child: AnimatedOpacity( + opacity: 0.5, + duration: const Duration(seconds: 2), + child: Image.asset( + 'images/main/realTime_connecting.png', + width: 220.w, + height: 220.w, + ), + ), + ), + onTap: () { + state.animationController.forward(); + }, + ), + ); + } + + @override + void dispose() { + state.animationController.dispose(); + CallTalk().finishAVData(); + super.dispose(); + } +}