Browse Source

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

newdev_shunjiawei
wolfplus2048 1 year ago
parent
commit
b2b70dd935
  1. 33
      lib/core/utils/synchrodata.dart
  2. 28
      lib/data/models/user_Info.dart
  3. 9
      lib/data/services/network/api.dart
  4. 16
      lib/data/services/speech_impl/azure_tts_service.dart
  5. 8
      lib/data/services/speech_impl/voice_clone_tts_service.dart
  6. 41
      lib/data/services/tts_service.dart
  7. 62
      lib/modules/explore/views/explore_view.dart
  8. 101
      lib/modules/meeting/views/generate_bottom_sheet.dart
  9. 14
      lib/modules/meeting/views/meeting_details_view.dart
  10. 1075
      lib/modules/meeting/views/meeting_view.dart
  11. 92
      lib/modules/meeting/views/more_bottom_sheet.dart
  12. 129
      lib/modules/pairing/controllers/pairing_controller.dart
  13. 51
      lib/modules/pairing/views/pairing_view.dart
  14. 73
      lib/modules/pairing/views/pairing_view_new.dart
  15. 3
      lib/modules/settings/views/settings_view.dart
  16. 85
      lib/modules/translation/controllers/translation_controller.dart
  17. 20
      lib/modules/translation/views/translation_list_view.dart
  18. 8
      lib/modules/translation/views/translation_view.dart
  19. 13
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  20. 18
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  21. 7
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

33
lib/core/utils/synchrodata.dart

