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

View File

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

View File

@ -30,9 +30,10 @@ class MotorPowerLogic extends BaseGetXController {
state.lockSetInfoData.value.lockSettingInfo!.openDirectionValue =
state.motorTorsion.value;
eventBus
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
showToast('操作成功'.tr);
showToast('操作成功'.tr, something: () {
eventBus
.fire(PassCurrentLockInformationEvent(state.lockSetInfoData.value));
});
}
}
@ -80,6 +81,7 @@ class MotorPowerLogic extends BaseGetXController {
switch (status) {
case 0x00:
//
cancelBlueConnetctToastTimer();
_setLockSetGeneralSetting();
break;
case 0x06:
@ -123,6 +125,10 @@ class MotorPowerLogic extends BaseGetXController {
// ()
Future<void> sendOpenDoorDirection() async {
showEasyLoading();
showBlueConnetctToastTimer(action: () {
dismissEasyLoading();
});
BlueManage().blueSendData(BlueManage().connectDeviceName,
(BluetoothConnectionState connectionState) async {
if (connectionState == BluetoothConnectionState.connected) {
@ -149,6 +155,10 @@ class MotorPowerLogic extends BaseGetXController {
needAuthor: 1,
publicKey: getPublicKeyList,
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;
Timer? _syncTimer; //
Timer? _audioTimer; //
Timer? _networkQualityTimer; //
int _startTime = 0; //
int bufferSize = 40; //
int audioBufferSize = 500; //
//
final List<double> _lastFewFps = <double>[]; //
int frameIntervalMs = 45; // 4522FPS
int frameIntervalMs = 83; // 8312FPS
int audioFrameIntervalMs = 20; // 4522FPS
int minFrameIntervalMs = 30; // 33 FPS
int maxFrameIntervalMs = 100; // 1 FPS
int minFrameIntervalMs = 83; // 12 FPS
int maxFrameIntervalMs = 166; // 6 FPS
//
final List<int> _bufferedAudioFrames = <int>[];
//
final int maxImageCacheCount = 40; //
final Map<String, ui.Image> _imageCache = {};
///
void _initFlutterPcmSound() {
const int sampleRate = 8000;
@ -150,63 +158,205 @@ class TalkViewLogic extends BaseGetXController {
///
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() {
Future.delayed(Duration(milliseconds: 800), () {
//
_networkQualityTimer ??=
Timer.periodic(const Duration(seconds: 5), _checkNetworkQuality);
_startTime = DateTime.now().millisecondsSinceEpoch;
_syncTimer ??=
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
//
_adjustFrameInterval();
//
_monitorFrameStability();
});
});
}
///
void _adjustFrameInterval() {
int newFrameIntervalMs = frameIntervalMs;
if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
//
frameIntervalMs += 5;
} else if (state.videoBuffer.length > 20 &&
frameIntervalMs > minFrameIntervalMs) {
//
frameIntervalMs -= 5;
//
int targetInterval = _calculateTargetInterval();
//
if (frameIntervalMs != targetInterval) {
// 2ms使
frameIntervalMs += (targetInterval > frameIntervalMs) ? 2 : -2;
//
frameIntervalMs =
frameIntervalMs.clamp(minFrameIntervalMs, maxFrameIntervalMs);
//
if ((frameIntervalMs - targetInterval).abs() >= 5) {
_rebuildTimers();
}
}
//
if (newFrameIntervalMs != frameIntervalMs) {
frameIntervalMs = newFrameIntervalMs;
//
_syncTimer?.cancel();
_syncTimer =
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
//
_playVideoFrames();
});
// int newFrameIntervalMs = frameIntervalMs;
// if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
// //
// frameIntervalMs += 5;
// } else if (state.videoBuffer.length > 20 &&
// frameIntervalMs > minFrameIntervalMs) {
// //
// frameIntervalMs -= 5;
// }
// //
// 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 =
Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (timer) {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime;
///
void _monitorFrameStability() {
const stabilityThreshold = 5; //
final currentFps = 1000 / frameIntervalMs;
//
if (state.audioBuffer.isNotEmpty &&
state.audioBuffer.first.durationMs <= elapsedTime) {
//
if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0));
} else {
//
//
//
state.audioBuffer.removeAt(0);
}
}
});
if (_lastFewFps.length >= 10) {
_lastFewFps.removeAt(0);
}
_lastFewFps.add(currentFps);
//
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) /
_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();
}
///
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
void onClose() {
_stopPlayG711Data(); //
@ -416,7 +584,13 @@ class TalkViewLogic extends BaseGetXController {
_audioTimer = null; //
state.oneMinuteTimeTimer?.cancel();
state.oneMinuteTimeTimer = null;
//
_networkQualityTimer?.cancel();
_lastFewFps.clear();
stopProcessingAudio();
//
_imageCache.clear();
super.onClose();
}

View File

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

View File

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