Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
fdp 12 months ago
parent
commit
92053de1ff
  1. 4
      lib/data/services/db/sqflite_api.dart
  2. 160
      lib/data/services/meeting/meeting_task_service.dart
  3. 36
      lib/data/services/version_update_service.dart
  4. 1
      lib/modules/agent/controllers/agent_controller.dart
  5. 200
      lib/modules/agent/views/agent_view.dart
  6. 1
      lib/modules/agent/views/music_card.dart
  7. 127
      lib/modules/login/constants/country_codes.dart
  8. 154
      lib/modules/login/controllers/login_controller.dart
  9. 4
      lib/modules/meeting/controllers/meeting_details_controller.dart
  10. 37
      lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart
  11. 1
      lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart
  12. 26
      lib/modules/meeting/views/meeting_details_view.dart
  13. 1
      lib/modules/meeting/views/meeting_home_view.dart
  14. 1
      lib/modules/meeting/views/meeting_view.dart
  15. 42
      lib/modules/meeting/views/template/template_all_view.dart
  16. 184
      lib/modules/meeting/views/template/template_details_view.dart
  17. 86
      lib/modules/meeting/views/template/template_edit_view.dart
  18. 73
      lib/modules/meeting/views/template/template_view.dart
  19. 22
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  20. 2
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  21. 10
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift
  22. 740
      local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift

4
lib/data/services/db/sqflite_api.dart