@ -8,7 +8,11 @@ import '../../data/services/network/api.dart';
class Synchrodata { class Synchrodata {
static final GetStorage _storage = GetStorage(); static final GetStorage _storage = GetStorage();
static List _records = _storage.read("operation_records") ?? []; static List<Map<String, dynamic>> _records =
(_storage.read("operation_records") as List?)
?.map((e) => Map<String, dynamic>.from(e as Map))
.toList() ??
[];
static List _delids = _storage.read("operation_delids") ?? []; static List _delids = _storage.read("operation_delids") ?? [];
static bool _isSynchro = false; static bool _isSynchro = false;
@ -30,7 +34,7 @@ class Synchrodata {
switch (method) { switch (method) {
case 'insert': case 'insert':
_records.add({ _records.add({
...params, ...Map<String, dynamic>.from(params),
'id': id, 'id': id,
'number': params['id'], 'number': params['id'],
'uid': userId, 'uid': userId,
@ -38,19 +42,19 @@ class Synchrodata {
_storage.write("operation_records", _records); _storage.write("operation_records", _records);
break; break;
case 'update': case 'update':
Map? foundElement = _records.firstWhere( Map<String, dynamic>? foundElement = _records.firstWhere(
(element) => element['id'] == id, (element) => element['id'] == id,
orElse: () => null, orElse: () => {},
); );
if (foundElement != null) { if (foundElement.isNotEmpty) {
foundElement.addAll({ foundElement.addAll({
...params, ...Map<String, dynamic>.from(params),
'id': id, 'id': id,
'number': params['id'], 'number': params['id'],
}); });
} else { } else {
_records.add({ _records.add({
...params, ...Map<String, dynamic>.from(params),
'id': id, 'id': id,
'number': params['id'], 'number': params['id'],
'uid': userId, 'uid': userId,
@ -75,15 +79,18 @@ class Synchrodata {
// 获取需要修改的全部数据 // 获取需要修改的全部数据
List putRecords = []; List putRecords = [];
for (var element in _records) { for (var element in _records) {
final result = await SqfliteApi.getMeetingDetails(element['number']); Map result = {'details': {}};
final speakerList = List speakerList = [];
await SqfliteApi.getMeetingSpeaker(element['number']); if (!_delids.contains(element['id'])) {
result = await SqfliteApi.getMeetingDetails(element['number']);
speakerList = await SqfliteApi.getMeetingSpeaker(element['number']);
}
putRecords.add({ putRecords.add({
...result, ...Map<String, dynamic>.from(result),
...result['details'], ...Map<String, dynamic>.from(result['details']),
'rtype': result['type'], 'rtype': result['type'],
'original': jsonEncode(speakerList), 'original': jsonEncode(speakerList),
...element, ...Map<String, dynamic>.from(element),
}); });
} }
var result = await Api.putOperationRecord({ var result = await Api.putOperationRecord({

28
lib/data/models/user_Info.dart

@ -14,14 +14,15 @@ class User {
String name; String name;
String uid; String uid;
String avatar; String avatar;
String deviceid;
String language; String language;
String token; String token;
String mail; String mail;
String phone; String phone;
String phonemac; String phonemac;
String password; String password;
int membershipLevel = 0; //添加会员等级字段,0表示普通用户 int viplv = 0; //添加会员等级字段,0表示普通用户
String earphonemac;
String earphoneauthcode;
// final Int64 lastbettime; // final Int64 lastbettime;
// final Int64 createtime; // final Int64 createtime;
// final String wxopenid; // final String wxopenid;
@ -33,14 +34,15 @@ class User {
required this.name, required this.name,
required this.uid, required this.uid,
required this.avatar, required this.avatar,
required this.deviceid,
required this.language, required this.language,
required this.token, required this.token,
required this.mail, required this.mail,
required this.phone, required this.phone,
required this.phonemac, required this.phonemac,
required this.password, required this.password,
this.membershipLevel = 0, // 添加默认值 required this.viplv, // 添加默认值
required this.earphonemac,
required this.earphoneauthcode,
// required this.lastbettime, // required this.lastbettime,
// required this.createtime, // required this.createtime,
// required this.wxopenid, // required this.wxopenid,
@ -60,14 +62,16 @@ class User {
name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值 name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值
uid: (userData?['uid'] as String?) ?? '', uid: (userData?['uid'] as String?) ?? '',
avatar: (userData?['avatar'] as String?) ?? '', avatar: (userData?['avatar'] as String?) ?? '',
deviceid: (userData?['deviceid'] as String?) ?? '',
language: (userData?['language'] as String?) ?? 'en', language: (userData?['language'] as String?) ?? 'en',
mail: (userData?['mail'] as String?) ?? '', mail: (userData?['mail'] as String?) ?? '',
phone: (userData?['phone'] as String?) ?? '', phone: (userData?['phone'] as String?) ?? '',
phonemac: (userData?['phonemac'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '',
password: (userData?['password'] as String?) ?? '', password: (userData?['password'] as String?) ?? '',
membershipLevel: (userData?['membershipLevel'] as int?) ?? 0, // 提供默认值 viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
earphonemac: (userData?['earphonemac'] as String?) ?? '',
earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '',
// lastbettime: _parseTimestamp(userData['lastbettime']), // lastbettime: _parseTimestamp(userData['lastbettime']),
// createtime: _parseTimestamp(userData['createtime']), // createtime: _parseTimestamp(userData['createtime']),
// wxopenid: (userData?['wxopenid'] as String?) ?? '', // wxopenid: (userData?['wxopenid'] as String?) ?? '',
@ -85,13 +89,16 @@ class User {
name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值 name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值
uid: (userData?['uid'] as String?) ?? '', uid: (userData?['uid'] as String?) ?? '',
avatar: (userData?['avatar'] as String?) ?? '', avatar: (userData?['avatar'] as String?) ?? '',
deviceid: (userData?['deviceid'] as String?) ?? '',
language: (userData?['language'] as String?) ?? 'en', language: (userData?['language'] as String?) ?? 'en',
mail: (userData?['mail'] as String?) ?? '', mail: (userData?['mail'] as String?) ?? '',
phone: (userData?['phone'] as String?) ?? '', phone: (userData?['phone'] as String?) ?? '',
phonemac: (userData?['phonemac'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '',
password: (userData?['password'] as String?) ?? '', password: (userData?['password'] as String?) ?? '',
viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值
earphonemac: (userData?['earphonemac'] as String?) ?? '',
earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '',
// lastbettime: _parseTimestamp(userData['lastbettime']), // lastbettime: _parseTimestamp(userData['lastbettime']),
// createtime: _parseTimestamp(userData['createtime']), // createtime: _parseTimestamp(userData['createtime']),
// wxopenid: (userData?['wxopenid'] as String?) ?? '', // wxopenid: (userData?['wxopenid'] as String?) ?? '',
@ -106,14 +113,17 @@ class User {
'name': name, 'name': name,
'uid': uid, 'uid': uid,
'avatar': avatar, 'avatar': avatar,
'deviceid': deviceid,
'language': language, 'language': language,
'token': token, 'token': token,
'mail': mail, 'mail': mail,
'phone': phone, 'phone': phone,
'phonemac': phonemac, 'phonemac': phonemac,
'password': password, 'password': password,
'membershipLevel': membershipLevel, 'viplv': viplv,
'earphonemac': earphonemac,
'earphoneauthcode': earphoneauthcode,
// 'lastbettime': lastbettime, // 'lastbettime': lastbettime,
// 'createtime': createtime, // 'createtime': createtime,
// 'wxopenid': wxopenid, // 'wxopenid': wxopenid,

9
lib/data/services/network/api.dart

@ -126,4 +126,13 @@ class Api {
params: params, params: params,
); );
} }
//绑定设备
static bindearphone([params]) {
return DioManager().request(
NWMethod.post,
'/api/home/user_bindearphone',
params: params,
);
}
} }

16
lib/data/services/speech_impl/azure_tts_service.dart

@ -413,4 +413,18 @@ class AzureTtsService extends GetxService implements TtsService {
Logger.error('释放资源失败: ${e.toString()}'); Logger.error('释放资源失败: ${e.toString()}');
} }
} }
}
@override
Future<bool> setAudioOutputDevice(int type) async {
try {
// 开始合成,传递参数
final result = await _channel.invokeMethod('setAudioOutputDevice', {
'type': type,
});
return result;
} catch (e) {
Logger.error('音频输出设备类型失败: ${e.toString()}');
return false;
}
}
}

8
lib/data/services/speech_impl/voice_clone_tts_service.dart

@ -390,4 +390,10 @@ class VoiceCloneTtsService extends GetxService implements TtsService {
Logger.error('释放资源失败: ${e.toString()}'); Logger.error('释放资源失败: ${e.toString()}');
} }
} }
}
@override
Future<bool> setAudioOutputDevice(int type) {
// TODO: implement setAudioOutputDevice
throw UnimplementedError();
}
}

41
lib/data/services/tts_service.dart

@ -2,52 +2,53 @@ import 'dart:async';
/// 语音合成服务接口 /// 语音合成服务接口
abstract class TtsService { abstract class TtsService {
/// 当前选择的语音 /// 当前选择的语音
String get currentVoice; String get currentVoice;
/// 当前是否正在播放 /// 当前是否正在播放
bool get isSpeaking; bool get isSpeaking;
/// TTS事件流,用于监听语音合成事件 /// TTS事件流,用于监听语音合成事件
Stream<TtsEvent> get onEvent; Stream<TtsEvent> get onEvent;
/// 初始化语音合成服务 /// 初始化语音合成服务
Future<bool> initialize({ Future<bool> initialize({
List<String>? supportedLanguages, List<String>? supportedLanguages,
}); });
/// 设置语音 /// 设置语音
Future<bool> setVoice(String voiceName); Future<bool> setVoice(String voiceName);
/// 单次播放文本,完成后返回 /// 单次播放文本,完成后返回
Future<bool> speakOnce(String text); Future<bool> speakOnce(String text);
/// 将文本加入播放队列,支持连续播放多段文本 /// 将文本加入播放队列,支持连续播放多段文本
Future<bool> speakStream(String text); Future<bool> speakStream(String text);
/// 播放队列中剩余文本 /// 播放队列中剩余文本
Future<bool> flushStream(); Future<bool> flushStream();
/// 停止播放 /// 停止播放
Future<bool> stop(); Future<bool> stop();
/// 释放资源 /// 释放资源
Future<void> dispose(); Future<void> dispose();
/// 切换音频输出设备
Future<bool> setAudioOutputDevice(int type);
} }
/// TTS事件类型 /// TTS事件类型
enum TtsEventType { enum TtsEventType {
/// 开始播放 /// 开始播放
started, started,
/// 播放完成 /// 播放完成
completed, completed,
/// 播放取消 /// 播放取消
canceled, canceled,
/// 播放错误 /// 播放错误
error, error,
} }
@ -56,15 +57,15 @@ enum TtsEventType {
class TtsEvent { class TtsEvent {
/// 事件类型 /// 事件类型
final TtsEventType type; final TtsEventType type;
/// 错误信息(仅在 error 类型中有效) /// 错误信息(仅在 error 类型中有效)
final String error; final String error;
TtsEvent({ TtsEvent({
required this.type, required this.type,
this.error = '', this.error = '',
}); });
/// 创建错误事件的快捷构造函数 /// 创建错误事件的快捷构造函数
factory TtsEvent.error(String errorMessage) { factory TtsEvent.error(String errorMessage) {
return TtsEvent( return TtsEvent(
@ -72,12 +73,12 @@ class TtsEvent {
error: errorMessage, error: errorMessage,
); );
} }
/// 检查是否为错误 /// 检查是否为错误
bool get isError => type == TtsEventType.error; bool get isError => type == TtsEventType.error;
@override @override
String toString() { String toString() {
return 'TtsEvent{type: $type, error: $error}'; return 'TtsEvent{type: $type, error: $error}';
} }
} }

62
lib/modules/explore/views/explore_view.dart

@ -126,37 +126,37 @@ class ExploreView extends GetView<ExploreController> {
], ],
), ),
// 搜索框 // // 搜索框
SizedBox(height: 16.h), // SizedBox(height: 16.h),
Container( // Container(
decoration: BoxDecoration( // decoration: BoxDecoration(
color: // color:
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100], // isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r), // borderRadius: BorderRadius.circular(12.r),
), // ),
child: TextField( // child: TextField(
controller: controller.searchController, // controller: controller.searchController,
decoration: InputDecoration( // decoration: InputDecoration(
hintText: 'searchAi'.tr, // 搜索AI功能... // hintText: 'searchAi'.tr, // 搜索AI功能...
hintStyle: TextStyle( // hintStyle: TextStyle(
fontSize: 14.sp, // fontSize: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[400], // color: isDarkMode ? Colors.grey[400] : Colors.grey[400],
), // ),
prefixIcon: Icon( // prefixIcon: Icon(
Icons.search, // Icons.search,
size: 20.sp, // size: 20.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[400], // color: isDarkMode ? Colors.grey[400] : Colors.grey[400],
), // ),
border: InputBorder.none, // border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 12.h), // contentPadding: EdgeInsets.symmetric(vertical: 12.h),
), // ),
textInputAction: TextInputAction.search, // textInputAction: TextInputAction.search,
onSubmitted: (_) => controller.search(), // onSubmitted: (_) => controller.search(),
style: TextStyle( // style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black, // color: isDarkMode ? Colors.white : Colors.black,
), // ),
), // ),
), // ),
], ],
), ),
); );

101
lib/modules/meeting/views/generate_bottom_sheet.dart

@ -9,7 +9,9 @@ import 'record_language_bottom_sheet.dart';
import 'template_view.dart'; import 'template_view.dart';
class GenerateBottomSheet extends StatefulWidget { class GenerateBottomSheet extends StatefulWidget {
const GenerateBottomSheet({super.key}); final bool isSummarizeAgain;
const GenerateBottomSheet({super.key, this.isSummarizeAgain = false});
@override @override
State<GenerateBottomSheet> createState() => _GenerateBottomSheetState(); State<GenerateBottomSheet> createState() => _GenerateBottomSheetState();
@ -214,56 +216,57 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
}, },
), ),
), ),
Container( if (!widget.isSummarizeAgain)
margin: EdgeInsets.symmetric(horizontal: 12.w), Container(
decoration: BoxDecoration( margin: EdgeInsets.symmetric(horizontal: 12.w),
color: Colors.white, decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r), color: Colors.white,
), borderRadius: BorderRadius.circular(8.r),
child: Column( ),
children: [ child: Column(
_row( children: [
'区分说话人', _row(
widget: Transform.scale( '区分说话人',
scale: 0.8, widget: Transform.scale(
alignment: Alignment.centerRight, scale: 0.8,
child: Switch( alignment: Alignment.centerRight,
value: _isSpeaker, child: Switch(
onChanged: (bool value) { value: _isSpeaker,
setState(() { onChanged: (bool value) {
_isSpeaker = value; setState(() {
}); _isSpeaker = value;
}, });
},
),
), ),
), ),
), _row(
_row( '录音语言',
'录音语言', value: _languageText,
value: _languageText, isBorder: false,
isBorder: false, onTap: () {
onTap: () { Get.bottomSheet(
Get.bottomSheet( RecordLanguageBottomSheet(_language),
RecordLanguageBottomSheet(_language), isScrollControlled: true,
isScrollControlled: true, backgroundColor: Colors.grey[100],
backgroundColor: Colors.grey[100], shape: RoundedRectangleBorder(
shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(
borderRadius: BorderRadius.vertical( top: Radius.circular(12.r),
top: Radius.circular(12.r), ),
), ),
), ).then((value) {
).then((value) { if (value != null) {
if (value != null) { setState(() {
setState(() { _languageText = value['name'];
_languageText = value['name']; _language = value['value'];
_language = value['value']; });
}); }
} });
}); },
}, ),
), ],
], ),
), ),
),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w), padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w),
child: GestureDetector( child: GestureDetector(
@ -279,6 +282,10 @@ class _GenerateBottomSheetState extends State<GenerateBottomSheet> {
_storage.write("meeting_last_template_Data", _templateData); _storage.write("meeting_last_template_Data", _templateData);
_storage.write("meeting_template_list", _templateList); _storage.write("meeting_template_list", _templateList);
Get.back(); Get.back();
if (widget.isSummarizeAgain) {
_controller.meetingDetails.value.tasktype = 2;
_controller.meetingDetails.refresh();
}
_controller.summaryCreate(); _controller.summaryCreate();
}, },
child: Container( child: Container(

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

@ -10,6 +10,7 @@ import 'package:lottie/lottie.dart';
import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors.dart';
import '../controllers/meeting_details_controller.dart'; import '../controllers/meeting_details_controller.dart';
import 'mind_map_tab.dart'; import 'mind_map_tab.dart';
import 'more_bottom_sheet.dart';
import 'overview_tab.dart'; import 'overview_tab.dart';
import 'share_bottom_sheet.dart'; import 'share_bottom_sheet.dart';
import 'speech_tab.dart'; import 'speech_tab.dart';
@ -189,7 +190,18 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
size: 16.sp, size: 16.sp,
), ),
), ),
onPressed: () {}, onPressed: () {
Get.bottomSheet(
const MoreBottomSheet(),
isScrollControlled: true,
backgroundColor: Colors.grey[100],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
);
},
), ),
], ],
); );

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

