Browse Source

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

newdev_shunjiawei
lxm 1 year ago
parent
commit
3321487fcd
  1. 8
      ios/Runner/GoogleService-Info.plist
  2. 37
      lib/core/utils/synchrodata.dart
  3. 1
      lib/modules/meeting/controllers/meeting_controller.dart
  4. 30
      lib/modules/meeting/controllers/meeting_details_controller.dart
  5. 3
      lib/modules/meeting/controllers/meeting_record_controller.dart
  6. 2
      lib/modules/meeting/controllers/mind_map_controller.dart
  7. 102
      lib/modules/meeting/views/meeting_details_view.dart
  8. 96
      lib/modules/translation/controllers/translation_controller.dart
  9. 8
      lib/modules/translation/views/translation_list_view.dart
  10. 26
      lib/modules/translation/views/translation_view.dart
  11. 2
      pubspec.yaml

8
ios/Runner/GoogleService-Info.plist

@ -3,9 +3,9 @@
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278.apps.googleusercontent.com</string>
<string>907754492821-tl2blr39g82re40sttkve36ovt6qcu6c.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278</string>
<string>com.googleusercontent.apps.907754492821-tl2blr39g82re40sttkve36ovt6qcu6c</string>
<key>ANDROID_CLIENT_ID</key>
<string>907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com</string>
<key>API_KEY</key>
@ -15,7 +15,7 @@
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.yunqiinnovation.deepsound</string>
<string>com.chuangxiangyunqi.deapsound</string>
<key>PROJECT_ID</key>
<string>deepsound-e0e3d</string>
<key>STORAGE_BUCKET</key>
@ -31,6 +31,6 @@
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1:907754492821:ios:246794fc44b23174145c9c</string>
<string>1:907754492821:ios:e4b4f3b37e58a7d1145c9c</string>
</dict>
</plist>

37
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();
}

1
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;

30
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(

3
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/");

2
lib/modules/meeting/controllers/mind_map_controller.dart

@ -27,7 +27,7 @@ class MindMapController extends GetxController {
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>MindMap</title>
<script>${_meetingDetailsController.markmapLibJs}</script>
<script>${_meetingDetailsController.d3Js}</script>

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

@ -81,20 +81,50 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
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<Color>(
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<MeetingDetailsController> {
),
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(

96
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) {

8
lib/modules/translation/views/translation_list_view.dart

@ -340,7 +340,11 @@ class TranslationListView extends GetView<TranslationListController> {
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<TranslationListController> {
if (newName.isEmpty) return;
final dir = file.parent.path;
final newPath = '$dir/$newName';
final newPath = '$dir/$newName.wav';
try {
await file.rename(newPath);

26
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<TranslationController> {
}) {
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);
}

2
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

Loading…
Cancel
Save