Merge remote-tracking branch 'origin/develop_liyi' into develop_liyi
This commit is contained in:
commit
64563c90a1
@ -56,7 +56,7 @@
|
||||
},
|
||||
flushingTime: 0, // 禁用自动刷新
|
||||
clearBuffer: false, // 保留解码缓存
|
||||
maxBufferLength: 2,
|
||||
fps:20,
|
||||
onReady: () => {
|
||||
console.log('播放器初始化完成');
|
||||
// 通知Flutter端准备就绪
|
||||
|
||||
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart';
|
||||
|
||||
import '../../../blue/io_reply.dart';
|
||||
import '../../lockMian/entity/lockListInfo_entity.dart';
|
||||
@ -9,7 +10,7 @@ import '../../lockMian/entity/lockListInfo_entity.dart';
|
||||
|
||||
class LockDetailState {
|
||||
Rx<LockListInfoItemEntity> keyInfos = LockListInfoItemEntity().obs;
|
||||
|
||||
final Rx<LockSetInfoData> lockSetInfoData = LockSetInfoData().obs;
|
||||
late StreamSubscription<Reply> replySubscription;
|
||||
StreamSubscription? lockSetOpenOrCloseCheckInRefreshLockDetailWithAttendanceEvent;
|
||||
StreamSubscription? LockSetChangeSetRefreshLockDetailWithTypeSubscription;
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:network_info_plus/network_info_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:star_lock/appRouters.dart';
|
||||
import 'package:star_lock/app_settings/app_settings.dart';
|
||||
import 'package:star_lock/blue/io_gateway/io_gateway_configuringWifi.dart';
|
||||
import 'package:star_lock/blue/io_gateway/io_gateway_getStatus.dart';
|
||||
@ -68,7 +69,12 @@ class ConfiguringWifiLogic extends BaseGetXController {
|
||||
secretKey: secretKey,
|
||||
peerId: peerId,
|
||||
);
|
||||
Get.close(2);
|
||||
|
||||
if (state.pageName.value == 'lockSet') {
|
||||
Get.close(2);
|
||||
} else {
|
||||
Get.offAllNamed(Routers.starLockMain);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,7 +47,8 @@ class _ConfiguringWifiPageState extends State<ConfiguringWifiPage>
|
||||
SubmitBtn(
|
||||
btnName: '确定'.tr,
|
||||
onClick: () {
|
||||
logic.senderConfiguringWifiAction();
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
logic.senderConfiguringWifiAction();
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@ -9,6 +9,7 @@ class ConfiguringWifiState{
|
||||
ConfiguringWifiState() {
|
||||
var map = Get.arguments;
|
||||
lockSetInfoData.value = map['lockSetInfoData'];
|
||||
pageName.value = map['pageName'];
|
||||
lockBasicInfo.value = lockSetInfoData.value.lockBasicInfo!;
|
||||
if (map['wifiName'] != null) {
|
||||
wifiName.value = map['wifiName'];
|
||||
@ -20,6 +21,7 @@ class ConfiguringWifiState{
|
||||
Rx<LockBasicInfo> lockBasicInfo = LockBasicInfo().obs;
|
||||
|
||||
RxString wifiName = ''.obs;
|
||||
RxString pageName = ''.obs;
|
||||
RxBool ifCurrentScreen = true.obs; // 是否是当前界面,用于判断是否需要针对当前界面进行展示
|
||||
RxInt sureBtnState = 0.obs;// 0普通状态(可用) 1连接中(不可用)
|
||||
|
||||
|
||||
@ -24,11 +24,18 @@ class _WifiListPageState extends State<WifiListPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: TitleAppBar(
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (state.pageName.value == 'lockSet') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: TitleAppBar(
|
||||
barTitle: 'WIFI列表'.tr,
|
||||
haveBack: true,
|
||||
haveBack: state.pageName.value == 'lockSet',
|
||||
actionsList: <Widget>[
|
||||
TextButton(
|
||||
child: Text(
|
||||
@ -38,45 +45,50 @@ class _WifiListPageState extends State<WifiListPage> {
|
||||
onPressed: logic.senderGetWifiListWifiAction,
|
||||
),
|
||||
],
|
||||
backgroundColor: AppColors.mainColor),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Obx(() => state.wifiNameDataList.value.isNotEmpty
|
||||
? ListView.builder(
|
||||
itemCount: state.wifiNameDataList.value.length,
|
||||
itemBuilder: (BuildContext c, int index) {
|
||||
Map wifiNameStr = state.wifiNameDataList.value[index];
|
||||
return _messageListItem(
|
||||
wifiNameStr['wifiName'], wifiNameStr['rssi'], () {
|
||||
Get.toNamed(Routers.configuringWifiPage, arguments: {
|
||||
'lockSetInfoData': state.lockSetInfoData.value,
|
||||
'wifiName': wifiNameStr['wifiName'],
|
||||
backgroundColor: AppColors.mainColor,
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Obx(() => state.wifiNameDataList.value.isNotEmpty
|
||||
? ListView.builder(
|
||||
itemCount: state.wifiNameDataList.value.length,
|
||||
itemBuilder: (BuildContext c, int index) {
|
||||
Map wifiNameStr = state.wifiNameDataList.value[index];
|
||||
return _messageListItem(
|
||||
wifiNameStr['wifiName'], wifiNameStr['rssi'], () {
|
||||
Get.toNamed(Routers.configuringWifiPage,
|
||||
arguments: {
|
||||
'lockSetInfoData':
|
||||
state.lockSetInfoData.value,
|
||||
'wifiName': wifiNameStr['wifiName'],
|
||||
'pageName': state.pageName.value,
|
||||
});
|
||||
});
|
||||
})
|
||||
: NoData(
|
||||
noDataHeight: 1.sh -
|
||||
ScreenUtil().statusBarHeight -
|
||||
ScreenUtil().bottomBarHeight -
|
||||
64.h)),
|
||||
),
|
||||
SubmitBtn(
|
||||
btnName: '手动配网'.tr,
|
||||
fontSize: 28.sp,
|
||||
borderRadius: 20.w,
|
||||
padding: EdgeInsets.only(top: 25.w, bottom: 25.w),
|
||||
onClick: () {
|
||||
Get.toNamed(Routers.configuringWifiPage,
|
||||
arguments: <String, LockSetInfoData>{
|
||||
'lockSetInfoData': state.lockSetInfoData.value
|
||||
});
|
||||
})
|
||||
: NoData(
|
||||
noDataHeight: 1.sh -
|
||||
ScreenUtil().statusBarHeight -
|
||||
ScreenUtil().bottomBarHeight -
|
||||
64.h)),
|
||||
),
|
||||
SubmitBtn(
|
||||
btnName: '手动配网'.tr,
|
||||
fontSize: 28.sp,
|
||||
borderRadius: 20.w,
|
||||
padding: EdgeInsets.only(top: 25.w, bottom: 25.w),
|
||||
onClick: () {
|
||||
Get.toNamed(Routers.configuringWifiPage,
|
||||
arguments: <String, LockSetInfoData>{
|
||||
'lockSetInfoData': state.lockSetInfoData.value
|
||||
});
|
||||
}),
|
||||
SizedBox(
|
||||
height: 64.h,
|
||||
)
|
||||
],
|
||||
));
|
||||
}),
|
||||
SizedBox(
|
||||
height: 64.h,
|
||||
)
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _messageListItem(String wifiName, String rssi, Function() action) {
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../lockSet/lockSetInfo_entity.dart';
|
||||
|
||||
class WifiListState{// 0普通状态(可用) 1连接中(不可用)
|
||||
class WifiListState {
|
||||
// 0普通状态(可用) 1连接中(不可用)
|
||||
WifiListState() {
|
||||
final map = Get.arguments;
|
||||
lockSetInfoData.value = map['lockSetInfoData'];
|
||||
pageName.value = map['pageName'];
|
||||
lockBasicInfo.value = lockSetInfoData.value.lockBasicInfo!;
|
||||
}
|
||||
final RxList<Map<String, String>> wifiNameDataList = <Map<String, String>>[].obs;
|
||||
|
||||
final RxList<Map<String, String>> wifiNameDataList =
|
||||
<Map<String, String>>[].obs;
|
||||
Rx<LockSetInfoData> lockSetInfoData = LockSetInfoData().obs;
|
||||
Rx<LockBasicInfo> lockBasicInfo = LockBasicInfo().obs;
|
||||
|
||||
RxBool ifCurrentScreen = true.obs; // 是否是当前界面,用于判断是否需要针对当前界面进行展示
|
||||
RxInt sureBtnState = 0.obs;
|
||||
}
|
||||
RxString pageName = ''.obs;
|
||||
}
|
||||
|
||||
@ -595,10 +595,10 @@ class _LockSetPageState extends State<LockSetPage>
|
||||
isHaveLine: true,
|
||||
isHaveDirection: true,
|
||||
action: () {
|
||||
Get.toNamed(Routers.wifiListPage,
|
||||
arguments: <String, LockSetInfoData>{
|
||||
'lockSetInfoData': state.lockSetInfoData.value
|
||||
});
|
||||
Get.toNamed(Routers.wifiListPage, arguments: {
|
||||
'lockSetInfoData': state.lockSetInfoData.value,
|
||||
'pageName': 'lockSet'
|
||||
});
|
||||
// Get.toNamed(Routers.configuringWifiPage, arguments: {
|
||||
// 'lockSetInfoData': state.lockSetInfoData.value
|
||||
// });
|
||||
|
||||
@ -170,7 +170,7 @@ class _VideoLogDetailPageState extends State<VideoLogDetailPage> {
|
||||
|
||||
_buildImageItem(RecordListData recordData) {
|
||||
return RotatedBox(
|
||||
quarterTurns: 1,
|
||||
quarterTurns: -1,
|
||||
child: Image.network(
|
||||
recordData.imagesUrl!,
|
||||
fit: BoxFit.cover,
|
||||
|
||||
@ -345,6 +345,8 @@ class LockFeature {
|
||||
this.isSupportCatEye,
|
||||
this.isSupportBackupBattery,
|
||||
this.isNoSupportedBlueBroadcast,
|
||||
this.wifiLockType,
|
||||
this.wifi,
|
||||
});
|
||||
|
||||
LockFeature.fromJson(Map<String, dynamic> json) {
|
||||
@ -360,6 +362,8 @@ class LockFeature {
|
||||
isSupportCatEye = json['isSupportCatEye'];
|
||||
isSupportBackupBattery = json['isSupportBackupBattery'];
|
||||
isNoSupportedBlueBroadcast = json['isNoSupportedBlueBroadcast'];
|
||||
wifiLockType = json['wifiLockType'];
|
||||
wifi = json['wifi'];
|
||||
}
|
||||
|
||||
int? password;
|
||||
@ -374,6 +378,8 @@ class LockFeature {
|
||||
int? isSupportCatEye;
|
||||
int? isSupportBackupBattery;
|
||||
int? isNoSupportedBlueBroadcast;
|
||||
int? wifiLockType;
|
||||
int? wifi;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
@ -389,6 +395,8 @@ class LockFeature {
|
||||
data['isSupportCatEye'] = isSupportCatEye;
|
||||
data['isSupportBackupBattery'] = isSupportBackupBattery;
|
||||
data['isNoSupportedBlueBroadcast'] = isNoSupportedBlueBroadcast;
|
||||
data['wifiLockType'] = wifiLockType;
|
||||
data['wifi'] = wifi;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:star_lock/apm/apm_helper.dart';
|
||||
import 'package:star_lock/appRouters.dart';
|
||||
|
||||
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart';
|
||||
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart';
|
||||
import 'package:star_lock/mine/addLock/saveLock/entity/SaveLockEntity.dart';
|
||||
|
||||
import '../../../app_settings/app_settings.dart';
|
||||
@ -408,10 +409,11 @@ class SaveLockLogic extends BaseGetXController {
|
||||
|
||||
final String getMobile = (await Storage.getMobile())!;
|
||||
ApmHelper.instance.trackEvent('save_lock_result', {
|
||||
'lock_name':BlueManage().connectDeviceName,
|
||||
'account':getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
|
||||
'date':DateTool().getNowDateWithType(1),
|
||||
'save_lock_result':'成功',
|
||||
'lock_name': BlueManage().connectDeviceName,
|
||||
'account':
|
||||
getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
|
||||
'date': DateTool().getNowDateWithType(1),
|
||||
'save_lock_result': '成功',
|
||||
});
|
||||
backAction();
|
||||
// await senderCustomPasswords();
|
||||
@ -424,10 +426,11 @@ class SaveLockLogic extends BaseGetXController {
|
||||
|
||||
final String getMobile = (await Storage.getMobile())!;
|
||||
ApmHelper.instance.trackEvent('save_lock_result', {
|
||||
'lock_name':BlueManage().connectDeviceName,
|
||||
'account':getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
|
||||
'date':DateTool().getNowDateWithType(1),
|
||||
'save_lock_result':'${entity.errorCode}--${entity.errorMsg}',
|
||||
'lock_name': BlueManage().connectDeviceName,
|
||||
'account':
|
||||
getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
|
||||
'date': DateTool().getNowDateWithType(1),
|
||||
'save_lock_result': '${entity.errorCode}--${entity.errorMsg}',
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -482,20 +485,52 @@ class SaveLockLogic extends BaseGetXController {
|
||||
// );
|
||||
// }
|
||||
|
||||
void backAction() {
|
||||
void backAction() async {
|
||||
eventBus.fire(RefreshLockListInfoDataEvent(clearScanDevices: true));
|
||||
BlueManage().disconnect();
|
||||
Future<void>.delayed(const Duration(seconds: 1), () {
|
||||
Get.close(state.isFromMap == 1 ? (CommonDataManage().seletLockType == 0 ? 4 : 5) : (CommonDataManage().seletLockType == 0 ? 5 : 6));
|
||||
});
|
||||
//刚刚配对完,需要对开锁页锁死 2 秒
|
||||
Future<void>.delayed(const Duration(milliseconds: 200), () {
|
||||
if (Get.isRegistered<LockDetailLogic>()) {
|
||||
Get.find<LockDetailLogic>()
|
||||
.functionBlocker
|
||||
.countdownProhibited(duration: const Duration(seconds: 2));
|
||||
|
||||
// 查询锁设置信息
|
||||
final LockSetInfoEntity entity =
|
||||
await ApiRepository.to.getLockSettingInfoDataIsNotLoadingIcon(
|
||||
lockId: state.lockId.toString(),
|
||||
);
|
||||
if (entity.errorCode!.codeIsSuccessful) {
|
||||
state.lockSetInfoData.value = entity.data!;
|
||||
if (state.lockSetInfoData.value.lockFeature?.wifi == 1) {
|
||||
// await Future<void>.delayed(const Duration(seconds: 1), () {c
|
||||
// Get.close(state.isFromMap == 1
|
||||
// ? (CommonDataManage().seletLockType == 0 ? 4 : 5)
|
||||
// : (CommonDataManage().seletLockType == 0 ? 5 : 6));
|
||||
// });
|
||||
// //刚刚配对完,需要对开锁页锁死 2 秒
|
||||
// await Future<void>.delayed(const Duration(milliseconds: 200), () {
|
||||
// if (Get.isRegistered<LockDetailLogic>()) {
|
||||
// Get.find<LockDetailLogic>()
|
||||
// .functionBlocker
|
||||
// .countdownProhibited(duration: const Duration(seconds: 2));
|
||||
// }
|
||||
// });
|
||||
// 如果是wifi锁,需要配置WIFI
|
||||
Get.toNamed(Routers.wifiListPage, arguments: {
|
||||
'lockSetInfoData': state.lockSetInfoData.value,
|
||||
'pageName': 'saveLock'
|
||||
});
|
||||
} else {
|
||||
Future<void>.delayed(const Duration(seconds: 1), () {
|
||||
Get.close(state.isFromMap == 1
|
||||
? (CommonDataManage().seletLockType == 0 ? 4 : 5)
|
||||
: (CommonDataManage().seletLockType == 0 ? 5 : 6));
|
||||
});
|
||||
//刚刚配对完,需要对开锁页锁死 2 秒
|
||||
Future<void>.delayed(const Duration(milliseconds: 200), () {
|
||||
if (Get.isRegistered<LockDetailLogic>()) {
|
||||
Get.find<LockDetailLogic>()
|
||||
.functionBlocker
|
||||
.countdownProhibited(duration: const Duration(seconds: 2));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -3,6 +3,7 @@ import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart';
|
||||
|
||||
import '../../../blue/blue_manage.dart';
|
||||
|
||||
@ -25,7 +26,7 @@ class SaveLockState {
|
||||
RxString aliName = ''.obs;
|
||||
RxInt pwdTimestamp = 0.obs;
|
||||
RxMap addressInfo = {}.obs;
|
||||
|
||||
final Rx<LockSetInfoData> lockSetInfoData = LockSetInfoData().obs;
|
||||
TextEditingController aliNameController = TextEditingController();
|
||||
FocusNode focusNode = FocusNode();
|
||||
|
||||
|
||||
@ -292,21 +292,20 @@ class _MineSetPageState extends State<MineSetPage>
|
||||
SizedBox(
|
||||
height: 10.h,
|
||||
),
|
||||
Obx(() {
|
||||
// AppLog.log('state.currentLanguageName: ${state.currentLanguageName} state.currentLanguage.value: ${state.currentLanguage.value}');
|
||||
return CommonItem(
|
||||
leftTitel: '多语言'.tr,
|
||||
rightTitle: state.currentLanguageName,
|
||||
isHaveLine: true,
|
||||
isHaveDirection: true,
|
||||
action: () async {
|
||||
// Get.toNamed(Routers.mineMultiLanguagePage);
|
||||
await Get.toNamed(Routers.mineMultiLanguagePage)!.then((value) {
|
||||
state.currentLanguage.value = value['currentLanguage'];
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
// AppLog.log('state.currentLanguageName: ${state.currentLanguageName} state.currentLanguage.value: ${state.currentLanguage.value}');
|
||||
CommonItem(
|
||||
leftTitel: '多语言'.tr,
|
||||
rightTitle: state.currentLanguageName,
|
||||
isHaveLine: true,
|
||||
isHaveDirection: true,
|
||||
action: () async {
|
||||
// Get.toNamed(Routers.mineMultiLanguagePage);
|
||||
await Get.toNamed(Routers.mineMultiLanguagePage)!.then((value) {
|
||||
state.currentLanguage.value = value['currentLanguage'];
|
||||
setState(() {});
|
||||
});
|
||||
}),
|
||||
}),
|
||||
/* 2024-01-12 会议确定去掉“锁屏” by DaisyWu
|
||||
Obx(() => CommonItem(
|
||||
leftTitel: TranslationLoader.lanKeys!.lockScreen!.tr,
|
||||
|
||||
@ -423,6 +423,15 @@ class ApiProvider extends BaseProvider {
|
||||
'lockId': lockId,
|
||||
}));
|
||||
|
||||
// 获取所有锁设置信息
|
||||
Future<Response> getLockSettingInfoDataIsNotLoadingIcon(String lockId) =>
|
||||
post(
|
||||
getLockSettingURL.toUrl,
|
||||
jsonEncode({
|
||||
'lockId': lockId,
|
||||
}),
|
||||
isUnShowLoading: true);
|
||||
|
||||
// 删除锁
|
||||
Future<Response> deletLockInfo(int lockId) => post(
|
||||
deletLockURL.toUrl,
|
||||
|
||||
@ -486,6 +486,12 @@ class ApiRepository {
|
||||
final res = await apiProvider.getLockSettingInfoData(lockId);
|
||||
return LockSetInfoEntity.fromJson(res.body);
|
||||
}
|
||||
// 获取所有锁设置信息(不显示加载框)
|
||||
Future<LockSetInfoEntity> getLockSettingInfoDataIsNotLoadingIcon(
|
||||
{required String lockId}) async {
|
||||
final res = await apiProvider.getLockSettingInfoDataIsNotLoadingIcon(lockId);
|
||||
return LockSetInfoEntity.fromJson(res.body);
|
||||
}
|
||||
|
||||
// 删除锁
|
||||
Future<LockListInfoEntity> deletOwnerLockData({required int lockId}) async {
|
||||
|
||||
@ -160,6 +160,8 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
|
||||
final TalkDataH264Frame talkDataH264Frame = TalkDataH264Frame();
|
||||
talkDataH264Frame.mergeFromBuffer(talkData.content);
|
||||
frameHandler.handleFrame(talkDataH264Frame);
|
||||
AppLog.log(
|
||||
"帧:${talkDataH264Frame.frameType},帧序号:${talkDataH264Frame.frameSeq},对应I帧序号:${talkDataH264Frame.frameSeqI}");
|
||||
}
|
||||
|
||||
/// 处理图片数据
|
||||
|
||||
@ -1,52 +1,51 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:star_lock/app_settings/app_settings.dart';
|
||||
import '../../proto/talk_data_h264_frame.pb.dart';
|
||||
|
||||
class H264FrameHandler {
|
||||
final Map<int, TalkDataH264Frame> _frameBuffer = {};
|
||||
final LinkedHashMap<int, TalkDataH264Frame> _frameBuffer = LinkedHashMap();
|
||||
final void Function(List<int> frameData) onCompleteFrame;
|
||||
int _lastProcessedSeq = -1;
|
||||
|
||||
final LinkedHashMap<int, TalkDataH264Frame_FrameTypeE> _frameTypeIndex = LinkedHashMap();
|
||||
|
||||
H264FrameHandler({required this.onCompleteFrame});
|
||||
|
||||
void handleFrame(TalkDataH264Frame frame) {
|
||||
// 存储帧
|
||||
_frameBuffer[frame.frameSeq] = frame;
|
||||
_frameTypeIndex[frame.frameSeq] = frame.frameType;
|
||||
|
||||
// 检查是否可以组装完整的 GOP (Group of Pictures)
|
||||
_tryAssembleFrames(frame.frameSeq);
|
||||
}
|
||||
|
||||
void _tryAssembleFrames(int currentSeq) {
|
||||
// 找到连续的帧序列
|
||||
final List<int> sortedSeqs = _frameBuffer.keys.toList()..sort();
|
||||
final List<int> framesToProcess = [];
|
||||
int? startFrameSeq;
|
||||
|
||||
// 从当前帧开始向前找到最近的 I 帧或 P 帧
|
||||
int? startFrameSeq;
|
||||
for (var seq in sortedSeqs.reversed) {
|
||||
final frame = _frameBuffer[seq];
|
||||
if (frame?.frameType == TalkDataH264Frame_FrameTypeE.I) {
|
||||
for (int seq = currentSeq; seq >= 0; seq--) {
|
||||
final frameType = _frameTypeIndex[seq];
|
||||
if (frameType == null) continue;
|
||||
if (frameType == TalkDataH264Frame_FrameTypeE.I) {
|
||||
startFrameSeq = seq;
|
||||
break;
|
||||
} else if (frame?.frameType == TalkDataH264Frame_FrameTypeE.P) {
|
||||
// 检查 P 帧是否有对应的 I 帧
|
||||
if (_frameBuffer.containsKey(frame?.frameSeqI)) {
|
||||
} else if (frameType == TalkDataH264Frame_FrameTypeE.P) {
|
||||
if (_frameBuffer.containsKey(_frameBuffer[seq]!.frameSeqI)) {
|
||||
startFrameSeq = seq;
|
||||
break;
|
||||
} else {
|
||||
// 丢弃没有对应 I 帧的 P 帧
|
||||
_frameBuffer.remove(seq);
|
||||
_frameTypeIndex.remove(seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startFrameSeq != null) {
|
||||
// 收集从 I 帧或 P 帧开始的连续帧
|
||||
int expectedSeq = startFrameSeq;
|
||||
for (var seq in sortedSeqs.where((s) => s >= startFrameSeq!)) {
|
||||
if (seq != expectedSeq) break;
|
||||
for (int seq = startFrameSeq; _frameBuffer.containsKey(seq); seq++) {
|
||||
framesToProcess.add(seq);
|
||||
expectedSeq++;
|
||||
}
|
||||
|
||||
if (framesToProcess.isNotEmpty) {
|
||||
@ -57,28 +56,50 @@ class H264FrameHandler {
|
||||
}
|
||||
}
|
||||
|
||||
void _clearOldFrames(int currentSeq) {
|
||||
// 清理比当前帧序列旧的帧
|
||||
_frameBuffer.removeWhere((seq, frame) => seq < currentSeq - 200); // 调整阈值
|
||||
}
|
||||
|
||||
void _processFrames(List<int> frameSeqs) {
|
||||
// 按顺序组装帧数据
|
||||
final List<int> assembledData = [];
|
||||
// final List<int> assembledData = [];
|
||||
//
|
||||
// for (var seq in frameSeqs) {
|
||||
// final frame = _frameBuffer[seq]!;
|
||||
// assembledData.addAll(frame.frameData);
|
||||
//
|
||||
// // 处理完后从缓冲区移除
|
||||
// _frameBuffer.remove(seq);
|
||||
// }
|
||||
//
|
||||
// // 回调完整的帧数据
|
||||
// onCompleteFrame(assembledData);
|
||||
// Calculate the total length of the assembled data
|
||||
int totalLength = frameSeqs.fold(
|
||||
0, (sum, seq) => sum + _frameBuffer[seq]!.frameData.length);
|
||||
|
||||
// Allocate a buffer for the assembled data
|
||||
final assembledData = Uint8List(totalLength);
|
||||
int offset = 0;
|
||||
|
||||
for (var seq in frameSeqs) {
|
||||
final frame = _frameBuffer[seq]!;
|
||||
assembledData.addAll(frame.frameData);
|
||||
assembledData.setRange(
|
||||
offset, offset + frame.frameData.length, frame.frameData);
|
||||
offset += frame.frameData.length;
|
||||
|
||||
// 处理完后从缓冲区移除
|
||||
// Remove the frame from the buffer after processing
|
||||
_frameBuffer.remove(seq);
|
||||
_frameTypeIndex.remove(seq);
|
||||
}
|
||||
|
||||
// 回调完整的帧数据
|
||||
// Callback with the complete frame data
|
||||
onCompleteFrame(assembledData);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_frameBuffer.clear();
|
||||
}
|
||||
|
||||
void _clearOldFrames(int currentSeq) {
|
||||
// 清理比当前帧序列旧的帧
|
||||
_frameBuffer.removeWhere((seq, frame) => seq < currentSeq - 200); // 调整阈值
|
||||
_frameTypeIndex.removeWhere((seq, frameType) => seq < currentSeq - 200);
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,7 +112,7 @@ class StartChartManage {
|
||||
|
||||
// 默认通话的期望数据格式
|
||||
TalkExpectReq _defaultTalkExpect = TalkExpectReq(
|
||||
videoType: [VideoTypeE.H264],
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
|
||||
@ -1119,7 +1119,7 @@ class StartChartManage {
|
||||
|
||||
void reSetDefaultTalkExpect() {
|
||||
_defaultTalkExpect = TalkExpectReq(
|
||||
videoType: [VideoTypeE.H264],
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
}
|
||||
@ -1131,7 +1131,7 @@ class StartChartManage {
|
||||
/// 修改预期接收到的数据
|
||||
void sendOnlyImageVideoTalkExpectData() {
|
||||
final talkExpectReq = TalkExpectReq(
|
||||
videoType: [VideoTypeE.H264],
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [],
|
||||
);
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
@ -1141,7 +1141,7 @@ class StartChartManage {
|
||||
/// 修改预期接收到的数据
|
||||
void sendImageVideoAndG711AudioTalkExpectData() {
|
||||
final talkExpectReq = TalkExpectReq(
|
||||
videoType: [VideoTypeE.H264],
|
||||
videoType: [VideoTypeE.IMAGE],
|
||||
audioType: [AudioTypeE.G711],
|
||||
);
|
||||
changeTalkExpectDataTypeAndReStartTalkExpectMessageTimer(
|
||||
|
||||
@ -46,6 +46,8 @@ class TalkViewLogic extends BaseGetXController {
|
||||
int audioFrameIntervalMs = 20; // 初始帧间隔设置为45毫秒(约22FPS)
|
||||
int minFrameIntervalMs = 30; // 最小帧间隔(约33 FPS)
|
||||
int maxFrameIntervalMs = 100; // 最大帧间隔(约1 FPS)
|
||||
// 定义音频帧缓冲和发送函数
|
||||
List<int> _bufferedAudioFrames = <int>[];
|
||||
|
||||
/// 初始化音频播放器
|
||||
void _initFlutterPcmSound() {
|
||||
@ -533,15 +535,17 @@ class TalkViewLogic extends BaseGetXController {
|
||||
|
||||
//开始录音
|
||||
Future<void> startProcessingAudio() async {
|
||||
// 增加录音帧监听器和错误监听器
|
||||
state.voiceProcessor?.addFrameListener(_onFrame);
|
||||
state.voiceProcessor?.addErrorListener(_onError);
|
||||
try {
|
||||
if (await state.voiceProcessor?.hasRecordAudioPermission() ?? false) {
|
||||
await state.voiceProcessor?.start(state.frameLength, state.sampleRate);
|
||||
final bool? isRecording = await state.voiceProcessor?.isRecording();
|
||||
state.isRecordingAudio.value = isRecording!;
|
||||
state.startRecordingAudioTime.value = DateTime.now();
|
||||
|
||||
// 增加录音帧监听器和错误监听器
|
||||
state.voiceProcessor
|
||||
?.addFrameListeners(<VoiceProcessorFrameListener>[_onFrame]);
|
||||
state.voiceProcessor?.addErrorListener(_onError);
|
||||
} else {
|
||||
// state.errorMessage.value = 'Recording permission not granted';
|
||||
}
|
||||
@ -576,23 +580,23 @@ class TalkViewLogic extends BaseGetXController {
|
||||
|
||||
// 音频帧处理
|
||||
Future<void> _onFrame(List<int> frame) async {
|
||||
// 预处理和转码操作放到异步计算线程
|
||||
// final processedFrame = await compute(preprocessAudio, frame);
|
||||
// final list = listLinearToALaw(processedFrame);
|
||||
final List<int> processedFrame = preprocessAudio(frame);
|
||||
final List<int> list = listLinearToALaw(processedFrame);
|
||||
_bufferedAudioFrames.addAll(list);
|
||||
|
||||
final int ms = DateTime.now().millisecondsSinceEpoch -
|
||||
state.startRecordingAudioTime.value.millisecondsSinceEpoch;
|
||||
|
||||
// 发送音频数据到UDP
|
||||
await StartChartManage().sendTalkDataMessage(
|
||||
talkData: TalkData(
|
||||
content: list,
|
||||
contentType: TalkData_ContentTypeE.G711,
|
||||
durationMs: ms,
|
||||
),
|
||||
);
|
||||
Future.delayed(const Duration(milliseconds: 1000)).whenComplete(() async {
|
||||
// 发送音频数据到UDP
|
||||
await StartChartManage().sendTalkDataMessage(
|
||||
talkData: TalkData(
|
||||
content: list,
|
||||
contentType: TalkData_ContentTypeE.G711,
|
||||
durationMs: ms,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 错误监听
|
||||
@ -662,9 +666,31 @@ class TalkViewLogic extends BaseGetXController {
|
||||
// return processedList;
|
||||
// }
|
||||
|
||||
List<int> listLinearToALaw(List<int> pcmList) {
|
||||
final List<int> aLawList = [];
|
||||
List<int> adjustVolume(List<int> pcmList, double volume) {
|
||||
final List<int> adjustedPcmList = [];
|
||||
for (int pcmVal in pcmList) {
|
||||
// 调整音量
|
||||
int adjustedPcmVal = (pcmVal * volume).round();
|
||||
|
||||
// 裁剪到 16-bit PCM 范围
|
||||
if (adjustedPcmVal > 32767) {
|
||||
adjustedPcmVal = 32767;
|
||||
} else if (adjustedPcmVal < -32768) {
|
||||
adjustedPcmVal = -32768;
|
||||
}
|
||||
|
||||
adjustedPcmList.add(adjustedPcmVal);
|
||||
}
|
||||
return adjustedPcmList;
|
||||
}
|
||||
|
||||
List<int> listLinearToALaw(List<int> pcmList) {
|
||||
// 先调节音量
|
||||
final List<int> adjustedPcmList = adjustVolume(pcmList, 5.0);
|
||||
|
||||
// 再进行 A-law 编码
|
||||
final List<int> aLawList = [];
|
||||
for (int pcmVal in adjustedPcmList) {
|
||||
final int aLawVal = linearToALaw(pcmVal);
|
||||
aLawList.add(aLawVal);
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ class TalkViewState {
|
||||
RxList<int> listAudioData = <int>[].obs; //得到的音频流字节数据
|
||||
GlobalKey globalKey = GlobalKey();
|
||||
|
||||
Timer? oneMinuteTimeTimer; // 定时器超过60秒关闭当前界面
|
||||
Timer? oneMinuteTimeTimer; // 定时器超过60秒关闭当前界面
|
||||
RxInt oneMinuteTime = 0.obs; // 定时器秒数
|
||||
|
||||
// 定时器如果发送了接听的命令 而没收到回复就每秒重复发送10次
|
||||
@ -89,4 +89,4 @@ class TalkViewState {
|
||||
RxBool isLongPressing = false.obs; // 旋转角度(以弧度为单位)
|
||||
RxBool hasAudioData = false.obs; // 是否有音频数据
|
||||
RxInt lastAudioTimestamp = 0.obs; // 最后接收到的音频数据的时间戳
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user