File diff suppressed because it is too large

92
lib/modules/meeting/views/more_bottom_sheet.dart

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '../controllers/meeting_controller.dart';
import '../controllers/meeting_details_controller.dart';
import 'generate_bottom_sheet.dart';
class MoreBottomSheet extends StatefulWidget {
const MoreBottomSheet({super.key});
@override
State<MoreBottomSheet> createState() => _MoreBottomSheetState();
}
class _MoreBottomSheetState extends State<MoreBottomSheet> {
final _controller = Get.find<MeetingController>();
final _detailsController = Get.find<MeetingDetailsController>();
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 10.w),
if (_detailsController.meetingDetails.value.tasktype == 3)
_shareItem(Icons.refresh, '重新总结', onTap: () {
Get.back();
Get.bottomSheet(
const GenerateBottomSheet(
isSummarizeAgain: true,
),
isScrollControlled: true,
backgroundColor: Colors.grey[100],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
);
}),
_shareItem(Icons.delete_outline, '删除', onTap: () async {
_controller.deleteMeeting(
_detailsController.meetingData.value.id,
_detailsController.meetingData.value.filepath,
);
Get.back();
Get.back();
}),
],
),
);
}
Widget _shareItem(
IconData icon,
String title, {
VoidCallback? onTap,
}) {
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 15.w),
margin: EdgeInsets.only(bottom: 10.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
children: [
Icon(
icon,
color: Colors.black87,
),
10.horizontalSpace,
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 14.sp,
color: Colors.black87,
),
),
),
],
),
),
);
}
}

