Merge branch 'develop_liyi' into canary_release
This commit is contained in:
commit
88fc39cce1
@ -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!,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -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; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||
int frameIntervalMs = 83; // 初始帧间隔设置为83毫秒(12FPS)
|
||||
int audioFrameIntervalMs = 20; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user