Merge branch 'develop_liyi' into 'develop'
Develop liyi See merge request StarlockTeam/app-starlock!21
This commit is contained in:
commit
88b66be9a1
@ -1,86 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<title>play</title>
|
||||
</head>
|
||||
<style>
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden; /* 防止滚动条出现 */
|
||||
overflow: hidden;
|
||||
/* 防止滚动条出现 */
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: white;
|
||||
overflow: hidden; /* 防止滚动条出现 */
|
||||
overflow: hidden;
|
||||
/* 防止滚动条出现 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#player {
|
||||
object-fit:cover;
|
||||
object-fit: cover;
|
||||
height: 56vh;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
</style>
|
||||
|
||||
<body>
|
||||
|
||||
<video autoplay muted poster="images/loader-thumb.jpg"
|
||||
id="player">
|
||||
</video>
|
||||
<script src="jmuxer.min.js"></script>
|
||||
<script>
|
||||
<video autoplay muted poster="images/loader-thumb.jpg" id="player">
|
||||
</video>
|
||||
<script src="jmuxer.min.js"></script>
|
||||
<script>
|
||||
|
||||
if (typeof JMuxer === 'undefined') {
|
||||
console.error("JMuxer is not defined. Check if jmuxer.min.js is loaded correctly.");
|
||||
} else {
|
||||
console.log("JMuxer loaded successfully.");
|
||||
}
|
||||
let jmuxer;
|
||||
window.onload = function() {
|
||||
try {
|
||||
jmuxer = new JMuxer({
|
||||
node: 'player',
|
||||
mode: 'video',
|
||||
debug: false,
|
||||
webgl: true, // 启用WebGL加速
|
||||
webglOptions: {
|
||||
preserveDrawingBuffer: false,
|
||||
antialias: false
|
||||
},
|
||||
flushingTime: 0, // 禁用自动刷新
|
||||
clearBuffer: false, // 保留解码缓存
|
||||
fps:20,
|
||||
onReady: () => {
|
||||
console.log('播放器初始化完成');
|
||||
// 通知Flutter端准备就绪
|
||||
window.Flutter.postMessage('ready');
|
||||
}
|
||||
});
|
||||
jmuxer.reset();
|
||||
console.log("JMuxer initialized.");
|
||||
} catch (e) {
|
||||
console.error("Error initializing JMuxer:", e);
|
||||
if (typeof JMuxer === 'undefined') {
|
||||
console.error("JMuxer is not defined. Check if jmuxer.min.js is loaded correctly.");
|
||||
} else {
|
||||
console.log("JMuxer loaded successfully.");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// Feed data from Flutter
|
||||
function feedDataFromFlutter(data) {
|
||||
const buffer = new Uint8Array(data);
|
||||
jmuxer.feed({
|
||||
video: buffer,
|
||||
|
||||
let jmuxer;
|
||||
window.onload = function () {
|
||||
try {
|
||||
jmuxer = new JMuxer({
|
||||
node: 'player',
|
||||
mode: 'video',
|
||||
debug: false,
|
||||
readfpsfromtrack: true,
|
||||
flushingTime: 0, // 立即刷新
|
||||
clearBuffer: true, // 丢弃延迟帧
|
||||
fps: 25, // 强制指定帧率
|
||||
onReady: () => {
|
||||
console.log('播放器初始化完成');
|
||||
// 通知Flutter端准备就绪
|
||||
window.Flutter.postMessage('ready');
|
||||
},
|
||||
onMissingVideoFrames: (missingFrames) => {
|
||||
// console.log('Missing video frames:', missingFrames);
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error initializing JMuxer:", e);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// Feed data from Flutter
|
||||
function feedDataFromFlutter(data) {
|
||||
const buffer = new Uint8Array(data);
|
||||
jmuxer.feed({
|
||||
video: buffer,
|
||||
duration: 40 // 每帧持续时间40ms(25fps)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Optional: notify Flutter
|
||||
@ -92,11 +93,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Function to return to Flutter page
|
||||
function returnToFlutter() {
|
||||
notifyFlutter("Returning to Flutter page");
|
||||
}
|
||||
</script>
|
||||
// Function to return to Flutter page
|
||||
function returnToFlutter() {
|
||||
notifyFlutter("Returning to Flutter page");
|
||||
}
|
||||
|
||||
// 添加清理方法
|
||||
function cleanupJMuxer() {
|
||||
if (jmuxer) {
|
||||
try {
|
||||
jmuxer.destroy();
|
||||
jmuxer = null;
|
||||
console.log('JMuxer cleaned up successfully');
|
||||
window.Flutter.postMessage('cleanup_complete');
|
||||
} catch (e) {
|
||||
console.error('Error cleaning up JMuxer:', e);
|
||||
window.Flutter.postMessage('cleanup_error');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
</html>
|
||||
@ -173,9 +173,9 @@
|
||||
"接收人信息": "收件人",
|
||||
"转移网关": "傳輸網關",
|
||||
"锁屏": "屏幕鎖定",
|
||||
"已关闭": "關閉",
|
||||
"已开启": "上",
|
||||
"开启": "開",
|
||||
"已关闭": "已關閉",
|
||||
"已开启": "已開啟",
|
||||
"开启": "開啟",
|
||||
"确定要开启重置键?": "繼續啟用Reset掣?",
|
||||
"确定要关闭重置键?": "繼續禁用Reset掣?",
|
||||
"隐藏无效开锁权限": "隱藏無效訪問權限",
|
||||
|
||||
@ -172,9 +172,9 @@
|
||||
"接收人信息": "收件人",
|
||||
"转移网关": "傳輸網關",
|
||||
"锁屏": "屏幕鎖定",
|
||||
"已关闭": "關",
|
||||
"已开启": "開啟",
|
||||
"开启": "打開",
|
||||
"已关闭": "已關閉",
|
||||
"已开启": "已開啟",
|
||||
"开启": "開啟",
|
||||
"确定要开启重置键?": "是否繼續啟用重置按鈕?",
|
||||
"确定要关闭重置键?": "是否繼續禁用重置按鈕?",
|
||||
"隐藏无效开锁权限": "隱藏無效訪問",
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:star_lock/apm/apm_helper.dart';
|
||||
import 'package:jverify/jverify.dart';
|
||||
import 'package:star_lock/appRouters.dart';
|
||||
import 'package:star_lock/app_settings/app_settings.dart';
|
||||
import 'package:star_lock/blue/blue_manage.dart';
|
||||
import 'package:star_lock/blue/io_tool/manager_event_bus.dart';
|
||||
import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart';
|
||||
import 'package:star_lock/login/login/entity/LoginEntity.dart';
|
||||
import 'package:star_lock/mine/mine/starLockMine_state.dart';
|
||||
import 'package:star_lock/mine/minePersonInfo/minePersonInfoPage/minePersonInfo_entity.dart';
|
||||
import 'package:star_lock/network/start_chart_api.dart';
|
||||
import 'package:star_lock/talk/starChart/entity/star_chart_register_node_entity.dart';
|
||||
import 'package:star_lock/tools/appFirstEnterHandle.dart';
|
||||
import 'package:star_lock/tools/baseGetXController.dart';
|
||||
import 'package:star_lock/translations/current_locale_tool.dart';
|
||||
|
||||
import '../../main/lockMian/lockMain/lockMain_logic.dart';
|
||||
import '../../mine/mine/starLockMine_logic.dart';
|
||||
@ -143,7 +145,7 @@ class StarLockLoginLogic extends BaseGetXController {
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
if (state.countryName != entity.data!.name) {
|
||||
ShowTipView().showSureAlertDialog(
|
||||
'国家地区的选择将影响数据安全,你当前选择的是'+state.countryName+'请确认后再继续'.tr,
|
||||
'国家地区的选择将影响数据安全,你当前选择的是' + state.countryName + '请确认后再继续'.tr,
|
||||
tipTitle: '确认国家或地区'.tr,
|
||||
sureStr: '我知道了'.tr);
|
||||
}
|
||||
@ -175,20 +177,43 @@ class StarLockLoginLogic extends BaseGetXController {
|
||||
state.canNext.value = state.pwdIsOK && state.isEmailOrPhone;
|
||||
}
|
||||
|
||||
late StreamSubscription _agreePrivacySubscription;
|
||||
|
||||
void _initEventListen() {
|
||||
_agreePrivacySubscription = eventBus
|
||||
.on<AgreePrivacyAgreement>()
|
||||
.listen((AgreePrivacyAgreement event) async {
|
||||
/// 检查ip如果属于国内才进行初始化
|
||||
final CheckIPEntity entity = await ApiRepository.to.checkIpAction(ip: '');
|
||||
String currentLanguage =
|
||||
CurrentLocaleTool.getCurrentLocaleString(); // 当前选择语言
|
||||
// 判断如果ip是国内的且选的是中文才初始化一键登录
|
||||
if (entity.data!.abbreviation?.toLowerCase() == 'cn' &&
|
||||
currentLanguage == 'zh_CN') {
|
||||
// 初始化一键登录服务
|
||||
await JverifyOneClickLoginManage();
|
||||
state.isCheckVerifyEnable.value =
|
||||
await JverifyOneClickLoginManage().checkVerifyEnable();
|
||||
AppLog.log('一键登录初始化认证结果:${state.isCheckVerifyEnable.value}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onInit() async {
|
||||
_initEventListen();
|
||||
super.onInit();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// 取消事件监听
|
||||
_agreePrivacySubscription.cancel();
|
||||
state.onClose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
//刷新设备信息
|
||||
//刷新设备信息
|
||||
Future<void> flushedDeviceInfo() async {
|
||||
XSConstantMacro().getDeviceInfoData().then((Map<String, dynamic> data) {
|
||||
state.deviceInfoMap.value = data;
|
||||
|
||||
@ -10,19 +10,23 @@ import 'package:path/path.dart' as path;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:star_lock/flavors.dart';
|
||||
import 'package:star_lock/login/login/starLock_login_logic.dart';
|
||||
import 'package:star_lock/login/register/entity/checkIP_entity.dart';
|
||||
import 'package:star_lock/mine/about/debug/debug_tool.dart';
|
||||
import 'package:star_lock/network/api_provider.dart';
|
||||
import 'package:star_lock/network/api_repository.dart';
|
||||
import 'package:star_lock/network/start_chart_api.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/impl/debug_Info_model.dart';
|
||||
import 'package:star_lock/talk/starChart/status/appLifecycle_observer.dart';
|
||||
import 'package:star_lock/tools/baseGetXController.dart';
|
||||
import 'package:star_lock/tools/bugly/bugly_tool.dart';
|
||||
import 'package:star_lock/tools/device_info_service.dart';
|
||||
import 'package:star_lock/tools/eventBusEventManage.dart';
|
||||
import 'package:star_lock/tools/jverify_one_click_login.dart';
|
||||
import 'package:star_lock/tools/platform_info_services.dart';
|
||||
import 'package:star_lock/tools/push/notification_service.dart';
|
||||
import 'package:star_lock/tools/push/xs_jPhush.dart';
|
||||
import 'package:star_lock/tools/storage.dart';
|
||||
import 'package:star_lock/translations/current_locale_tool.dart';
|
||||
import 'package:star_lock/translations/trans_lib.dart';
|
||||
|
||||
import 'apm/apm_helper.dart';
|
||||
@ -102,10 +106,20 @@ Future<void> privacySDKInitialization() async {
|
||||
await jpushProvider.initJPushService();
|
||||
NotificationService().init(); // 初始化通知服务
|
||||
|
||||
// 初始化一键登录服务
|
||||
final StarLockLoginLogic loginLogic = Get.put(StarLockLoginLogic());
|
||||
JverifyOneClickLoginManage();
|
||||
loginLogic.oneClickLoginAction();
|
||||
loginLogic.state.isCheckVerifyEnable.value =
|
||||
await JverifyOneClickLoginManage().checkVerifyEnable();
|
||||
/// 检查ip如果属于国内才进行初始化
|
||||
final CheckIPEntity entity = await ApiRepository.to.checkIpAction(ip: '');
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
String currentLanguage =
|
||||
CurrentLocaleTool.getCurrentLocaleString(); // 当前选择语言
|
||||
// 判断如果ip是国内的且选的是中文才初始化一键登录
|
||||
if (entity.data!.abbreviation?.toLowerCase() == 'cn' &&
|
||||
currentLanguage == 'zh_CN') {
|
||||
// 初始化一键登录服务
|
||||
final StarLockLoginLogic loginLogic = Get.put(StarLockLoginLogic());
|
||||
await JverifyOneClickLoginManage();
|
||||
loginLogic.state.isCheckVerifyEnable.value =
|
||||
await JverifyOneClickLoginManage().checkVerifyEnable();
|
||||
eventBus.fire(AgreePrivacyAgreement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ class AddICCardLogic extends BaseGetXController {
|
||||
final List<int> token = reply.data.sublist(5, 9);
|
||||
final List<String> saveStrList = changeIntListToStringList(token);
|
||||
Storage.setStringList(saveBlueToken, saveStrList);
|
||||
// AppLog.log('添加卡token:$token');
|
||||
AppLog.log('添加卡token:$token');
|
||||
|
||||
IoSenderManage.senderAddCardWithTimeCycleCoercionCommand(
|
||||
keyID: '1',
|
||||
@ -107,6 +107,14 @@ class AddICCardLogic extends BaseGetXController {
|
||||
token: token,
|
||||
isBeforeAddUser: false);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 12:
|
||||
// 管理员已满
|
||||
state.ifAddState.value = false;
|
||||
showToast('管理员已满'.tr, something: () {
|
||||
Get.back();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
state.ifAddState.value = false;
|
||||
@ -146,6 +154,7 @@ class AddICCardLogic extends BaseGetXController {
|
||||
Get.close(1);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 0x12:
|
||||
// 管理员已满
|
||||
showToast('管理员已满'.tr);
|
||||
Get.close(1);
|
||||
|
||||
@ -28,6 +28,7 @@ class CardListLogic extends BaseGetXController {
|
||||
|
||||
// 获取解析后的数据
|
||||
late StreamSubscription<Reply> _replySubscription;
|
||||
|
||||
void _initReplySubscription() {
|
||||
_replySubscription =
|
||||
EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
|
||||
@ -69,11 +70,15 @@ class CardListLogic extends BaseGetXController {
|
||||
userID: (await Storage.getUid())!,
|
||||
cardNo: state.deletCardNo,
|
||||
useCountLimit: 0xffff,
|
||||
operate: state.isDeletAll == true ? 3 : 2, // 0:注册 1:修改 2:删除 3:删除全部
|
||||
operate: state.isDeletAll == true ? 3 : 2,
|
||||
// 0:注册 1:修改 2:删除 3:删除全部
|
||||
isAdmin: 0,
|
||||
isForce: 0, // 是否是胁迫
|
||||
isRound: 0, // 是否是循环
|
||||
weekRound: 0, // 周循环
|
||||
isForce: 0,
|
||||
// 是否是胁迫
|
||||
isRound: 0,
|
||||
// 是否是循环
|
||||
weekRound: 0,
|
||||
// 周循环
|
||||
startDate: 0x11223344,
|
||||
endDate: 0x11223344,
|
||||
startTime: '0',
|
||||
@ -116,11 +121,15 @@ class CardListLogic extends BaseGetXController {
|
||||
userID: (await Storage.getUid())!,
|
||||
cardNo: state.deletCardNo,
|
||||
useCountLimit: 0xffff,
|
||||
operate: state.isDeletAll == true ? 3 : 2, // 0:注册 1:修改 2:删除 3:删除全部
|
||||
operate: state.isDeletAll == true ? 3 : 2,
|
||||
// 0:注册 1:修改 2:删除 3:删除全部
|
||||
isAdmin: 0,
|
||||
isForce: 0, // 是否是胁迫
|
||||
isRound: 0, // 是否是循环
|
||||
weekRound: 0, // 周循环
|
||||
isForce: 0,
|
||||
// 是否是胁迫
|
||||
isRound: 0,
|
||||
// 是否是循环
|
||||
weekRound: 0,
|
||||
// 周循环
|
||||
startDate: 0x11223344,
|
||||
endDate: 0x11223344,
|
||||
startTime: '0',
|
||||
@ -193,6 +202,7 @@ class CardListLogic extends BaseGetXController {
|
||||
|
||||
// 监听修改完详情之后刷新列表
|
||||
late StreamSubscription _teamEvent;
|
||||
|
||||
void _initRefreshAction() {
|
||||
_teamEvent = eventBus
|
||||
.on<OtherTypeRefreshListEvent>()
|
||||
@ -240,6 +250,7 @@ class CardListLogic extends BaseGetXController {
|
||||
|
||||
_initRefreshAction();
|
||||
}
|
||||
await getICCardListData(isRefresh: true);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -31,24 +31,24 @@ class _CardListPageState extends State<CardListPage> with RouteAware {
|
||||
final CardListLogic logic = Get.put(CardListLogic());
|
||||
final CardListState state = Get.find<CardListLogic>().state;
|
||||
|
||||
Future<void> getHttpData({required bool isRefresh}) async {
|
||||
final bool? isDemoMode = await Storage.getBool(ifIsDemoModeOrNot);
|
||||
if (isDemoMode == false) {
|
||||
logic
|
||||
.getICCardListData(isRefresh: isRefresh)
|
||||
.then((FingerprintListDataEntity value) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Future<void> logic.getICCardListData({required bool isRefresh}) async {
|
||||
// final bool? isDemoMode = await Storage.getBool(ifIsDemoModeOrNot);
|
||||
// if (isDemoMode == false) {
|
||||
// logic
|
||||
// .getICCardListData(isRefresh: isRefresh)
|
||||
// .then((FingerprintListDataEntity value) {
|
||||
// if (mounted) {
|
||||
// setState(() {});
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
getHttpData(isRefresh: true);
|
||||
// logic.getICCardListData(isRefresh: true);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -92,17 +92,17 @@ class _CardListPageState extends State<CardListPage> with RouteAware {
|
||||
),
|
||||
body: EasyRefreshTool(
|
||||
onRefresh: () {
|
||||
getHttpData(isRefresh: true);
|
||||
logic.getICCardListData(isRefresh: true);
|
||||
},
|
||||
onLoad: () {
|
||||
getHttpData(isRefresh: false);
|
||||
logic.getICCardListData(isRefresh: false);
|
||||
},
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
KeySearchWidget(
|
||||
editingController: state.searchController,
|
||||
onSubmittedAction: () {
|
||||
getHttpData(isRefresh: true);
|
||||
logic.getICCardListData(isRefresh: true);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 20.h),
|
||||
@ -115,10 +115,10 @@ class _CardListPageState extends State<CardListPage> with RouteAware {
|
||||
'fromType': 1 // 1从添加钥匙列表进入 2从考勤添加员工入口进入
|
||||
})!
|
||||
.then((value) {
|
||||
getHttpData(isRefresh: true);
|
||||
logic.getICCardListData(isRefresh: true);
|
||||
});
|
||||
// if (data != null) {
|
||||
// getHttpData(isRefresh: true);
|
||||
// logic.getICCardListData(isRefresh: true);
|
||||
// }
|
||||
},
|
||||
),
|
||||
@ -180,9 +180,9 @@ class _CardListPageState extends State<CardListPage> with RouteAware {
|
||||
arguments: <String, FingerprintItemData>{
|
||||
'fingerprintItemData': fingerprintItemData,
|
||||
})!
|
||||
.then((value) => getHttpData(isRefresh: true));
|
||||
.then((value) => logic.getICCardListData(isRefresh: true));
|
||||
// if (data != null) {
|
||||
// getHttpData(isRefresh: true);
|
||||
// logic.getICCardListData(isRefresh: true);
|
||||
// }
|
||||
}),
|
||||
);
|
||||
|
||||
@ -54,7 +54,7 @@ class AddFaceLogic extends BaseGetXController {
|
||||
|
||||
// 最大图片数
|
||||
state.maxRegCount.value = reply.data[11];
|
||||
// AppLog.log('人脸开始state.maxRegCount.value:${state.maxRegCount.value}');
|
||||
AppLog.log('人脸开始state.maxRegCount.value:${state.maxRegCount.value}');
|
||||
break;
|
||||
case 0x06:
|
||||
//无权限
|
||||
@ -89,6 +89,12 @@ class AddFaceLogic extends BaseGetXController {
|
||||
isBeforeAddUser: false
|
||||
);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 12:
|
||||
// 管理员已满
|
||||
showToast('管理员已满'.tr);
|
||||
Get.close(1);
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
state.ifAddState.value = false;
|
||||
@ -112,9 +118,9 @@ class AddFaceLogic extends BaseGetXController {
|
||||
Get.close(1);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 12:
|
||||
// 管理员已满
|
||||
showToast('管理员已满'.tr);
|
||||
state.ifAddState.value = false;
|
||||
Get.close(1);
|
||||
break;
|
||||
case 0xFD:
|
||||
@ -138,7 +144,7 @@ class AddFaceLogic extends BaseGetXController {
|
||||
// 添加人脸中
|
||||
// 当前注册数
|
||||
state.regIndex.value = reply.data[6];
|
||||
// AppLog.log('注册人脸过程state.regIndex.value:${state.regIndex.value}');
|
||||
AppLog.log('注册人脸过程state.regIndex.value:${state.regIndex.value}');
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ class FaceListLogic extends BaseGetXController {
|
||||
|
||||
// 获取解析后的数据
|
||||
late StreamSubscription<Reply> _replySubscription;
|
||||
|
||||
void _initReplySubscription() {
|
||||
_replySubscription =
|
||||
EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
|
||||
@ -77,11 +78,15 @@ class FaceListLogic extends BaseGetXController {
|
||||
userID: (await Storage.getUid())!,
|
||||
faceNo: state.deletFaceNo,
|
||||
useCountLimit: 0xffff,
|
||||
operate: state.isDeletAll == true ? 3 : 2, // 0:注册 1:修改 2:删除 3:删除全部
|
||||
operate: state.isDeletAll == true ? 3 : 2,
|
||||
// 0:注册 1:修改 2:删除 3:删除全部
|
||||
isAdmin: 0,
|
||||
isForce: 0, // 是否是胁迫
|
||||
isRound: 0, // 是否是循环
|
||||
weekRound: 0, // 周循环
|
||||
isForce: 0,
|
||||
// 是否是胁迫
|
||||
isRound: 0,
|
||||
// 是否是循环
|
||||
weekRound: 0,
|
||||
// 周循环
|
||||
startDate: 0x11223344,
|
||||
endDate: 0x11223344,
|
||||
startTime: '0',
|
||||
@ -277,11 +282,15 @@ class FaceListLogic extends BaseGetXController {
|
||||
userID: (await Storage.getUid())!,
|
||||
faceNo: state.deletFaceNo,
|
||||
useCountLimit: 0xffff,
|
||||
operate: state.isDeletAll == true ? 3 : 2, // 0:注册 1:修改 2:删除 3:删除全部
|
||||
operate: state.isDeletAll == true ? 3 : 2,
|
||||
// 0:注册 1:修改 2:删除 3:删除全部
|
||||
isAdmin: 0,
|
||||
isForce: 0, // 是否是胁迫
|
||||
isRound: 0, // 是否是循环
|
||||
weekRound: 0, // 周循环
|
||||
isForce: 0,
|
||||
// 是否是胁迫
|
||||
isRound: 0,
|
||||
// 是否是循环
|
||||
weekRound: 0,
|
||||
// 周循环
|
||||
startDate: 0x11223344,
|
||||
endDate: 0x11223344,
|
||||
startTime: '0',
|
||||
@ -348,8 +357,8 @@ class FaceListLogic extends BaseGetXController {
|
||||
lockId: state.lockId.value,
|
||||
);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
showToast('删除成功'.tr, something: () {
|
||||
getFaceListData(isRefresh: true);
|
||||
showToast('删除成功'.tr, something: () async {
|
||||
await getFaceListData(isRefresh: true);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -366,8 +375,8 @@ class FaceListLogic extends BaseGetXController {
|
||||
lockId: state.lockId.value,
|
||||
);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
showToast('重置成功'.tr, something: () {
|
||||
getFaceListData(isRefresh: true);
|
||||
showToast('重置成功'.tr, something: () async{
|
||||
await getFaceListData(isRefresh: true);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -403,11 +412,12 @@ class FaceListLogic extends BaseGetXController {
|
||||
|
||||
// 监听修改完详情之后刷新列表
|
||||
late StreamSubscription _teamEvent;
|
||||
|
||||
void _initRefreshAction() {
|
||||
_teamEvent = eventBus
|
||||
.on<OtherTypeRefreshListEvent>()
|
||||
.listen((OtherTypeRefreshListEvent event) {
|
||||
getFaceListData(isRefresh: true);
|
||||
.listen((OtherTypeRefreshListEvent event) async {
|
||||
await getFaceListData(isRefresh: true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -434,6 +444,7 @@ class FaceListLogic extends BaseGetXController {
|
||||
|
||||
// senderCheckingUserInfoCount();
|
||||
}
|
||||
getFaceListData(isRefresh: true);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
@ -31,24 +30,6 @@ class _FaceListPageState extends State<FaceListPage> with RouteAware {
|
||||
final FaceListLogic logic = Get.put(FaceListLogic());
|
||||
final FaceListState state = Get.find<FaceListLogic>().state;
|
||||
|
||||
Future<void> getHttpData({required bool isRefresh}) async {
|
||||
final bool? isDemoMode = await Storage.getBool(ifIsDemoModeOrNot);
|
||||
if (isDemoMode == false) {
|
||||
logic.getFaceListData(isRefresh: isRefresh).then((FingerprintListDataEntity value) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
getHttpData(isRefresh: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -92,17 +73,17 @@ class _FaceListPageState extends State<FaceListPage> with RouteAware {
|
||||
),
|
||||
body: EasyRefreshTool(
|
||||
onRefresh: () {
|
||||
getHttpData(isRefresh: true);
|
||||
logic.getFaceListData(isRefresh: true);
|
||||
},
|
||||
onLoad: () {
|
||||
getHttpData(isRefresh: false);
|
||||
logic.getFaceListData(isRefresh: false);
|
||||
},
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
KeySearchWidget(
|
||||
editingController: state.searchController,
|
||||
onSubmittedAction: () {
|
||||
getHttpData(isRefresh: true);
|
||||
logic.getFaceListData(isRefresh: true);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
@ -110,14 +91,15 @@ class _FaceListPageState extends State<FaceListPage> with RouteAware {
|
||||
),
|
||||
Expanded(child: _buildMainUI()),
|
||||
AddBottomWhiteBtn(
|
||||
btnName:
|
||||
'添加人脸'.tr,
|
||||
btnName: '添加人脸'.tr,
|
||||
onClick: () async {
|
||||
await Get.toNamed(Routers.addFaceTypePage, arguments: <String, int>{
|
||||
await Get.toNamed(Routers.addFaceTypePage,
|
||||
arguments: <String, int>{
|
||||
'lockId': state.lockId.value,
|
||||
'fromType': 1 // 1从添加钥匙列表进入 2从考勤添加员工入口进入
|
||||
})!.then((value) {
|
||||
getHttpData(isRefresh: true);
|
||||
})!
|
||||
.then((value) {
|
||||
logic.getFaceListData(isRefresh: true);
|
||||
});
|
||||
},
|
||||
),
|
||||
@ -154,9 +136,11 @@ class _FaceListPageState extends State<FaceListPage> with RouteAware {
|
||||
logic.getKeyType(getFaceItemData),
|
||||
logic.getKeyDateType(getFaceItemData), () async {
|
||||
await Get.toNamed(Routers.faceDetailPage,
|
||||
arguments: <String, FingerprintItemData>{
|
||||
arguments: <String, FingerprintItemData>{
|
||||
'faceItemData': getFaceItemData,
|
||||
})!.then((value) => getHttpData(isRefresh: true));
|
||||
})!
|
||||
.then((value) =>
|
||||
logic.getFaceListData(isRefresh: true));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -105,6 +105,14 @@ class AddFingerprintLogic extends BaseGetXController {
|
||||
token: token,
|
||||
isBeforeAddUser: false);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 12:
|
||||
// 管理员已满
|
||||
state.ifAddState.value = false;
|
||||
showToast('管理员已满'.tr, something: () {
|
||||
Get.back();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
state.ifAddState.value = false;
|
||||
@ -132,10 +140,12 @@ class AddFingerprintLogic extends BaseGetXController {
|
||||
Get.close(1);
|
||||
break;
|
||||
case 0xFE:
|
||||
// 管理员已满
|
||||
showToast('管理员已满'.tr);
|
||||
case 12:
|
||||
// 管理员已满
|
||||
state.ifAddState.value = false;
|
||||
Get.close(1);
|
||||
showToast('管理员已满'.tr, something: () {
|
||||
Get.back();
|
||||
});
|
||||
break;
|
||||
case 0xFD:
|
||||
// 用户已满
|
||||
|
||||
@ -404,8 +404,8 @@ class FingerprintListLogic extends BaseGetXController {
|
||||
void _initRefreshAction() {
|
||||
_teamEvent = eventBus
|
||||
.on<OtherTypeRefreshListEvent>()
|
||||
.listen((OtherTypeRefreshListEvent event) {
|
||||
getFingerprintsListData(isRefresh: true);
|
||||
.listen((OtherTypeRefreshListEvent event) async {
|
||||
await getFingerprintsListData(isRefresh: true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -460,7 +460,7 @@ class FingerprintListLogic extends BaseGetXController {
|
||||
if (isDemoMode == false) {
|
||||
_initReplySubscription();
|
||||
|
||||
_initRefreshAction();
|
||||
// _initRefreshAction();
|
||||
getFingerprintsListData(isRefresh: true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:star_lock/apm/apm_helper.dart';
|
||||
import 'package:star_lock/appRouters.dart';
|
||||
import 'package:star_lock/common/XSConstantMacro/XSConstantMacro.dart';
|
||||
import 'package:star_lock/login/login/entity/LoginEntity.dart';
|
||||
import 'package:star_lock/main/lockDetail/electronicKey/electronicKeyList/entity/ElectronicKeyListEntity.dart';
|
||||
@ -285,7 +286,7 @@ class LockDetailLogic extends BaseGetXController {
|
||||
final List<int> publicKeyData =
|
||||
state.keyInfos.value.bluetooth!.publicKey!.cast<int>();
|
||||
final List<String> saveStrList = changeIntListToStringList(publicKeyData);
|
||||
await Storage.setStringList(saveBluePublicKey, saveStrList);
|
||||
await Storage.setStringList(saveBluePublicKey, saveStrList);
|
||||
|
||||
// 私钥
|
||||
final List<int> privateKeyData =
|
||||
@ -298,7 +299,7 @@ class LockDetailLogic extends BaseGetXController {
|
||||
final List<int> signKeyData =
|
||||
state.keyInfos.value.bluetooth!.signKey!.cast<int>();
|
||||
final List<String> saveSignKeyList = changeIntListToStringList(signKeyData);
|
||||
await Storage.setStringList(saveBlueSignKey, saveSignKeyList);
|
||||
await Storage.setStringList(saveBlueSignKey, saveSignKeyList);
|
||||
|
||||
final bool ifHaveKey = await Storage.ifHaveKey(saveBlueToken);
|
||||
if (!ifHaveKey) {
|
||||
@ -814,7 +815,7 @@ class LockDetailLogic extends BaseGetXController {
|
||||
}
|
||||
});
|
||||
|
||||
eventBus
|
||||
state.DetailLockInfo = eventBus
|
||||
.on<PassCurrentLockInformationEvent>()
|
||||
.listen((PassCurrentLockInformationEvent event) {
|
||||
if (event.lockSetInfoData.lockSettingInfo != null &&
|
||||
@ -838,5 +839,12 @@ class LockDetailLogic extends BaseGetXController {
|
||||
state.keyInfos.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
state.SuccessfulDistributionNetworkEvent = eventBus
|
||||
.on<SuccessfulDistributionNetwork>()
|
||||
.listen((SuccessfulDistributionNetwork event) {
|
||||
// 配网成功获取一下配网信息
|
||||
requestDeviceNetworkInfo();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +89,6 @@ class _LockDetailPageState extends State<LockDetailPage>
|
||||
AppRouteObserver().routeObserver.subscribe(this, ModalRoute.of(context)!);
|
||||
state.isOpenLockNeedOnline.refresh();
|
||||
|
||||
logic.requestDeviceNetworkInfo();
|
||||
}
|
||||
|
||||
StreamSubscription? _lockRefreshLockDetailInfoDataEvent;
|
||||
@ -1024,7 +1023,7 @@ class _LockDetailPageState extends State<LockDetailPage>
|
||||
Widget widget = getBottomWidget()[index];
|
||||
return widget;
|
||||
},
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
// physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
)
|
||||
|
||||
|
||||
@ -14,6 +14,8 @@ class LockDetailState {
|
||||
late StreamSubscription<Reply> replySubscription;
|
||||
StreamSubscription? lockSetOpenOrCloseCheckInRefreshLockDetailWithAttendanceEvent;
|
||||
StreamSubscription? LockSetChangeSetRefreshLockDetailWithTypeSubscription;
|
||||
StreamSubscription? DetailLockInfo;
|
||||
StreamSubscription? SuccessfulDistributionNetworkEvent;
|
||||
|
||||
String lockNetToken = '0';
|
||||
int differentialTime = 0;// 服务器时间与本地时间差值
|
||||
|
||||
@ -62,18 +62,15 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
peerId: peerId,
|
||||
);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
// 设置锁的peerID
|
||||
StartChartManage().lockNetworkInfo = DeviceNetworkInfo(
|
||||
wifiName: wifiName,
|
||||
networkMac: networkMac,
|
||||
secretKey: secretKey,
|
||||
peerId: peerId,
|
||||
);
|
||||
|
||||
await _getUploadLockSet();
|
||||
showToast('配网成功'.tr, something: () async {
|
||||
eventBus
|
||||
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
||||
// 设置锁的peerID
|
||||
StartChartManage().lockNetworkInfo = DeviceNetworkInfo(
|
||||
wifiName: wifiName,
|
||||
networkMac: networkMac,
|
||||
secretKey: secretKey,
|
||||
peerId: peerId,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,8 +104,11 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
// WIFI配网结果
|
||||
Future<void> _replySenderConfiguringWifiResult(Reply reply) async {
|
||||
final int status = reply.data[2];
|
||||
state.sureBtnState.value = 0;
|
||||
state.isLoading.value = false;
|
||||
// state.sureBtnState.value = 0;
|
||||
|
||||
// 取消loading超时定时器
|
||||
state.loadingTimer?.cancel();
|
||||
state.loadingTimer = null;
|
||||
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
@ -135,21 +135,23 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
// 保存到缓存
|
||||
await Storage.saveLockNetWorkInfo(jsonMap);
|
||||
// 上报服务器
|
||||
updateNetworkInfo(
|
||||
updateNetworkInfo(
|
||||
peerId: peerId ?? '',
|
||||
wifiName: wifiName ?? '',
|
||||
secretKey: secretKey ?? '',
|
||||
deviceMac: deviceMac ?? '',
|
||||
networkMac: networkMac ?? '');
|
||||
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
dismissEasyLoading();
|
||||
dismissEasyLoading(); // 关闭loading
|
||||
cancelBlueConnetctToastTimer();
|
||||
if (state.loadingTimer != null) {
|
||||
state.loadingTimer!.cancel();
|
||||
state.loadingTimer = null;
|
||||
}
|
||||
cancelBlueConnetctToastTimer();
|
||||
|
||||
showToast('配网失败'.tr);
|
||||
state.isLoading.value = false;
|
||||
break;
|
||||
@ -165,7 +167,7 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
// 点击配置wifi
|
||||
Future<void> senderConfiguringWifiAction() async {
|
||||
AppLog.log('开始配网${EasyLoading.isShow}');
|
||||
EasyLoading.show();
|
||||
|
||||
if (state.isLoading.isTrue) {
|
||||
AppLog.log('正在配网中请勿重复点击');
|
||||
return;
|
||||
@ -179,10 +181,10 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
showToast('请输入WiFi密码'.tr);
|
||||
return;
|
||||
}
|
||||
if (state.sureBtnState.value == 1) {
|
||||
return;
|
||||
}
|
||||
state.sureBtnState.value = 1;
|
||||
// if (state.sureBtnState.value == 1) {
|
||||
// return;
|
||||
// }
|
||||
// state.sureBtnState.value = 1;
|
||||
|
||||
final GetGatewayConfigurationEntity entity =
|
||||
await ApiRepository.to.getGatewayConfigurationNotLoading(timeout: 60);
|
||||
@ -219,9 +221,13 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
// 如果为空,则直接赋值
|
||||
state.getGatewayConfigurationStr = "{\"userPeerld\": \"$appPeerId\"}";
|
||||
}
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(action: () {
|
||||
dismissEasyLoading();
|
||||
state.isLoading.value = false;
|
||||
});
|
||||
|
||||
AppLog.log('获取到配网信息===开始发送蓝牙指令${EasyLoading.isShow}');
|
||||
|
||||
// 发送配网指令
|
||||
BlueManage().blueSendData(
|
||||
BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState connectionState) async {
|
||||
@ -231,31 +237,19 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
password: state.wifiPWDController.text,
|
||||
gatewayConfigurationStr: state.getGatewayConfigurationStr,
|
||||
);
|
||||
} else if (connectionState == BluetoothConnectionState.disconnected) {
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
state.isLoading.value = false;
|
||||
// state.sureBtnState.value = 0;
|
||||
if (state.ifCurrentScreen.value == true) {
|
||||
showBlueConnetctToast();
|
||||
}
|
||||
}
|
||||
},
|
||||
isAddEquipment: true,
|
||||
isAddEquipment: false,
|
||||
);
|
||||
state.loadingTimer ??= Timer.periodic(Duration(milliseconds: 100), (timer) {
|
||||
if (!EasyLoading.isShow) {
|
||||
EasyLoading.show();
|
||||
}
|
||||
});
|
||||
|
||||
state.isLoading.value = true;
|
||||
// 添加15秒超时检查
|
||||
Future.delayed(const Duration(seconds: 15), () {
|
||||
if (state.isLoading.isTrue) {
|
||||
EasyLoading.dismiss();
|
||||
state.isLoading.value = false;
|
||||
state.sureBtnState.value = 0;
|
||||
if (state.loadingTimer != null) {
|
||||
state.loadingTimer!.cancel();
|
||||
state.loadingTimer = null;
|
||||
}
|
||||
showToast('配网失败'.tr);
|
||||
}
|
||||
});
|
||||
AppLog.log('发送方法执行完毕${EasyLoading.isShow}');
|
||||
}
|
||||
|
||||
// 获取设备状态
|
||||
@ -314,7 +308,7 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
|
||||
getWifiLockServiceIpAndPort();
|
||||
_initReplySubscription();
|
||||
getDevicesStatusAction();
|
||||
// getDevicesStatusAction();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -330,13 +324,13 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
|
||||
void _replyGatewayGetStatusReply(GatewayGetStatusReply reply) {
|
||||
final int status = reply.data[2];
|
||||
|
||||
//成功
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
//成功
|
||||
// state.sureBtnState.value = 0;
|
||||
cancelBlueConnetctToastTimer();
|
||||
dismissEasyLoading();
|
||||
// state.sureBtnState.value = 0;
|
||||
|
||||
final GetGatewayInfoModel gatewayModel = GetGatewayInfoModel();
|
||||
// 网关MAC地址
|
||||
@ -387,28 +381,17 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
}
|
||||
}
|
||||
|
||||
void _replyStatusInfo(reply) {}
|
||||
|
||||
// 上传数据获取设置
|
||||
Future<void> _getUploadLockSet() async {
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(action: () {
|
||||
dismissEasyLoading();
|
||||
});
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState connectionState) async {
|
||||
if (connectionState == BluetoothConnectionState.connected) {
|
||||
final List<String>? token = await Storage.getStringList(saveBlueToken);
|
||||
final List<int> getTokenList = changeStringListToIntList(token!);
|
||||
|
||||
_uploadLockSet(getTokenList);
|
||||
} else if (connectionState == BluetoothConnectionState.disconnected) {
|
||||
cancelBlueConnetctToastTimer();
|
||||
if (state.ifCurrentScreen.value == true) {
|
||||
showBlueConnetctToast();
|
||||
}
|
||||
}
|
||||
});
|
||||
final List<String>? token = await Storage.getStringList(saveBlueToken);
|
||||
final List<int> getTokenList = changeStringListToIntList(token!);
|
||||
|
||||
await _uploadLockSet(getTokenList);
|
||||
}
|
||||
|
||||
// 公共的上传锁设置
|
||||
@ -432,15 +415,15 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
// 上传数据获取锁设置解析
|
||||
Future<void> _replyUpdataLockSetReply(Reply reply) async {
|
||||
final int status = reply.data[2];
|
||||
dismissEasyLoading(); // 关闭loading
|
||||
cancelBlueConnetctToastTimer();
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
//成功
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
_lockDataUpload(
|
||||
await _lockDataUpload(
|
||||
uploadType: 1,
|
||||
recordType: 0,
|
||||
records: reply.data.sublist(7, reply.data.length));
|
||||
|
||||
break;
|
||||
case 0x06:
|
||||
//无权限
|
||||
@ -469,30 +452,18 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
records: records,
|
||||
isUnShowLoading: true);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
await Future.delayed((Duration(seconds: 1)));
|
||||
if (state.pageName.value == 'lockSet') {
|
||||
Get.close(2);
|
||||
} else {
|
||||
Get.offAllNamed(Routers.starLockMain);
|
||||
}
|
||||
dismissEasyLoading();
|
||||
if (state.loadingTimer != null) {
|
||||
state.loadingTimer!.cancel();
|
||||
state.loadingTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
showToast('配网成功'.tr, something: () {
|
||||
state.isLoading.value = false;
|
||||
if (state.pageName.value == 'lockSet') {
|
||||
Get.close(2);
|
||||
} else {
|
||||
Get.offAllNamed(Routers.starLockMain);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
dismissEasyLoading();
|
||||
// TODO: implement dispose
|
||||
super.dispose();
|
||||
state.isLoading.value = false;
|
||||
if (state.loadingTimer != null) {
|
||||
state.loadingTimer!.cancel();
|
||||
state.loadingTimer = null;
|
||||
eventBus
|
||||
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
||||
eventBus.fire(SuccessfulDistributionNetwork());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
@ -17,51 +16,59 @@ import '../../../../tools/eventBusEventManage.dart';
|
||||
import '../../../../tools/storage.dart';
|
||||
import 'normallyOpenMode_state.dart';
|
||||
|
||||
class NormallyOpenModeLogic extends BaseGetXController{
|
||||
class NormallyOpenModeLogic extends BaseGetXController {
|
||||
NormallyOpenModeState state = NormallyOpenModeState();
|
||||
|
||||
// 配置锁的常开模式设置
|
||||
Future<void> configPassageMode() async{
|
||||
if(state.weekDays.value.isEmpty){
|
||||
Future<void> configPassageMode() async {
|
||||
if (state.weekDays.value.isEmpty) {
|
||||
showToast('请选择常开日期'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
if(state.endTimeMinute.value < state.beginTimeMinute.value){
|
||||
if (state.endTimeMinute.value < state.beginTimeMinute.value) {
|
||||
showToast('结束时间不能小于开始时间哦'.tr);
|
||||
return;
|
||||
}
|
||||
|
||||
final List passageModeConfig = [];
|
||||
final Map<String, Object> map = <String, Object>{
|
||||
'isAllDay':state.isAllDay.value,
|
||||
'weekDays':state.weekDays.value,
|
||||
'startDate':state.beginTimeMinute.value,
|
||||
'endDate':state.endTimeMinute.value,
|
||||
'isAllDay': state.isAllDay.value,
|
||||
'weekDays': state.weekDays.value,
|
||||
'startDate': state.beginTimeMinute.value,
|
||||
'endDate': state.endTimeMinute.value,
|
||||
};
|
||||
passageModeConfig.add(map);
|
||||
|
||||
final LoginEntity entity = await ApiRepository.to.setNormallyModeData(
|
||||
lockId: state.lockSetInfoData.value.lockId!,
|
||||
passageMode:state.isOpenNormallyOpenMode.value == true ? 1:0,
|
||||
passageMode: state.isOpenNormallyOpenMode.value == true ? 1 : 0,
|
||||
passageModeConfig: passageModeConfig,
|
||||
);
|
||||
if(entity.errorCode!.codeIsSuccessful){
|
||||
showToast('操作成功'.tr, something: (){
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
showToast('操作成功'.tr, something: () {
|
||||
eventBus.fire(RefreshLockListInfoDataEvent());
|
||||
|
||||
state.lockSetInfoData.value.lockSettingInfo!.passageMode = state.isOpenNormallyOpenMode.value == true ? 1:0;
|
||||
eventBus.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
||||
eventBus.fire(LockSetChangeSetRefreshLockDetailWithType(2, state.lockSetInfoData.value.lockSettingInfo!.passageMode!.toString()));
|
||||
state.lockSetInfoData.value.lockSettingInfo!.passageMode =
|
||||
state.isOpenNormallyOpenMode.value == true ? 1 : 0;
|
||||
eventBus
|
||||
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
|
||||
eventBus.fire(LockSetChangeSetRefreshLockDetailWithType(
|
||||
2,
|
||||
state.lockSetInfoData.value.lockSettingInfo!.passageMode!
|
||||
.toString()));
|
||||
Get.back();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取解析后的数据
|
||||
late StreamSubscription<Reply> _replySubscription;
|
||||
|
||||
void _initReplySubscription() {
|
||||
_replySubscription = EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
|
||||
if(reply is SetSupportFunctionsWithParametersReply) {
|
||||
_replySubscription =
|
||||
EventBusManager().eventBus!.on<Reply>().listen((Reply reply) {
|
||||
if (reply is SetSupportFunctionsWithParametersReply) {
|
||||
_replySetSupportFunctionsWithParameters(reply);
|
||||
}
|
||||
|
||||
@ -93,7 +100,7 @@ class NormallyOpenModeLogic extends BaseGetXController{
|
||||
// 设置自动落锁数据解析
|
||||
Future<void> _replySetSupportFunctionsWithParameters(Reply reply) async {
|
||||
final int status = reply.data[2];
|
||||
switch(status){
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
//成功
|
||||
state.sureBtnState.value = 0;
|
||||
@ -111,38 +118,44 @@ class NormallyOpenModeLogic extends BaseGetXController{
|
||||
|
||||
// 设置支持功能(带参数)
|
||||
Future<void> sendAutoLock() async {
|
||||
if(state.sureBtnState.value == 1){
|
||||
if (state.sureBtnState.value == 1) {
|
||||
return;
|
||||
}
|
||||
state.sureBtnState.value = 1;
|
||||
|
||||
showEasyLoading();
|
||||
showBlueConnetctToastTimer(action: (){
|
||||
showBlueConnetctToastTimer(action: () {
|
||||
dismissEasyLoading();
|
||||
state.sureBtnState.value = 0;
|
||||
});
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName, (BluetoothConnectionState connectionState) async {
|
||||
BlueManage().blueSendData(BlueManage().connectDeviceName,
|
||||
(BluetoothConnectionState connectionState) async {
|
||||
if (connectionState == BluetoothConnectionState.connected) {
|
||||
final List<String>? privateKey = await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList = changeStringListToIntList(privateKey!);
|
||||
final List<String>? privateKey =
|
||||
await Storage.getStringList(saveBluePrivateKey);
|
||||
final List<int> getPrivateKeyList =
|
||||
changeStringListToIntList(privateKey!);
|
||||
|
||||
final List<String>? token = await Storage.getStringList(saveBlueToken);
|
||||
final List<int> getTokenList = changeStringListToIntList(token!);
|
||||
|
||||
final List<String>? publicKey = await Storage.getStringList(saveBluePublicKey);
|
||||
final List<int> getPublicKeyList = changeStringListToIntList(publicKey!);
|
||||
final List<String>? publicKey =
|
||||
await Storage.getStringList(saveBluePublicKey);
|
||||
final List<int> getPublicKeyList =
|
||||
changeStringListToIntList(publicKey!);
|
||||
|
||||
String weekStr = '00000000';
|
||||
for (var day in state.weekDays.value) {
|
||||
final int index = day % 7; // 将周日的索引转换为 0
|
||||
weekStr = '${weekStr.substring(0, index)}1${weekStr.substring(index + 1)}';
|
||||
final int index = day % 7; // 将周日的索引转换为 0
|
||||
weekStr =
|
||||
'${weekStr.substring(0, index)}1${weekStr.substring(index + 1)}';
|
||||
}
|
||||
// 倒序 weekStr
|
||||
weekStr = weekStr.split('').reversed.join('');
|
||||
final int number = int.parse(weekStr, radix: 2);
|
||||
|
||||
final List<int> list = <int>[];
|
||||
list.add(state.isOpenNormallyOpenMode.value == true ? 1:0);
|
||||
list.add(state.isOpenNormallyOpenMode.value == true ? 1 : 0);
|
||||
|
||||
final int bieginTime = state.beginTimeMinute.value;
|
||||
final double bieginDouble = bieginTime / 256;
|
||||
@ -159,7 +172,7 @@ class NormallyOpenModeLogic extends BaseGetXController{
|
||||
list.add(end1);
|
||||
list.add(end2);
|
||||
|
||||
list.add(state.isAllDay.value == 1 ? 1:0);
|
||||
list.add(state.isAllDay.value == 1 ? 1 : 0);
|
||||
list.add(number);
|
||||
list.add(0);
|
||||
|
||||
@ -177,7 +190,7 @@ class NormallyOpenModeLogic extends BaseGetXController{
|
||||
dismissEasyLoading();
|
||||
cancelBlueConnetctToastTimer();
|
||||
state.sureBtnState.value = 0;
|
||||
if(state.ifCurrentScreen.value == true){
|
||||
if (state.ifCurrentScreen.value == true) {
|
||||
showBlueConnetctToast();
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,6 +90,12 @@ class AddPalmLogic extends BaseGetXController {
|
||||
isBeforeAddUser: false
|
||||
);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 12:
|
||||
// 管理员已满
|
||||
showToast('管理员已满'.tr);
|
||||
Get.close(1);
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
state.ifAddState.value = false;
|
||||
@ -111,6 +117,7 @@ class AddPalmLogic extends BaseGetXController {
|
||||
Get.close(1);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 12:
|
||||
// 管理员已满
|
||||
showToast('管理员已满'.tr);
|
||||
Get.close(1);
|
||||
|
||||
@ -152,6 +152,13 @@ class PasswordKeyDetailLogic extends BaseGetXController {
|
||||
final List<String>? token = await Storage.getStringList(saveBlueToken);
|
||||
final List<int> getTokenList = changeStringListToIntList(token!);
|
||||
|
||||
int startTime = 0;
|
||||
int endTime = 0;
|
||||
if (state.itemData.value.keyboardPwdType != 2) {
|
||||
startTime = state.itemData.value.startDate! ~/ 1000;
|
||||
endTime = state.itemData.value.endDate! ~/ 1000;
|
||||
}
|
||||
|
||||
IoSenderManage.senderCustomPasswordsCommand(
|
||||
keyID: state.itemData.value.keyboardPwdId!.toString(),
|
||||
userID: await Storage.getUid(),
|
||||
@ -162,8 +169,8 @@ class PasswordKeyDetailLogic extends BaseGetXController {
|
||||
? (state.isDeletPasswordKey.value == true ? 2 : 1)
|
||||
: 3,
|
||||
isAdmin: state.isAdministrator.value == true ? 1 : 0,
|
||||
startTime: state.itemData.value.startDate! ~/ 1000,
|
||||
endTime: state.itemData.value.endDate! ~/ 1000,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
needAuthor: 1,
|
||||
isBeforeAddUser: false,
|
||||
signKey: signKeyDataList,
|
||||
|
||||
@ -90,6 +90,13 @@ class AddRemoteControlLogic extends BaseGetXController{
|
||||
isBeforeAddUser: false
|
||||
);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 12:
|
||||
// 管理员已满
|
||||
showToast('管理员已满'.tr);
|
||||
state.ifAddState.value = false;
|
||||
Get.close(1);
|
||||
break;
|
||||
default:
|
||||
//失败
|
||||
state.ifAddState.value = false;
|
||||
@ -110,6 +117,7 @@ class AddRemoteControlLogic extends BaseGetXController{
|
||||
Get.close(1);
|
||||
break;
|
||||
case 0xFE:
|
||||
case 0x12:
|
||||
// 管理员已满
|
||||
showToast('管理员已满'.tr);
|
||||
Get.close(1);
|
||||
|
||||
@ -93,7 +93,6 @@ class NearbyLockLogic extends BaseGetXController {
|
||||
showToast('固件升级完成'.tr);
|
||||
closeOTADAta();
|
||||
}
|
||||
dismissEasyLoading();
|
||||
});
|
||||
}
|
||||
|
||||
@ -160,6 +159,7 @@ class NearbyLockLogic extends BaseGetXController {
|
||||
// 获取星锁状态
|
||||
Future<void> _replyGetStarLockStatusInfo(Reply reply) async {
|
||||
final int status = reply.data[2];
|
||||
dismissEasyLoading();
|
||||
switch (status) {
|
||||
case 0x00:
|
||||
//成功
|
||||
@ -449,50 +449,6 @@ class NearbyLockLogic extends BaseGetXController {
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断 128-bit UUID 是否已配对
|
||||
bool isPaired128Bit(String serviceUuid) {
|
||||
if (serviceUuid.length != 36) return false; // 确保是 128-bit UUID
|
||||
try {
|
||||
String status = serviceUuid.substring(30, 32); // 获取第 31 和 32 位
|
||||
return status == '01'; // '01' 表示已配对
|
||||
} catch (e) {
|
||||
return false; // 如果索引越界或其他错误,返回 false
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断 128-bit UUID 是否休眠
|
||||
bool isSleeping128Bit(String serviceUuid) {
|
||||
if (serviceUuid.length != 36) return false; // 确保是 128-bit UUID
|
||||
try {
|
||||
String status = serviceUuid.substring(32, 34); // 获取第 33 和 34 位
|
||||
return status == '00'; // '00' 表示休眠
|
||||
} catch (e) {
|
||||
return false; // 如果索引越界或其他错误,返回 false
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断 32-bit UUID 是否已配对
|
||||
bool isPaired32Bit(String serviceUuid) {
|
||||
if (serviceUuid.length != 8) return false; // 确保是 32-bit UUID
|
||||
try {
|
||||
String status = serviceUuid.substring(3, 5); // 获取第 4 和 5 位
|
||||
return status == '01'; // '01' 表示已配对
|
||||
} catch (e) {
|
||||
return false; // 如果索引越界或其他错误,返回 false
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断 32-bit UUID 是否休眠
|
||||
bool isSleeping32Bit(String serviceUuid) {
|
||||
if (serviceUuid.length != 8) return false; // 确保是 32-bit UUID
|
||||
try {
|
||||
String status = serviceUuid.substring(5, 7); // 获取第 6 和 7 位
|
||||
return status == '00'; // '00' 表示休眠
|
||||
} catch (e) {
|
||||
return false; // 如果索引越界或其他错误,返回 false
|
||||
}
|
||||
}
|
||||
|
||||
void stopScanBlueList() {
|
||||
BlueManage().disconnect();
|
||||
BlueManage().stopScan();
|
||||
@ -743,8 +699,8 @@ class NearbyLockLogic extends BaseGetXController {
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
_replySubscription?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Future<void> getNearByLimits() async {
|
||||
|
||||
@ -456,10 +456,10 @@ class _MineSetPageState extends State<MineSetPage>
|
||||
// showLoginOutAlertTipDialog();
|
||||
}),
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 30.w, top: 30.h),
|
||||
padding: EdgeInsets.only(left: 30.w, top: 30.h),
|
||||
// color: Colors.red,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
TextButton(
|
||||
child: Text(
|
||||
|
||||
@ -1,7 +1,19 @@
|
||||
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
|
||||
|
||||
class TalkConstant {
|
||||
// TalkPing 未收到回复超时时间(s)
|
||||
static const int talkePingOverTime = 10;
|
||||
static const int talkeDataOverTime = 10;
|
||||
|
||||
// 收到TalkRequest 未处理超时时间(s)
|
||||
static const int talkeRequestOverTime = 30;
|
||||
|
||||
static TalkExpectReq ImageExpect = TalkExpectReq(
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
static TalkExpectReq H264Expect = TalkExpectReq(
|
||||
videoType: [VideoTypeE.H264],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import 'package:star_lock/talk/call/g711.dart';
|
||||
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
|
||||
import 'package:star_lock/talk/starChart/entity/scp_message.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/other/h264_frame_handler.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/scp_message_base_handle.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/scp_message_handle.dart';
|
||||
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
|
||||
@ -14,62 +15,6 @@ import 'package:star_lock/talk/starChart/proto/talk_data_h264_frame.pb.dart';
|
||||
// implements ScpMessageHandler {
|
||||
class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
implements ScpMessageHandler {
|
||||
factory UdpTalkDataHandler() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
UdpTalkDataHandler._internal();
|
||||
|
||||
static final UdpTalkDataHandler _instance = UdpTalkDataHandler._internal();
|
||||
|
||||
int _recentRecvDataRate = 0;
|
||||
int _recentRecvPacketCount = 0;
|
||||
int _recentSendDataRate = 0;
|
||||
int _recentSendPacketCount = 0;
|
||||
|
||||
int _lastRecvDataRate = 0;
|
||||
int _lastRecvPacketCount = 0;
|
||||
int _lastSendDataRate = 0;
|
||||
int _lastSendPacketCount = 0;
|
||||
|
||||
void updateRecvDataRate(int dataSize) {
|
||||
_recentRecvDataRate += dataSize;
|
||||
_recentRecvPacketCount++;
|
||||
}
|
||||
|
||||
void updateSendDataRate(int dataSize) {
|
||||
_recentSendDataRate += dataSize;
|
||||
_recentSendPacketCount++;
|
||||
}
|
||||
|
||||
void resetDataRates() {
|
||||
_lastRecvDataRate = _recentRecvDataRate;
|
||||
_lastRecvPacketCount = _recentRecvPacketCount;
|
||||
_lastSendDataRate = _recentSendDataRate;
|
||||
_lastSendPacketCount = _recentSendPacketCount;
|
||||
|
||||
_recentRecvDataRate = 0;
|
||||
_recentRecvPacketCount = 0;
|
||||
_recentSendDataRate = 0;
|
||||
_recentSendPacketCount = 0;
|
||||
}
|
||||
|
||||
int getLastRecvDataRate() {
|
||||
return _lastRecvDataRate;
|
||||
}
|
||||
|
||||
int getLastRecvPacketCount() {
|
||||
return _lastRecvPacketCount;
|
||||
}
|
||||
|
||||
int getLastSendDataRate() {
|
||||
return _lastSendDataRate;
|
||||
}
|
||||
|
||||
int getLastSendPacketCount() {
|
||||
return _lastSendPacketCount;
|
||||
}
|
||||
|
||||
@override
|
||||
void handleReq(ScpMessage scpMessage) {}
|
||||
|
||||
@ -95,6 +40,17 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
return buffer.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||
}
|
||||
|
||||
// 在类顶部添加异步日志方法
|
||||
void _asyncLog(String message) {
|
||||
Future.microtask(() {
|
||||
try {
|
||||
AppLog.log(message);
|
||||
} catch (e) {
|
||||
// 错误处理
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
deserializePayload(
|
||||
{required int payloadType,
|
||||
@ -105,10 +61,12 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
int? spTotal,
|
||||
int? spIndex,
|
||||
int? messageId}) {
|
||||
// AppLog.log(
|
||||
// '没有组包之前的每一个包的数据:${byte.length} messageId:$messageId spTotal:$spTotal spIndex:$spIndex PayloadLength:$PayloadLength,byte:${bufferToHexString(byte)}');
|
||||
// 获取统计信息
|
||||
final stats = PacketLossStatistics().getStatistics();
|
||||
_asyncLog('丢包统计: $stats');
|
||||
// _asyncLog(
|
||||
// '分包数据:messageId:$messageId [$spIndex/$spTotal] PayloadLength:$PayloadLength');
|
||||
if (messageType == MessageTypeConstant.RealTimeData) {
|
||||
// 回声测试
|
||||
if (spTotal != null &&
|
||||
spTotal > 1 &&
|
||||
messageId != null &&
|
||||
@ -161,8 +119,6 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame();
|
||||
talkDataH264Frame.mergeFromBuffer(talkData.content);
|
||||
frameHandler.handleFrame(talkDataH264Frame);
|
||||
// AppLog.log(
|
||||
// "帧:${talkDataH264Frame.frameType},帧序号:${talkDataH264Frame.frameSeq},对应I帧序号:${talkDataH264Frame.frameSeqI}");
|
||||
}
|
||||
|
||||
/// 处理图片数据
|
||||
|
||||
@ -25,8 +25,24 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
RxString currentLanguage =
|
||||
CurrentLocaleTool.getCurrentLocaleString().obs; // 当前选择语言
|
||||
|
||||
// 添加上次处理请求的时间戳
|
||||
int _lastRequestTime = 0;
|
||||
|
||||
@override
|
||||
void handleReq(ScpMessage scpMessage) async {
|
||||
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
// 确保与上次请求间隔至少1秒
|
||||
if (currentTime - _lastRequestTime < 1000) {
|
||||
// 如果间隔小于1秒,直接拒绝请求
|
||||
replyErrorMessage(scpMessage);
|
||||
AppLog.log('对讲请求过于频繁,已拒绝');
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新最后处理时间
|
||||
_lastRequestTime = currentTime;
|
||||
|
||||
// 判断是否登录账户
|
||||
final loginData = await Storage.getLoginData();
|
||||
|
||||
@ -47,6 +63,7 @@ class UdpTalkRequestHandler extends ScpMessageBaseHandle
|
||||
} else {
|
||||
// 拒绝接听,回复失败
|
||||
replyErrorMessage(scpMessage);
|
||||
AppLog.log('正在接听,回复拒绝');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,105 +1,17 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:star_lock/app_settings/app_settings.dart';
|
||||
import '../../proto/talk_data_h264_frame.pb.dart';
|
||||
|
||||
class H264FrameHandler {
|
||||
final LinkedHashMap<int, TalkDataH264Frame> _frameBuffer = LinkedHashMap();
|
||||
final void Function(List<int> frameData) onCompleteFrame;
|
||||
|
||||
final LinkedHashMap<int, TalkDataH264Frame_FrameTypeE> _frameTypeIndex = LinkedHashMap();
|
||||
final void Function(List<int> frameData) onCompleteFrame;
|
||||
|
||||
H264FrameHandler({required this.onCompleteFrame});
|
||||
|
||||
void handleFrame(TalkDataH264Frame frame) {
|
||||
// 存储帧
|
||||
_frameBuffer[frame.frameSeq] = frame;
|
||||
_frameTypeIndex[frame.frameSeq] = frame.frameType;
|
||||
|
||||
// 检查是否可以组装完整的 GOP (Group of Pictures)
|
||||
_tryAssembleFrames(frame.frameSeq);
|
||||
}
|
||||
|
||||
void _tryAssembleFrames(int currentSeq) {
|
||||
final List<int> framesToProcess = [];
|
||||
int? startFrameSeq;
|
||||
|
||||
// 从当前帧开始向前找到最近的 I 帧或 P 帧
|
||||
for (int seq = currentSeq; seq >= 0; seq--) {
|
||||
final frameType = _frameTypeIndex[seq];
|
||||
if (frameType == null) continue;
|
||||
if (frameType == TalkDataH264Frame_FrameTypeE.I) {
|
||||
startFrameSeq = seq;
|
||||
break;
|
||||
} else if (frameType == TalkDataH264Frame_FrameTypeE.P) {
|
||||
if (_frameBuffer.containsKey(_frameBuffer[seq]!.frameSeqI)) {
|
||||
startFrameSeq = seq;
|
||||
break;
|
||||
} else {
|
||||
_frameBuffer.remove(seq);
|
||||
_frameTypeIndex.remove(seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startFrameSeq != null) {
|
||||
for (int seq = startFrameSeq; _frameBuffer.containsKey(seq); seq++) {
|
||||
framesToProcess.add(seq);
|
||||
}
|
||||
|
||||
if (framesToProcess.isNotEmpty) {
|
||||
_processFrames(framesToProcess);
|
||||
}
|
||||
} else {
|
||||
_clearOldFrames(currentSeq);
|
||||
}
|
||||
}
|
||||
|
||||
void _processFrames(List<int> frameSeqs) {
|
||||
// 按顺序组装帧数据
|
||||
// final List<int> assembledData = [];
|
||||
//
|
||||
// for (var seq in frameSeqs) {
|
||||
// final frame = _frameBuffer[seq]!;
|
||||
// assembledData.addAll(frame.frameData);
|
||||
//
|
||||
// // 处理完后从缓冲区移除
|
||||
// _frameBuffer.remove(seq);
|
||||
// }
|
||||
//
|
||||
// // 回调完整的帧数据
|
||||
// onCompleteFrame(assembledData);
|
||||
// Calculate the total length of the assembled data
|
||||
int totalLength = frameSeqs.fold(
|
||||
0, (sum, seq) => sum + _frameBuffer[seq]!.frameData.length);
|
||||
|
||||
// Allocate a buffer for the assembled data
|
||||
final assembledData = Uint8List(totalLength);
|
||||
int offset = 0;
|
||||
|
||||
for (var seq in frameSeqs) {
|
||||
final frame = _frameBuffer[seq]!;
|
||||
assembledData.setRange(
|
||||
offset, offset + frame.frameData.length, frame.frameData);
|
||||
offset += frame.frameData.length;
|
||||
|
||||
// Remove the frame from the buffer after processing
|
||||
_frameBuffer.remove(seq);
|
||||
_frameTypeIndex.remove(seq);
|
||||
}
|
||||
|
||||
// Callback with the complete frame data
|
||||
onCompleteFrame(assembledData);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_frameBuffer.clear();
|
||||
}
|
||||
|
||||
void _clearOldFrames(int currentSeq) {
|
||||
// 清理比当前帧序列旧的帧
|
||||
_frameBuffer.removeWhere((seq, frame) => seq < currentSeq - 200); // 调整阈值
|
||||
_frameTypeIndex.removeWhere((seq, frameType) => seq < currentSeq - 200);
|
||||
onCompleteFrame(frame.frameData);
|
||||
}
|
||||
}
|
||||
|
||||
94
lib/talk/starChart/handle/other/packet_loss_statistics.dart
Normal file
94
lib/talk/starChart/handle/other/packet_loss_statistics.dart
Normal file
@ -0,0 +1,94 @@
|
||||
import 'dart:collection';
|
||||
|
||||
class PacketLossStatistics {
|
||||
static final PacketLossStatistics _instance =
|
||||
PacketLossStatistics._internal();
|
||||
factory PacketLossStatistics() => _instance;
|
||||
PacketLossStatistics._internal();
|
||||
|
||||
// 记录每个messageId的分包信息
|
||||
// key: messageId, value: {totalPackets, receivedPackets}
|
||||
final Map<int, PacketInfo> _packetsMap = HashMap();
|
||||
|
||||
// 统计信息
|
||||
int _totalMessages = 0; // 总消息数
|
||||
int _lostMessages = 0; // 丢包的消息数
|
||||
int _totalPackets = 0; // 总分包数
|
||||
int _lostPackets = 0; // 丢失的分包数
|
||||
|
||||
// 记录分包数据
|
||||
void recordPacket(int messageId, int currentIndex, int totalPackets) {
|
||||
if (!_packetsMap.containsKey(messageId)) {
|
||||
_packetsMap[messageId] = PacketInfo(totalPackets);
|
||||
_totalMessages++;
|
||||
_totalPackets += totalPackets;
|
||||
}
|
||||
|
||||
_packetsMap[messageId]!.receivedPackets.add(currentIndex);
|
||||
|
||||
// 如果收到了该messageId的最后一个包,进行统计
|
||||
if (currentIndex == totalPackets) {
|
||||
_checkPacketLoss(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查丢包情况
|
||||
void _checkPacketLoss(int messageId) {
|
||||
final info = _packetsMap[messageId]!;
|
||||
|
||||
// 检查是否有丢失的包
|
||||
int received = info.receivedPackets.length;
|
||||
if (received < info.totalPackets) {
|
||||
_lostMessages++;
|
||||
_lostPackets += (info.totalPackets - received);
|
||||
}
|
||||
|
||||
// 清理该messageId的记录,避免内存泄漏
|
||||
_packetsMap.remove(messageId);
|
||||
}
|
||||
|
||||
// 获取丢包率统计信息
|
||||
PacketLossInfo getStatistics() {
|
||||
if (_totalMessages == 0 || _totalPackets == 0) {
|
||||
return PacketLossInfo(0.0, 0.0);
|
||||
}
|
||||
|
||||
// 计算消息级别的丢包率
|
||||
double messageLossRate = (_lostMessages / _totalMessages) * 100;
|
||||
|
||||
// 计算分包级别的丢包率
|
||||
double packetLossRate = (_lostPackets / _totalPackets) * 100;
|
||||
|
||||
return PacketLossInfo(messageLossRate, packetLossRate);
|
||||
}
|
||||
|
||||
// 重置统计数据
|
||||
void reset() {
|
||||
_packetsMap.clear();
|
||||
_totalMessages = 0;
|
||||
_lostMessages = 0;
|
||||
_totalPackets = 0;
|
||||
_lostPackets = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 分包信息类
|
||||
class PacketInfo {
|
||||
final int totalPackets;
|
||||
final Set<int> receivedPackets = HashSet<int>();
|
||||
|
||||
PacketInfo(this.totalPackets);
|
||||
}
|
||||
|
||||
// 丢包统计信息类
|
||||
class PacketLossInfo {
|
||||
final double messageLossRate; // 消息丢失率
|
||||
final double packetLossRate; // 分包丢失率
|
||||
|
||||
PacketLossInfo(this.messageLossRate, this.packetLossRate);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Message Loss Rate: ${messageLossRate.toStringAsFixed(2)}%, Packet Loss Rate: ${packetLossRate.toStringAsFixed(2)}%';
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,6 @@ import 'dart:async';
|
||||
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
|
||||
|
||||
class TalkDataRepository {
|
||||
// 创建一个私有的构造函数,防止外部创建实例
|
||||
TalkDataRepository._() {
|
||||
_talkDataStreamController = StreamController<TalkData>.broadcast(
|
||||
onListen: () {
|
||||
@ -11,47 +10,26 @@ class TalkDataRepository {
|
||||
onCancel: () {
|
||||
_isListening = false;
|
||||
},
|
||||
sync: false, // 异步模式
|
||||
sync: false, // 改为同步模式以提高实时性
|
||||
);
|
||||
}
|
||||
|
||||
// 使用 _instance 来保存单例对象
|
||||
static final TalkDataRepository _instance = TalkDataRepository._();
|
||||
|
||||
// 提供一个静态方法来获取单例实例
|
||||
static TalkDataRepository get instance => _instance;
|
||||
|
||||
// 创建一个 StreamController
|
||||
late final StreamController<TalkData> _talkDataStreamController;
|
||||
|
||||
bool _isListening = false;
|
||||
|
||||
// 用于存储数据的缓冲区
|
||||
final List<TalkData> _buffer = [];
|
||||
// 直接返回原始流,不做转换
|
||||
Stream<TalkData> get talkDataStream => _talkDataStreamController.stream;
|
||||
|
||||
// 提供一个方法来获取 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);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// 提供一个方法来添加 TalkData 到 Stream
|
||||
void addTalkData(TalkData talkData) {
|
||||
if (_isListening) {
|
||||
_talkDataStreamController.add(talkData);
|
||||
}
|
||||
}
|
||||
|
||||
// 提供一个方法来关闭 StreamController
|
||||
void dispose() {
|
||||
_talkDataStreamController.close();
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import 'package:star_lock/talk/starChart/constant/ip_constant.dart';
|
||||
import 'package:star_lock/talk/starChart/constant/listen_addr_type_constant.dart';
|
||||
import 'package:star_lock/talk/starChart/constant/message_type_constant.dart';
|
||||
import 'package:star_lock/talk/starChart/constant/payload_type_constant.dart';
|
||||
import 'package:star_lock/talk/starChart/constant/talk_constant.dart';
|
||||
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
|
||||
import 'package:star_lock/talk/starChart/entity/relay_info_entity.dart';
|
||||
import 'package:star_lock/talk/starChart/entity/report_information_data.dart';
|
||||
@ -31,6 +32,7 @@ import 'package:star_lock/talk/starChart/exception/start_chart_message_exception
|
||||
import 'package:star_lock/talk/starChart/handle/impl/debug_Info_model.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/impl/udp_talk_data_handler.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/other/do_sign.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/other/talke_data_over_time_timer_manager.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/other/talke_ping_over_time_timer_manager.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/other/talke_request_over_time_timer_manager.dart';
|
||||
@ -111,10 +113,7 @@ class StartChartManage {
|
||||
final int _maxPayloadSize = 8 * 1024; // 分包大小
|
||||
|
||||
// 默认通话的期望数据格式
|
||||
TalkExpectReq _defaultTalkExpect = TalkExpectReq(
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
TalkExpectReq _defaultTalkExpect = TalkConstant.ImageExpect;
|
||||
|
||||
String relayPeerId = ''; // 中继peerId
|
||||
|
||||
@ -342,7 +341,7 @@ class StartChartManage {
|
||||
}
|
||||
}
|
||||
|
||||
// 发送RbcuConfirm 打洞确认
|
||||
// 发送打洞确认包
|
||||
void _sendRbcuConfirmMessage() async {
|
||||
RbcuConfirm(
|
||||
sessionId: _rbcuSessionId,
|
||||
@ -595,27 +594,39 @@ class StartChartManage {
|
||||
|
||||
// 发送拒绝接听消息
|
||||
void startTalkRejectMessageTimer() async {
|
||||
talkRejectTimer ??= Timer.periodic(
|
||||
Duration(seconds: _defaultIntervalTime),
|
||||
(Timer timer) async {
|
||||
_sendTalkRejectMessage();
|
||||
},
|
||||
);
|
||||
try {
|
||||
int count = 0;
|
||||
final int maxCount = 10; // 最大执行次数为10秒
|
||||
|
||||
// 设置状态为拒绝
|
||||
StartChartTalkStatus.instance.setRejected();
|
||||
// 停止播放铃声
|
||||
AudioPlayerManager().stopRingtone();
|
||||
// 停止发送通话保持消息、通话预期数据请求
|
||||
stopTalkExpectMessageTimer();
|
||||
stopTalkPingMessageTimer();
|
||||
stopCallRequestMessageTimer();
|
||||
stopSendingRbcuInfoMessages();
|
||||
stopSendingRbcuProBeMessages();
|
||||
// 取消定时器
|
||||
talkRejectTimer ??= Timer.periodic(
|
||||
Duration(seconds: _defaultIntervalTime),
|
||||
(Timer timer) async {
|
||||
_sendTalkRejectMessage();
|
||||
count++;
|
||||
if (count >= maxCount) {
|
||||
timer.cancel();
|
||||
talkRejectTimer = null;
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
AppLog.log("startTalkRejectMessageTimer e:${e}");
|
||||
} finally {
|
||||
// 设置状态为拒绝
|
||||
StartChartTalkStatus.instance.setRejected();
|
||||
// 停止播放铃声
|
||||
AudioPlayerManager().stopRingtone();
|
||||
// 停止发送通话保持消息、通话预期数据请求
|
||||
stopTalkExpectMessageTimer();
|
||||
stopTalkPingMessageTimer();
|
||||
stopCallRequestMessageTimer();
|
||||
stopSendingRbcuInfoMessages();
|
||||
stopSendingRbcuProBeMessages();
|
||||
// 取消定时器
|
||||
|
||||
talkePingOverTimeTimerManager.cancel();
|
||||
talkDataOverTimeTimerManager.cancel();
|
||||
talkePingOverTimeTimerManager.cancel();
|
||||
talkDataOverTimeTimerManager.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// 发送期望接受消息
|
||||
@ -1031,6 +1042,14 @@ class StartChartManage {
|
||||
final int payloadType = scpMessage.PayloadType ?? 0;
|
||||
final int messageType = scpMessage.MessageType ?? 0;
|
||||
try {
|
||||
// 记录分包数据用于统计丢包率
|
||||
if (scpMessage.SpIndex != null &&
|
||||
scpMessage.SpTotal != null &&
|
||||
scpMessage.MessageId != null) {
|
||||
PacketLossStatistics().recordPacket(
|
||||
scpMessage.MessageId!, scpMessage.SpIndex!, scpMessage.SpTotal!);
|
||||
}
|
||||
|
||||
final ScpMessageHandler handler =
|
||||
ScpMessageHandlerFactory.createHandler(payloadType);
|
||||
if (messageType == MessageTypeConstant.Req) {
|
||||
@ -1126,10 +1145,7 @@ class StartChartManage {
|
||||
}
|
||||
|
||||
void reSetDefaultTalkExpect() {
|
||||
_defaultTalkExpect = TalkExpectReq(
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
_defaultTalkExpect = TalkConstant.ImageExpect;
|
||||
}
|
||||
|
||||
TalkExpectReq getDefaultTalkExpect() {
|
||||
@ -1148,10 +1164,7 @@ class StartChartManage {
|
||||
|
||||
/// 修改预期接收到的数据
|
||||
void sendImageVideoAndG711AudioTalkExpectData() {
|
||||
final talkExpectReq = TalkExpectReq(
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
final talkExpectReq = TalkConstant.ImageExpect;
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
talkExpect: talkExpectReq);
|
||||
}
|
||||
@ -1212,6 +1225,7 @@ class StartChartManage {
|
||||
await Storage.removerStarChartRegisterNodeInfo();
|
||||
// 关闭udp服务
|
||||
closeUdpSocket();
|
||||
PacketLossStatistics().reset();
|
||||
}
|
||||
|
||||
/// 重置数据
|
||||
|
||||
@ -28,17 +28,16 @@ class AppLifecycleObserver extends WidgetsBindingObserver {
|
||||
|
||||
void onAppPaused() {
|
||||
// 处理应用程序进入后台的逻辑
|
||||
print('App has entered the background.');
|
||||
if (StartChartManage().talkStatus.status ==
|
||||
TalkStatus.passiveCallWaitingAnswer ||
|
||||
StartChartManage().talkStatus.status ==
|
||||
TalkStatus.proactivelyCallWaitingAnswer) {
|
||||
StartChartManage().startTalkHangupMessageTimer();
|
||||
StartChartManage().startTalkRejectMessageTimer();
|
||||
// 如果是等待接听时就退出页面
|
||||
|
||||
final status = StartChartManage().talkStatus.status;
|
||||
|
||||
if (status == TalkStatus.passiveCallWaitingAnswer ||
|
||||
status == TalkStatus.proactivelyCallWaitingAnswer ||
|
||||
status == TalkStatus.answeredSuccessfully ||
|
||||
status == TalkStatus.uninitialized) {
|
||||
StartChartManage().destruction();
|
||||
Get.back();
|
||||
}
|
||||
StartChartManage().destruction();
|
||||
}
|
||||
|
||||
void onAppResumed() async {
|
||||
|
||||
@ -38,8 +38,8 @@ class TalkViewLogic extends BaseGetXController {
|
||||
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
||||
|
||||
final int minBufferSize = 2; // 最小缓冲2帧,约166ms
|
||||
final int maxBufferSize = 8; // 最大缓冲8帧,约666ms
|
||||
int bufferSize = 3; // 初始化为默认大小
|
||||
final int maxBufferSize = 20; // 最大缓冲8帧,约666ms
|
||||
int bufferSize = 8; // 初始化为默认大小
|
||||
// 修改音频相关的成员变量
|
||||
final int minAudioBufferSize = 1; // 音频最小缓冲1帧
|
||||
final int maxAudioBufferSize = 3; // 音频最大缓冲3帧
|
||||
@ -56,6 +56,9 @@ class TalkViewLogic extends BaseGetXController {
|
||||
|
||||
final Map<String, ui.Image> _imageCache = {};
|
||||
|
||||
// 添加一个变量用于记录上一帧的时间戳
|
||||
int _lastFrameTimestamp = 0; // 初始值为 0
|
||||
|
||||
// 添加帧率计算相关变量
|
||||
int _frameCount = 0;
|
||||
int _lastFpsUpdateTime = 0;
|
||||
@ -100,11 +103,11 @@ class TalkViewLogic extends BaseGetXController {
|
||||
// 判断数据类型,进行分发处理
|
||||
switch (contentType) {
|
||||
case TalkData_ContentTypeE.G711:
|
||||
// 第一帧到达时记录开始时间
|
||||
if (_isFirstAudioFrame) {
|
||||
_startAudioTime = currentTime;
|
||||
_isFirstAudioFrame = false;
|
||||
}
|
||||
// // 第一帧到达时记录开始时间
|
||||
// if (_isFirstAudioFrame) {
|
||||
// _startAudioTime = currentTime;
|
||||
// _isFirstAudioFrame = false;
|
||||
// }
|
||||
|
||||
// 计算音频延迟
|
||||
final expectedTime = _startAudioTime + talkData.durationMs;
|
||||
@ -130,15 +133,16 @@ class TalkViewLogic extends BaseGetXController {
|
||||
if (_isFirstFrame) {
|
||||
_startTime = currentTime;
|
||||
_isFirstFrame = false;
|
||||
// AppLog.log('记录第一帧的时间戳${currentTime},${talkData.durationMs}');
|
||||
AppLog.log('第一帧帧的时间戳:${talkData.durationMs}');
|
||||
}
|
||||
// AppLog.log('其他帧的时间戳:${talkData.durationMs}');
|
||||
// 计算帧间间隔
|
||||
if (_lastFrameTimestamp != 0) {
|
||||
final int frameInterval = talkData.durationMs - _lastFrameTimestamp;
|
||||
_adjustBufferSize(frameInterval); // 根据帧间间隔调整缓冲区
|
||||
}
|
||||
_lastFrameTimestamp = talkData.durationMs; // 更新上一帧时间戳
|
||||
|
||||
// 计算实际延迟:当前时间 - 预期播放时间
|
||||
final expectedTime = _startTime + talkData.durationMs;
|
||||
final videoDelay = currentTime - expectedTime; // 修改延迟计算方式
|
||||
|
||||
// 动态调整缓冲区
|
||||
_adjustBufferSize(videoDelay);
|
||||
// 然后添加到播放缓冲区
|
||||
if (state.videoBuffer.length >= bufferSize) {
|
||||
state.videoBuffer.removeAt(0);
|
||||
@ -180,20 +184,18 @@ class TalkViewLogic extends BaseGetXController {
|
||||
state.listData.value = Uint8List.fromList(oldestFrame.content);
|
||||
state.videoBuffer.removeAt(oldestIndex); // 移除已播放的帧
|
||||
|
||||
// 更新帧率计算
|
||||
_frameCount++;
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final elapsed = currentTime - _lastFpsUpdateTime;
|
||||
// // 更新帧率计算
|
||||
// _frameCount++;
|
||||
// final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
// final elapsed = currentTime - _lastFpsUpdateTime;
|
||||
//
|
||||
// if (elapsed >= 1000) {
|
||||
// // 每秒更新一次
|
||||
// state.fps.value = (_frameCount * 1000 / elapsed).round();
|
||||
// _frameCount = 0;
|
||||
// _lastFpsUpdateTime = currentTime;
|
||||
// }
|
||||
|
||||
if (elapsed >= 1000) {
|
||||
// 每秒更新一次
|
||||
state.fps.value = (_frameCount * 1000 / elapsed).round();
|
||||
_frameCount = 0;
|
||||
_lastFpsUpdateTime = currentTime;
|
||||
}
|
||||
// AppLog.log('🎬 播放帧 - 缓冲区剩余: ${state.videoBuffer.length}/${bufferSize}, '
|
||||
// '播放延迟: ${currentTime - oldestFrame.durationMs}ms, '
|
||||
// '帧时间戳: ${oldestFrame.durationMs}');
|
||||
} else {
|
||||
// AppLog.log('⚠️ 帧未找到缓存 - Key: $cacheKey');
|
||||
state.videoBuffer.removeAt(oldestIndex); // 移除无法播放的帧
|
||||
@ -257,19 +259,21 @@ class TalkViewLogic extends BaseGetXController {
|
||||
}
|
||||
|
||||
// 新增:动态调整缓冲区大小的方法
|
||||
void _adjustBufferSize(int delay) {
|
||||
const int delayThresholdHigh = 250; // 高延迟阈值(约3帧的时间)
|
||||
const int delayThresholdLow = 166; // 低延迟阈值(约2帧的时间)
|
||||
void _adjustBufferSize(int frameInterval) {
|
||||
const int frameDuration = 83; // 假设每帧的时间间隔为 83ms(12fps)
|
||||
const int delayThresholdHigh = frameDuration * 2; // 高延迟阈值(2帧时间)
|
||||
const int delayThresholdLow = frameDuration; // 低延迟阈值(1帧时间)
|
||||
const int adjustInterval = 1; // 每次调整1帧
|
||||
|
||||
if (delay > delayThresholdHigh && bufferSize < maxBufferSize) {
|
||||
// 延迟较大,增加缓冲区
|
||||
if (frameInterval > delayThresholdHigh && bufferSize < maxBufferSize) {
|
||||
// 帧间间隔较大,增加缓冲区
|
||||
bufferSize = min(bufferSize + adjustInterval, maxBufferSize);
|
||||
// AppLog.log('📈 增加缓冲区 - 当前大小: $bufferSize, 延迟: ${delay}ms');
|
||||
} else if (delay < delayThresholdLow && bufferSize > minBufferSize) {
|
||||
// 延迟较小,减少缓冲区
|
||||
AppLog.log('📈 增加缓冲区 - 当前大小: $bufferSize, 帧间间隔: ${frameInterval}ms');
|
||||
} else if (frameInterval < delayThresholdLow &&
|
||||
bufferSize > minBufferSize) {
|
||||
// 帧间间隔较小,减少缓冲区
|
||||
bufferSize = max(bufferSize - adjustInterval, minBufferSize);
|
||||
// AppLog.log('📉 减少缓冲区 - 当前大小: $bufferSize, 延迟: ${delay}ms');
|
||||
AppLog.log('📉 减少缓冲区 - 当前大小: $bufferSize, 帧间间隔: ${frameInterval}ms');
|
||||
}
|
||||
}
|
||||
|
||||
@ -493,6 +497,7 @@ class TalkViewLogic extends BaseGetXController {
|
||||
_initAudioRecorder();
|
||||
|
||||
requestPermissions();
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -615,7 +615,7 @@ class _TalkViewPageState extends State<TalkViewPage>
|
||||
state.videoBuffer.clear();
|
||||
state.listData.value = Uint8List(0);
|
||||
CallTalk().finishAVData();
|
||||
UdpTalkDataHandler().resetDataRates();
|
||||
// UdpTalkDataHandler().resetDataRates();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:math'; // Import the math package to use sqrt
|
||||
@ -23,11 +24,13 @@ import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart';
|
||||
import 'package:star_lock/network/api_repository.dart';
|
||||
import 'package:star_lock/talk/call/g711.dart';
|
||||
import 'package:star_lock/talk/starChart/constant/talk_status.dart';
|
||||
import 'package:star_lock/talk/starChart/handle/other/packet_loss_statistics.dart';
|
||||
import 'package:star_lock/talk/starChart/proto/talk_data.pb.dart';
|
||||
import 'package:star_lock/talk/starChart/proto/talk_expect.pb.dart';
|
||||
import 'package:star_lock/talk/starChart/star_chart_manage.dart';
|
||||
import 'package:star_lock/talk/starChart/views/talkView/talk_view_state.dart';
|
||||
import 'package:star_lock/talk/starChart/webView/h264_web_view_state.dart';
|
||||
import 'package:star_lock/tools/G711Tool.dart';
|
||||
import 'package:star_lock/tools/bugly/bugly_tool.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
@ -38,6 +41,14 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
|
||||
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
|
||||
|
||||
// 添加模拟数据相关变量
|
||||
static const int CHUNK_SIZE = 4096;
|
||||
Timer? _mockDataTimer;
|
||||
// 定义音频帧缓冲和发送函数
|
||||
final List<int> _bufferedAudioFrames = <int>[];
|
||||
final Queue<List<int>> _frameBuffer = Queue<List<int>>();
|
||||
static const int FRAME_BUFFER_SIZE = 25;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
@ -57,7 +68,7 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
_loadLocalHtml();
|
||||
// 创建流数据监听
|
||||
_createFramesStreamListen();
|
||||
|
||||
// playLocalTestVideo();
|
||||
_startListenTalkStatus();
|
||||
state.talkStatus.value = state.startChartTalkStatus.status;
|
||||
// 初始化音频播放器
|
||||
@ -86,11 +97,43 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
|
||||
void _createFramesStreamListen() async {
|
||||
state.talkDataRepository.talkDataStream.listen((TalkData event) async {
|
||||
// 发送数据给js处理
|
||||
_sendBufferedData(event.content);
|
||||
// 添加新帧到缓冲区
|
||||
_frameBuffer.add(event.content);
|
||||
|
||||
// 当缓冲区超过最大容量时,发送最早的帧并移除
|
||||
while (_frameBuffer.length > FRAME_BUFFER_SIZE) {
|
||||
if (_frameBuffer.isNotEmpty) {
|
||||
final frame = _frameBuffer.removeFirst();
|
||||
await _sendBufferedData(frame);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 播放本地测试视频文件
|
||||
// Future<void> playLocalTestVideo() async {
|
||||
// try {
|
||||
// ByteData data = await rootBundle.load('assets/html/demo.h264');
|
||||
// List<int> bytes = data.buffer.asUint8List();
|
||||
//
|
||||
// int offset = 0;
|
||||
// _mockDataTimer = Timer.periodic(Duration(milliseconds: 40), (timer) {
|
||||
// if (offset >= bytes.length) {
|
||||
// timer.cancel();
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// int end = min(offset + CHUNK_SIZE, bytes.length);
|
||||
// List<int> chunk = bytes.sublist(offset, end);
|
||||
// _sendBufferedData(chunk);
|
||||
//
|
||||
// offset += CHUNK_SIZE;
|
||||
// });
|
||||
// } catch (e) {
|
||||
// AppLog.log('加载测试视频文件失败: $e');
|
||||
// }
|
||||
// }
|
||||
|
||||
/// 加载html文件
|
||||
Future<void> _loadLocalHtml() async {
|
||||
// 加载 HTML 文件内容
|
||||
@ -226,15 +269,17 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
|
||||
//开始录音
|
||||
Future<void> startProcessingAudio() async {
|
||||
// 增加录音帧监听器和错误监听器
|
||||
state.voiceProcessor?.addFrameListener(_onFrame);
|
||||
state.voiceProcessor?.addErrorListener(_onError);
|
||||
try {
|
||||
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
|
||||
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
|
||||
final bool? isRecording = await state.voiceProcessor?.isRecording();
|
||||
state.isRecordingAudio.value = isRecording!;
|
||||
state.startRecordingAudioTime.value = DateTime.now();
|
||||
|
||||
// 增加录音帧监听器和错误监听器
|
||||
state.voiceProcessor
|
||||
?.addFrameListeners(<VoiceProcessorFrameListener>[_onFrame]);
|
||||
state.voiceProcessor?.addErrorListener(_onError);
|
||||
} else {
|
||||
// state.errorMessage.value = 'Recording permission not granted';
|
||||
}
|
||||
@ -254,8 +299,8 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
state.endRecordingAudioTime.value = DateTime.now();
|
||||
|
||||
// 计算录音的持续时间
|
||||
final duration = state.endRecordingAudioTime.value!
|
||||
.difference(state.startRecordingAudioTime.value!);
|
||||
final Duration duration = state.endRecordingAudioTime.value
|
||||
.difference(state.startRecordingAudioTime.value);
|
||||
|
||||
state.recordingAudioTime.value = duration.inSeconds;
|
||||
} on PlatformException catch (ex) {
|
||||
@ -267,25 +312,71 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
}
|
||||
}
|
||||
|
||||
// 音频帧处理
|
||||
// 音频帧处理
|
||||
Future<void> _onFrame(List<int> frame) async {
|
||||
// 预处理和转码操作放到异步计算线程
|
||||
// final processedFrame = await compute(preprocessAudio, frame);
|
||||
// final list = listLinearToALaw(processedFrame);
|
||||
final List<int> processedFrame = preprocessAudio(frame);
|
||||
final List<int> list = listLinearToALaw(processedFrame);
|
||||
// 添加最大缓冲限制
|
||||
if (_bufferedAudioFrames.length > state.frameLength * 3) {
|
||||
_bufferedAudioFrames.clear(); // 清空过多积累的数据
|
||||
return;
|
||||
}
|
||||
|
||||
final int ms = DateTime.now().millisecondsSinceEpoch -
|
||||
state.startRecordingAudioTime.value.millisecondsSinceEpoch;
|
||||
// 首先应用固定增益提升基础音量
|
||||
List<int> amplifiedFrame = _applyGain(frame, 1.6);
|
||||
// 编码为G711数据
|
||||
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0表示A-law
|
||||
_bufferedAudioFrames.addAll(encodedData);
|
||||
// 使用相对时间戳
|
||||
final int ms = DateTime.now().millisecondsSinceEpoch % 1000000; // 使用循环时间戳
|
||||
int getFrameLength = state.frameLength;
|
||||
if (Platform.isIOS) {
|
||||
getFrameLength = state.frameLength * 2;
|
||||
}
|
||||
|
||||
// 发送音频数据到UDP
|
||||
await StartChartManage().sendTalkDataMessage(
|
||||
talkData: TalkData(
|
||||
content: list,
|
||||
contentType: TalkData_ContentTypeE.G711,
|
||||
durationMs: ms,
|
||||
),
|
||||
);
|
||||
// 添加发送间隔控制
|
||||
if (_bufferedAudioFrames.length >= state.frameLength) {
|
||||
try {
|
||||
await StartChartManage().sendTalkDataMessage(
|
||||
talkData: TalkData(
|
||||
content: _bufferedAudioFrames,
|
||||
contentType: TalkData_ContentTypeE.G711,
|
||||
durationMs: ms,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
_bufferedAudioFrames.clear(); // 确保清理缓冲区
|
||||
}
|
||||
} else {
|
||||
_bufferedAudioFrames.addAll(encodedData);
|
||||
}
|
||||
}
|
||||
|
||||
// 错误监听
|
||||
void _onError(VoiceProcessorException error) {
|
||||
AppLog.log(error.message!);
|
||||
}
|
||||
|
||||
// 添加音频增益处理方法
|
||||
List<int> _applyGain(List<int> pcmData, double gainFactor) {
|
||||
List<int> result = List<int>.filled(pcmData.length, 0);
|
||||
|
||||
for (int i = 0; i < pcmData.length; i++) {
|
||||
// PCM数据通常是有符号的16位整数
|
||||
int sample = pcmData[i];
|
||||
|
||||
// 应用增益
|
||||
double amplified = sample * gainFactor;
|
||||
|
||||
// 限制在有效范围内,防止溢出
|
||||
if (amplified > 32767) {
|
||||
amplified = 32767;
|
||||
} else if (amplified < -32768) {
|
||||
amplified = -32768;
|
||||
}
|
||||
|
||||
result[i] = amplified.toInt();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 挂断
|
||||
@ -297,6 +388,9 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
// 拒绝
|
||||
StartChartManage().startTalkRejectMessageTimer();
|
||||
}
|
||||
// _mockDataTimer?.cancel();
|
||||
// _mockDataTimer = null;
|
||||
PacketLossStatistics().reset();
|
||||
Get.back();
|
||||
}
|
||||
|
||||
@ -315,7 +409,7 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
});
|
||||
|
||||
final LockSetInfoEntity lockSetInfoEntity =
|
||||
await ApiRepository.to.getLockSettingInfoData(
|
||||
await ApiRepository.to.getLockSettingInfoData(
|
||||
lockId: lockId.toString(),
|
||||
);
|
||||
if (lockSetInfoEntity.errorCode!.codeIsSuccessful) {
|
||||
@ -333,93 +427,11 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
}
|
||||
}
|
||||
|
||||
List<int> preprocessAudio(List<int> pcmList) {
|
||||
// 简单的降噪处理
|
||||
final List<int> processedList = [];
|
||||
for (int pcmVal in pcmList) {
|
||||
// 简单的降噪示例:将小于阈值的信号置为0
|
||||
if (pcmVal.abs() < 200) {
|
||||
pcmVal = 0;
|
||||
}
|
||||
processedList.add(pcmVal);
|
||||
}
|
||||
return processedList;
|
||||
}
|
||||
|
||||
List<int> listLinearToALaw(List<int> pcmList) {
|
||||
final List<int> aLawList = [];
|
||||
for (int pcmVal in pcmList) {
|
||||
final int aLawVal = linearToALaw(pcmVal);
|
||||
aLawList.add(aLawVal);
|
||||
}
|
||||
return aLawList;
|
||||
}
|
||||
|
||||
int linearToALaw(int pcmVal) {
|
||||
const int ALAW_MAX = 0x7FFF; // 32767
|
||||
const int ALAW_BIAS = 0x84; // 132
|
||||
|
||||
int mask;
|
||||
int seg;
|
||||
int aLawVal;
|
||||
|
||||
// Handle sign
|
||||
if (pcmVal < 0) {
|
||||
pcmVal = -pcmVal;
|
||||
mask = 0x7F; // 127 (sign bit is 1)
|
||||
} else {
|
||||
mask = 0xFF; // 255 (sign bit is 0)
|
||||
}
|
||||
|
||||
// Add bias and clamp to ALAW_MAX
|
||||
pcmVal += ALAW_BIAS;
|
||||
if (pcmVal > ALAW_MAX) {
|
||||
pcmVal = ALAW_MAX;
|
||||
}
|
||||
|
||||
// Determine segment
|
||||
seg = search(pcmVal);
|
||||
|
||||
// Calculate A-law value
|
||||
if (seg >= 8) {
|
||||
aLawVal = 0x7F ^ mask; // Clamp to maximum value
|
||||
} else {
|
||||
int quantized = (pcmVal >> (seg + 3)) & 0xF;
|
||||
aLawVal = (seg << 4) | quantized;
|
||||
aLawVal ^= 0xD5; // XOR with 0xD5 to match standard A-law table
|
||||
}
|
||||
|
||||
return aLawVal;
|
||||
}
|
||||
|
||||
int search(int val) {
|
||||
final List<int> table = [
|
||||
0xFF, // Segment 0
|
||||
0x1FF, // Segment 1
|
||||
0x3FF, // Segment 2
|
||||
0x7FF, // Segment 3
|
||||
0xFFF, // Segment 4
|
||||
0x1FFF, // Segment 5
|
||||
0x3FFF, // Segment 6
|
||||
0x7FFF // Segment 7
|
||||
];
|
||||
const int size = 8;
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (val <= table[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
// 错误监听
|
||||
void _onError(VoiceProcessorException error) {
|
||||
AppLog.log(error.message!);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// TODO: implement dispose
|
||||
// _mockDataTimer?.cancel();
|
||||
// _mockDataTimer = null;
|
||||
super.dispose();
|
||||
StartChartManage().startTalkHangupMessageTimer();
|
||||
state.animationController.dispose();
|
||||
@ -429,5 +441,6 @@ class H264WebViewLogic extends BaseGetXController {
|
||||
state.oneMinuteTimeTimer = null;
|
||||
stopProcessingAudio();
|
||||
StartChartManage().reSetDefaultTalkExpect();
|
||||
_frameBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,3 +195,12 @@ class RogerThatLockInfoDataEvent {
|
||||
class GetGatewayListRefreshUI {
|
||||
GetGatewayListRefreshUI();
|
||||
}
|
||||
/// 同意隐私协议
|
||||
class AgreePrivacyAgreement {
|
||||
AgreePrivacyAgreement();
|
||||
}
|
||||
|
||||
/// 配网成功
|
||||
class SuccessfulDistributionNetwork {
|
||||
SuccessfulDistributionNetwork();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user