diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist
index c90ada3a3..590c85a5f 100644
--- a/ios/Runner/GoogleService-Info.plist
+++ b/ios/Runner/GoogleService-Info.plist
@@ -3,9 +3,9 @@
CLIENT_ID
- 907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278.apps.googleusercontent.com
+ 907754492821-tl2blr39g82re40sttkve36ovt6qcu6c.apps.googleusercontent.com
REVERSED_CLIENT_ID
- com.googleusercontent.apps.907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278
+ com.googleusercontent.apps.907754492821-tl2blr39g82re40sttkve36ovt6qcu6c
ANDROID_CLIENT_ID
907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com
API_KEY
@@ -15,7 +15,7 @@
PLIST_VERSION
1
BUNDLE_ID
- com.yunqiinnovation.deepsound
+ com.chuangxiangyunqi.deapsound
PROJECT_ID
deepsound-e0e3d
STORAGE_BUCKET
@@ -31,6 +31,6 @@
IS_SIGNIN_ENABLED
GOOGLE_APP_ID
- 1:907754492821:ios:246794fc44b23174145c9c
+ 1:907754492821:ios:e4b4f3b37e58a7d1145c9c
\ No newline at end of file
diff --git a/lib/core/utils/synchrodata.dart b/lib/core/utils/synchrodata.dart
index b25638663..5cca0d2e1 100644
--- a/lib/core/utils/synchrodata.dart
+++ b/lib/core/utils/synchrodata.dart
@@ -128,7 +128,6 @@ class Synchrodata {
'title': element['title'] ?? '',
'type': element['rtype'] ?? '',
'seconds': element['seconds'] ?? 0,
- 'filepath': '',
'audiourl': element['audiourl'] ?? '',
'tasktype': element['tasktype'] ?? 0,
'creationtime': element['creationtime'] ?? 0,
@@ -157,7 +156,10 @@ class Synchrodata {
whereArgs: [element['number']],
);
} else {
- await txn.insert('meeting', meetingData);
+ await txn.insert('meeting', {
+ ...meetingData,
+ 'filepath': '',
+ });
await txn.insert('meetingdetails', meetingDetailsData);
}
if (element['original'].isNotEmpty) {
@@ -169,19 +171,34 @@ class Synchrodata {
List speakerList = jsonDecode(element['original']);
if (existSpeakerList.isNotEmpty) {
final batch = txn.batch();
- for (var item in speakerList) {
- batch.update(
- 'meetingspeaker',
- item,
- where: 'id = ?',
- whereArgs: [item['id']],
- );
+ for (var i = 0; i < existSpeakerList.length; i++) {
+ Map item = speakerList[i] ?? {};
+ if (item.isNotEmpty) {
+ batch.update(
+ 'meetingspeaker',
+ {
+ 'meetingid': item['meetingid'],
+ 'content': item['content'],
+ 'starttime': item['starttime'],
+ 'endtime': item['endtime'],
+ 'speaker': item['speaker'],
+ },
+ where: 'id = ?',
+ whereArgs: [existSpeakerList[i]['id']],
+ );
+ }
}
await batch.commit();
} else {
final batch = txn.batch();
for (var item in speakerList) {
- batch.insert('meetingspeaker', item);
+ batch.insert('meetingspeaker', {
+ 'meetingid': item['meetingid'],
+ 'content': item['content'],
+ 'starttime': item['starttime'],
+ 'endtime': item['endtime'],
+ 'speaker': item['speaker'],
+ });
}
await batch.commit();
}
diff --git a/lib/modules/meeting/controllers/meeting_controller.dart b/lib/modules/meeting/controllers/meeting_controller.dart
index 955c2b82f..f7c34edbd 100644
--- a/lib/modules/meeting/controllers/meeting_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_controller.dart
@@ -322,6 +322,7 @@ class MeetingController extends GetxController {
} else {
FilePickerResult? result = await FilePicker.platform.pickFiles(
withData: true,
+ type: FileType.audio,
);
if (result != null) {
PlatformFile file = result.files.first;
diff --git a/lib/modules/meeting/controllers/meeting_details_controller.dart b/lib/modules/meeting/controllers/meeting_details_controller.dart
index 8ac33d0fd..c97e7ed53 100644
--- a/lib/modules/meeting/controllers/meeting_details_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_details_controller.dart
@@ -1,7 +1,9 @@
import 'dart:async';
import 'dart:convert';
+import 'dart:io';
import 'package:audio_waveforms/audio_waveforms.dart';
+import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_easyloading/flutter_easyloading.dart';
@@ -9,6 +11,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http;
+import 'package:path_provider/path_provider.dart';
import '../../../core/utils/logger.dart';
import '../../../core/utils/synchrodata.dart';
@@ -58,6 +61,9 @@ class MeetingDetailsController extends GetxController
String markmapToolbarJs = '';
String saveSvgAsPngJs = '';
+ RxBool isDownload = false.obs;
+ RxDouble downloadProgress = 0.0.obs;
+
String _systemPrompt = '';
String _userId = '';
@@ -205,6 +211,30 @@ class MeetingDetailsController extends GetxController
await rootBundle.loadString('assets/js/saveSvgAsPng.min.js');
}
+ void downloadAudio() async {
+ if (isDownload.value) return;
+ isDownload.value = true;
+ final directory = await getApplicationDocumentsDirectory();
+ final directoryDir = Directory('${directory.path}/DownloadAudio');
+ if (!await directoryDir.exists()) {
+ await directoryDir.create(recursive: true);
+ }
+ final fileName = meetingData.value.audiourl.split('/').last;
+ final String filePath = '${directory.path}/DownloadAudio/$fileName';
+ await Dio().download(
+ meetingData.value.audiourl,
+ filePath,
+ onReceiveProgress: (int received, int total) {
+ downloadProgress.value = total > 0 ? received / total : 0.0;
+ },
+ );
+ meetingData.value.filepath = filePath;
+ meetingData.refresh();
+ _loadPlayerController();
+ SqfliteApi.editMeetingTitle(id, {'filePath': filePath});
+ isDownload.value = false;
+ }
+
void editSpeakerText() async {
if (editSpeakerTextId.value != 0 && editSpeakerTextChange.isNotEmpty) {
Map? foundElement = editSpeakerTextList.firstWhere(
diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart
index 1dfe1df3e..8b58e7378 100644
--- a/lib/modules/meeting/controllers/meeting_record_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_record_controller.dart
@@ -138,6 +138,7 @@ class MeetingRecordController extends GetxController
_bleManager.closeCodec();
}
_asrService.stopRecord(true);
+ _asrService.setPause();
_asrService.stopContinuousRecognition();
isRecording.value = false;
}
@@ -214,7 +215,7 @@ class MeetingRecordController extends GetxController
_recognitionSubscription?.cancel();
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
-
+ _asrService.setContinue();
// Prepare storage directory
final appDir = await appDocDir;
final dir = Directory("${appDir.path}/MeetingAudio/");
diff --git a/lib/modules/meeting/controllers/mind_map_controller.dart b/lib/modules/meeting/controllers/mind_map_controller.dart
index 8fe7b67e6..d30244f29 100644
--- a/lib/modules/meeting/controllers/mind_map_controller.dart
+++ b/lib/modules/meeting/controllers/mind_map_controller.dart
@@ -27,7 +27,7 @@ class MindMapController extends GetxController {
-
+
MindMap
diff --git a/lib/modules/meeting/views/meeting_details_view.dart b/lib/modules/meeting/views/meeting_details_view.dart
index 1217d09d2..79640b2a6 100644
--- a/lib/modules/meeting/views/meeting_details_view.dart
+++ b/lib/modules/meeting/views/meeting_details_view.dart
@@ -81,20 +81,50 @@ class MeetingDetailsView extends GetView {
children: [
GestureDetector(
onTap: () {
- if (controller.isPlay.value) {
- controller.playerController.pausePlayer();
+ if (controller.meetingData.value.filepath.isEmpty) {
+ controller.downloadAudio();
} else {
- controller.playerController.startPlayer();
+ if (controller.isPlay.value) {
+ controller.playerController.pausePlayer();
+ } else {
+ controller.playerController.startPlayer();
+ }
}
},
child: Obx(
- () => Icon(
- controller.isPlay.value
- ? Icons.pause_circle
- : Icons.play_circle,
- color: Colors.black,
- size: 30.w,
- ),
+ () => !controller.isLoading.value &&
+ controller.meetingData.value.filepath.isEmpty
+ ? Obx(
+ () => Stack(
+ alignment: Alignment.center,
+ children: [
+ if (controller.isDownload.value)
+ SizedBox(
+ width: 26.w,
+ height: 26.w,
+ child: CircularProgressIndicator(
+ value: controller.downloadProgress.value,
+ valueColor:
+ const AlwaysStoppedAnimation(
+ Colors.red),
+ strokeWidth: 2.w,
+ ),
+ ),
+ Icon(
+ Icons.download_for_offline,
+ color: Colors.black,
+ size: 30.w,
+ ),
+ ],
+ ),
+ )
+ : Icon(
+ controller.isPlay.value
+ ? Icons.pause_circle
+ : Icons.play_circle,
+ color: Colors.black,
+ size: 30.w,
+ ),
),
),
4.horizontalSpace,
@@ -446,20 +476,52 @@ class MeetingDetailsView extends GetView {
),
GestureDetector(
onTap: () {
- if (controller.isPlay.value) {
- controller.playerController.pausePlayer();
+ if (controller.meetingData.value.filepath.isEmpty) {
+ controller.downloadAudio();
} else {
- controller.playerController.startPlayer();
+ if (controller.isPlay.value) {
+ controller.playerController.pausePlayer();
+ } else {
+ controller.playerController.startPlayer();
+ }
}
},
child: Obx(
- () => Icon(
- controller.isPlay.value
- ? Icons.pause_circle
- : Icons.play_circle,
- color: Colors.black,
- size: 48.w,
- ),
+ () => !controller.isLoading.value &&
+ controller
+ .meetingData.value.filepath.isEmpty
+ ? Obx(
+ () => Stack(
+ alignment: Alignment.center,
+ children: [
+ if (controller.isDownload.value)
+ SizedBox(
+ width: 42.w,
+ height: 42.w,
+ child: CircularProgressIndicator(
+ value: controller
+ .downloadProgress.value,
+ valueColor:
+ const AlwaysStoppedAnimation<
+ Color>(Colors.red),
+ strokeWidth: 3.w,
+ ),
+ ),
+ Icon(
+ Icons.download_for_offline,
+ color: Colors.black,
+ size: 48.w,
+ ),
+ ],
+ ),
+ )
+ : Icon(
+ controller.isPlay.value
+ ? Icons.pause_circle
+ : Icons.play_circle,
+ color: Colors.black,
+ size: 48.w,
+ ),
),
),
GestureDetector(
diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart
index dd1fc980b..b40408dbc 100644
--- a/lib/modules/translation/controllers/translation_controller.dart
+++ b/lib/modules/translation/controllers/translation_controller.dart
@@ -106,13 +106,16 @@ class TranslationController extends GetxController {
_asrService.setContinue();
lastActiveSpeaker.value = id;
} else {
- _asrService.setPause();
if (fTFTranslationResult != '' && !isPlayback) {
isPlayback = true;
await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value);
await Future.delayed(const Duration(milliseconds: 1300));
await playTranslatedText(fTFTranslationResult!);
fTFTranslationResult = '';
+ } else if ((translationHistory.isEmpty ||
+ !translationHistory.last.isIntermediate) &&
+ currentMode.value == 'faceToFace') {
+ _asrService.setPause();
}
}
}
@@ -165,7 +168,7 @@ class TranslationController extends GetxController {
}
// 加载历史记录
- //loadTranslationHistory();
+ loadTranslationHistory();
// 初始化服务
_initServices();
@@ -462,6 +465,7 @@ class TranslationController extends GetxController {
if (translationHistory.isEmpty || !translationHistory.last.isIntermediate) {
currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString();
+
final newItem = TranslationItem(
sourceText: text,
translatedText: '',
@@ -474,10 +478,15 @@ class TranslationController extends GetxController {
isIntermediate: true,
);
// 添加新项后滚动到底部
- translationHistory.add(newItem);
+ if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
+ _asrService.setPause();
+ } else {
+ translationHistory.add(newItem);
+ }
_scrollToBottom();
} else {
// 更新当前项
+
translationHistory.last.sourceText = text;
translationHistory.refresh();
// 更新后滚动到底部
@@ -509,7 +518,9 @@ class TranslationController extends GetxController {
// 更新后滚动到底部
_scrollToBottom();
saveTranslationHistory();
-
+ if (activeSpeaker.value == 0 && currentMode.value == 'faceToFace') {
+ _asrService.setPause();
+ }
// 使用项目的时间戳进行翻译
translateText(text, translationHistory.last.timestamp, isFinal: true);
} else {
@@ -526,14 +537,16 @@ class TranslationController extends GetxController {
translationHistory.last.sessionId != currentSessionId,
isIntermediate: false,
);
-
+ if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
+ _asrService.setPause();
+ } //else {
translationHistory.add(newItem);
translationHistory.refresh();
_scrollToBottom();
saveTranslationHistory();
-
// 使用新项目的时间戳进行翻译
translateText(text, newItem.timestamp, isFinal: true);
+ // }
}
}
@@ -546,13 +559,33 @@ class TranslationController extends GetxController {
String? translationResult;
try {
- // 根据检测到的语言确定源语言和目标语言
- final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value;
- final detectedSourceLanguageCode =
- shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
- final detectedTargetLanguageCode =
- shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value;
-
+ var detectedSourceLanguageCode;
+ var detectedTargetLanguageCode;
+ if ((currentMode.value == 'faceToFace' && activeSpeaker.value == 1) ||
+ (currentMode.value == 'faceToFace' &&
+ activeSpeaker.value == 0 &&
+ lastActiveSpeaker.value == 1)) {
+ detectedLanguageCode = sourceLanguageCode.value;
+ detectedSourceLanguageCode = sourceLanguageCode.value;
+ detectedTargetLanguageCode = targetLanguageCode.value;
+ } else if ((currentMode.value == 'faceToFace' &&
+ activeSpeaker.value == 2) ||
+ (currentMode.value == 'faceToFace' &&
+ activeSpeaker.value == 0 &&
+ lastActiveSpeaker.value == 2)) {
+ // 根据检测到的语言确定源语言和目标语言
+ detectedLanguageCode = targetLanguageCode.value;
+ detectedSourceLanguageCode = targetLanguageCode.value;
+ detectedTargetLanguageCode = sourceLanguageCode.value;
+ } else {
+ // 根据检测到的语言确定源语言和目标语言
+ final bool shouldSwap =
+ detectedLanguageCode == sourceLanguageCode.value;
+ detectedSourceLanguageCode =
+ shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
+ detectedTargetLanguageCode =
+ shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value;
+ }
// 调用翻译服务
translationResult = await _translationService.translateText(
text: sourceText,
@@ -560,6 +593,7 @@ class TranslationController extends GetxController {
targetLanguageCode: detectedTargetLanguageCode,
);
+ /// }
if (translationResult != null) {
// 查找匹配的项目 - 优先使用timestamp查找
int index = -1;
@@ -577,26 +611,22 @@ class TranslationController extends GetxController {
saveTranslationHistory();
// 更新后滚动到底部
_scrollToBottom();
- if (isFinal && currentMode.value == 'faceToFace') {
- print("音频输出文本2$translationResult");
- fTFTranslationResult =
- (fTFTranslationResult ?? '') + translationResult;
-
- if (activeSpeaker.value == 0 &&
- !isPlayback &&
- currentMode.value == 'faceToFace') {
- isPlayback = true;
- await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value);
- await Future.delayed(const Duration(milliseconds: 1300));
- await playTranslatedText(fTFTranslationResult!);
- fTFTranslationResult = '';
- }
- }
// 播放TTS - 只在最终结果时播放
- if (isTtsEnabled.value &&
- isFinal &&
- currentMode.value != 'faceToFace') {
- await playTranslatedText(translationResult);
+ if (isFinal) {
+ if (isTtsEnabled.value && currentMode.value != 'faceToFace') {
+ await playTranslatedText(translationResult);
+ } else {
+ fTFTranslationResult =
+ (fTFTranslationResult ?? '') + translationResult;
+ print("translationResult=$translationResult");
+ if (activeSpeaker.value == 0 && !isPlayback) {
+ isPlayback = true;
+ await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value);
+ await Future.delayed(const Duration(milliseconds: 1300));
+ await playTranslatedText(fTFTranslationResult!);
+ fTFTranslationResult = '';
+ }
+ }
}
} else {
Logger.info(
@@ -623,7 +653,7 @@ class TranslationController extends GetxController {
final voiceName = _languageManager
.getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ??
'en-US-AriaNeural';
-
+ print("voiceName=$voiceName,text$text");
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text);
} catch (e) {
diff --git a/lib/modules/translation/views/translation_list_view.dart b/lib/modules/translation/views/translation_list_view.dart
index 994882077..dd13b7521 100644
--- a/lib/modules/translation/views/translation_list_view.dart
+++ b/lib/modules/translation/views/translation_list_view.dart
@@ -340,7 +340,11 @@ class TranslationListView extends GetView {
FileSystemEntity file,
) {
final TextEditingController _renameController = TextEditingController(
- text: file.path.split('/').last,
+ text: () {
+ final fileName = file.path.split('/').last;
+ final dotIndex = fileName.lastIndexOf('.');
+ return dotIndex != -1 ? fileName.substring(0, dotIndex) : fileName;
+ }(),
);
Get.bottomSheet(
@@ -432,7 +436,7 @@ class TranslationListView extends GetView {
if (newName.isEmpty) return;
final dir = file.parent.path;
- final newPath = '$dir/$newName';
+ final newPath = '$dir/$newName.wav';
try {
await file.rename(newPath);
diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart
index e2bf87d54..e529bd98d 100644
--- a/lib/modules/translation/views/translation_view.dart
+++ b/lib/modules/translation/views/translation_view.dart
@@ -1,3 +1,5 @@
+import 'dart:async';
+
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -528,27 +530,29 @@ class TranslationView extends GetView {
}) {
bool isActive = activeSpeaker == speakerId;
bool isDisabled = activeSpeaker != 0 && activeSpeaker != speakerId;
-
+ Timer? _holdTimer; // 增加延迟触发的计时器
return Listener(
onPointerDown: (_) {
- if (activeSpeaker == 0) {
- // 没有激活的按钮时才能激活
- if (speakerId == 1) {
- // 耳机收音 - 恢复原始音频状态
- controller.restoreOriginalAudioState();
- } else {
- // 手机收音 - 禁用蓝牙音频
- controller.disableBluetoothAudio();
+ // 设置300ms延迟防止误触
+ _holdTimer = Timer(const Duration(milliseconds: 200), () {
+ if (activeSpeaker == 0) {
+ if (speakerId == 1) {
+ controller.restoreOriginalAudioState();
+ } else {
+ controller.disableBluetoothAudio();
+ }
+ controller.setActiveSpeaker(speakerId);
}
- controller.setActiveSpeaker(speakerId);
- }
+ });
},
onPointerUp: (_) {
+ _holdTimer?.cancel();
if (isActive) {
controller.setActiveSpeaker(0);
}
},
onPointerCancel: (_) {
+ _holdTimer?.cancel();
if (isActive) {
controller.setActiveSpeaker(0);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 0298f6e64..6fb187beb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -36,7 +36,7 @@ dependencies:
webview_flutter: ^4.13.0
flutter_easyloading: ^3.0.5
package_info_plus: ^8.3.0
- file_picker: ^10.1.5
+ file_picker: ^10.2.0
haptic_feedback: ^0.5.1+1
tencentcloud_cos_sdk_plugin: ^1.2.3
photo_view: ^0.15.0