129
lib/modules/pairing/controllers/pairing_controller.dart

@ -1,4 +1,7 @@
import 'package:app_settings/app_settings.dart'; import 'package:app_settings/app_settings.dart';
import 'package:deep_voice/data/models/user_Info.dart';
import 'package:deep_voice/data/services/network/api.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'dart:async'; import 'dart:async';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
@ -15,9 +18,10 @@ enum PairingState {
checking, // 检查中(蓝牙、权限等) checking, // 检查中(蓝牙、权限等)
waitingClassic, // 等待经典蓝牙连接 waitingClassic, // 等待经典蓝牙连接
verifying, // 验证设备 verifying, // 验证设备
manualbinding, //手动绑定设备
associating, // 正在进行BLE设备关联 associating, // 正在进行BLE设备关联
waitingBle, // 等待BLE连接 waitingBle, //等待BLE连接
completed, // 配对完成 completed, //配对完成
error, // 错误状态 error, // 错误状态
} }
@ -31,6 +35,7 @@ enum ErrorType {
deviceBound, // 设备已被绑定 deviceBound, // 设备已被绑定
associationFailed, // 关联失败 associationFailed, // 关联失败
bleTimeout, // BLE连接超时 bleTimeout, // BLE连接超时
authnFailed, // 授权失败
networkError, // 网络错误 networkError, // 网络错误
} }
@ -53,6 +58,7 @@ class PairingController extends GetxController {
final isLoading = false.obs; final isLoading = false.obs;
final showConnectButton = false.obs; final showConnectButton = false.obs;
final showRetryButton = false.obs; final showRetryButton = false.obs;
final showAutoCodeInput = false.obs;
final showSkipButton = false.obs; final showSkipButton = false.obs;
final connectButtonText = '连接设备'.obs; final connectButtonText = '连接设备'.obs;
final retryButtonText = '重试'.obs; final retryButtonText = '重试'.obs;
@ -68,6 +74,7 @@ class PairingController extends GetxController {
// ========== 当前设备信息 ========== // ========== 当前设备信息 ==========
BluetoothDevice? _currentClassicDevice; BluetoothDevice? _currentClassicDevice;
String? _verifiedDeviceAddress; String? _verifiedDeviceAddress;
TextEditingController codeInputController = TextEditingController();
// Public getter for view access // Public getter for view access
BluetoothDevice? get currentDevice => _currentClassicDevice; BluetoothDevice? get currentDevice => _currentClassicDevice;
@ -236,6 +243,7 @@ class PairingController extends GetxController {
showConnectButton.value = false; showConnectButton.value = false;
showRetryButton.value = false; showRetryButton.value = false;
showSkipButton.value = false; showSkipButton.value = false;
showAutoCodeInput.value = false;
switch (state) { switch (state) {
case PairingState.idle: case PairingState.idle:
@ -276,7 +284,12 @@ class PairingController extends GetxController {
_startBleConnection(); _startBleConnection();
}); });
break; break;
case PairingState.manualbinding:
statusText.value = '需要手动版定授权码...';
isLoading.value = false;
showAutoCodeInput.value = true;
showSkipButton.value = true;
break;
case PairingState.completed: case PairingState.completed:
statusText.value = '配对成功!'; statusText.value = '配对成功!';
isLoading.value = false; isLoading.value = false;
@ -306,6 +319,10 @@ class PairingController extends GetxController {
showRetryButton.value = true; showRetryButton.value = true;
retryButtonText.value = '重新关联'; retryButtonText.value = '重新关联';
break; break;
case ErrorType.authnFailed:
showRetryButton.value = true;
retryButtonText.value = '重新授权';
break;
default: default:
showRetryButton.value = true; showRetryButton.value = true;
retryButtonText.value = '重试'; retryButtonText.value = '重试';
@ -393,44 +410,48 @@ class PairingController extends GetxController {
Future<void> _verifyDevice(BluetoothDevice device) async { Future<void> _verifyDevice(BluetoothDevice device) async {
try { try {
Logger.i(_tag, '开始验证设备: ${device.name} (${device.address})'); Logger.i(_tag, '开始验证设备: ${device.name} (${device.address})');
// TODO: 实现API验证逻辑 // TODO: 实现API验证逻辑
// 暂时模拟验证通过 // 暂时模拟验证通过
await Future.delayed(const Duration(seconds: 2)); // await Future.delayed(const Duration(seconds: 2));;
if (await authbindingDevice()) {
// 检查设备名称是否包含 DeepSound 或 DeapSound // 检查设备名称是否包含 DeepSound 或 DeapSound
final deviceName = device.name ?? ''; final deviceName = device.name ?? '';
if (deviceName.contains('DeepSound') || if (deviceName.contains('DeepSound') ||
deviceName.contains('DeapSound')) { deviceName.contains('DeapSound')) {
Logger.i(_tag, '设备验证通过'); Logger.i(_tag, '设备验证通过');
_verifiedDeviceAddress = device.address; _verifiedDeviceAddress = device.address;
// 检查是否已有BLE关联 // 检查是否已有BLE关联
final pairedMacAddress = await _bleManager.getPairedMacAddress(); final pairedMacAddress = await _bleManager.getPairedMacAddress();
Logger.i(_tag, '已关联的MAC地址: $pairedMacAddress'); Logger.i(_tag, '已关联的MAC地址: $pairedMacAddress');
if (pairedMacAddress != null && if (pairedMacAddress != null &&
pairedMacAddress.toUpperCase() == device.address.toUpperCase()) { pairedMacAddress.toUpperCase() == device.address.toUpperCase()) {
// 已有关联,检查BLE连接状态 // 已有关联,检查BLE连接状态
Logger.i(_tag, '设备已关联,检查BLE连接状态'); Logger.i(_tag, '设备已关联,检查BLE连接状态');
final currentBleState = await _bleManager.getConnectionState(); final currentBleState = await _bleManager.getConnectionState();
Logger.i(_tag, '验证时BLE状态: $currentBleState'); Logger.i(_tag, '验证时BLE状态: $currentBleState');
if (currentBleState == 2) { if (currentBleState == 2) {
// BleConnectionState.connected // BleConnectionState.connected
Logger.i(_tag, 'BLE已连接,直接完成配对'); Logger.i(_tag, 'BLE已连接,直接完成配对');
_transition(PairingState.completed); _transition(PairingState.completed);
} else {
Logger.i(_tag, 'BLE未连接,等待BLE连接');
_transition(PairingState.waitingBle);
}
} else { } else {
Logger.i(_tag, 'BLE未连接,等待BLE连接'); // 没有关联或关联的是其他设备,需要进行关联
_transition(PairingState.waitingBle); Logger.i(_tag, '需要进行BLE设备关联');
_transition(PairingState.associating);
} }
} else { } else {
// 没有关联或关联的是其他设备,需要进行关联 // _setError(ErrorType.deviceNotMatch, '请连接 DeepSound 设备');
Logger.i(_tag, '需要进行BLE设备关联'); _transition(PairingState.completed);
_transition(PairingState.associating);
} }
} else { } else {
_setError(ErrorType.deviceNotMatch, '请连接 DeepSound 设备'); //验证失败 进入手动授权
_transition(PairingState.manualbinding);
} }
} catch (e) { } catch (e) {
Logger.e(_tag, '设备验证异常: $e'); Logger.e(_tag, '设备验证异常: $e');
@ -504,6 +525,46 @@ class PairingController extends GetxController {
} }
} }
//自动绑定设备
Future<bool> authbindingDevice() async {
if (User.instance.earphonemac == currentDevice?.address) {
// 已经绑定
return true;
}
// 调用BLE管理器的自动绑定方法
final response = await Api.bindearphone({
"earphonemac": currentDevice?.address,
"earphoneauthcode": "",
});
Logger.i(_tag, '手动授权结果 ${response}');
if (response != null) {
Logger.i(_tag, '设备自动绑定成功');
User.instance.earphonemac = currentDevice?.address ?? '';
return true;
} else {
Logger.e(_tag, '手动绑定失败!');
return false;
}
}
/// 用户点击授权码输入框
void onAutoCodeInputTap() async {
// 调用BLE管理器的自动绑定方法
final response = await Api.bindearphone({
"earphonemac": currentDevice?.address,
"earphoneauthcode": codeInputController.text.trim(),
});
Logger.i(_tag, '手动授权结果 ${response}');
if (response != null) {
_transition(PairingState.verifying);
_verifyDevice(_currentClassicDevice!);
} else {
Logger.e(_tag, '手动绑定失败!');
_setError(ErrorType.authnFailed, '设备授权失败,请检查授权码');
}
}
/// 导航到主页 /// 导航到主页
void _navigateToHome() { void _navigateToHome() {
Future.delayed(const Duration(seconds: 1), () async { Future.delayed(const Duration(seconds: 1), () async {

51
lib/modules/pairing/views/pairing_view.dart

@ -336,6 +336,8 @@ class PairingView extends GetView<PairingController> {
return '扫描超时,请确保设备在附近'; return '扫描超时,请确保设备在附近';
case ErrorType.networkError: case ErrorType.networkError:
return '网络错误,请检查网络连接'; return '网络错误,请检查网络连接';
case ErrorType.authnFailed:
return '授权失败,请检查授权码是否正确';
default: default:
return '配对失败,请重试'; return '配对失败,请重试';
} }
@ -433,7 +435,7 @@ class PairingView extends GetView<PairingController> {
final showConnectButton = controller.showConnectButton.value; final showConnectButton = controller.showConnectButton.value;
final showRetryButton = controller.showRetryButton.value; final showRetryButton = controller.showRetryButton.value;
final showSkipButton = controller.showSkipButton.value; final showSkipButton = controller.showSkipButton.value;
final showAutoCodeInput = controller.showAutoCodeInput.value;
return Container( return Container(
width: double.infinity, width: double.infinity,
child: Column( child: Column(
@ -490,6 +492,51 @@ class PairingView extends GetView<PairingController> {
), ),
), ),
), ),
if (showAutoCodeInput)
Column(
children: [
// 输入框
SizedBox(
width: double.infinity,
height: 48.h,
child: TextField(
controller: controller
.codeInputController, // 你可以在 Controller 中声明这个
decoration: InputDecoration(
hintText: '请输入代码',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
contentPadding: EdgeInsets.symmetric(horizontal: 16.w),
),
),
),
SizedBox(height: 12.h),
SizedBox(
width: double.infinity,
height: 48.h,
child: ElevatedButton(
onPressed: controller.onAutoCodeInputTap,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(Get.context!).primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
elevation: 0,
),
child: Text(
'授权绑定',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
],
),
// 自动输入代码按钮
// 跳过按钮 // 跳过按钮
if (showSkipButton) ...[ if (showSkipButton) ...[
@ -510,11 +557,9 @@ class PairingView extends GetView<PairingController> {
), ),
), ),
], ],
], ],
), ),
); );
}); });
} }
} }

