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