Merge branch 'develop_liyi' into canary_release

This commit is contained in:
Liuyf 2025-03-13 10:17:44 +08:00
commit 88fc39cce1
6 changed files with 282 additions and 96 deletions

View File

@ -54,9 +54,11 @@ class DoorLockLogLogic extends BaseGetXController {
switch (status) { switch (status) {
case 0x00: case 0x00:
dismissEasyLoading();
cancelBlueConnetctToastTimer();
// //
final int dataLength = (reply.data[5] << 8) + reply.data[6]; final int dataLength = (reply.data[5] << 8) + reply.data[6];
// AppLog.log("dataLength:$dataLength"); AppLog.log("dataLength:$dataLength");
// var dataLength = reply.data[5]; // var dataLength = reply.data[5];
if (dataLength > 0) { if (dataLength > 0) {
reply.data.removeRange(0, 7); reply.data.removeRange(0, 7);
@ -108,6 +110,8 @@ class DoorLockLogLogic extends BaseGetXController {
state.ifHaveNext = false; state.ifHaveNext = false;
} }
lockRecordUploadData(uploadList); lockRecordUploadData(uploadList);
} else {
showToast('暂无最新记录'.tr);
} }
break; break;
case 0x06: case 0x06:
@ -117,6 +121,7 @@ class DoorLockLogLogic extends BaseGetXController {
default: default:
// //
dismissEasyLoading(); dismissEasyLoading();
cancelBlueConnetctToastTimer();
break; break;
} }
} }
@ -148,30 +153,23 @@ class DoorLockLogLogic extends BaseGetXController {
).toString(); ).toString();
showEasyLoading(); showEasyLoading();
showBlueConnetctToastTimer( showBlueConnetctToastTimer(action: () async {
isShowBlueConnetctToast: true, dismissEasyLoading();
action: () async { final String getMobile = (await Storage.getMobile())!;
cancelBlueConnetctToastTimer(); ApmHelper.instance.trackEvent('check_doorLockLog', {
'lockName': state.keyInfos.value.lockName!,
'account':
getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
'date': DateTool().getNowDateWithType(1),
'open_lock_result': '超时',
});
final String getMobile = (await Storage.getMobile())!; BuglyTool.uploadException(
ApmHelper.instance.trackEvent('check_doorLockLog', { message: '查询锁记录超时-查询锁记录失败',
'lockName': state.keyInfos.value.lockName!, detail: '添加密码超时,查询锁记录失败--senderReferEventRecordTimeCommand:$command',
'account': eventStr: '查询锁记录事件超时',
getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!, upload: true);
'date': DateTool().getNowDateWithType(1), });
'open_lock_result': '超时',
});
BuglyTool.uploadException(
message: '查询锁记录超时-查询锁记录失败',
detail:
'添加密码超时,查询锁记录失败--senderReferEventRecordTimeCommand:$command',
eventStr: '查询锁记录事件超时',
upload: true);
if (state.isLockReceiveResponse == false) {
dismissEasyLoading();
}
});
BlueManage().blueSendData(BlueManage().connectDeviceName, BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState connectionStateState) async { (BluetoothConnectionState connectionStateState) async {
if (connectionStateState == BluetoothConnectionState.connected) { if (connectionStateState == BluetoothConnectionState.connected) {
@ -303,20 +301,22 @@ class DoorLockLogLogic extends BaseGetXController {
lockId: state.keyInfos.value.lockId.toString(), records: list); lockId: state.keyInfos.value.lockId.toString(), records: list);
final String getMobile = (await Storage.getMobile())!; final String getMobile = (await Storage.getMobile())!;
if (entity.errorCode!.codeIsSuccessful) { if (entity.errorCode!.codeIsSuccessful) {
if (state.ifHaveNext == true) { showToast('操作成功'.tr, something: () async {
showEasyLoading(); dismissEasyLoading();
getLockRecordLastUploadDataTime(); if (state.ifHaveNext == true) {
} else { showEasyLoading();
ApmHelper.instance.trackEvent('check_doorLockLog', { getLockRecordLastUploadDataTime();
'lockName': state.keyInfos.value.lockName!, } else {
'account': ApmHelper.instance.trackEvent('check_doorLockLog', {
getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!, 'lockName': state.keyInfos.value.lockName!,
'date': DateTool().getNowDateWithType(1), 'account':
'open_lock_result': '成功', getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
}); 'date': DateTool().getNowDateWithType(1),
mockNetworkDataRequest(isRefresh: true); 'open_lock_result': '成功',
} });
dismissEasyLoading(); mockNetworkDataRequest(isRefresh: true);
}
});
} else { } else {
ApmHelper.instance.trackEvent('check_doorLockLog', { ApmHelper.instance.trackEvent('check_doorLockLog', {
'lockName': state.keyInfos.value.lockName!, 'lockName': state.keyInfos.value.lockName!,

View File

@ -95,6 +95,9 @@ class _SendEmailNotificationPageState extends State<SendEmailNotificationPage> {
maxLength: 1000, maxLength: 1000,
textAlign: TextAlign.start, textAlign: TextAlign.start,
controller: state.templateContentController, controller: state.templateContentController,
keyboardType: TextInputType.multiline, //
textInputAction: TextInputAction.done, //
onEditingComplete: () => FocusScope.of(context).unfocus(), //
style: TextStyle( style: TextStyle(
color: Colors.black, color: Colors.black,
fontSize: 22.sp, fontSize: 22.sp,

View File

@ -30,9 +30,10 @@ class MotorPowerLogic extends BaseGetXController {
state.lockSetInfoData.value.lockSettingInfo!.openDirectionValue = state.lockSetInfoData.value.lockSettingInfo!.openDirectionValue =
state.motorTorsion.value; state.motorTorsion.value;
eventBus showToast('操作成功'.tr, something: () {
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value)); eventBus
showToast('操作成功'.tr); .fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
});
} }
} }
@ -80,6 +81,7 @@ class MotorPowerLogic extends BaseGetXController {
switch (status) { switch (status) {
case 0x00: case 0x00:
// //
cancelBlueConnetctToastTimer();
_setLockSetGeneralSetting(); _setLockSetGeneralSetting();
break; break;
case 0x06: case 0x06:
@ -123,6 +125,10 @@ class MotorPowerLogic extends BaseGetXController {
// () // ()
Future<void> sendOpenDoorDirection() async { Future<void> sendOpenDoorDirection() async {
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
});
BlueManage().blueSendData(BlueManage().connectDeviceName, BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async { (BluetoothConnectionState connectionState) async {
if (connectionState == BluetoothConnectionState.connected) { if (connectionState == BluetoothConnectionState.connected) {
@ -149,6 +155,10 @@ class MotorPowerLogic extends BaseGetXController {
needAuthor: 1, needAuthor: 1,
publicKey: getPublicKeyList, publicKey: getPublicKeyList,
privateKey: getPrivateKeyList); privateKey: getPrivateKeyList);
} else if (connectionState == BluetoothConnectionState.disconnected) {
dismissEasyLoading();
cancelBlueConnetctToastTimer();
showBlueConnetctToast();
} }
}); });
} }

View File

@ -38,17 +38,25 @@ class TalkViewLogic extends BaseGetXController {
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state; final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
Timer? _syncTimer; // Timer? _syncTimer; //
Timer? _audioTimer; // Timer? _audioTimer; //
Timer? _networkQualityTimer; //
int _startTime = 0; // int _startTime = 0; //
int bufferSize = 40; // int bufferSize = 40; //
int audioBufferSize = 500; // int audioBufferSize = 500; //
//
final List<double> _lastFewFps = <double>[]; //
int frameIntervalMs = 45; // 4522FPS int frameIntervalMs = 83; // 8312FPS
int audioFrameIntervalMs = 20; // 4522FPS int audioFrameIntervalMs = 20; // 4522FPS
int minFrameIntervalMs = 30; // 33 FPS int minFrameIntervalMs = 83; // 12 FPS
int maxFrameIntervalMs = 100; // 1 FPS int maxFrameIntervalMs = 166; // 6 FPS
// //
final List<int> _bufferedAudioFrames = <int>[]; final List<int> _bufferedAudioFrames = <int>[];
//
final int maxImageCacheCount = 40; //
final Map<String, ui.Image> _imageCache = {};
/// ///
void _initFlutterPcmSound() { void _initFlutterPcmSound() {
const int sampleRate = 8000; const int sampleRate = 8000;
@ -150,63 +158,205 @@ class TalkViewLogic extends BaseGetXController {
/// ///
void _playVideoData(TalkData talkData) async { void _playVideoData(TalkData talkData) async {
state.listData.value = Uint8List.fromList(talkData.content); try {
// key
String cacheKey = talkData.content.hashCode.toString();
//
if (_imageCache.containsKey(cacheKey)) {
// 使
state.currentImage.value = _imageCache[cacheKey];
} else {
// List<int> Uint8List
final Uint8List uint8Data = Uint8List.fromList(talkData.content);
// 线
ui.Image? image = await decodeImageFromList(uint8Data);
//
if (_imageCache.length >= maxImageCacheCount) {
_imageCache.remove(_imageCache.keys.first);
}
//
_imageCache[cacheKey] = image;
state.currentImage.value = image;
}
//
state.listData.value = Uint8List.fromList(talkData.content);
} catch (e) {
print('视频帧解码错误: $e');
}
// state.listData.value = Uint8List.fromList(talkData.content);
} }
/// ///
void _startPlayback() { void _startPlayback() {
Future.delayed(Duration(milliseconds: 800), () { Future.delayed(Duration(milliseconds: 800), () {
//
_networkQualityTimer ??=
Timer.periodic(const Duration(seconds: 5), _checkNetworkQuality);
_startTime = DateTime.now().millisecondsSinceEpoch; _startTime = DateTime.now().millisecondsSinceEpoch;
_syncTimer ??= _syncTimer ??=
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) { Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
// //
_adjustFrameInterval(); _adjustFrameInterval();
//
_monitorFrameStability();
}); });
}); });
} }
/// ///
void _adjustFrameInterval() { void _adjustFrameInterval() {
int newFrameIntervalMs = frameIntervalMs; //
if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) { int targetInterval = _calculateTargetInterval();
//
frameIntervalMs += 5; //
} else if (state.videoBuffer.length > 20 && if (frameIntervalMs != targetInterval) {
frameIntervalMs > minFrameIntervalMs) { // 2ms使
// frameIntervalMs += (targetInterval > frameIntervalMs) ? 2 : -2;
frameIntervalMs -= 5;
//
frameIntervalMs =
frameIntervalMs.clamp(minFrameIntervalMs, maxFrameIntervalMs);
//
if ((frameIntervalMs - targetInterval).abs() >= 5) {
_rebuildTimers();
}
} }
// // int newFrameIntervalMs = frameIntervalMs;
if (newFrameIntervalMs != frameIntervalMs) { // if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
frameIntervalMs = newFrameIntervalMs; // //
// // frameIntervalMs += 5;
_syncTimer?.cancel(); // } else if (state.videoBuffer.length > 20 &&
_syncTimer = // frameIntervalMs > minFrameIntervalMs) {
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) { // //
// // frameIntervalMs -= 5;
_playVideoFrames(); // }
}); // //
// if (newFrameIntervalMs != frameIntervalMs) {
// frameIntervalMs = newFrameIntervalMs;
// //
// _syncTimer?.cancel();
// _syncTimer =
// Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
// //
// _playVideoFrames();
// });
//
// _audioTimer?.cancel();
// _audioTimer =
// Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (timer) {
// final currentTime = DateTime.now().millisecondsSinceEpoch;
// final elapsedTime = currentTime - _startTime;
//
// //
// if (state.audioBuffer.isNotEmpty &&
// state.audioBuffer.first.durationMs <= elapsedTime) {
// //
// if (state.isOpenVoice.value) {
// _playAudioData(state.audioBuffer.removeAt(0));
// } else {
// //
// //
// //
// state.audioBuffer.removeAt(0);
// }
// }
// });
// }
}
_audioTimer?.cancel(); ///
_audioTimer = void _monitorFrameStability() {
Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (timer) { const stabilityThreshold = 5; //
final currentTime = DateTime.now().millisecondsSinceEpoch; final currentFps = 1000 / frameIntervalMs;
final elapsedTime = currentTime - _startTime;
// if (_lastFewFps.length >= 10) {
if (state.audioBuffer.isNotEmpty && _lastFewFps.removeAt(0);
state.audioBuffer.first.durationMs <= elapsedTime) { }
// _lastFewFps.add(currentFps);
if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0)); //
} else { if (_lastFewFps.length >= 5) {
// double mean = _lastFewFps.reduce((a, b) => a + b) / _lastFewFps.length;
// double variance =
// _lastFewFps.map((fps) => pow(fps - mean, 2)).reduce((a, b) => a + b) /
state.audioBuffer.removeAt(0); _lastFewFps.length;
} double stdDev = sqrt(variance);
}
}); //
if (stdDev > stabilityThreshold) {
_smoothFrameRate(mean);
}
}
}
///
void _checkNetworkQuality(Timer timer) {
final bufferHealth = state.videoBuffer.length / bufferSize;
if (bufferHealth < 0.3) {
// 30%
//
frameIntervalMs = min(frameIntervalMs + 10, maxFrameIntervalMs);
_rebuildTimers();
} else if (bufferHealth > 0.7) {
// 70%
//
frameIntervalMs = max(frameIntervalMs - 5, minFrameIntervalMs);
_rebuildTimers();
}
}
///
int _calculateTargetInterval() {
const int optimalBufferSize = 15; //
const int bufferTolerance = 5; //
if (state.videoBuffer.length < optimalBufferSize - bufferTolerance) {
//
return (frameIntervalMs * 1.2).round();
} else if (state.videoBuffer.length > optimalBufferSize + bufferTolerance) {
//
return (frameIntervalMs * 0.8).round();
}
return frameIntervalMs;
}
///
void _rebuildTimers() {
//
_syncTimer?.cancel();
_audioTimer?.cancel();
//
_syncTimer =
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
_playVideoFrames();
});
// 使
_audioTimer =
Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (timer) {
_processAudioFrame();
});
}
///
void _processAudioFrame() {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime;
while (state.audioBuffer.isNotEmpty &&
state.audioBuffer.first.durationMs <= elapsedTime) {
if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0));
} else {
state.audioBuffer.removeAt(0);
}
} }
} }
@ -404,6 +554,24 @@ class TalkViewLogic extends BaseGetXController {
requestPermissions(); requestPermissions();
} }
///
void _smoothFrameRate(double targetFps) {
//
int targetInterval = (1000 / targetFps).round();
// 使
double weight = 0.3; //
frameIntervalMs =
(frameIntervalMs * (1 - weight) + targetInterval * weight).round();
//
frameIntervalMs =
frameIntervalMs.clamp(minFrameIntervalMs, maxFrameIntervalMs);
//
_rebuildTimers();
}
@override @override
void onClose() { void onClose() {
_stopPlayG711Data(); // _stopPlayG711Data(); //
@ -416,7 +584,13 @@ class TalkViewLogic extends BaseGetXController {
_audioTimer = null; // _audioTimer = null; //
state.oneMinuteTimeTimer?.cancel(); state.oneMinuteTimeTimer?.cancel();
state.oneMinuteTimeTimer = null; state.oneMinuteTimeTimer = null;
//
_networkQualityTimer?.cancel();
_lastFewFps.clear();
stopProcessingAudio(); stopProcessingAudio();
//
_imageCache.clear();
super.onClose(); super.onClose();
} }

View File

@ -133,20 +133,16 @@ class _TalkViewPageState extends State<TalkViewPage>
child: SizedBox.expand( child: SizedBox.expand(
child: RotatedBox( child: RotatedBox(
quarterTurns: -1, quarterTurns: -1,
child: Image.memory( child: Obx(
state.listData.value, () => state.currentImage.value != null
width: ScreenUtil().scaleWidth, ? RawImage(
height: ScreenUtil().scaleHeight, image: state.currentImage.value,
gaplessPlayback: true, width: ScreenUtil().scaleWidth,
fit: BoxFit.cover, height: ScreenUtil().scaleHeight,
filterQuality: FilterQuality.high, fit: BoxFit.cover,
errorBuilder: ( filterQuality: FilterQuality.high,
BuildContext context, )
Object error, : Container(color: Colors.transparent),
StackTrace? stackTrace,
) {
return Container(color: Colors.transparent);
},
), ),
), ),
), ),

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_voice_processor/flutter_voice_processor.dart'; import 'package:flutter_voice_processor/flutter_voice_processor.dart';
@ -89,4 +90,6 @@ class TalkViewState {
RxBool isLongPressing = false.obs; // RxBool isLongPressing = false.obs; //
RxBool hasAudioData = false.obs; // RxBool hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; // RxInt lastAudioTimestamp = 0.obs; //
//
final Rx<ui.Image?> currentImage = Rx<ui.Image?>(null);
} }