@ -293,6 +293,7 @@ class SqfliteApi {
limit: 4,
where: 'categoryid = ?',
whereArgs: [element['id']],
orderBy: 'id ${element['name'] == '自定义' ? 'DESC' : 'ASC'}',
);
element['template'] = templateList
.map((item) => {
@ -305,12 +306,13 @@ class SqfliteApi {
}
// 获取商务会议助手模版列表
static Future getMeetingTemplateList(int id) async {
static Future getMeetingTemplateList(int id, {bool isCustom = false}) async {
final db = await DatabaseHelper().database;
return await db.query(
'meetingtemplate',
where: 'categoryid = ?',
whereArgs: [id],
orderBy: 'id ${isCustom ? 'DESC' : 'ASC'}',
);
}

160
lib/data/services/meeting/meeting_task_service.dart

@ -10,29 +10,35 @@ import '../db/sqflite_api.dart';
import '../deapsound_ai_service.dart';
import 'meeting_upload_service.dart';
///会议任务服务类,负责管理会议音频的转写任务处理流程,包括任务提交、状态轮询、结果处理和AI分析等功能。
class MeetingTaskService extends GetxService {
final GetStorage _storage = GetStorage();
MeetingUploadService get _uploadService => Get.find<MeetingUploadService>();
List _taskList = [];
RxList taskFinishList = [].obs;
List _taskList = []; //待处理的会议任务列表
RxList taskFinishList = [].obs; //已处理的会议任务列表
bool _isTask = false;
bool _isTask = false; //是否有正在处理的任务
String _userId = '';
//字节跳动(火山引擎)语音服务配置
final String _appid = '2316081933';
final String _token = 'mcJTh6OelBqU_L78lgGd77Nyrc_-5-En';
final String _cluster = 'volc_auc_tele_flash';
final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc';
final String _cluster = 'volc_auc_video_flash';
final String _cluster1 = 'volc.bigasr.auc';
final String _serviceUrl = 'https://openspeech.bytedance.com/api/v1/auc'; //
final String _serviceUrl1 =
'https://openspeech.bytedance.com/api/v3/auc/bigmodel'; //大模型录音文件识别api地址
Map<String, String> _headers = {};
Map<String, String> _headers1 = {};
int meetingId = 0;
RxInt tasktype = 1.obs;
List textList = [];
String summary = '';
String overview = '';
RxInt tasktype = 1.obs; //任务类型,1-转写中,2-总结中,3-完成
List textList = []; //转写结果列表
String summary = ''; //AI总结内容
String overview = ''; //AI概览内容
@override
void onInit() {
@ -41,6 +47,14 @@ class MeetingTaskService extends GetxService {
'Authorization': 'Bearer; $_token',
'Content-Type': 'application/json',
};
_headers1 = {
'X-Api-App-Key': _appid,
'X-Api-Access-Key': _token,
'X-Api-Resource-Id': _cluster1,
'X-Api-Request-Id': '',
'X-Api-Sequence': '-1',
};
_getAgent();
_getTaskList();
}
@ -56,6 +70,14 @@ class MeetingTaskService extends GetxService {
_executeTask();
}
///添加会议任务
///[id] 会议ID
///[audiourl] 音频URL
///[taskid] 任务ID
///[isSpeaker] 是否为发言人
///[language] 语言
///[prompt] 提示词
///[outline] 大纲
Future<void> addTask(int id, String audiourl, String taskid, bool isSpeaker,
String language, String prompt, String outline) async {
if (audiourl.isEmpty) {
@ -81,6 +103,14 @@ class MeetingTaskService extends GetxService {
}
}
///添加会议任务列表
///[id] 会议ID
///[audiourl] 音频URL
///[taskId] 任务ID
///[isSpeaker] 是否为发言人
///[language] 语言
///[prompt] 提示词
///[outline] 大纲
Future<void> addTaskList(int id, String audiourl, String taskId,
bool isSpeaker, String language, String prompt, String outline) async {
if (taskId.isEmpty) {
@ -103,14 +133,15 @@ class MeetingTaskService extends GetxService {
_executeTask();
}
///执行会议任务
void _executeTask() async {
if (_isTask) return;
_isTask = true;
while (_taskList.isNotEmpty) {
await Future.delayed(const Duration(seconds: 2));
for (var element in List.from(_taskList)) {
await _fileRecognize(element);
Logger.info("lxm--轮询---会议任务列表$_taskList");
for (var task in List.from(_taskList)) {
await _fileRecognize(task);
}
}
_isTask = false;
@ -133,9 +164,40 @@ class MeetingTaskService extends GetxService {
}
}
//
Future<String> _submitTask(
String audiourl, bool isSpeaker, String language) async {
String format = _getFileType(audiourl);
final taskId = DateTime.now().millisecondsSinceEpoch.toString();
_headers1['X-Api-Request-Id'] = taskId;
Logger.info("lxm--大模型asr请求头$_headers1");
final requestBody1 = jsonEncode({
"user": {
"uid": _userId,
},
"audio": {
"url": audiourl,
"language": language,
"format": format,
},
"request": {
"model_name": "bigmodel", // 模型名称,目前只有bigmodel
"model_version": "400", // 模型版本,400模型性能略有提升,且ITN有较大优化
//"enable_itn": true, // 启用文本规范化,默认为true
"enable_punc": true, // 启用标点,默认为false
//"enable_ddc": false, // 启用语义顺滑,默认为false
"enable_speaker_info": isSpeaker, // 启用说话人聚类分离,10人以内效果较好
//"enable_channel_split": false, // 启用双声道识别,默认为false
//"show_utterances": true, // 输出语音停顿、分句、分词信息
//"show_speech_rate": false, // 分句信息携带语速
//"show_volume": false, // 分句信息携带音量
//"enable_lid": false, // 启用语种识别
}
});
Logger.info("lxm--大模型asr请求体$requestBody1");
final requestBody = jsonEncode({
"app": {
@ -158,18 +220,24 @@ class MeetingTaskService extends GetxService {
try {
final response = await http.post(
Uri.parse('$_serviceUrl/submit'),
headers: _headers,
body: requestBody,
Uri.parse('$_serviceUrl1/submit'),
headers: _headers1,
body: requestBody1,
);
Logger.info("lxm--大模型asr响应体${response.body}");
Logger.info("lxm--大模型asr上传响应头${response.headers['x-tt-logid']}");
Logger.info(
"lxm--大模型asr上传服务端返回的状态码${response.headers['x-api-status-code']}");
Logger.info("lxm--大模型asr上传服务端返回的消息${response.headers['x-api-message']}");
if (response.statusCode != 200) {
throw Exception('Submit failed with status: ${response.statusCode}');
}
final respDic = jsonDecode(response.body);
final taskId = respDic['resp']['id'] as String;
Logger.debug('taskId: $taskId');
//final respDic = jsonDecode(response.body);
//final taskId = respDic['resp']['id'] as String;
//Logger.debug('taskId: $taskId');
return taskId;
} catch (e) {
Logger.error('Submit error: ${e.toString()}');
@ -182,6 +250,8 @@ class MeetingTaskService extends GetxService {
return array[array.length - 1];
}
///轮询语音转文字任务状态
///[taskId] 任务ID
Future<Map<String, dynamic>> _queryTask(String taskId) async {
final queryBody = jsonEncode({
"appid": _appid,
@ -191,12 +261,20 @@ class MeetingTaskService extends GetxService {
});
try {
_headers1['X-Api-Request-Id'] = taskId;
final response = await http.post(
Uri.parse('$_serviceUrl/query'),
headers: _headers,
body: queryBody,
Uri.parse('$_serviceUrl1/query'),
headers: _headers1,
body: jsonEncode({}),
);
// Logger.info("lxm---大模型asr响应体${response.body}");
// Logger.info("lxm---大模型asr响应头${response.headers}");
// Logger.info("lxm---大模型asr响应状态码${response.statusCode}");
// Logger.info("lxm---大模型asr响应体logid${response.headers['x-tt-logid']}");
// Logger.info("lxm---大模型asr服务端返回的状态码${response.headers['x-api-status-code']}");
// Logger.info("lxm---大模型asr服务端返回的消息${response.headers['x-api-message']}");
if (response.statusCode != 200) {
throw Exception('Query failed with status: ${response.statusCode}');
}
@ -209,20 +287,24 @@ class MeetingTaskService extends GetxService {
}
}
///轮询语音转文字任务状态,若任务完成则处理转写结果
///[item] 包含任务ID、会议ID、AI总结提示词、AI概览提示词的Map
Future<void> _fileRecognize(Map item) async {
try {
var respDic = await _queryTask(item['taskid']);
var code = respDic['resp']['code'] as int;
//var code = respDic['resp']['code'] as int;
Logger.info("lxm---大模型asr响应体结果$respDic['result']['text']");
if (code == 1000) {
if (respDic['result']['text'] != null) {
await _addTextList(
item['id'],
respDic['resp']['utterances'],
item['prompt'],
item['outline'],
item['id'], //会议ID
respDic['result']['utterances'], //语音转文字结果
item['prompt'], //AI总结提示词
item['outline'], //AI概览提示词
);
return;
} else if (code < 2000) {
} else {
Logger.debug("FileRecognize failed");
return;
}
@ -231,11 +313,18 @@ class MeetingTaskService extends GetxService {
}
}
///处理语音识别完成后的转写结果,包括数据库存储、状态更新和触发AI分析
///[id] 会议ID
///[utterances] 语音转文字结果
///[prompt] AI总结提示词
///[outline] AI概览提示词
Future<void> _addTextList(
int id, List utterances, String prompt, String outline) async {
String text = '';
String personnel = '';
List<Map<String, Object?>> list = [];
// 处理转写结果,将每个句子的开始时间、结束时间、发言人和内容存储到列表中
for (var element in utterances) {
String speaker = '';
if (element['additions'].isNotEmpty) {
@ -254,8 +343,10 @@ class MeetingTaskService extends GetxService {
});
text += '${element['text']}\n';
}
// 存储处理后的转写结果到数据库
var result = await SqfliteApi.insertMeetingSpeaker(list);
if (result != null) {
// 更新会议状态为“总结中”
await SqfliteApi.editMeeting(
id,
{'tasktype': 2, 'personnel': personnel},
@ -282,9 +373,16 @@ class MeetingTaskService extends GetxService {
_storage.write("meeting_task_list", _taskList);
_storage.write("meeting_task_finish_list", taskFinishList);
}
// 触发AI分析,分别请求总结和概览
aiServiceMessage(id, text, prompt, outline);
}
///触发AI分析,分别请求总结和概览
///[id] 会议ID
///[text] 语音转文字结果
///[prompt] AI总结提示词
///[outline] AI概览提示词
void aiServiceMessage(int id, String text, String prompt, String outline) {
int awaitQuantity = 0;
String summaryText = '';
@ -300,6 +398,7 @@ class MeetingTaskService extends GetxService {
).then((value) {
awaitQuantity++;
summaryText = value;
Logger.info("lxm---大模型asr总结结果$summaryText");
if (awaitQuantity >= 2) {
_aiServiceValue(id, summaryText, overviewText);
}
@ -315,12 +414,17 @@ class MeetingTaskService extends GetxService {
).then((value) {
awaitQuantity++;
overviewText = value;
Logger.info("lxm---大模型asr概览结果$overviewText");
if (awaitQuantity >= 2) {
_aiServiceValue(id, summaryText, overviewText);
}
});
}
///处理AI分析完成后的结果,包括数据库存储和状态更新
///[id] 会议ID
///[summaryText] 总结结果
///[overviewText] 概览结果
void _aiServiceValue(int id, String summaryText, String overviewText) {
SqfliteApi.editMeeting(id, {
'tasktype': 3,

36
lib/data/services/version_update_service.dart

@ -211,6 +211,7 @@ class VersionUpdateService extends GetxService {
/// 获取当前平台信息
Future<PlatformInfo> getCurrentPlatformInfo() async {
if (Platform.isIOS) {
await _getIosDeviceBrand();
return const PlatformInfo(
platform: 'ios',
storeType: AppStoreType.appStore,
@ -310,6 +311,41 @@ class VersionUpdateService extends GetxService {
}
}
/// 获取iOS设备品牌信息
Future<void> _getIosDeviceBrand() async {
try {
final deviceInfo = DeviceInfoPlugin();
final iosInfo = await deviceInfo.iosInfo;
// 获取设备型号信息
final model =
iosInfo.model.toLowerCase(); // 例如: "iPhone", "iPad", "iPod touch"
//print('lxm---model: $model');
final utsname = iosInfo.utsname; // 包含更详细的设备信息
//print('lxm---utsname: $utsname');
final machineId = utsname.machine.toLowerCase(); // 例如: "iPhone14,2"
//print('lxm---machineId: $machineId');
// 根据设备型号确定品牌
if (model.contains('iphone') || machineId.contains('iphone')) {
_deviceBrand = 'iphone';
} else if (model.contains('ipad') || machineId.contains('ipad')) {
_deviceBrand = 'ipad';
} else if (model.contains('ipod') || machineId.contains('ipod')) {
_deviceBrand = 'ipod';
} else if (model.contains('mac') || machineId.contains('mac')) {
_deviceBrand = 'mac';
} else {
_deviceBrand = 'ios'; // 默认值
}
_logger.d('iOS设备品牌: $_deviceBrand (model: $model, machine: $machineId)');
} catch (e) {
_logger.e('获取iOS设备品牌失败: $e');
_deviceBrand = 'ios'; // 默认值
}
}
/// 判断是否需要强制更新
/// 当主版本号(第一位数字)不同时返回true
bool _shouldForceUpdate(String current, String latest) {

1
lib/modules/agent/controllers/agent_controller.dart

@ -1002,6 +1002,7 @@ class AgentController extends GetxController with WidgetsBindingObserver {
if (function_name.contains("music")) {
Future.delayed(Duration(seconds: 1), () {
_qqmusicManager.syncCurrentPlayInfo(isOpenPlay: false);
musicStatus();
});
// }
}

200
lib/modules/agent/views/agent_view.dart

@ -25,6 +25,9 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
late MessageActionController messageActionController;
double _keyboardHeight = 0;
// 👇 新增:用于控制 TextField 焦点
final FocusNode _textFocusNode = FocusNode();
@override
void initState() {
super.initState();
@ -35,6 +38,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
@override
void dispose() {
_textFocusNode.dispose();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@ -286,65 +290,102 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
children: [
// 聊天内容
Expanded(
child: Obx(() {
return Align(
alignment: Alignment.topCenter,
child: ListView.builder(
controller: controller.scrollController,
shrinkWrap: true,
reverse: true,
padding: EdgeInsets.symmetric(
vertical: 16.h, horizontal: 16.w),
itemCount: controller.messages.length +
(controller.hasMoreMessages.value ? 1 : 0),
itemBuilder: (context, index) {
// 如果是最后一项且有更多消息,显示加载指示器
if (index == controller.messages.length &&
controller.hasMoreMessages.value) {
return _buildLoadMoreIndicator();
}
child: GestureDetector(
onTap: () {
// 点击聊天区域时,收起键盘并切换工具显示状态
FocusScope.of(context).unfocus();
if (controller.isPlusOptionsExpanded.value) {
controller.collapsePlusOptions();
}
},
child: Obx(() {
return Align(
alignment: Alignment.topCenter,
child: ListView.builder(
controller: controller.scrollController,
shrinkWrap: true,
reverse: true,
padding: EdgeInsets.symmetric(
vertical: 16.h, horizontal: 16.w),
itemCount: controller.messages.length +
(controller.hasMoreMessages.value ? 1 : 0),
itemBuilder: (context, index) {
// 如果是最后一项且有更多消息,显示加载指示器
if (index == controller.messages.length &&
controller.hasMoreMessages.value) {
return _buildLoadMoreIndicator();
}
final message = controller
.messages[controller.messages.length - 1 - index];
return index == 0
? IntroStepBuilder(
group: 'ai',
order: 4,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
controller.noviceGuideService.aiStepsList[
controller
.noviceGuideService.aiStep]['end'],
onFinish: () async {
if (controller.noviceGuideService.aiStep <
5) {
await Future.delayed(
const Duration(milliseconds: 300));
controller.noviceGuideService.aiStep++;
Intro.of(Get.context!)
.start(group: 'ai', reset: true);
}
},
onTap: () async {
params.onFinish();
if (controller.noviceGuideService.aiStep <
5) {
await Future.delayed(
const Duration(milliseconds: 300));
controller.noviceGuideService.aiStep++;
Intro.of(Get.context!)
.start(group: 'ai', reset: true);
}
},
);
},
builder:
(BuildContext context, GlobalKey key) =>
Padding(
key: key,
final message = controller
.messages[controller.messages.length - 1 - index];
return index == 0
? IntroStepBuilder(
group: 'ai',
order: 4,
getOverlayPosition:
IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder(
params,
controller.noviceGuideService.aiStepsList[
controller.noviceGuideService
.aiStep]['end'],
onFinish: () async {
if (controller
.noviceGuideService.aiStep <
5) {
await Future.delayed(const Duration(
milliseconds: 300));
controller
.noviceGuideService.aiStep++;
Intro.of(Get.context!)
.start(group: 'ai', reset: true);
}
},
onTap: () async {
params.onFinish();
if (controller
.noviceGuideService.aiStep <
5) {
await Future.delayed(const Duration(
milliseconds: 300));
controller
.noviceGuideService.aiStep++;
Intro.of(Get.context!)
.start(group: 'ai', reset: true);
}
},
);
},
builder:
(BuildContext context, GlobalKey key) =>
Padding(
key: key,
padding: EdgeInsets.only(bottom: 8.h),
child: GestureDetector(
onLongPressStart:
(LongPressStartDetails details) {
messageActionController
.onLongPressMessage(context,
isDarkMode, details, message);
},
child: MessageBubble(
sessionid: message.sessionid,
isUser: message.isUser,
message: message.text,
timestamp: message.timestamp,
isRecognizing: message.isRecognizing,
hasImage: message.hasImage,
imagePath: message.imagePath,
meta: message.meta,
functionCalls: message.functionCalls,
isToolCallsExpanded:
message.isToolCallsExpanded,
),
),
),
)
: Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: GestureDetector(
onLongPressStart:
@ -367,35 +408,12 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
message.isToolCallsExpanded,
),
),
),
)
: Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: GestureDetector(
onLongPressStart:
(LongPressStartDetails details) {
messageActionController.onLongPressMessage(
context, isDarkMode, details, message);
},
child: MessageBubble(
sessionid: message.sessionid,
isUser: message.isUser,
message: message.text,
timestamp: message.timestamp,
isRecognizing: message.isRecognizing,
hasImage: message.hasImage,
imagePath: message.imagePath,
meta: message.meta,
functionCalls: message.functionCalls,
isToolCallsExpanded:
message.isToolCallsExpanded,
),
),
);
},
),
);
}),
);
},
),
);
}),
),
),
// 输入区域
@ -553,6 +571,13 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
return GestureDetector(
onTap: () {
controller.toggleInputMode();
// 切换模式时,确保焦点正确
if (!isTextMode) {
// 切换到语音模式时,请求焦点
Future.delayed(Duration.zero, () {
_textFocusNode.requestFocus();
});
}
},
child: Container(
width: 44.w,
@ -652,6 +677,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
controller.isImageInputActive.value;
return TextField(
focusNode: _textFocusNode, // 绑定焦点节点
controller: controller.textController,
maxLines: isImageActive
? 3

1
lib/modules/agent/views/music_card.dart

@ -17,7 +17,6 @@ Widget buildMusicItem(
'coverUrl': image,
};
controller.musicStatus(); //刷新状态
return Container(
padding: EdgeInsets.all(14.w),
decoration: BoxDecoration(

127
lib/modules/login/constants/country_codes.dart

@ -0,0 +1,127 @@
// Country dialing codes list extracted for maintainability and reuse.
final List<Map<String, String>> kCountryCodes = [
{'name': '邮箱', 'code': '', 'flag': '✉️'}, // 保持首位
{'name': '中国', 'code': '+86', 'flag': '🇨🇳'}, // 保持第二位
// 按国家代码数字升序排列(去除+号后排序)
{'name': '美国', 'code': '+1', 'flag': '🇺🇸'},
{'name': '加拿大', 'code': '+1', 'flag': '🇨🇦'},
{'name': '俄罗斯', 'code': '+7', 'flag': '🇷🇺'},
{'name': '英国', 'code': '+44', 'flag': '🇬🇧'},
{'name': '德国', 'code': '+49', 'flag': '🇩🇪'},
{'name': '意大利', 'code': '+39', 'flag': '🇮🇹'},
{'name': '法国', 'code': '+33', 'flag': '🇫🇷'},
{'name': '西班牙', 'code': '+34', 'flag': '🇪🇸'},
{'name': '南非', 'code': '+27', 'flag': '🇿🇦'},
{'name': '沙特阿拉伯', 'code': '+966', 'flag': '🇸🇦'},
{'name': '土耳其', 'code': '+90', 'flag': '🇹🇷'},
{'name': '日本', 'code': '+81', 'flag': '🇯🇵'},
{'name': '韩国', 'code': '+82', 'flag': '🇰🇷'},
{'name': '新加坡', 'code': '+65', 'flag': '🇸🇬'},
{'name': '马来西亚', 'code': '+60', 'flag': '🇲🇾'},
{'name': '泰国', 'code': '+66', 'flag': '🇹🇭'},
{'name': '越南', 'code': '+84', 'flag': '🇻🇳'},
{'name': '印度', 'code': '+91', 'flag': '🇮🇳'},
{'name': '巴基斯坦', 'code': '+92', 'flag': '🇵🇰'},
{'name': '菲律宾', 'code': '+63', 'flag': '🇵🇭'},
{'name': '印度尼西亚', 'code': '+62', 'flag': '🇮🇩'},
{'name': '阿联酋', 'code': '+971', 'flag': '🇦🇪'},
{'name': '墨西哥', 'code': '+52', 'flag': '🇲🇽'},
{'name': '巴西', 'code': '+55', 'flag': '🇧🇷'},
{'name': '阿根廷', 'code': '+54', 'flag': '🇦🇷'},
{'name': '澳大利亚', 'code': '+61', 'flag': '🇦🇺'},
{'name': '新西兰', 'code': '+64', 'flag': '🇳🇿'},
// 中国地区(保持最后)
{'name': '中国香港', 'code': '+852', 'flag': '🇭🇰'},
{'name': '中国澳门', 'code': '+853', 'flag': '🇲🇴'},
{'name': '中国台湾', 'code': '+886', 'flag': '🇹🇼'},
// 添加的其他主要国家
{'name': '埃及', 'code': '+20', 'flag': '🇪🇬'},
{'name': '希腊', 'code': '+30', 'flag': '🇬🇷'},
{'name': '荷兰', 'code': '+31', 'flag': '🇳🇱'},
{'name': '比利时', 'code': '+32', 'flag': '🇧🇪'},
{'name': '匈牙利', 'code': '+36', 'flag': '🇭🇺'},
{'name': '罗马尼亚', 'code': '+40', 'flag': '🇷🇴'},
{'name': '瑞士', 'code': '+41', 'flag': '🇨🇭'},
{'name': '奥地利', 'code': '+43', 'flag': '🇦🇹'},
{'name': '丹麦', 'code': '+45', 'flag': '🇩🇰'},
{'name': '瑞典', 'code': '+46', 'flag': '🇸🇪'},
{'name': '挪威', 'code': '+47', 'flag': '🇳🇴'},
{'name': '波兰', 'code': '+48', 'flag': '🇵🇱'},
{'name': '秘鲁', 'code': '+51', 'flag': '🇵🇪'},
{'name': '智利', 'code': '+56', 'flag': '🇨🇱'},
{'name': '哥伦比亚', 'code': '+57', 'flag': '🇨🇴'},
{'name': '委内瑞拉', 'code': '+58', 'flag': '🇻🇪'},
{'name': '葡萄牙', 'code': '+351', 'flag': '🇵🇹'},
{'name': '卢森堡', 'code': '+352', 'flag': '🇱🇺'},
{'name': '爱尔兰', 'code': '+353', 'flag': '🇮🇪'},
{'name': '冰岛', 'code': '+354', 'flag': '🇮🇸'},
{'name': '阿尔巴尼亚', 'code': '+355', 'flag': '🇦🇱'},
{'name': '马耳他', 'code': '+356', 'flag': '🇲🇹'},
{'name': '塞浦路斯', 'code': '+357', 'flag': '🇨🇾'},
{'name': '芬兰', 'code': '+358', 'flag': '🇫🇮'},
{'name': '保加利亚', 'code': '+359', 'flag': '🇧🇬'},
{'name': '立陶宛', 'code': '+370', 'flag': '🇱🇹'},
{'name': '拉脱维亚', 'code': '+371', 'flag': '🇱🇻'},
{'name': '爱沙尼亚', 'code': '+372', 'flag': '🇪🇪'},
{'name': '乌克兰', 'code': '+380', 'flag': '🇺🇦'},
{'name': '塞尔维亚', 'code': '+381', 'flag': '🇷🇸'},
{'name': '克罗地亚', 'code': '+385', 'flag': '🇭🇷'},
{'name': '斯洛文尼亚', 'code': '+386', 'flag': '🇸🇮'},
{'name': '捷克', 'code': '+420', 'flag': '🇨🇿'},
{'name': '斯洛伐克', 'code': '+421', 'flag': '🇸🇰'},
{'name': '危地马拉', 'code': '+502', 'flag': '🇬🇹'},
{'name': '萨尔瓦多', 'code': '+503', 'flag': '🇸🇻'},
{'name': '洪都拉斯', 'code': '+504', 'flag': '🇭🇳'},
{'name': '尼加拉瓜', 'code': '+505', 'flag': '🇳🇮'},
{'name': '哥斯达黎加', 'code': '+506', 'flag': '🇨🇷'},
{'name': '巴拿马', 'code': '+507', 'flag': '🇵🇦'},
{'name': '海地', 'code': '+509', 'flag': '🇭🇹'},
{'name': '玻利维亚', 'code': '+591', 'flag': '🇧🇴'},
{'name': '厄瓜多尔', 'code': '+593', 'flag': '🇪🇨'},
{'name': '巴拉圭', 'code': '+595', 'flag': '🇵🇾'},
{'name': '乌拉圭', 'code': '+598', 'flag': '🇺🇾'},
{'name': '摩洛哥', 'code': '+212', 'flag': '🇲🇦'},
{'name': '阿尔及利亚', 'code': '+213', 'flag': '🇩🇿'},
{'name': '突尼斯', 'code': '+216', 'flag': '🇹🇳'},
{'name': '尼日利亚', 'code': '+234', 'flag': '🇳🇬'},
{'name': '加纳', 'code': '+233', 'flag': '🇬🇭'},
{'name': '埃塞俄比亚', 'code': '+251', 'flag': '🇪🇹'},
{'name': '肯尼亚', 'code': '+254', 'flag': '🇰🇪'},
{'name': '坦桑尼亚', 'code': '+255', 'flag': '🇹🇿'},
{'name': '乌干达', 'code': '+256', 'flag': '🇺🇬'},
{'name': '孟加拉国', 'code': '+880', 'flag': '🇧🇩'},
{'name': '以色列', 'code': '+972', 'flag': '🇮🇱'},
{'name': '黎巴嫩', 'code': '+961', 'flag': '🇱🇧'},
{'name': '约旦', 'code': '+962', 'flag': '🇯🇴'},
{'name': '伊拉克', 'code': '+964', 'flag': '🇮🇶'},
{'name': '科威特', 'code': '+965', 'flag': '🇰🇼'},
{'name': '巴林', 'code': '+973', 'flag': '🇧🇭'},
{'name': '卡塔尔', 'code': '+974', 'flag': '🇶🇦'},
{'name': '阿曼', 'code': '+968', 'flag': '🇴🇲'},
{'name': '阿富汗', 'code': '+93', 'flag': '🇦🇫'},
{'name': '斯里兰卡', 'code': '+94', 'flag': '🇱🇰'},
{'name': '缅甸', 'code': '+95', 'flag': '🇲🇲'},
{'name': '尼泊尔', 'code': '+977', 'flag': '🇳🇵'},
{'name': '蒙古', 'code': '+976', 'flag': '🇲🇳'},
{'name': '柬埔寨', 'code': '+855', 'flag': '🇰🇭'},
{'name': '老挝', 'code': '+856', 'flag': '🇱🇦'},
{'name': '文莱', 'code': '+673', 'flag': '🇧🇳'},
{'name': '东帝汶', 'code': '+670', 'flag': '🇹🇱'},
{'name': '斐济', 'code': '+679', 'flag': '🇫🇯'},
{'name': '所罗门群岛', 'code': '+677', 'flag': '🇸🇧'},
{'name': '刚果民主共和国', 'code': '+243', 'flag': '🇨🇩'},
{'name': '安哥拉', 'code': '+244', 'flag': '🇦🇴'},
{'name': '苏丹', 'code': '+249', 'flag': '🇸🇩'},
{'name': '马尔代夫', 'code': '+960', 'flag': '🇲🇻'},
{'name': '不丹', 'code': '+975', 'flag': '🇧🇹'},
{'name': '摩纳哥', 'code': '+377', 'flag': '🇲🇨'},
{'name': '圣马力诺', 'code': '+378', 'flag': '🇸🇲'},
{'name': '牙买加', 'code': '+1-876', 'flag': '🇯🇲'},
{'name': '巴哈马', 'code': '+1-242', 'flag': '🇧🇸'},
{'name': '巴布亚新几内亚', 'code': '+675', 'flag': '🇵🇬'},
{'name': '萨摩亚', 'code': '+685', 'flag': '🇼🇸'},
];

154
lib/modules/login/controllers/login_controller.dart

@ -7,6 +7,7 @@ import 'package:flutter/foundation.dart';
import 'package:logger/web.dart';
import 'package:get_storage/get_storage.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:crypto/crypto.dart'; // 用于生成设备指纹
import 'dart:convert';
import '../../../data/services/db/sqflite_api.dart';
import 'facebookLogin.dart';
@ -21,6 +22,7 @@ import '../../../../data/models/user_Info.dart';
import '../../../data/models/channel_app_model.dart';
import '../../../data/services/version_update_service.dart';
import '../../../modules/common/widgets/privacy_policy_dialog.dart';
import '../constants/country_codes.dart';
enum LoginViewState {
welcome, // 欢迎页面
@ -42,7 +44,7 @@ class LoginController extends GetxController {
UserChannel _userChannel = UserChannel.OFFICIAL; // 默认渠道
final isChinaMainland = false.obs; //是否中国大陆地区
final _hasAcceptedPrivacy = false.obs;
final _hasAcceptedPrivacy = false.obs; //是否同意隐私政策
bool get hasAcceptedPrivacy => _hasAcceptedPrivacy.value;
set hasAcceptedPrivacy(bool value) => _hasAcceptedPrivacy.value = value;
@ -53,131 +55,7 @@ class LoginController extends GetxController {
String get selectedCountryCode => _selectedCountryCode.value;
String get selectedCountryFlag => _selectedCountryFlag.value;
// 预设国家编码列表
final List<Map<String, String>> countryCodes = [
{'name': '邮箱', 'code': '', 'flag': '✉️'}, // 保持首位
{'name': '中国', 'code': '+86', 'flag': '🇨🇳'}, // 保持第二位
// 按国家代码数字升序排列(去除+号后排序)
{'name': '美国', 'code': '+1', 'flag': '🇺🇸'},
{'name': '加拿大', 'code': '+1', 'flag': '🇨🇦'},
{'name': '俄罗斯', 'code': '+7', 'flag': '🇷🇺'},
{'name': '英国', 'code': '+44', 'flag': '🇬🇧'},
{'name': '德国', 'code': '+49', 'flag': '🇩🇪'},
{'name': '意大利', 'code': '+39', 'flag': '🇮🇹'},
{'name': '法国', 'code': '+33', 'flag': '🇫🇷'},
{'name': '西班牙', 'code': '+34', 'flag': '🇪🇸'},
{'name': '南非', 'code': '+27', 'flag': '🇿🇦'},
{'name': '沙特阿拉伯', 'code': '+966', 'flag': '🇸🇦'},
{'name': '土耳其', 'code': '+90', 'flag': '🇹🇷'},
{'name': '日本', 'code': '+81', 'flag': '🇯🇵'},
{'name': '韩国', 'code': '+82', 'flag': '🇰🇷'},
{'name': '新加坡', 'code': '+65', 'flag': '🇸🇬'},
{'name': '马来西亚', 'code': '+60', 'flag': '🇲🇾'},
{'name': '泰国', 'code': '+66', 'flag': '🇹🇭'},
{'name': '越南', 'code': '+84', 'flag': '🇻🇳'},
{'name': '印度', 'code': '+91', 'flag': '🇮🇳'},
{'name': '巴基斯坦', 'code': '+92', 'flag': '🇵🇰'},
{'name': '菲律宾', 'code': '+63', 'flag': '🇵🇭'},
{'name': '印度尼西亚', 'code': '+62', 'flag': '🇮🇩'},
{'name': '阿联酋', 'code': '+971', 'flag': '🇦🇪'},
{'name': '墨西哥', 'code': '+52', 'flag': '🇲🇽'},
{'name': '巴西', 'code': '+55', 'flag': '🇧🇷'},
{'name': '阿根廷', 'code': '+54', 'flag': '🇦🇷'},
{'name': '澳大利亚', 'code': '+61', 'flag': '🇦🇺'},
{'name': '新西兰', 'code': '+64', 'flag': '🇳🇿'},
// 中国地区(保持最后)
{'name': '中国香港', 'code': '+852', 'flag': '🇭🇰'},
{'name': '中国澳门', 'code': '+853', 'flag': '🇲🇴'},
{'name': '中国台湾', 'code': '+886', 'flag': '🇹🇼'},
// 添加的其他主要国家
{'name': '埃及', 'code': '+20', 'flag': '🇪🇬'},
{'name': '希腊', 'code': '+30', 'flag': '🇬🇷'},
{'name': '荷兰', 'code': '+31', 'flag': '🇳🇱'},
{'name': '比利时', 'code': '+32', 'flag': '🇧🇪'},
{'name': '匈牙利', 'code': '+36', 'flag': '🇭🇺'},
{'name': '罗马尼亚', 'code': '+40', 'flag': '🇷🇴'},
{'name': '瑞士', 'code': '+41', 'flag': '🇨🇭'},
{'name': '奥地利', 'code': '+43', 'flag': '🇦🇹'},
{'name': '丹麦', 'code': '+45', 'flag': '🇩🇰'},
{'name': '瑞典', 'code': '+46', 'flag': '🇸🇪'},
{'name': '挪威', 'code': '+47', 'flag': '🇳🇴'},
{'name': '波兰', 'code': '+48', 'flag': '🇵🇱'},
{'name': '秘鲁', 'code': '+51', 'flag': '🇵🇪'},
{'name': '智利', 'code': '+56', 'flag': '🇨🇱'},
{'name': '哥伦比亚', 'code': '+57', 'flag': '🇨🇴'},
{'name': '委内瑞拉', 'code': '+58', 'flag': '🇻🇪'},
{'name': '葡萄牙', 'code': '+351', 'flag': '🇵🇹'},
{'name': '卢森堡', 'code': '+352', 'flag': '🇱🇺'},
{'name': '爱尔兰', 'code': '+353', 'flag': '🇮🇪'},
{'name': '冰岛', 'code': '+354', 'flag': '🇮🇸'},
{'name': '阿尔巴尼亚', 'code': '+355', 'flag': '🇦🇱'},
{'name': '马耳他', 'code': '+356', 'flag': '🇲🇹'},
{'name': '塞浦路斯', 'code': '+357', 'flag': '🇨🇾'},
{'name': '芬兰', 'code': '+358', 'flag': '🇫🇮'},
{'name': '保加利亚', 'code': '+359', 'flag': '🇧🇬'},
{'name': '立陶宛', 'code': '+370', 'flag': '🇱🇹'},
{'name': '拉脱维亚', 'code': '+371', 'flag': '🇱🇻'},
{'name': '爱沙尼亚', 'code': '+372', 'flag': '🇪🇪'},
{'name': '乌克兰', 'code': '+380', 'flag': '🇺🇦'},
{'name': '塞尔维亚', 'code': '+381', 'flag': '🇷🇸'},
{'name': '克罗地亚', 'code': '+385', 'flag': '🇭🇷'},
{'name': '斯洛文尼亚', 'code': '+386', 'flag': '🇸🇮'},
{'name': '捷克', 'code': '+420', 'flag': '🇨🇿'},
{'name': '斯洛伐克', 'code': '+421', 'flag': '🇸🇰'},
{'name': '危地马拉', 'code': '+502', 'flag': '🇬🇹'},
{'name': '萨尔瓦多', 'code': '+503', 'flag': '🇸🇻'},
{'name': '洪都拉斯', 'code': '+504', 'flag': '🇭🇳'},
{'name': '尼加拉瓜', 'code': '+505', 'flag': '🇳🇮'},
{'name': '哥斯达黎加', 'code': '+506', 'flag': '🇨🇷'},
{'name': '巴拿马', 'code': '+507', 'flag': '🇵🇦'},
{'name': '海地', 'code': '+509', 'flag': '🇭🇹'},
{'name': '玻利维亚', 'code': '+591', 'flag': '🇧🇴'},
{'name': '厄瓜多尔', 'code': '+593', 'flag': '🇪🇨'},
{'name': '巴拉圭', 'code': '+595', 'flag': '🇵🇾'},
{'name': '乌拉圭', 'code': '+598', 'flag': '🇺🇾'},
{'name': '摩洛哥', 'code': '+212', 'flag': '🇲🇦'},
{'name': '阿尔及利亚', 'code': '+213', 'flag': '🇩🇿'},
{'name': '突尼斯', 'code': '+216', 'flag': '🇹🇳'},
{'name': '尼日利亚', 'code': '+234', 'flag': '🇳🇬'},
{'name': '加纳', 'code': '+233', 'flag': '🇬🇭'},
{'name': '埃塞俄比亚', 'code': '+251', 'flag': '🇪🇹'},
{'name': '肯尼亚', 'code': '+254', 'flag': '🇰🇪'},
{'name': '坦桑尼亚', 'code': '+255', 'flag': '🇹🇿'},
{'name': '乌干达', 'code': '+256', 'flag': '🇺🇬'},
{'name': '孟加拉国', 'code': '+880', 'flag': '🇧🇩'},
{'name': '以色列', 'code': '+972', 'flag': '🇮🇱'},
{'name': '黎巴嫩', 'code': '+961', 'flag': '🇱🇧'},
{'name': '约旦', 'code': '+962', 'flag': '🇯🇴'},
{'name': '伊拉克', 'code': '+964', 'flag': '🇮🇶'},
{'name': '科威特', 'code': '+965', 'flag': '🇰🇼'},
{'name': '巴林', 'code': '+973', 'flag': '🇧🇭'},
{'name': '卡塔尔', 'code': '+974', 'flag': '🇶🇦'},
{'name': '阿曼', 'code': '+968', 'flag': '🇴🇲'},
{'name': '阿富汗', 'code': '+93', 'flag': '🇦🇫'},
{'name': '斯里兰卡', 'code': '+94', 'flag': '🇱🇰'},
{'name': '缅甸', 'code': '+95', 'flag': '🇲🇲'},
{'name': '尼泊尔', 'code': '+977', 'flag': '🇳🇵'},
{'name': '蒙古', 'code': '+976', 'flag': '🇲🇳'},
{'name': '柬埔寨', 'code': '+855', 'flag': '🇰🇭'},
{'name': '老挝', 'code': '+856', 'flag': '🇱🇦'},
{'name': '文莱', 'code': '+673', 'flag': '🇧🇳'},
{'name': '东帝汶', 'code': '+670', 'flag': '🇹🇱'},
{'name': '斐济', 'code': '+679', 'flag': '🇫🇯'},
{'name': '所罗门群岛', 'code': '+677', 'flag': '🇸🇧'},
{'name': '刚果民主共和国', 'code': '+243', 'flag': '🇨🇩'},
{'name': '安哥拉', 'code': '+244', 'flag': '🇦🇴'},
{'name': '苏丹', 'code': '+249', 'flag': '🇸🇩'},
{'name': '马尔代夫', 'code': '+960', 'flag': '🇲🇻'},
{'name': '不丹', 'code': '+975', 'flag': '🇧🇹'},
{'name': '摩纳哥', 'code': '+377', 'flag': '🇲🇨'},
{'name': '圣马力诺', 'code': '+378', 'flag': '🇸🇲'},
{'name': '牙买加', 'code': '+1-876', 'flag': '🇯🇲'},
{'name': '巴哈马', 'code': '+1-242', 'flag': '🇧🇸'},
{'name': '巴布亚新几内亚', 'code': '+675', 'flag': '🇵🇬'},
{'name': '萨摩亚', 'code': '+685', 'flag': '🇼🇸'},
];
final List<Map<String, String>> countryCodes = kCountryCodes;
// 初始化默认选择的国家代码
void _initDefaultCountryCode() {
@ -236,7 +114,7 @@ class LoginController extends GetxController {
_verificationCodeController = TextEditingController();
isChinaMainland.value =
Get.deviceLocale?.countryCode?.toUpperCase() == 'CN';
Get.deviceLocale?.countryCode?.toUpperCase() == 'CN'; //是否中国大陆地区
// 初始化默认国家代码
_initDefaultCountryCode();
@ -304,9 +182,27 @@ class LoginController extends GetxController {
_deviceId = androidInfo.id;
} else if (GetPlatform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
_deviceId = iosInfo.identifierForVendor ?? 'unknown';
//print('lxm---iosInfo: $iosInfo.toString()');
final utsname = iosInfo.utsname; // 包含更详细的设备信息
//print('lxm---utsname: $utsname');
final machineId = utsname.machine.toLowerCase(); // 例如: "iPhone14,2"
Logger().d('lxm---machineId: $machineId');
final systemVersion = utsname.release; // 例如: "16.4.1"
Logger().d('lxm---systemVersion: $systemVersion');
try {
// 组合 machine 和 systemVersion 生成指纹
final fingerprint = '$machineId|$systemVersion';
final hashedFingerprint =
md5.convert(utf8.encode(fingerprint)).toString();
Logger()
.d('生成设备指纹: $hashedFingerprint (基于 $machineId 和 $systemVersion)');
_deviceId = hashedFingerprint;
} catch (e) {
Logger().e('生成设备指纹失败: $e');
_deviceId = iosInfo.identifierForVendor ?? 'unknown';
}
}
Logger().d('deviceId: $_deviceId');
} catch (e) {
Logger().e('获取设备信息失败: $e');
_deviceId = 'unknown';

4
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -69,6 +69,10 @@ class MeetingDetailsController extends GetxController
RxBool isDownload = false.obs;
RxDouble downloadProgress = 0.0.obs;
List categoryTemplateList = [];
Map editTemplate = {};
int deleteTemplateId = 0;
@override
void onInit() {
super.onInit();

37
lib/modules/meeting/views/bottomSheet/generate_bottom_sheet.dart

@ -46,7 +46,6 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
'auto_fix_high': Icons.auto_fix_high,
};
List _categoryTemplateList = [];
List _originalTemplate = [];
Map _templateData = {};
@ -60,10 +59,13 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
}
void _getTemplate() async {
_categoryTemplateList = await SqfliteApi.getMeetingCategoryTemplateList();
_controller.categoryTemplateList =
await SqfliteApi.getMeetingCategoryTemplateList();
_controller.editTemplate = {};
_controller.deleteTemplateId = 0;
_lastTemplateData = _storage.read("meeting_last_template_Data") ?? {};
_originalTemplate = _storage.read("meeting_template_list") ??
_categoryTemplateList[0]['template'];
_controller.categoryTemplateList[0]['template'];
if (_lastTemplateData.isNotEmpty) {
_templateData = _lastTemplateData;
} else {
@ -162,7 +164,6 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
() => TemplateView(
_templateData,
_lastTemplateData['id'] ?? 0,
_categoryTemplateList,
),
preventDuplicates: false,
);
@ -182,6 +183,34 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
_templateList = list;
});
}
if (_controller.editTemplate.isNotEmpty) {
Map? editMap = _templateList.firstWhere(
(element) =>
element['id'] == _controller.editTemplate['id'],
orElse: () => null);
if (editMap != null) {
editMap['name'] = _controller.editTemplate['name'];
editMap['desc'] = _controller.editTemplate['desc'];
editMap['prompt'] = _controller.editTemplate['prompt'];
}
if (_controller.editTemplate['id'] == _templateData['id']) {
_templateData['name'] = _controller.editTemplate['name'];
_templateData['desc'] = _controller.editTemplate['desc'];
_templateData['prompt'] =
_controller.editTemplate['prompt'];
}
setState(() {});
}
if (_controller.deleteTemplateId != 0) {
_templateList.removeWhere((element) =>
element['id'] == _controller.deleteTemplateId);
if (_controller.deleteTemplateId == _templateData['id']) {
_templateData = _templateList[0];
}
setState(() {});
_storage.write("meeting_last_template_Data", {});
_storage.write("meeting_template_list", _templateList);
}
},
child: Row(
children: [

1
lib/modules/meeting/views/bottomSheet/navigation_bar_bottom_sheet.dart

@ -5,6 +5,7 @@ import 'package:get/get.dart';
import '../../../../routes/app_routes.dart';
import '../../controllers/meeting_home_controller.dart';
///会议底部导航栏抽屉,提供会议列表、会议详情、会议设置
class NavigationBarBottomSheet extends GetView<MeetingHomeController> {
const NavigationBarBottomSheet({super.key});

26
lib/modules/meeting/views/meeting_details_view.dart

@ -17,6 +17,7 @@ import 'bottomSheet/share_bottom_sheet.dart';
import 'tabs/speech_tab.dart';
import 'tabs/summary_tab.dart';
///会议音频详情页面,提供会议音频播放、会议音频分享、会议音频更多操作
class MeetingDetailsView extends GetView<MeetingDetailsController> {
const MeetingDetailsView({super.key});
@ -40,10 +41,9 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Obx(
() => AppBar(
backgroundColor:
isDarkMode
? (controller.isTop.value ? Colors.black : Colors.grey[900])
: (controller.isTop.value ? Colors.grey[50] : Colors.grey[200]),
backgroundColor: isDarkMode
? (controller.isTop.value ? Colors.black : Colors.grey[900])
: (controller.isTop.value ? Colors.grey[50] : Colors.grey[200]),
surfaceTintColor: Colors.transparent,
elevation: 0.5,
leading: controller.isSpeakerText.value
@ -202,7 +202,8 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
Get.bottomSheet(
const ShareBottomSheet(),
isScrollControlled: true,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100],
backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.grey[100],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
@ -234,7 +235,8 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
Get.bottomSheet(
const MoreBottomSheet(),
isScrollControlled: true,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[100],
backgroundColor:
isDarkMode ? Colors.grey[800] : Colors.grey[100],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
@ -301,10 +303,14 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
child: Obx(
() => Row(
children: [
_iconItem(Icons.summarize, 'overview'.tr, 0), // 对应中文:概览
_iconItem(Icons.message, 'transcription'.tr, 1), // 对应中文:转写
_iconItem(Icons.article, 'summary'.tr, 2), // 对应中文:总结
_iconItem(Icons.lan, 'mindMap'.tr, 3), // 对应中文:思维导图
_iconItem(
Icons.summarize, 'overview'.tr, 0), // 对应中文:概览
_iconItem(Icons.message, 'transcription'.tr,
1), // 对应中文:转写
_iconItem(
Icons.article, 'summary'.tr, 2), // 对应中文:总结
_iconItem(
Icons.lan, 'mindMap'.tr, 3), // 对应中文:思维导图
],
),
),

1
lib/modules/meeting/views/meeting_home_view.dart

@ -11,6 +11,7 @@ import '../model/meeting_model.dart';
import '../utils/meeting_ui_utils.dart';
import 'bottomSheet/edit_title_bottom_sheet.dart';
///会议主页面,提供会议列表、会议详情、会议设置
class MeetingHomeView extends StatefulWidget {
const MeetingHomeView({super.key});

1
lib/modules/meeting/views/meeting_view.dart

@ -9,6 +9,7 @@ import '../utils/meeting_ui_utils.dart';
import 'bottomSheet/filtrate_drawer.dart';
import 'bottomSheet/navigation_bar_bottom_sheet.dart';
///meeting模块主容器页面,提供底部导航栏、浮动按钮、侧板抽屉
class MeetingView extends GetView<MeetingController> {
const MeetingView({super.key});

42
lib/modules/meeting/views/template/template_all_view.dart

@ -3,6 +3,8 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../../../data/services/db/sqflite_api.dart';
import '../../controllers/meeting_details_controller.dart';
import 'template_details_view.dart';
class TemplateAllView extends StatefulWidget {
final String title;
@ -19,6 +21,8 @@ class TemplateAllView extends StatefulWidget {
}
class _TemplateAllViewState extends State<TemplateAllView> {
final _controller = Get.find<MeetingDetailsController>();
final Map _icons = {
'auto_stories': Icons.auto_stories,
'auto_graph': Icons.auto_graph,
@ -47,8 +51,9 @@ class _TemplateAllViewState extends State<TemplateAllView> {
_getTemplate();
}
void _getTemplate() async {
List list = await SqfliteApi.getMeetingTemplateList(widget.categoryid);
Future _getTemplate() async {
List list = await SqfliteApi.getMeetingTemplateList(widget.categoryid,
isCustom: widget.title == '自定义');
List templateList = [];
for (var element in list) {
Map item = {
@ -245,6 +250,39 @@ class _TemplateAllViewState extends State<TemplateAllView> {
),
),
),
Positioned(
top: 0,
right: 0,
child: GestureDetector(
onTap: () async {
await Get.to(() => TemplateDetailsView(template,
isEdit: widget.title == '自定义'));
if (_controller.editTemplate.isNotEmpty) {
await _getTemplate();
if (_controller.editTemplate['id'] == _templateData['id']) {
_templateData['name'] = _controller.editTemplate['name'];
_templateData['desc'] = _controller.editTemplate['desc'];
_templateData['prompt'] =
_controller.editTemplate['prompt'];
}
}
if (_controller.deleteTemplateId != 0) {
await _getTemplate();
if (_controller.deleteTemplateId == _templateData['id']) {
_templateData = {};
}
}
},
child: Padding(
padding: EdgeInsets.all(10.w),
child: Icon(
Icons.fullscreen,
color: Colors.grey[600],
size: 26.w,
),
),
),
),
if (widget.lastTemplateId == template['id'])
Positioned(
top: 0,

184
lib/modules/meeting/views/template/template_details_view.dart

@ -3,10 +3,31 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
class TemplateDetailsView extends StatelessWidget {
import '../../../../data/services/db/sqflite_api.dart';
import '../../../../data/services/network/api.dart';
import '../../controllers/meeting_details_controller.dart';
import 'template_edit_view.dart';
class TemplateDetailsView extends StatefulWidget {
final Map data;
final bool isEdit;
const TemplateDetailsView(this.data, {super.key, this.isEdit = false});
@override
State<TemplateDetailsView> createState() => _TemplateDetailsViewState();
}
class _TemplateDetailsViewState extends State<TemplateDetailsView> {
final _controller = Get.find<MeetingDetailsController>();
Map _templateData = {};
const TemplateDetailsView(this.data, {super.key});
@override
void initState() {
super.initState();
_templateData = widget.data;
}
@override
Widget build(BuildContext context) {
@ -55,7 +76,7 @@ class TemplateDetailsView extends StatelessWidget {
Padding(
padding: EdgeInsets.only(top: 20.w),
child: Text(
data['name'],
_templateData['name'],
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold,
@ -75,7 +96,7 @@ class TemplateDetailsView extends StatelessWidget {
Wrap(
spacing: 5.w,
children: List.generate(
data['tag'].length,
_templateData['tag'].length,
(int idx) {
return Container(
padding:
@ -85,7 +106,7 @@ class TemplateDetailsView extends StatelessWidget {
borderRadius: BorderRadius.circular(4.r),
),
child: Text(
data['tag'][idx],
_templateData['tag'][idx],
style: TextStyle(
fontSize: 10.sp,
color: Colors.grey[600],
@ -106,7 +127,7 @@ class TemplateDetailsView extends StatelessWidget {
),
),
Text(
data['desc'],
_templateData['desc'],
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[600],
@ -123,61 +144,130 @@ class TemplateDetailsView extends StatelessWidget {
),
),
MarkdownBody(
data: data['prompt'],
data: _templateData['prompt'],
),
],
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () {},
child: Container(
height: 48.w,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Colors.black,
if (widget.isEdit)
Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w),
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () async {
var result = await Get.to(
() => TemplateEditView(data: _templateData));
if (result != null) {
setState(() {
_templateData['name'] = result['name'];
_templateData['desc'] = result['desc'];
_templateData['prompt'] = result['prompt'];
});
}
},
child: Container(
height: 48.w,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: Colors.black,
),
),
),
child: Text(
'修改',
style: TextStyle(
fontSize: 16.sp,
color: Colors.black,
child: Text(
'修改',
style: TextStyle(
fontSize: 16.sp,
color: Colors.black,
),
),
),
),
),
),
15.horizontalSpace,
Expanded(
child: GestureDetector(
onTap: () {},
child: Container(
height: 48.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
'删除',
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
15.horizontalSpace,
Expanded(
child: GestureDetector(
onTap: () {
Get.dialog(
AlertDialog(
backgroundColor:
isDarkMode ? Colors.grey[900] : Colors.white,
title: Text(
'删除模版',
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
),
),
content: Text(
'确定要删除当前模版吗?',
style: TextStyle(
color: isDarkMode
? Colors.grey[400]
: Colors.grey[600],
),
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'cancel'.tr,
style: TextStyle(
color: isDarkMode
? Colors.grey[400]
: Colors.grey[600],
),
),
),
TextButton(
onPressed: () async {
Get.back();
var response = await Api.delEchomeetTemplates({
'ids': [_templateData['id']],
});
if (response != null) {
await SqfliteApi.deleteMeetingTemplate(
_templateData['id']);
_controller.categoryTemplateList =
await SqfliteApi
.getMeetingCategoryTemplateList();
_controller.deleteTemplateId =
_templateData['id'];
Get.back();
}
},
child: Text(
'confirm'.tr,
style: const TextStyle(
color: Colors.red,
),
),
),
],
),
);
},
child: Container(
height: 48.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
'删除',
style: TextStyle(
fontSize: 16.sp,
color: Colors.white,
),
),
),
),
),
),
],
],
),
),
),
],
);
}

86
lib/modules/meeting/views/template/template_edit_view.dart

@ -2,18 +2,25 @@ import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import '../../../../data/services/db/sqflite_api.dart';
import '../../../../data/services/network/api.dart';
import '../../controllers/meeting_details_controller.dart';
class TemplateEditView extends StatefulWidget {
const TemplateEditView({super.key});
final Map? data;
final int? categoryid;
const TemplateEditView({super.key, this.data, this.categoryid});
@override
State<TemplateEditView> createState() => _TemplateEditViewState();
}
class _TemplateEditViewState extends State<TemplateEditView> {
final _controller = Get.find<MeetingDetailsController>();
final TextEditingController _nameController = TextEditingController();
final TextEditingController _descController = TextEditingController();
final TextEditingController _promptController = TextEditingController();
@ -23,6 +30,11 @@ class _TemplateEditViewState extends State<TemplateEditView> {
@override
void initState() {
super.initState();
if (widget.data != null) {
_nameController.text = widget.data?['name'];
_descController.text = widget.data?['desc'];
_promptController.text = widget.data?['prompt'];
}
}
@override
@ -61,7 +73,7 @@ class _TemplateEditViewState extends State<TemplateEditView> {
onPressed: () => Get.back(),
),
title: Text(
'创建模版',
widget.data != null ? '编辑模版' : '创建模版',
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
@ -204,7 +216,7 @@ class _TemplateEditViewState extends State<TemplateEditView> {
void _submit() async {
EasyLoading.show();
Map data = {
"template": {
"template": <String, Object?>{
'ttype': '自定义',
'tags': '',
'icon': 'auto_fix_high',
@ -214,11 +226,71 @@ class _TemplateEditViewState extends State<TemplateEditView> {
'template': _promptController.text,
},
};
var response = await Api.addEchomeetTemplates(data);
if (response != null) {
print('----$response--------');
// await SqfliteApi.insertMeetingTemplate(data['template']);
if (widget.data != null) {
data['template']['id'] = widget.data?['id'];
var response = await Api.updateEchomeetTemplates(data);
if (response != null) {
await SqfliteApi.editMeetingTemplate(widget.data?['id'], {
'name': _nameController.text,
'desc': _descController.text,
'prompt': _promptController.text,
});
_controller.editTemplate = {
'id': widget.data?['id'],
'name': _nameController.text,
'desc': _descController.text,
'prompt': _promptController.text,
};
final GetStorage storage = GetStorage();
Map lastTemplateData = storage.read("meeting_last_template_Data") ?? {};
List originalTemplate = storage.read("meeting_template_list") ?? [];
if (lastTemplateData.isNotEmpty &&
lastTemplateData['id'] == widget.data?['id']) {
lastTemplateData['name'] = _nameController.text;
lastTemplateData['desc'] = _descController.text;
lastTemplateData['prompt'] = _promptController.text;
storage.write("meeting_last_template_Data", lastTemplateData);
}
if (originalTemplate.isNotEmpty) {
Map? originalMap = originalTemplate.firstWhere(
(element) => element['id'] == widget.data?['id'],
orElse: () => null);
if (originalMap != null) {
originalMap['name'] = _nameController.text;
originalMap['desc'] = _descController.text;
originalMap['prompt'] = _promptController.text;
storage.write("meeting_template_list", originalTemplate);
}
}
}
} else {
var response = await Api.addEchomeetTemplates(data);
if (response != null) {
await SqfliteApi.insertMeetingTemplate({
"id": response['template']['id'],
'categoryid': widget.categoryid,
'name': _nameController.text,
'icon': 'auto_fix_high',
'tag': '',
'desc': _descController.text,
'outline': '您将扮演一名会议事件归纳专家,能准确抓住会议中事件的关键点,并每个事件只需一句话精简总结。',
'prompt': _promptController.text,
});
_controller.editTemplate = {
'id': response['template']['id'],
'name': _nameController.text,
'desc': _descController.text,
'prompt': _promptController.text,
};
}
}
_controller.categoryTemplateList =
await SqfliteApi.getMeetingCategoryTemplateList();
Get.back(result: {
'name': _nameController.text,
'desc': _descController.text,
'prompt': _promptController.text,
});
EasyLoading.dismiss();
}
}

73
lib/modules/meeting/views/template/template_view.dart

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../../controllers/meeting_details_controller.dart';
import 'template_all_view.dart';
import 'template_details_view.dart';
import 'template_edit_view.dart';
@ -9,16 +10,16 @@ import 'template_edit_view.dart';
class TemplateView extends StatefulWidget {
final Map templateData;
final int lastTemplateId;
final List categoryList;
const TemplateView(this.templateData, this.lastTemplateId, this.categoryList,
{super.key});
const TemplateView(this.templateData, this.lastTemplateId, {super.key});
@override
State<TemplateView> createState() => _TemplateViewState();
}
class _TemplateViewState extends State<TemplateView> {
final _controller = Get.find<MeetingDetailsController>();
final Map _icons = {
'auto_stories': Icons.auto_stories,
'auto_graph': Icons.auto_graph,
@ -38,7 +39,7 @@ class _TemplateViewState extends State<TemplateView> {
'auto_fix_high': Icons.auto_fix_high,
};
final List _categoryTemplateList = [];
List _categoryTemplateList = [];
Map _templateData = {};
@override
@ -47,8 +48,9 @@ class _TemplateViewState extends State<TemplateView> {
_getTemplate();
}
void _getTemplate() async {
for (var element in widget.categoryList) {
void _getTemplate() {
_categoryTemplateList = [];
for (var element in _controller.categoryTemplateList) {
List templateList = [];
bool isAdd = false;
if (element['id'] == widget.templateData['categoryid']) {
@ -71,7 +73,28 @@ class _TemplateViewState extends State<TemplateView> {
'template': templateList,
});
}
_templateData = widget.templateData;
setState(() {
_templateData = widget.templateData;
});
}
void _returnCallback() {
if (_controller.editTemplate.isNotEmpty) {
_getTemplate();
if (_controller.editTemplate['id'] == _templateData['id']) {
_templateData['name'] = _controller.editTemplate['name'];
_templateData['desc'] = _controller.editTemplate['desc'];
_templateData['prompt'] = _controller.editTemplate['prompt'];
}
}
if (_controller.deleteTemplateId != 0) {
_getTemplate();
if (_controller.deleteTemplateId == _templateData['id']) {
_templateData = {};
}
_categoryTemplateList.last['template'].removeWhere(
(element) => element['id'] == _controller.deleteTemplateId);
}
}
@override
@ -127,13 +150,15 @@ class _TemplateViewState extends State<TemplateView> {
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w),
child: GestureDetector(
onTap: () {
Get.back(result: _templateData);
if (_templateData.isNotEmpty) {
Get.back(result: _templateData);
}
},
child: Container(
height: 48.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.black,
color: _templateData.isNotEmpty ? Colors.black : Colors.grey,
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
@ -155,13 +180,14 @@ class _TemplateViewState extends State<TemplateView> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: () {
Get.to(() => TemplateAllView(
onTap: () async {
await Get.to(() => TemplateAllView(
data['name'],
data['id'],
_templateData,
widget.lastTemplateId,
));
_returnCallback();
},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.w),
@ -189,7 +215,7 @@ class _TemplateViewState extends State<TemplateView> {
child: isLast && data['template'].length == 0
? Padding(
padding: EdgeInsets.symmetric(horizontal: 8.w),
child: _createTemplate(),
child: _createTemplate(data['id']),
)
: ListView.builder(
itemCount: data['template'].length,
@ -200,11 +226,11 @@ class _TemplateViewState extends State<TemplateView> {
return isLast && index == 0
? Row(
children: [
_createTemplate(),
_template(template),
_createTemplate(data['id']),
_template(template, true),
],
)
: _template(template);
: _template(template, false);
},
),
),
@ -212,7 +238,7 @@ class _TemplateViewState extends State<TemplateView> {
);
}
Widget _template(Map template) {
Widget _template(Map template, bool isEdit) {
return Container(
width: 200.w,
margin: EdgeInsets.symmetric(horizontal: 4.w),
@ -309,8 +335,10 @@ class _TemplateViewState extends State<TemplateView> {
top: 0,
right: 0,
child: GestureDetector(
onTap: () {
Get.to(() => TemplateDetailsView(template));
onTap: () async {
await Get.to(
() => TemplateDetailsView(template, isEdit: isEdit));
_returnCallback();
},
child: Padding(
padding: EdgeInsets.all(10.w),
@ -371,10 +399,13 @@ class _TemplateViewState extends State<TemplateView> {
);
}
Widget _createTemplate() {
Widget _createTemplate(int categoryid) {
return GestureDetector(
onTap: () {
Get.to(() => const TemplateEditView());
onTap: () async {
await Get.to(() => TemplateEditView(categoryid: categoryid));
if (_controller.editTemplate.isNotEmpty) {
_getTemplate();
}
},
child: Container(
width: 200.w,

22
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -480,13 +480,14 @@ object AgentService : CoroutineScope {
"phone_call" -> {
// 通话模式:禁用空闲检测,保持持续激活
Log.d(TAG, "通话模式:禁用空闲检测")
startIdleCheck(mode,maxIdleSeconds)
return
}
else -> {
// 未知模式:使用默认的空闲检测
Log.w(TAG, "未知识别模式: $mode,使用默认空闲检测")
startIdleCheck(maxIdleSeconds)
startIdleCheck(mode,maxIdleSeconds)
}
}
}
@ -494,7 +495,7 @@ object AgentService : CoroutineScope {
/**
* 启动空闲检测
*/
private fun startIdleCheck(idleSeconds: Int = maxIdleSeconds) {
private fun startIdleCheck(mode: String,idleSeconds: Int = maxIdleSeconds) {
stopIdleCheck() // 先停止现有的检查
if (!isRecognitionActive) return
@ -503,8 +504,8 @@ object AgentService : CoroutineScope {
idleCheckJob = launch {
try {
delay(idleSeconds * 1000L)
if (isActive && isRecognitionActive && !isTtsSpeaking && !isAiStreaming) {
Log.d(TAG, "空闲检测触发自动停止,isActive: $isActive, isRecognitionActive: $isRecognitionActive, isTtsSpeaking: $isTtsSpeaking, isAiStreaming: $isAiStreaming, mode: $mode")
if (isActive && isRecognitionActive && !isTtsSpeaking && !isAiStreaming && mode != "phone_call") {
Log.d(TAG, "空闲检测触发自动停止,模式: $currentRecognitionMode")
BleService.closeCodec()
stopRecognition()
@ -517,6 +518,10 @@ object AgentService : CoroutineScope {
)
)
}
if (isActive && isRecognitionActive && !isTtsSpeaking && !isAiStreaming && mode == "phone_call") {
Log.d(TAG, "空闲检测触发自动停止,模式: $currentRecognitionMode")
startRecognition(false, mode)
}
} catch (e: CancellationException) {
// 协程被取消,正常行为
} catch (e: Exception) {
@ -531,6 +536,7 @@ object AgentService : CoroutineScope {
private fun stopIdleCheck() {
idleCheckJob?.cancel()
idleCheckJob = null
Log.d(TAG, "空闲检测已停止")
}
/**
@ -643,11 +649,9 @@ object AgentService : CoroutineScope {
override fun onRecognizing(sessionid:String, recognizing: String, detectedLanguage: String) {
if (recognizing.isNotEmpty()&&isKeepResult==true) {
// 检测到语音,更新状态
_hasSpeechDetected.set(true)
Log.d(TAG, "ai识别中重置定时器")
restartIdleCheck()
_hasSpeechDetected.set(true)
Log.d(TAG, "ai识别中重置定时器")
restartIdleCheck()
sendEvent(

2
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt

@ -1006,7 +1006,7 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
val pcmData = extractPcmFromWav(audioData)
if (pcmData.isNotEmpty()) {
FileLogger.d(tag, "提取PCM数据,大小=${pcmData.size}字节")
BleService.writeExternalRightAudioData(pcmData)
BleService.writeExternalAudioData(pcmData)
}
else {
FileLogger.e(tag, "提取PCM数据失败,音频数据可能不是WAV格式")

10
local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureSpeechPlugin.swift

@ -18,7 +18,7 @@ import ble_service
// 日志标签
private let tag = "AzureSpeechPlugin"
// 日志对象
private let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin")
internal let log = OSLog(subsystem: "com.azure.speech", category: "AzureSpeechPlugin")
// MARK: - 防抖相关(iOS 原生层)
// 函数级注释:用于限制 startContinuousRecognition/stopContinuousRecognition 在极短时间内的重复触发
@ -269,7 +269,7 @@ private func sendAstEvent(_ event: [String: Any]) {
* @param wavData WAV格式的音频数据
* @return 纯PCM音频数据,如果提取失败则返回空Data
*/
private func extractPcmFromWav(_ wavData: Data) -> Data {
internal func extractPcmFromWav(_ wavData: Data) -> Data {
// WAV文件头最小长度为44字节
guard wavData.count >= 44 else {
os_log("WAV数据长度不足,无法解析文件头", log: log, type: .error)
@ -1011,15 +1011,15 @@ private class AstEventCallback: IntegratedSpeechTranslationService.ServiceEventC
public func onSynthesisAudioGenerated(text: String, audioData: Data) {
guard let plugin = plugin else { return }
plugin.os_log("语音合成音频生成,文本=%@,音频数据大小=%d", log: plugin.log, type: .info, text, audioData.count)
os_log("语音合成音频生成,文本=%@,音频数据大小=%d", log: plugin.log, type: .info, text, audioData.count)
// Azure TTS返回的是RIFF WAV格式,需要去除文件头提取PCM数据
let pcmData = plugin.extractPcmFromWav(audioData)
if !pcmData.isEmpty {
plugin.os_log("提取PCM数据,大小=%d字节", log: plugin.log, type: .info, pcmData.count)
os_log("提取PCM数据,大小=%d字节", log: plugin.log, type: .info, pcmData.count)
BleService.shared.writeExternalAudioData(data: pcmData)
} else {
plugin.os_log("提取PCM数据失败,音频数据可能不是WAV格式", log: plugin.log, type: .error)
os_log("提取PCM数据失败,音频数据可能不是WAV格式", log: plugin.log, type: .error)
}
// plugin?.sendAsrEvent([

740
local_plugins/music_service/ios/music_service/Sources/music_service/MusicService.swift

File diff suppressed because it is too large
Loading…
Cancel
Save