Merge branch 'develop_liyi' into canary_release
This commit is contained in:
commit
52dc86018d
BIN
android/app/src/main/res/raw/ring.mp3
Executable file
BIN
android/app/src/main/res/raw/ring.mp3
Executable file
Binary file not shown.
@ -1145,5 +1145,6 @@
|
||||
"2.在APP里开启锁的远程开锁功能(这个功能默认是关闭的)。如果没有这个选项,则锁不支持Google Home": "2. Enable the remote unlocking function of the lock in the APP (this function is turned off by default). If this option is not available, the lock will not support Google Home",
|
||||
"3.安装Google Home APP,点击左上角的加号按钮": "3. Install the Google Home app and click the plus button in the upper left corner",
|
||||
"网关通电后,长按重置按钮5秒,蓝色指示灯闪烁时点击下一步": "After the gateway is powered on, press and hold the reset button for 5 seconds. Click Next when the blue indicator light flashes",
|
||||
"暂无最新记录": "There are currently no latest records available",
|
||||
"网关添加成功": "Gateway added successfully"
|
||||
}
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
{
|
||||
"星锁": "星空鎖",
|
||||
"锁通通": "鎖定直通",
|
||||
"点击开锁,长按闭锁": "掂解鎖,按住鎖定",
|
||||
"星锁": "星鎖",
|
||||
"锁通通": "鎖通通",
|
||||
"点击开锁,长按闭锁": "點擊開鎖,長按閉鎖",
|
||||
"考勤": "出席",
|
||||
"考勤设置": "考勤設置",
|
||||
"电子钥匙": "eKey",
|
||||
"电子钥匙": "電子鑰匙",
|
||||
"添加卡": "添加卡",
|
||||
"卡号": "卡號",
|
||||
"添加指纹": "添加指紋",
|
||||
@ -185,7 +185,7 @@
|
||||
"退出": "註銷",
|
||||
"删除账号": "刪除帳戶",
|
||||
"个人信息": "賬戶信息",
|
||||
"头像": "化身",
|
||||
"头像": "頭像",
|
||||
"昵称": "暱稱",
|
||||
"请输入昵称": "請輸入您的暱稱",
|
||||
"修改昵称": "重命名",
|
||||
@ -373,7 +373,7 @@
|
||||
"未打卡": "暫無記錄",
|
||||
"钥匙将在": "此ekey將在",
|
||||
"天后失效": "天",
|
||||
"电量更新时间:": "電池更新時間:",
|
||||
"电量更新时间:": "電量更新時間:",
|
||||
"新增配件": "加",
|
||||
"钥匙不可用": "密鑰不可用",
|
||||
"正在开锁中...": "解鎖。。。",
|
||||
@ -505,7 +505,7 @@
|
||||
"您的钥匙已过期": "您的密鑰已過期",
|
||||
"常开模式开启": "鎖處於Passage Mode",
|
||||
"超级管理员": "超級管理員",
|
||||
"授权管理员": "設為admin",
|
||||
"授权管理员": "授權管理員",
|
||||
"普通用户": "普通用戶",
|
||||
"余": "平衡",
|
||||
"天": "日",
|
||||
@ -718,7 +718,7 @@
|
||||
"钥匙无效": "密鑰無效",
|
||||
"操作失败,请确认锁是否在附近,或重启手机蓝牙后再试。": "無法連接到鎖。請重新啟動手機嘅Blutooth並重試。",
|
||||
"如果是全自动锁,请使屏幕变亮": "如果係全自動鎖,請讓屏幕更光",
|
||||
"正在尝试闭锁……": "嘗試鎖定。 請稍候。。。",
|
||||
"正在尝试闭锁……": "正在嘗試閉鎖……",
|
||||
"清空记录": "清除記錄",
|
||||
"是否要删除操作记录?": "繼續刪除記錄?",
|
||||
"被删除的记录不能恢复": "刪除後無法恢復記錄。",
|
||||
@ -815,7 +815,7 @@
|
||||
"配置网络": "配置網絡",
|
||||
"你好": "你好",
|
||||
"成功": "成功的",
|
||||
"类型选择": "鍵入select",
|
||||
"类型选择": "類型選擇",
|
||||
"请选择要使用哪种类型": "請選擇要使用的類型",
|
||||
"系统邮件(推荐)": "系統電子郵件(推薦)",
|
||||
"系统短信(推荐)": "系統短信(推薦)",
|
||||
@ -1100,8 +1100,8 @@
|
||||
"英语": "英文",
|
||||
"Google Home操作流程的值": "1.使用智能鎖APP添加鎖和網關\n\n2.喺APP中開啟鎖嘅遠程解鎖功能(此功能默認關閉)。 如果冇此選項,鎖唔撐Google Home\n\n3.安裝Google Home APP,點擊左上角嘅“+”按鈕\n\n4.在“設置”頁面上,選擇“與Google合作”\n\n5.搜索“ScienerSmart”,使用智能鎖APP賬號和密碼進行授權",
|
||||
"密码需至少包含数字/字母/字符中的2种组合": "密碼必須至少包含以下2個:數字、字母同特殊字符",
|
||||
"已开锁": "解鎖",
|
||||
"已闭锁": "鎖",
|
||||
"已开锁": "已開鎖",
|
||||
"已闭锁": "已閉鎖",
|
||||
"两次密码不一致哦": "密碼不一緻",
|
||||
"中功率": "中等功率",
|
||||
"常规使用": "經常使用",
|
||||
|
||||
@ -1148,5 +1148,6 @@
|
||||
"4.在设置页面,选择与Google协同工作": "4.在设置页面,选择与Google协同工作",
|
||||
"5.搜索": "5.搜索",
|
||||
",并用智能锁APP的账号和密码进行授权": ",并用智能锁APP的账号和密码进行授权",
|
||||
"暂无最新记录": "暂无最新记录",
|
||||
"网关添加成功": "网关添加成功"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"星锁": "星形鎖",
|
||||
"锁通通": "鎖定通過",
|
||||
"星锁": "星鎖",
|
||||
"锁通通": "鎖通通",
|
||||
"点击开锁,长按闭锁": "觸摸以解鎖,按住以鎖定",
|
||||
"考勤": "出席情況",
|
||||
"考勤设置": "考勤設置",
|
||||
@ -267,9 +267,9 @@
|
||||
"您可通过邮件将密码、电子钥匙信息发给接收人。": "電子郵件可用於向收件人發送密碼和ekey信息。",
|
||||
"购买实名认证提示": "啟用該功能後,您需要使用指紋,臉部或帳戶密碼才能打開該應用程序。 3分鐘不用再驗證",
|
||||
"请选择你希望的实名认证频次": "請選擇您想要的實名認證頻率",
|
||||
"仅首次": "第一次",
|
||||
"仅首次": "僅首次",
|
||||
"每日一次": "每天一次",
|
||||
"每周一次": "每周一次",
|
||||
"每周一次": "每週一次",
|
||||
"每月一次": "每月一次",
|
||||
"当前状态": "當前狀態",
|
||||
"试用中": "在審判中",
|
||||
@ -1081,8 +1081,8 @@
|
||||
"重置后,该锁的掌静脉都将被删除哦,确认要重置吗?": "重置後,鎖的掌靜脈將被刪除。 是否確實要重置?",
|
||||
"在线": "在線",
|
||||
"离线": "離線",
|
||||
"购买记录": "採購記錄",
|
||||
"使用记录": "用戶記錄",
|
||||
"购买记录": "購買記錄",
|
||||
"使用记录": "使用記錄",
|
||||
"失效时间要大于当前时间": "過期時間必須長於當前時間",
|
||||
"修改名字": "編輯名稱",
|
||||
"时": "小時",
|
||||
|
||||
@ -1148,5 +1148,6 @@
|
||||
"4.在设置页面,选择与Google协同工作": "4.在设置页面,选择与Google协同工作",
|
||||
"5.搜索": "5.搜索",
|
||||
",并用智能锁APP的账号和密码进行授权": ",并用智能锁APP的账号和密码进行授权",
|
||||
"暂无最新记录": "暂无最新记录",
|
||||
"网关添加成功": "网关添加成功"
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
@ -87,7 +87,8 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
|
||||
onTap: () {
|
||||
state.isIphoneType.value = true;
|
||||
},
|
||||
child: Obx(() => Container(
|
||||
child: Obx(
|
||||
() => Container(
|
||||
width: 170.w,
|
||||
height: 60.h,
|
||||
decoration: state.isIphoneType.value
|
||||
@ -99,39 +100,52 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
|
||||
width: 1.0, color: AppColors.greyLineColor))
|
||||
: null,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'手机'.tr,
|
||||
style: TextStyle(
|
||||
color: state.isIphoneType.value
|
||||
? Colors.white
|
||||
: Colors.black),
|
||||
)))),
|
||||
child: Text(
|
||||
'手机'.tr,
|
||||
style: TextStyle(
|
||||
color: state.isIphoneType.value
|
||||
? Colors.white
|
||||
: Colors.black),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
state.isIphoneType.value = false;
|
||||
},
|
||||
child: Obx(() => Container(
|
||||
child: Obx(
|
||||
() => Container(
|
||||
height: 60.h,
|
||||
// color: Colors.red,
|
||||
decoration: state.isIphoneType.value
|
||||
? null
|
||||
: BoxDecoration(
|
||||
color: AppColors.mainColor,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(30.h)),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(
|
||||
30.h,
|
||||
),
|
||||
),
|
||||
border: Border.all(
|
||||
width: 1.0,
|
||||
color: AppColors.greyLineColor)),
|
||||
width: 1.0,
|
||||
color: AppColors.greyLineColor,
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'邮箱'.tr,
|
||||
style: TextStyle(
|
||||
child: Text(
|
||||
'邮箱'.tr,
|
||||
style: TextStyle(
|
||||
color: state.isIphoneType.value
|
||||
? Colors.black
|
||||
: Colors.white),
|
||||
)))),
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -157,8 +171,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
|
||||
children: <Widget>[
|
||||
SizedBox(width: 5.w),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'你所在的国家/地区'.tr,
|
||||
child: Text('你所在的国家/地区'.tr,
|
||||
style: TextStyle(
|
||||
fontSize: 26.sp, color: AppColors.blackColor))),
|
||||
SizedBox(width: 20.w),
|
||||
@ -213,8 +226,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
|
||||
height: 30.w,
|
||||
),
|
||||
),
|
||||
hintText:
|
||||
state.isIphoneType.value ? '请输入手机号'.tr : '请输入邮箱'.tr,
|
||||
hintText: state.isIphoneType.value ? '请输入手机号'.tr : '请输入邮箱'.tr,
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
// FilteringTextInputFormatter.allow(RegExp('[0-9]')),
|
||||
@ -236,7 +248,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
|
||||
height: 30.w,
|
||||
),
|
||||
),
|
||||
hintText:'请输入密码'.tr,
|
||||
hintText: '请输入密码'.tr,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
LengthLimitingTextInputFormatter(20),
|
||||
]),
|
||||
@ -282,8 +294,7 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
|
||||
height: 30.w,
|
||||
),
|
||||
),
|
||||
hintText:
|
||||
'请输入验证码'.tr,
|
||||
hintText: '请输入验证码'.tr,
|
||||
inputFormatters: <TextInputFormatter>[
|
||||
LengthLimitingTextInputFormatter(20),
|
||||
]),
|
||||
@ -292,29 +303,29 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
|
||||
width: 20.w,
|
||||
),
|
||||
Obx(() => GestureDetector(
|
||||
onTap:
|
||||
(state.canSendCode.value && state.canResend.value)
|
||||
? () async {
|
||||
// Navigator.pushNamed(context, Routers.safetyVerificationPage, arguments: {"countryCode":"+86", "account":state.phoneOrEmailStr.value});
|
||||
final Object? result = await Navigator.pushNamed(
|
||||
context, Routers.safetyVerificationPage,
|
||||
arguments: <String, Object>{
|
||||
'countryCode': state.countryCode,
|
||||
'account': state.phoneOrEmailStr.value
|
||||
});
|
||||
state.xWidth.value =
|
||||
(result! as Map<String, dynamic>)['xWidth'];
|
||||
logic.sendValidationCode();
|
||||
}
|
||||
: null,
|
||||
onTap: (state.canSendCode.value && state.canResend.value)
|
||||
? () async {
|
||||
// Navigator.pushNamed(context, Routers.safetyVerificationPage, arguments: {"countryCode":"+86", "account":state.phoneOrEmailStr.value});
|
||||
final Object? result = await Navigator.pushNamed(
|
||||
context, Routers.safetyVerificationPage,
|
||||
arguments: <String, Object>{
|
||||
'countryCode': state.countryCode,
|
||||
'account': state.phoneOrEmailStr.value
|
||||
});
|
||||
state.xWidth.value =
|
||||
(result! as Map<String, dynamic>)['xWidth'];
|
||||
logic.sendValidationCode();
|
||||
}
|
||||
: null,
|
||||
child: Container(
|
||||
width: 180.w,
|
||||
// height: 60.h,
|
||||
padding: EdgeInsets.all(10.h),
|
||||
decoration: BoxDecoration(
|
||||
color: (state.canSendCode.value && state.canResend.value)
|
||||
? AppColors.mainColor
|
||||
: Colors.grey,
|
||||
color:
|
||||
(state.canSendCode.value && state.canResend.value)
|
||||
? AppColors.mainColor
|
||||
: Colors.grey,
|
||||
borderRadius: BorderRadius.circular(5)),
|
||||
child: Center(
|
||||
child: Text(state.btnText.value,
|
||||
@ -361,29 +372,29 @@ class _StarLockRegisterPageState extends State<StarLockRegisterPage> {
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: GestureDetector(
|
||||
child: Text(
|
||||
'《${'用户协议'.tr}》',
|
||||
child: Text('《${'用户协议'.tr}》',
|
||||
style: TextStyle(
|
||||
color: AppColors.mainColor, fontSize: 20.sp)),
|
||||
onTap: () {
|
||||
Get.toNamed(Routers.webviewShowPage, arguments: <String, String>{
|
||||
'url': XSConstantMacro.userAgreementURL,
|
||||
'title': '用户协议'.tr
|
||||
});
|
||||
Get.toNamed(Routers.webviewShowPage,
|
||||
arguments: <String, String>{
|
||||
'url': XSConstantMacro.userAgreementURL,
|
||||
'title': '用户协议'.tr
|
||||
});
|
||||
},
|
||||
)),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: GestureDetector(
|
||||
child: Text(
|
||||
'《${'隐私政策'.tr}》',
|
||||
child: Text('《${'隐私政策'.tr}》',
|
||||
style: TextStyle(
|
||||
color: AppColors.mainColor, fontSize: 20.sp)),
|
||||
onTap: () {
|
||||
Get.toNamed(Routers.webviewShowPage, arguments: <String, String>{
|
||||
'url': XSConstantMacro.privacyPolicyURL,
|
||||
'title': '隐私政策'.tr
|
||||
});
|
||||
Get.toNamed(Routers.webviewShowPage,
|
||||
arguments: <String, String>{
|
||||
'url': XSConstantMacro.privacyPolicyURL,
|
||||
'title': '隐私政策'.tr
|
||||
});
|
||||
},
|
||||
)),
|
||||
],
|
||||
|
||||
@ -531,7 +531,8 @@ class _SendElectronicKeyViewState extends State<SendElectronicKeyView>
|
||||
SizedBox(
|
||||
height: 10.h,
|
||||
),
|
||||
if (logic.emailOrPhone != null)
|
||||
if (logic.emailOrPhone != null &&
|
||||
logic.state.currentLanguage.value == 'zh_CN')
|
||||
OutLineBtn(
|
||||
btnName: '微信通知'.tr,
|
||||
onClick: () {
|
||||
|
||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_native_contact_picker/flutter_native_contact_picker.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:star_lock/tools/dateTool.dart';
|
||||
import 'package:star_lock/translations/current_locale_tool.dart';
|
||||
|
||||
class SendElectronicKeyViewState {
|
||||
//循环
|
||||
@ -42,4 +43,7 @@ class SendElectronicKeyViewState {
|
||||
final String permanentTips = '接收者可以使用此App开关锁'.tr; //永久
|
||||
final String onceLimitTips = '单次钥匙有效期为1小时,只能使用一次'.tr; //单次
|
||||
final String cycleLimitTips = '接收者可以在有效期内的固定时间段里,不限次数使用'.tr;
|
||||
|
||||
RxString currentLanguage =
|
||||
CurrentLocaleTool.getCurrentLocaleString().obs; // 当前选择语言
|
||||
}
|
||||
|
||||
@ -71,6 +71,7 @@ class _ValueAddedServicesRecordPageState
|
||||
// 购买记录
|
||||
class _PurchaseRecords extends StatelessWidget {
|
||||
const _PurchaseRecords({required this.buyRecordList, required this.logic});
|
||||
|
||||
final List<UseItemData> buyRecordList;
|
||||
final ValueAddedServicesRecordLogic logic;
|
||||
|
||||
@ -134,6 +135,7 @@ class _PurchaseRecords extends StatelessWidget {
|
||||
// 使用记录
|
||||
class _UseRecordsTable extends StatelessWidget {
|
||||
const _UseRecordsTable({required this.useRecordList, required this.logic});
|
||||
|
||||
final List<UseItemData> useRecordList;
|
||||
final ValueAddedServicesRecordLogic logic;
|
||||
|
||||
@ -172,9 +174,12 @@ class _UseRecordsTable extends StatelessWidget {
|
||||
Text(
|
||||
logic.state.useCountStr.value,
|
||||
style: TextStyle(
|
||||
fontSize: 24.sp,
|
||||
color: AppColors.blackColor,
|
||||
fontWeight: FontWeight.bold),
|
||||
fontSize: 24.sp,
|
||||
color: AppColors.blackColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis, // 超出显示省略号
|
||||
maxLines: 1, // 限制为单行
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
if (itemData.authStatus == 0)
|
||||
|
||||
@ -43,7 +43,7 @@ class ScpMessageBaseHandle {
|
||||
// 存储每个 messageId 对应的分包数据
|
||||
static Map<String, List<List<int>>> _packetBuffer = {};
|
||||
final Map<String, Timer> _packetTimers = {};
|
||||
final Duration _timeoutDuration = Duration(seconds: 10); // 分包组包最大超时时间
|
||||
final Duration _timeoutDuration = Duration(seconds: 3); // 分包组包最大超时时间
|
||||
|
||||
// 通话数据流的单例流数据处理类
|
||||
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
|
||||
@ -106,40 +106,104 @@ class ScpMessageBaseHandle {
|
||||
required int payloadType,
|
||||
}) {
|
||||
// 初始化分包列表
|
||||
String key = '$messageId-$payloadType';
|
||||
if (!_packetBuffer.containsKey(key)) {
|
||||
_packetBuffer[key] = List.filled(spTotal, []);
|
||||
_startTimer(key);
|
||||
}
|
||||
// 使用更高效的key生成方式
|
||||
final key = '${messageId}_$payloadType';
|
||||
|
||||
// 打印每个包的信息
|
||||
// AppLog.log(
|
||||
// '📦 收到分包 - MessageId: $messageId, 总包数: $spTotal, 当前包序号: $spIndex');
|
||||
|
||||
// 检查分包索引是否在合法范围内
|
||||
if (spIndex < 1 || spIndex > spTotal) {
|
||||
// print(
|
||||
// 'Invalid spTotal: $spTotal spIndex: $spIndex for messageId: $messageId');
|
||||
AppLog.log(
|
||||
'❌ 分包序号异常 - MessageId: $messageId, 总包数: $spTotal, 无效包序号: $spIndex');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查分包索引是否在合法范围内(提前检查可以避免后续无效操作)
|
||||
if (spIndex < 1 || spIndex > spTotal) return null;
|
||||
|
||||
// 初始化分包列表(使用固定长度列表提高性能)
|
||||
var packets = _packetBuffer[key];
|
||||
if (packets == null) {
|
||||
// 预分配固定大小的列表,避免动态扩容
|
||||
packets = List<List<int>>.filled(spTotal, const [], growable: false);
|
||||
_packetBuffer[key] = packets;
|
||||
_startTimer(key);
|
||||
// AppLog.log('📝 新建分包缓存 - MessageId: $messageId, 预期总包数: $spTotal');
|
||||
}
|
||||
|
||||
// 存储当前分包
|
||||
_packetBuffer[key]![spIndex - 1] = byte;
|
||||
packets[spIndex - 1] = byte;
|
||||
|
||||
// 检查是否接收到所有分包
|
||||
if (_packetBuffer[key]!.every((packet) => packet.isNotEmpty)) {
|
||||
// 重组所有分包
|
||||
Uint8List completePayload = Uint8List.fromList(
|
||||
_packetBuffer[key]!.expand((packet) => packet).toList());
|
||||
// 清除已重组和超时的分包数据
|
||||
// 优化检查逻辑,使用循环替代every
|
||||
var isComplete = true;
|
||||
var totalLength = 0;
|
||||
for (var i = 0; i < packets.length; i++) {
|
||||
if (packets[i].isEmpty) {
|
||||
isComplete = false;
|
||||
} else {
|
||||
totalLength += packets[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
if (isComplete) {
|
||||
// 预分配确切大小的buffer,避免扩容
|
||||
final buffer = Uint8List(totalLength);
|
||||
var offset = 0;
|
||||
|
||||
// 直接复制数据,避免使用expand
|
||||
for (var packet in packets) {
|
||||
buffer.setRange(offset, offset + packet.length, packet);
|
||||
offset += packet.length;
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
_clearPacketData(key);
|
||||
|
||||
// 使用重组的包构造成TalkData
|
||||
// 构造TalkData
|
||||
if (payloadType == PayloadTypeConstant.talkData) {
|
||||
final talkData = TalkData();
|
||||
talkData.mergeFromBuffer(completePayload);
|
||||
talkData.mergeFromBuffer(buffer);
|
||||
return talkData;
|
||||
}
|
||||
} else {
|
||||
// 如果分包尚未接收完全,返回 null 或其他指示符
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
// if (!_packetBuffer.containsKey(key)) {
|
||||
// _packetBuffer[key] = List.filled(spTotal, []);
|
||||
// _startTimer(key);
|
||||
// }
|
||||
//
|
||||
// // 检查分包索引是否在合法范围内
|
||||
// if (spIndex < 1 || spIndex > spTotal) {
|
||||
// // print(
|
||||
// // 'Invalid spTotal: $spTotal spIndex: $spIndex for messageId: $messageId');
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// // 存储当前分包
|
||||
// _packetBuffer[key]![spIndex - 1] = byte;
|
||||
//
|
||||
// // 检查是否接收到所有分包
|
||||
// if (_packetBuffer[key]!.every((packet) => packet.isNotEmpty)) {
|
||||
// // 重组所有分包
|
||||
// Uint8List completePayload = Uint8List.fromList(
|
||||
// _packetBuffer[key]!.expand((packet) => packet).toList());
|
||||
// // 清除已重组和超时的分包数据
|
||||
// _clearPacketData(key);
|
||||
//
|
||||
// // 使用重组的包构造成TalkData
|
||||
// if (payloadType == PayloadTypeConstant.talkData) {
|
||||
// final talkData = TalkData();
|
||||
// talkData.mergeFromBuffer(completePayload);
|
||||
// return talkData;
|
||||
// }
|
||||
// } else {
|
||||
// // 如果分包尚未接收完全,返回 null 或其他指示符
|
||||
// return null;
|
||||
// }
|
||||
}
|
||||
|
||||
// 启动定时器
|
||||
|
||||
@ -36,25 +36,24 @@ class TalkViewLogic extends BaseGetXController {
|
||||
final TalkViewState state = TalkViewState();
|
||||
|
||||
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>[]; // 存储最近的帧率数据
|
||||
final int minBufferSize = 2; // 最小缓冲2帧,约166ms
|
||||
final int maxBufferSize = 8; // 最大缓冲8帧,约666ms
|
||||
int bufferSize = 3; // 初始化为默认大小
|
||||
// 修改音频相关的成员变量
|
||||
final int minAudioBufferSize = 1; // 音频最小缓冲1帧
|
||||
final int maxAudioBufferSize = 3; // 音频最大缓冲3帧
|
||||
int audioBufferSize = 2; // 音频默认缓冲2帧
|
||||
|
||||
// 添加开始时间记录
|
||||
int _startTime = 0; // 开始播放时间戳
|
||||
int _startAudioTime = 0; // 开始播放时间戳
|
||||
bool _isFirstFrame = true; // 是否是第一帧
|
||||
bool _isFirstAudioFrame = true; // 是否是第一帧
|
||||
|
||||
int frameIntervalMs = 83; // 初始帧间隔设置为83毫秒(12FPS)
|
||||
int audioFrameIntervalMs = 20; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||
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 = {};
|
||||
|
||||
/// 初始化音频播放器
|
||||
@ -91,24 +90,173 @@ class TalkViewLogic extends BaseGetXController {
|
||||
void _startListenTalkData() {
|
||||
state.talkDataRepository.talkDataStream.listen((TalkData talkData) async {
|
||||
final contentType = talkData.contentType;
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
// 判断数据类型,进行分发处理
|
||||
switch (contentType) {
|
||||
case TalkData_ContentTypeE.G711:
|
||||
// 第一帧到达时记录开始时间
|
||||
if (_isFirstAudioFrame) {
|
||||
_startAudioTime = currentTime;
|
||||
_isFirstAudioFrame = false;
|
||||
}
|
||||
|
||||
// 计算音频延迟
|
||||
final expectedTime = _startAudioTime + talkData.durationMs;
|
||||
final audioDelay = currentTime - expectedTime;
|
||||
|
||||
// 如果延迟太大,清空缓冲区并直接播放
|
||||
if (audioDelay > 500) {
|
||||
state.audioBuffer.clear();
|
||||
if (state.isOpenVoice.value) {
|
||||
_playAudioFrames();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (state.audioBuffer.length >= audioBufferSize) {
|
||||
state.audioBuffer.removeAt(0); // 丢弃最旧的数据
|
||||
}
|
||||
state.audioBuffer.add(talkData); // 添加新数据
|
||||
// 添加音频播放逻辑,与视频类似
|
||||
_playAudioFrames();
|
||||
break;
|
||||
case TalkData_ContentTypeE.Image:
|
||||
if (state.videoBuffer.length >= bufferSize) {
|
||||
state.videoBuffer.removeAt(0); // 丢弃最旧的数据
|
||||
// 第一帧到达时记录开始时间
|
||||
if (_isFirstFrame) {
|
||||
_startTime = currentTime;
|
||||
_isFirstFrame = false;
|
||||
AppLog.log('记录第一帧的时间戳${currentTime},${talkData.durationMs}');
|
||||
}
|
||||
state.videoBuffer.add(talkData); // 添加新数据
|
||||
|
||||
// 计算实际延迟:当前时间 - 预期播放时间
|
||||
final expectedTime = _startTime + talkData.durationMs;
|
||||
final videoDelay = currentTime - expectedTime; // 修改延迟计算方式
|
||||
|
||||
// 动态调整缓冲区
|
||||
_adjustBufferSize(videoDelay);
|
||||
// 然后添加到播放缓冲区
|
||||
if (state.videoBuffer.length >= bufferSize) {
|
||||
state.videoBuffer.removeAt(0);
|
||||
}
|
||||
state.videoBuffer.add(talkData);
|
||||
// 先进行解码和缓存
|
||||
await _decodeAndCacheFrame(talkData);
|
||||
// 最后尝试播放
|
||||
_playVideoFrames();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 修改:视频帧播放逻辑
|
||||
void _playVideoFrames() {
|
||||
// 如果缓冲区为空或未达到目标大小,不进行播放
|
||||
if (state.videoBuffer.isEmpty || state.videoBuffer.length < bufferSize) {
|
||||
// AppLog.log('📊 缓冲中 - 当前缓冲区大小: ${state.videoBuffer.length}/${bufferSize}');
|
||||
return;
|
||||
}
|
||||
// 找出时间戳最小的帧(最旧的帧)
|
||||
TalkData? oldestFrame;
|
||||
int oldestIndex = -1;
|
||||
for (int i = 0; i < state.videoBuffer.length; i++) {
|
||||
if (oldestFrame == null ||
|
||||
state.videoBuffer[i].durationMs < oldestFrame.durationMs) {
|
||||
oldestFrame = state.videoBuffer[i];
|
||||
oldestIndex = i;
|
||||
}
|
||||
}
|
||||
// 确保找到了有效帧
|
||||
if (oldestFrame != null && oldestIndex != -1) {
|
||||
final cacheKey = oldestFrame.content.hashCode.toString();
|
||||
|
||||
// 使用缓存的解码图片更新显示
|
||||
if (_imageCache.containsKey(cacheKey)) {
|
||||
state.currentImage.value = _imageCache[cacheKey];
|
||||
state.listData.value = Uint8List.fromList(oldestFrame.content);
|
||||
state.videoBuffer.removeAt(oldestIndex); // 移除已播放的帧
|
||||
|
||||
// AppLog.log('🎬 播放帧 - 缓冲区剩余: ${state.videoBuffer.length}/${bufferSize}, '
|
||||
// '播放延迟: ${currentTime - oldestFrame.durationMs}ms, '
|
||||
// '帧时间戳: ${oldestFrame.durationMs}');
|
||||
} else {
|
||||
// AppLog.log('⚠️ 帧未找到缓存 - Key: $cacheKey');
|
||||
state.videoBuffer.removeAt(oldestIndex); // 移除无法播放的帧
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:音频帧播放逻辑
|
||||
void _playAudioFrames() {
|
||||
// 如果缓冲区为空或未达到目标大小,不进行播放
|
||||
// 音频缓冲区要求更小,以减少延迟
|
||||
if (state.audioBuffer.isEmpty ||
|
||||
state.audioBuffer.length < audioBufferSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 找出时间戳最小的音频帧
|
||||
TalkData? oldestFrame;
|
||||
int oldestIndex = -1;
|
||||
for (int i = 0; i < state.audioBuffer.length; i++) {
|
||||
if (oldestFrame == null ||
|
||||
state.audioBuffer[i].durationMs < oldestFrame.durationMs) {
|
||||
oldestFrame = state.audioBuffer[i];
|
||||
oldestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// 确保找到了有效帧
|
||||
if (oldestFrame != null && oldestIndex != -1) {
|
||||
if (state.isOpenVoice.value) {
|
||||
// 播放音频
|
||||
_playAudioData(oldestFrame);
|
||||
}
|
||||
state.audioBuffer.removeAt(oldestIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:解码和缓存帧的方法
|
||||
Future<void> _decodeAndCacheFrame(TalkData talkData) async {
|
||||
try {
|
||||
String cacheKey = talkData.content.hashCode.toString();
|
||||
|
||||
// 如果该帧还没有被缓存,则进行解码和缓存
|
||||
if (!_imageCache.containsKey(cacheKey)) {
|
||||
final Uint8List uint8Data = Uint8List.fromList(talkData.content);
|
||||
final ui.Image image = await decodeImageFromList(uint8Data);
|
||||
|
||||
// 管理缓存大小
|
||||
if (_imageCache.length >= bufferSize) {
|
||||
_imageCache.remove(_imageCache.keys.first);
|
||||
}
|
||||
|
||||
// 添加到缓存
|
||||
_imageCache[cacheKey] = image;
|
||||
|
||||
// AppLog.log('📥 缓存新帧 - 缓存数: ${_imageCache.length}, Key: $cacheKey');
|
||||
}
|
||||
} catch (e) {
|
||||
AppLog.log('❌ 帧解码错误: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:动态调整缓冲区大小的方法
|
||||
void _adjustBufferSize(int delay) {
|
||||
const int delayThresholdHigh = 250; // 高延迟阈值(约3帧的时间)
|
||||
const int delayThresholdLow = 166; // 低延迟阈值(约2帧的时间)
|
||||
const int adjustInterval = 1; // 每次调整1帧
|
||||
|
||||
if (delay > delayThresholdHigh && bufferSize < maxBufferSize) {
|
||||
// 延迟较大,增加缓冲区
|
||||
bufferSize = min(bufferSize + adjustInterval, maxBufferSize);
|
||||
AppLog.log('📈 增加缓冲区 - 当前大小: $bufferSize, 延迟: ${delay}ms');
|
||||
} else if (delay < delayThresholdLow && bufferSize > minBufferSize) {
|
||||
// 延迟较小,减少缓冲区
|
||||
bufferSize = max(bufferSize - adjustInterval, minBufferSize);
|
||||
AppLog.log('📉 减少缓冲区 - 当前大小: $bufferSize, 延迟: ${delay}ms');
|
||||
}
|
||||
}
|
||||
|
||||
/// 监听对讲状态
|
||||
void _startListenTalkStatus() {
|
||||
state.startChartTalkStatus.statusStream.listen((talkStatus) {
|
||||
@ -156,231 +304,6 @@ class TalkViewLogic extends BaseGetXController {
|
||||
}
|
||||
}
|
||||
|
||||
/// 播放视频数据
|
||||
void _playVideoData(TalkData talkData) async {
|
||||
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 targetInterval = _calculateTargetInterval();
|
||||
|
||||
// 平滑过渡到目标帧率,避免突变
|
||||
if (frameIntervalMs != targetInterval) {
|
||||
// 每次最多调整2ms,使变化更平滑
|
||||
frameIntervalMs += (targetInterval > frameIntervalMs) ? 2 : -2;
|
||||
|
||||
// 确保在合理范围内
|
||||
frameIntervalMs =
|
||||
frameIntervalMs.clamp(minFrameIntervalMs, maxFrameIntervalMs);
|
||||
|
||||
// 只在帧间隔变化超过阈值时才重建定时器
|
||||
if ((frameIntervalMs - targetInterval).abs() >= 5) {
|
||||
_rebuildTimers();
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
/// 监控帧率稳定性
|
||||
void _monitorFrameStability() {
|
||||
const stabilityThreshold = 5; // 帧率波动阈值
|
||||
final currentFps = 1000 / frameIntervalMs;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _playVideoFrames() {
|
||||
final currentTime = DateTime.now().millisecondsSinceEpoch;
|
||||
final elapsedTime = currentTime - _startTime;
|
||||
|
||||
// 播放合适的视频帧
|
||||
// 跳帧策略:如果缓冲区中有多个帧,且它们的时间戳都在当前时间之前,则播放最新的帧
|
||||
int maxFramesToProcess = 5; // 每次最多处理 5 帧
|
||||
int processedFrames = 0;
|
||||
|
||||
while (state.videoBuffer.isNotEmpty &&
|
||||
state.videoBuffer.first.durationMs <= elapsedTime &&
|
||||
processedFrames < maxFramesToProcess) {
|
||||
if (state.videoBuffer.length > 1) {
|
||||
state.videoBuffer.removeAt(0);
|
||||
} else {
|
||||
_playVideoData(state.videoBuffer.removeAt(0));
|
||||
}
|
||||
processedFrames++;
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止播放音频
|
||||
void _stopPlayG711Data() async {
|
||||
await FlutterPcmSound.pause();
|
||||
@ -546,7 +469,7 @@ class TalkViewLogic extends BaseGetXController {
|
||||
_initFlutterPcmSound();
|
||||
|
||||
// 启动播放定时器
|
||||
_startPlayback();
|
||||
// _startPlayback();
|
||||
|
||||
// 初始化录音控制器
|
||||
_initAudioRecorder();
|
||||
@ -554,39 +477,16 @@ 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(); // 停止播放音频
|
||||
state.listData.value = Uint8List(0); // 清空视频数据
|
||||
state.audioBuffer.clear(); // 清空音频缓冲区
|
||||
state.videoBuffer.clear(); // 清空视频缓冲区
|
||||
_syncTimer?.cancel(); // 取消定时器
|
||||
_syncTimer = null; // 释放定时器引用
|
||||
_audioTimer?.cancel();
|
||||
_audioTimer = null; // 释放定时器引用
|
||||
|
||||
state.oneMinuteTimeTimer?.cancel();
|
||||
state.oneMinuteTimeTimer = null;
|
||||
// 添加新的清理代码
|
||||
_networkQualityTimer?.cancel();
|
||||
_lastFewFps.clear();
|
||||
|
||||
stopProcessingAudio();
|
||||
// 清理图片缓存
|
||||
_imageCache.clear();
|
||||
|
||||
@ -55,7 +55,6 @@ class TalkViewState {
|
||||
|
||||
// 星图对讲相关状态
|
||||
List<TalkData> audioBuffer = <TalkData>[].obs;
|
||||
List<TalkData> audioBuffer2 = <TalkData>[].obs;
|
||||
List<TalkData> activeAudioBuffer = <TalkData>[].obs;
|
||||
List<TalkData> activeVideoBuffer = <TalkData>[].obs;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user