73
lib/modules/pairing/views/pairing_view_new.dart

@ -21,17 +21,17 @@ class PairingView extends GetView<PairingController> {
children: [ children: [
// 顶部标题 // 顶部标题
_buildHeader(isDarkMode), _buildHeader(isDarkMode),
// 中间内容 // 中间内容
Expanded( Expanded(
child: Center( child: Center(
child: Obx(() => _buildContent(isDarkMode)), child: Obx(() => _buildContent(isDarkMode)),
), ),
), ),
// 底部按钮 // 底部按钮
Obx(() => _buildButtons(isDarkMode)), Obx(() => _buildButtons(isDarkMode)),
SizedBox(height: 20.h), SizedBox(height: 20.h),
], ],
), ),
@ -69,15 +69,15 @@ class PairingView extends GetView<PairingController> {
Widget _buildContent(bool isDarkMode) { Widget _buildContent(bool isDarkMode) {
final state = controller.currentState; final state = controller.currentState;
final isLoading = controller.isLoading.value; final isLoading = controller.isLoading.value;
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// 状态图标 // 状态图标
_buildStateIcon(state, isLoading, isDarkMode), _buildStateIcon(state, isLoading, isDarkMode),
SizedBox(height: 40.h), SizedBox(height: 40.h),
// 状态文本 // 状态文本
Text( Text(
controller.statusText.value, controller.statusText.value,
@ -87,7 +87,7 @@ class PairingView extends GetView<PairingController> {
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
// 错误详情 // 错误详情
if (state == PairingState.error) ...[ if (state == PairingState.error) ...[
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -132,48 +132,51 @@ class PairingView extends GetView<PairingController> {
), ),
); );
} }
IconData iconData; IconData iconData;
Color iconColor; Color iconColor;
switch (state) { switch (state) {
case PairingState.idle: case PairingState.idle:
case PairingState.checking: case PairingState.checking:
iconData = Icons.bluetooth_searching; iconData = Icons.bluetooth_searching;
iconColor = Colors.blue; iconColor = Colors.blue;
break; break;
case PairingState.waitingClassic: case PairingState.waitingClassic:
iconData = Icons.bluetooth; iconData = Icons.bluetooth;
iconColor = Colors.grey; iconColor = Colors.grey;
break; break;
case PairingState.verifying: case PairingState.verifying:
iconData = Icons.verified_user; iconData = Icons.verified_user;
iconColor = Colors.orange; iconColor = Colors.orange;
break; break;
case PairingState.associating: case PairingState.associating:
iconData = Icons.link; iconData = Icons.link;
iconColor = Colors.blue; iconColor = Colors.blue;
break; break;
case PairingState.waitingBle: case PairingState.waitingBle:
iconData = Icons.sync; iconData = Icons.sync;
iconColor = Colors.blue; iconColor = Colors.blue;
break; break;
case PairingState.manualbinding:
iconData = Icons.blind;
iconColor = Colors.blue;
break;
case PairingState.completed: case PairingState.completed:
iconData = Icons.check_circle; iconData = Icons.check_circle;
iconColor = Colors.green; iconColor = Colors.green;
break; break;
case PairingState.error: case PairingState.error:
iconData = Icons.error; iconData = Icons.error;
iconColor = Colors.red; iconColor = Colors.red;
break; break;
} }
return Container( return Container(
width: 100.w, width: 100.w,
height: 100.w, height: 100.w,
@ -193,8 +196,9 @@ class PairingView extends GetView<PairingController> {
Widget _buildButtons(bool isDarkMode) { Widget _buildButtons(bool isDarkMode) {
final showConnectButton = controller.showConnectButton.value; final showConnectButton = controller.showConnectButton.value;
final showRetryButton = controller.showRetryButton.value; final showRetryButton = controller.showRetryButton.value;
final showAutoCodeInput = controller.showAutoCodeInput.value;
final showSkipButton = controller.showSkipButton.value; final showSkipButton = controller.showSkipButton.value;
return Column( return Column(
children: [ children: [
// 主要操作按钮 // 主要操作按钮
@ -204,16 +208,40 @@ class PairingView extends GetView<PairingController> {
controller.onConnectTap, controller.onConnectTap,
isDarkMode, isDarkMode,
), ),
if (showRetryButton) if (showRetryButton)
_buildPrimaryButton( _buildPrimaryButton(
'重试', '重试',
controller.onRetryTap, controller.onRetryTap,
isDarkMode, isDarkMode,
), ),
SizedBox(height: 12.h), SizedBox(height: 12.h),
if (showAutoCodeInput)
// 自动输入代码按钮
SizedBox(
width: double.infinity,
height: 48.h,
child: ElevatedButton(
onPressed: controller.onAutoCodeInputTap,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(Get.context!).primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
elevation: 0,
),
child: Text(
'自动输入代码',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
// 跳过按钮 // 跳过按钮
if (showSkipButton) if (showSkipButton)
TextButton( TextButton(
@ -231,7 +259,8 @@ class PairingView extends GetView<PairingController> {
} }
/// 构建主要按钮 /// 构建主要按钮
Widget _buildPrimaryButton(String text, VoidCallback onPressed, bool isDarkMode) { Widget _buildPrimaryButton(
String text, VoidCallback onPressed, bool isDarkMode) {
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
height: 48.h, height: 48.h,
@ -255,4 +284,4 @@ class PairingView extends GetView<PairingController> {
), ),
); );
} }
} }

3
lib/modules/settings/views/settings_view.dart

@ -200,8 +200,7 @@ class SettingsView extends GetView<SettingsController> {
2: 'proUser', //'专业用户' 2: 'proUser', //'专业用户'
}; };
final membershipString = membershipMap[ final membershipString = membershipMap[
controller controller.userInfo.value.viplv] ??
.userInfo.value.membershipLevel] ??
'Unknown'; 'Unknown';
return Text( return Text(
membershipString, membershipString,

85
lib/modules/translation/controllers/translation_controller.dart

@ -17,6 +17,13 @@ import '../../../data/models/events/voice_interaction_event.dart';
import '../../../data/services/asr_service.dart'; import '../../../data/services/asr_service.dart';
import '../../../data/services/ble_manager.dart'; import '../../../data/services/ble_manager.dart';
// 切换音频输出设备的枚举
enum AudioOutputDevice {
defaultDevice, // 默认(如有耳机优先耳机,否则扬声器)
headphones, // 强制耳机
speaker, // 强制扬声器
}
class TranslationController extends GetxController { class TranslationController extends GetxController {
// 服务 // 服务
final AsrService _asrService = Get.find<AsrService>(); final AsrService _asrService = Get.find<AsrService>();
@ -55,6 +62,7 @@ class TranslationController extends GetxController {
final appDocDir = getApplicationDocumentsDirectory(); final appDocDir = getApplicationDocumentsDirectory();
var appDir; var appDir;
var dir; var dir;
Future<void> changeTranslationMode(String mode) async { Future<void> changeTranslationMode(String mode) async {
currentMode.value = mode; currentMode.value = mode;
currentModeTitle.value = _getModeTitle(mode); currentModeTitle.value = _getModeTitle(mode);
@ -104,7 +112,7 @@ class TranslationController extends GetxController {
//_setupEventListeners(); //_setupEventListeners();
// 加载上次保存的语言设置 // 加载上次保存的语言设置
_loadSavedLanguages(); _loadSavedLanguages();
_requestStoragePermission();
// 初始化语言代码 // 初始化语言代码
final sourceAsrCode = final sourceAsrCode =
_languageManager.getAsrCodeByChineseName(sourceLanguage.value); _languageManager.getAsrCodeByChineseName(sourceLanguage.value);
@ -175,25 +183,27 @@ class TranslationController extends GetxController {
} }
// 请求存储权限 // 请求存储权限
void _requestStoragePermission() async { Future<bool> _requestStoragePermission() async {
// final BuildContext context = Get.overlayContext!; // final BuildContext context = Get.overlayContext!;
try {
final bool granted = await PermissionUtil.instance.requestPermission(
permissionType: Permission.storage,
permissionName: "存储",
explanationText: "我们需要访问存储权限,以便您可以选择并上传音频文件。",
permanentDenialText: "检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。",
);
PermissionUtil.instance
.requestPermission(
// context: context,
permissionType: Permission.storage,
permissionName: "存储",
explanationText: "我们需要访问存储权限,以便您可以选择并上传音频文件。",
permanentDenialText: "检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。",
)
.then((granted) {
if (granted) { if (granted) {
Logger.d("Permission", "用户授予了存储权限"); Logger.d("Permission", "用户授予了存储权限");
return true;
} else { } else {
Logger.d("Permission", "用户未授予存储权限"); Logger.d("Permission", "用户未授予存储权限");
// 可以提示用户无法进行文件操作 return false;
} }
}); } catch (e) {
Logger.e("Permission", "请求存储权限时出错: $e");
return false;
}
} }
// /// 设置事件监听器 // /// 设置事件监听器
// void _setupEventListeners() { // void _setupEventListeners() {
@ -224,6 +234,11 @@ class TranslationController extends GetxController {
await _asrService.initialize( await _asrService.initialize(
supportedLanguages: asrSupportedLanguages, supportedLanguages: asrSupportedLanguages,
); );
final bool storage = await _requestStoragePermission();
if (!storage) {
Logger.d("Permission", "未授予存储权限,无法提供录音功能");
return;
}
appDir = await appDocDir; // 先 await 获取目录 appDir = await appDocDir; // 先 await 获取目录
dir = Directory("${appDir.path}/TranslatAudio"); dir = Directory("${appDir.path}/TranslatAudio");
if (!await dir.exists()) { if (!await dir.exists()) {
@ -266,10 +281,10 @@ class TranslationController extends GetxController {
Future<void> startRecognition() async { Future<void> startRecognition() async {
Logger.info('开始语音识别'); Logger.info('开始语音识别');
// 等待权限请求完成并获取结果 // 等待权限请求完成并获取结果
final bool granted = await _requestRecordPermission(); final bool record = await _requestRecordPermission();
// 如果权限被拒绝,直接返回 // 如果权限被拒绝,直接返回
if (!granted) { if (!record) {
Logger.d("Permission", "录音权限被拒绝,无法开始录音"); Logger.d("Permission", "录音权限被拒绝,无法开始录音");
return; return;
} }
@ -328,6 +343,11 @@ class TranslationController extends GetxController {
await _asrService.startContinuousRecognition(_audioSourceType); await _asrService.startContinuousRecognition(_audioSourceType);
isRecognizing.value = true; isRecognizing.value = true;
if (isRecording.value && isRecognizing.value) { if (isRecording.value && isRecognizing.value) {
final bool storage = await _requestStoragePermission();
if (!storage) {
Logger.d("Permission", "未授予存储权限,无法提供录音功能");
return;
}
final formattedTime = final formattedTime =
DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); DateFormat('yyyyMMdd_HHmmss').format(DateTime.now());
await _asrService.enableRecord( await _asrService.enableRecord(
@ -516,15 +536,32 @@ class TranslationController extends GetxController {
detectedSourceLanguageCode; detectedSourceLanguageCode;
translationHistory[index].targetLanguageCode = translationHistory[index].targetLanguageCode =
detectedTargetLanguageCode; detectedTargetLanguageCode;
translationHistory.refresh(); print("判断是否是面对面模式且语言一致: ${detectedSourceLanguageCode}");
saveTranslationHistory(); // 判断是否是面对面模式且语言一致
// 更新后滚动到底部 final bool isFaceToFaceSameLang =
_scrollToBottom(); detectedSourceLanguageCode == sourceLanguageCode.value &&
currentMode.value == "faceToFace";
// 播放TTS - 只在最终结果时播放
if (isTtsEnabled.value && isFinal) { // 设置音频输出设备
// Logger.info('播放翻译文本: ${translationResult}'); var isSuccess = await _ttsService.setAudioOutputDevice(
await playTranslatedText(translationResult); isFaceToFaceSameLang
? AudioOutputDevice.speaker.index
: AudioOutputDevice.headphones.index,
);
if (isSuccess) {
print('设置音频输出设备成功');
translationHistory.refresh();
saveTranslationHistory();
// 更新后滚动到底部
_scrollToBottom();
// 播放TTS - 只在最终结果时播放
if (isTtsEnabled.value && isFinal) {
// Logger.info('播放翻译文本: ${translationResult}');
await playTranslatedText(translationResult);
}
} else {
print('设置音频输出设备失败');
} }
} else { } else {
Logger.info( Logger.info(

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

@ -278,15 +278,16 @@ class TranslationListView extends GetView<TranslationListController> {
), ),
_popupMenuEntry( _popupMenuEntry(
Icon( Icon(
Icons.delete_outline, Icons.smart_button,
size: 20.w, size: 20.w,
color: isDarkMode ? Colors.red[700] : Colors.red, color: isDarkMode ? Colors.white : Colors.black87,
), ),
'删除', '智能转写',
isDarkMode ? Colors.red[700]! : Colors.red, isDarkMode ? Colors.white : Colors.black87,
() { () {
file.delete(); Get.toNamed(Routes.meeting, arguments: {
controller.loadTranslatAudio(); // 刷新列表 'filePath': file.path,
});
}, },
), ),
_popupMenuEntry( _popupMenuEntry(
@ -295,12 +296,11 @@ class TranslationListView extends GetView<TranslationListController> {
size: 20.w, size: 20.w,
color: isDarkMode ? Colors.red[700] : Colors.red, color: isDarkMode ? Colors.red[700] : Colors.red,
), ),
'导入', '删除',
isDarkMode ? Colors.red[700]! : Colors.red, isDarkMode ? Colors.red[700]! : Colors.red,
() { () {
Get.toNamed(Routes.meeting, arguments: { file.delete();
'filePath': file.path, controller.loadTranslatAudio(); // 刷新列表
});
}, },
), ),
], ],

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

@ -110,7 +110,9 @@ class TranslationView extends GetView<TranslationController> {
// TTS toggle button // TTS toggle button
Obx(() => IconButton( Obx(() => IconButton(
icon: Icon( icon: Icon(
controller.isRecording.value ? Icons.mic : Icons.mic_off, controller.isRecording.value
? Icons.mic
: Icons.fiber_manual_record,
color: isDarkMode ? Colors.white : Colors.black, color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp, size: 24.sp,
), ),
@ -122,7 +124,7 @@ class TranslationView extends GetView<TranslationController> {
// 历史记录按钮 // 历史记录按钮
IconButton( IconButton(
icon: Icon( icon: Icon(
Icons.history, Icons.folder,
color: isDarkMode ? Colors.white : Colors.black, color: isDarkMode ? Colors.white : Colors.black,
size: 24.sp, size: 24.sp,
), ),
@ -134,7 +136,7 @@ class TranslationView extends GetView<TranslationController> {
// 返回时不自动开始翻译,只需重新设置滚动监听器 // 返回时不自动开始翻译,只需重新设置滚动监听器
controller.setupScrollListener(); controller.setupScrollListener();
}, },
tooltip: '查看历史记录', tooltip: '查看录音文件',
), ),
], ],
bottom: PreferredSize( bottom: PreferredSize(

13
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt

@ -147,13 +147,14 @@ class AzureAsrHelper(private val context: Context) {
} }
// 录音文件类 // 录音文件类
recordfile = RecordFile; recordfile = RecordFile;
//进入界面手机麦克风就被占用,导致其他app无法使用麦克风,在开始录音再去申请
// 创建识别器 // 创建识别器
val setupSuccess = setupRecognizer() // val setupSuccess = setupRecognizer()
if (setupSuccess) { // if (setupSuccess) {
// 优化:初始化完成后进行预热 // // 优化:初始化完成后进行预热
warmupRecognizer() // warmupRecognizer()
} // }
return setupSuccess return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(tag, "初始化失败: ${e.message}") Log.e(tag, "初始化失败: ${e.message}")
return false return false

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

@ -15,7 +15,7 @@ import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType import com.deep_voice.speech.tts.TtsEventType
import com.yunqiinnovation.ble_service.BleService import com.yunqiinnovation.ble_service.BleService
import com.deep_voice.speech.tts.AudioOutputDevice
/** AzureSpeechPlugin */ /** AzureSpeechPlugin */
class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
private val tag = "AzureSpeechPlugin" private val tag = "AzureSpeechPlugin"
@ -501,6 +501,22 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin {
azureTtsHelper.release() azureTtsHelper.release()
result.success(true) result.success(true)
} }
"setAudioOutputDevice" -> {
val type = call.argument<Int>("type")
if (type == 0) {
// 默认(如果有耳机选耳机,否则使用系统扬声器)
azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.DEFAULT)
} else if (type == 1) {
// 强制使用耳机
azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.HEADPHONES)
} else if (type == 2) {
// 强制使用扬声器
azureTtsHelper.setAudioOutputDevice(AudioOutputDevice.SPEAKER)
}
result.success(true)
}
else -> { else -> {
result.notImplemented() result.notImplemented()

7
local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureTtsHelper.kt

@ -213,6 +213,7 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
override fun setAudioOutputDevice(device: AudioOutputDevice) { override fun setAudioOutputDevice(device: AudioOutputDevice) {
// Azure TTS使用系统默认的音频路由 // Azure TTS使用系统默认的音频路由
// 音频输出设备的控制需要通过Android的AudioManager实现 // 音频输出设备的控制需要通过Android的AudioManager实现
FileLogger.e(TAG, "音频输出设备类型device=: ${device}")
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
audioManager?.let { manager -> audioManager?.let { manager ->
when (device) { when (device) {
@ -223,7 +224,11 @@ class AzureTtsHelper(private val context: Context) : ITtsService {
} }
AudioOutputDevice.SPEAKER -> { AudioOutputDevice.SPEAKER -> {
// 强制使用扬声器 // 强制使用扬声器
manager.mode = AudioManager.MODE_NORMAL /* MODE_NORMAL 默认媒体模式 ❌ 不能强制扬声器(会被耳机覆盖)
MODE_IN_COMMUNICATION VoIP 通信模式 ✅ 可以强制使用扬声器或听筒
MODE_IN_CALL 电话模式(仅限系统) ✅ 更强但风险大,不推荐使用
*/
manager.mode = AudioManager.MODE_IN_COMMUNICATION
manager.isSpeakerphoneOn = true manager.isSpeakerphoneOn = true
} }
AudioOutputDevice.HEADPHONES -> { AudioOutputDevice.HEADPHONES -> {

Loading…
Cancel
Save