Browse Source

Merge branch 'main' of https://git.onewaygame.cn/deepsound/deepvoice_client into tanls

newdev_shunjiawei
tanlongsheng 1 year ago
parent
commit
9701b3718a
  1. 31
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt
  2. 5
      lib/data/services/asr_service.dart
  3. 18
      lib/data/services/speech_impl/azure_asr_service.dart
  4. 239
      lib/data/services/speech_impl/flutter_asr_service.dart
  5. 6
      lib/data/services/speech_impl/xunfei_asr_service.dart
  6. 12
      lib/data/services/spotify_service.dart
  7. 28
      lib/modules/chat/controllers/chat_controller.dart
  8. 310
      lib/modules/translation/controllers/translation_controller.dart
  9. 32
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt
  10. 16
      local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt
  11. 2
      local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt
  12. 2
      local_plugins/jieli/lib/jieli.dart
  13. 2
      local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt
  14. 2
      local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt
  15. BIN
      local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock
  16. 4
      local_plugins/opus/.gradle/buildOutputCleanup/cache.properties
  17. 86
      local_plugins/spotify_sdk/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkHelper.kt

31
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/VoiceInteractionHandler.kt

@ -12,6 +12,8 @@ import com.yunqiinnovation.azure_speech.AzureAsrHelper
import com.yunqiinnovation.azure_speech.AzureTtsHelper
import com.yunqiinnovation.open_ai_service.OpenAIService
import com.yunqiinnovation.open_ai_service.SystemFunctionHandler
import de.minimalme.spotify_sdk.SpotifySdkHelper
import kotlinx.serialization.json.JsonObject
/**
@ -38,7 +40,9 @@ class VoiceInteractionHandler(
// OpenAI服务
private val openAIService = OpenAIService(context.applicationContext)
private var spotifyHelper: SpotifySdkHelper? = null
// 当前用户输入
private var currentUserInput = ""
@ -271,6 +275,17 @@ class VoiceInteractionHandler(
override fun onFunctionCall(call: JSONObject) {
FileLogger.d(TAG, "processWithOpenAI 收到函数调用请求: ${call.getString("name")}")
// Log.e("OpenAIServicePlugin", "context:${call.toString()}")
// val context = call["context"]
// if (call.has("meta")) {
// val meta = call.get("meta")
// val metaJsonObject: JsonObject = meta as JsonObject
// Log.e("OpenAIServicePlugin", "工具调用结果 context:${context} meta:${meta.toString()}")
// if (metaJsonObject.containsKey("card_spotify")) {
// val card_spotify:JsonObject = metaJsonObject.get("card_spotify") as JsonObject
// playSpotifyMusic(card_spotify.get("name") as String)
// }
// }
}
}
)
@ -314,7 +329,19 @@ class VoiceInteractionHandler(
notifyStateChanged()
}
}
/*
* 播放音乐。Spotify
* */
fun playSpotifyMusic(url:String){
if (spotifyHelper == null){
spotifyHelper = SpotifySdkHelper(context).apply {
initialize(spotifyclientid,spotifyRedirectUrl)
}
}
spotifyHelper?.play(url)
}
/**
* 播放AI回复
*/

5
lib/data/services/asr_service.dart

@ -16,6 +16,11 @@ abstract class AsrService {
/// 返回识别的最终结果
Future<RecognitionEvent> recognizeOnce();
/// 开始连续语音识别
///
/// 推送音频数据到连续识别中
Future<void> pushAudioData(Uint8List data);
/// 开始连续语音识别
///
/// 返回一个包含识别事件的流

18
lib/data/services/speech_impl/azure_asr_service.dart

@ -288,4 +288,22 @@ class AzureAsrService extends GetxService implements AsrService {
_isInitialized = false;
}
}
@override
Future<void> pushAudioData(Uint8List data) async {
try {
// 开始连续识别
final bool result = await _channel.invokeMethod('pushAudioData', {
'AudioData': data // Flutter 会自动将 Uint8List 转换为 ByteBuffer
});
if (!result) {
_cleanupEventStream();
throw Exception('开始pushAudioData失败');
}
} catch (e) {
Logger.error('开始pushAudioData失败: $e');
rethrow;
}
}
}

239
lib/data/services/speech_impl/flutter_asr_service.dart

@ -11,26 +11,26 @@ import '../asr_service.dart';
enum AsrState {
/// 未初始化
notInitialized,
/// 已初始化但未开始监听
initialized,
/// 正在监听
listening,
/// 正在处理识别结果
processing,
/// 识别完成
done,
/// 发生错误
error,
}
/// Flutter ASR服务,负责本地语音转文本功能
/// 使用speech_to_text插件实现语音识别功能
class FlutterAsrService extends GetxService implements AsrService {
class FlutterAsrService extends GetxService implements AsrService {
static final FlutterAsrService to = Get.put(FlutterAsrService());
// speech_to_text 实例
@ -40,34 +40,34 @@ class FlutterAsrService extends GetxService implements AsrService {
final List<String> _supportedLanguages = [];
@override
List<String> get supportedLanguages => _supportedLanguages;
// 当前选择的语言
final RxString _currentLocale = ''.obs;
String get currentLocale => _currentLocale.value;
// 是否可用
final RxBool _isAvailable = false.obs;
bool get isAvailable => _isAvailable.value;
// 是否正在监听
bool _isListening = false;
// 当前ASR状态
final Rx<AsrState> _asrState = AsrState.notInitialized.obs;
AsrState get asrState => _asrState.value;
// 连续识别模式的结果流控制器
StreamController<RecognitionEvent>? _continuousRecognitionController;
FlutterAsrService() {
_initSpeech();
}
/// 初始化语音识别
Future<void> _initSpeech() async {
try {
_asrState.value = AsrState.notInitialized;
// 初始化语音识别,使用较短的最终超时时间,提高响应速度
_isAvailable.value = await _speech.initialize(
onStatus: _onSpeechStatus,
@ -75,11 +75,11 @@ class FlutterAsrService extends GetxService implements AsrService {
debugLogging: false,
finalTimeout: const Duration(milliseconds: 800), // 更短的最终超时时间,加速结果返回
);
if (_isAvailable.value) {
// 获取支持的语言列表
final systemLocales = await _speech.locales();
// 清空并重新填充支持的语言列表
_supportedLanguages.clear();
for (var locale in systemLocales) {
@ -87,7 +87,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_supportedLanguages.add(locale.localeId);
}
}
// 设置默认语言(优先使用系统语言或中文)
var systemLocale = await _speech.systemLocale();
if (systemLocale != null) {
@ -97,7 +97,7 @@ class FlutterAsrService extends GetxService implements AsrService {
} else if (_supportedLanguages.isNotEmpty) {
_currentLocale.value = _supportedLanguages.first;
}
_asrState.value = AsrState.initialized;
Logger.info('语音识别初始化成功,支持 ${_supportedLanguages.length} 种语言');
} else {
@ -110,11 +110,11 @@ class FlutterAsrService extends GetxService implements AsrService {
Logger.error('语音识别初始化失败: $e');
}
}
/// 处理语音识别状态变化
void _onSpeechStatus(String status) {
Logger.info('语音识别状态: $status');
switch (status) {
case 'listening':
_asrState.value = AsrState.listening;
@ -129,31 +129,33 @@ class FlutterAsrService extends GetxService implements AsrService {
case 'done':
_asrState.value = AsrState.done;
_isListening = false;
// 在连续识别模式下,当一次识别完成后立即开始下一次识别
if (_continuousRecognitionController != null && !_continuousRecognitionController!.isClosed) {
if (_continuousRecognitionController != null &&
!_continuousRecognitionController!.isClosed) {
_restartContinuousRecognition();
}
break;
}
}
/// 处理语音识别错误
void _onSpeechError(SpeechRecognitionError error) {
Logger.error('语音识别错误: ${error.errorMsg} (永久性错误: ${error.permanent})');
_asrState.value = AsrState.error;
// 向连续识别控制器发送错误事件(如果存在)
_continuousRecognitionController?.add(
RecognitionEvent.error(error.errorMsg)
);
_continuousRecognitionController
?.add(RecognitionEvent.error(error.errorMsg));
// 在连续识别模式下,尝试从错误中恢复(非永久性错误)
if (!error.permanent && _continuousRecognitionController != null && !_continuousRecognitionController!.isClosed) {
if (!error.permanent &&
_continuousRecognitionController != null &&
!_continuousRecognitionController!.isClosed) {
_restartContinuousRecognition();
}
}
/// 启动语音识别的统一方法
Future<bool> _startListening({
required Function(SpeechRecognitionResult) onResult,
@ -168,36 +170,37 @@ class FlutterAsrService extends GetxService implements AsrService {
// 延长等待时间,确保资源完全释放
await Future.delayed(const Duration(milliseconds: 800));
}
// 在启动前再次检查状态
if (_speech.isListening) {
Logger.warning('停止识别后,仍处于监听状态,可能有系统冲突');
await _speech.stop();
await Future.delayed(const Duration(milliseconds: 500));
}
// 启动语音识别前确保状态正确
_asrState.value = AsrState.listening;
// 启动语音识别
final result = await _speech.listen(
onResult: onResult,
localeId: _currentLocale.value,
listenFor: listenFor,
pauseFor: pauseFor,
listenOptions: stt.SpeechListenOptions(
partialResults: true,
autoPunctuation: autoPunctuation,
// 使用独白模式,减少与系统交互冲突
listenMode: stt.ListenMode.dictation,
),
) ?? false;
onResult: onResult,
localeId: _currentLocale.value,
listenFor: listenFor,
pauseFor: pauseFor,
listenOptions: stt.SpeechListenOptions(
partialResults: true,
autoPunctuation: autoPunctuation,
// 使用独白模式,减少与系统交互冲突
listenMode: stt.ListenMode.dictation,
),
) ??
false;
if (!result) {
Logger.error('语音识别启动失败');
_asrState.value = AsrState.error;
}
return result;
} catch (e) {
Logger.error('启动语音识别失败: $e');
@ -205,33 +208,35 @@ class FlutterAsrService extends GetxService implements AsrService {
return false;
}
}
/// 重新启动连续识别
Future<void> _restartContinuousRecognition() async {
try {
// 确保控制器有效
if (_continuousRecognitionController == null || _continuousRecognitionController!.isClosed) {
if (_continuousRecognitionController == null ||
_continuousRecognitionController!.isClosed) {
Logger.info('连续识别控制器已关闭,跳过重启');
return;
}
// 检查系统状态,避免频繁重启
if (_speech.isListening) {
Logger.info('当前已在监听中,无需重启');
return;
}
// 延长等待时间,避免与系统资源冲突
await Future.delayed(const Duration(milliseconds: 1000));
// 再次检查状态
if (_continuousRecognitionController == null || _continuousRecognitionController!.isClosed) {
if (_continuousRecognitionController == null ||
_continuousRecognitionController!.isClosed) {
Logger.info('延迟期间控制器已关闭,跳过重启');
return;
}
Logger.info('尝试重新启动连续识别');
// 使用统一方法启动识别
final success = await _startListening(
onResult: _onSpeechResult,
@ -239,49 +244,42 @@ class FlutterAsrService extends GetxService implements AsrService {
listenFor: const Duration(seconds: 30),
pauseFor: const Duration(seconds: 4),
);
if (success) {
Logger.info('连续识别重启成功');
} else {
Logger.error('连续识别重启失败');
_continuousRecognitionController?.add(
RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突')
);
_continuousRecognitionController
?.add(RecognitionEvent.error('重启语音识别失败,可能存在系统资源冲突'));
}
} catch (e) {
Logger.error('重启连续识别失败: $e');
// 发送错误给控制器
_continuousRecognitionController?.add(
RecognitionEvent.error('重启失败: $e')
);
_continuousRecognitionController?.add(RecognitionEvent.error('重启失败: $e'));
}
}
/// 处理语音识别结果
void _onSpeechResult(SpeechRecognitionResult result) {
// 如果是连续识别,发送到连续识别控制器
if (_continuousRecognitionController != null) {
if (result.finalResult) {
Logger.info('收到最终识别结果: "${result.recognizedWords}"');
_continuousRecognitionController!.add(
RecognitionEvent.finalResult(
text: result.recognizedWords,
detectedLanguage: currentLocale,
)
);
_continuousRecognitionController!.add(RecognitionEvent.finalResult(
text: result.recognizedWords,
detectedLanguage: currentLocale,
));
} else {
_continuousRecognitionController!.add(
RecognitionEvent(
type: RecognitionEventType.intermediateResult,
text: result.recognizedWords,
detectedLanguage: currentLocale,
)
);
_continuousRecognitionController!.add(RecognitionEvent(
type: RecognitionEventType.intermediateResult,
text: result.recognizedWords,
detectedLanguage: currentLocale,
));
}
}
}
/// 设置当前语言
void setLocale(String localeId) {
if (_supportedLanguages.contains(localeId)) {
@ -291,7 +289,7 @@ class FlutterAsrService extends GetxService implements AsrService {
Logger.error('不支持的语言: $localeId');
}
}
@override
Future<bool> initialize({
List<String>? supportedLanguages,
@ -301,7 +299,7 @@ class FlutterAsrService extends GetxService implements AsrService {
_supportedLanguages.clear();
_supportedLanguages.addAll(supportedLanguages);
}
// 初始化语音识别
await _initSpeech();
return _isAvailable.value;
@ -310,24 +308,24 @@ class FlutterAsrService extends GetxService implements AsrService {
return false;
}
}
@override
Future<RecognitionEvent> recognizeOnce() async {
try {
if (!_isAvailable.value) {
return RecognitionEvent.error('语音识别不可用');
}
if (_isListening) {
await stopContinuousRecognition();
}
// 创建Completer来等待最终结果
final completer = Completer<RecognitionEvent>();
// 开始监听
_asrState.value = AsrState.listening;
// 定义结果处理函数
void resultListener(SpeechRecognitionResult result) {
if (result.finalResult) {
@ -338,21 +336,21 @@ class FlutterAsrService extends GetxService implements AsrService {
));
}
}
// 使用统一方法启动识别,单次识别使用更短的时间参数
final success = await _startListening(
onResult: resultListener,
listenFor: const Duration(seconds: 15),
pauseFor: const Duration(seconds: 2),
);
if (!success) {
return RecognitionEvent.error('启动语音识别失败');
}
// 设置较短的超时时间
return await completer.future.timeout(
const Duration(seconds: 20), // 略长于listenFor以确保获取到结果
const Duration(seconds: 20), // 略长于listenFor以确保获取到结果
onTimeout: () {
_speech.stop();
return RecognitionEvent.error('语音识别超时');
@ -363,7 +361,7 @@ class FlutterAsrService extends GetxService implements AsrService {
return RecognitionEvent.error('$e');
}
}
@override
Future<Stream<RecognitionEvent>> startContinuousRecognition() async {
try {
@ -373,43 +371,42 @@ class FlutterAsrService extends GetxService implements AsrService {
controller.close();
return controller.stream;
}
// 如果已经在监听,先停止
if (_isListening) {
await stopContinuousRecognition();
}
// 创建新的流控制器
_continuousRecognitionController = StreamController<RecognitionEvent>.broadcast();
_continuousRecognitionController =
StreamController<RecognitionEvent>.broadcast();
// 开始会话
_continuousRecognitionController!.add(
RecognitionEvent(type: RecognitionEventType.sessionStarted)
);
_continuousRecognitionController!
.add(RecognitionEvent(type: RecognitionEventType.sessionStarted));
// 开始连续监听
_asrState.value = AsrState.listening;
// 使用统一方法启动识别,连续识别使用较长的参数
final success = await _startListening(
onResult: _onSpeechResult,
);
if (!success) {
// 添加错误事件,但保留流
_continuousRecognitionController!.add(
RecognitionEvent.error('启动连续语音识别失败')
);
_continuousRecognitionController!
.add(RecognitionEvent.error('启动连续语音识别失败'));
// 返回已有的流控制器
return _continuousRecognitionController!.stream;
}
_isListening = true;
return _continuousRecognitionController!.stream;
} catch (e) {
Logger.error('启动连续语音识别失败: $e');
// 如果已经创建了控制器,使用它
if (_continuousRecognitionController != null) {
_continuousRecognitionController!.add(RecognitionEvent.error('$e'));
@ -422,7 +419,7 @@ class FlutterAsrService extends GetxService implements AsrService {
}
}
}
@override
Future<bool> stopContinuousRecognition() async {
try {
@ -430,36 +427,35 @@ class FlutterAsrService extends GetxService implements AsrService {
if (_speech.isListening) {
await _speech.stop();
}
_isListening = false;
// 发送会话结束事件
_continuousRecognitionController?.add(
RecognitionEvent(type: RecognitionEventType.sessionStopped)
);
_continuousRecognitionController
?.add(RecognitionEvent(type: RecognitionEventType.sessionStopped));
// 关闭流控制器
await _continuousRecognitionController?.close();
_continuousRecognitionController = null;
_asrState.value = AsrState.done;
return true;
} catch (e) {
Logger.error('停止连续语音识别失败: $e');
// 强制重置状态
_isListening = false;
_asrState.value = AsrState.error;
return false;
}
}
@override
bool isContinuousRecognitionActive() {
return _isListening;
}
@override
Future<void> dispose() async {
try {
@ -467,15 +463,18 @@ class FlutterAsrService extends GetxService implements AsrService {
if (_speech.isListening) {
await _speech.stop();
}
// 关闭控制器
await _continuousRecognitionController?.close();
_continuousRecognitionController = null;
_isListening = false;
_asrState.value = AsrState.notInitialized;
} catch (e) {
Logger.error('释放语音识别资源失败: $e');
}
}
}
@override
Future<void> pushAudioData(Uint8List data) async {}
}

6
lib/data/services/speech_impl/xunfei_asr_service.dart

@ -269,4 +269,10 @@ class XunfeiAsrService extends GetxService implements AsrService {
_isInitialized = false;
}
}
@override
Future<void> pushAudioData(Uint8List data) {
// TODO: implement pushAudioData
throw UnimplementedError();
}
}

12
lib/data/services/spotify_service.dart

@ -20,15 +20,17 @@ class SpotifService {
/// 初始化OpenAI服务
Future<bool> initialize() async {
if (!isInitialized) {
await getAccessToken();
await connectToSpotifyRemote();
isInitialized = true;
final _env = _storage.read("ENV") as Map<String, String>;
SPOTIFY_CLIENT_ID = _env['SPOTIFY_CLIENT_ID'] ?? '';
SPOTIFY_REDIRECT_URL = _env['SPOTIFY_REDIRECT_URL'] ?? '';
printInfo(info: "SpotifService----------- initialize");
}
printInfo(
info:
"SpotifService----------- initialize SPOTIFY_CLIENT_ID:${SPOTIFY_CLIENT_ID} SPOTIFY_REDIRECT_URL:${SPOTIFY_REDIRECT_URL}");
await getAccessToken();
await connectToSpotifyRemote();
isInitialized = true;
}
return true;
}

28
lib/modules/chat/controllers/chat_controller.dart

@ -113,8 +113,9 @@ class ChatController extends GetxController {
// 获取Agent
final Agent? foundAgent = AgentProvider.getAgentById(agentId);
if (foundAgent == null) {
Logger.error('无法找到Agent: $agentId');
Get.back();
Logger.error('无法找到Agent: $agentId');
return;
}
@ -403,18 +404,23 @@ class ChatController extends GetxController {
messages: messageHistory,
systemPrompt: agent.systemPrompt,
);
Logger.info(
'liwei----------- 发送用户提问 ${text}',
);
// 订阅响应流
_aiResponseSubscription = responseStream.listen(
(chunk) {
if (_shouldCancelAiResponse) return;
Logger.info('chat ${chunk}');
if (chunk is Map) {
Logger.info(
'liwei-----------收到卡片消息 ${chunk}',
);
_updateIntermediateMessage(meta: chunk);
if (chunk['card_spotify'] != null) {
play(chunk['card_spotify']);
}
} else {
} else if (chunk is String) {
fullResponse += chunk;
// 更新中间状态的消息内容
@ -422,11 +428,15 @@ class ChatController extends GetxController {
// 如果TTS启用,使用流式处理发送到TTS
if (isTtsEnabled.value) {
_ttsService.speakStream(chunk);
String str = chunk.replaceAll('*', '');
_ttsService.speakStream(str);
}
}
},
onDone: () {
Logger.info(
'liwei-----------结束当前问题 ${text}',
);
if (_shouldCancelAiResponse) return;
// 完成响应,将isIntermediate设为false
@ -459,6 +469,10 @@ class ChatController extends GetxController {
final recentMessages = messages.where((m) => !m.isIntermediate).toList();
final startIndex =
recentMessages.length > 10 ? recentMessages.length - 10 : 0;
messageHistory.add({
'role': 'user',
'content': '我当前所在城市是$_city,我的经纬度是$_latLng',
});
for (int i = startIndex; i < recentMessages.length; i++) {
final m = recentMessages[i];
@ -466,12 +480,6 @@ class ChatController extends GetxController {
'role': m.role,
'content': m.content,
});
if (i == recentMessages.length - 2) {
messageHistory.add({
'role': 'user',
'content': '当需要获取我的位置时,我当前在$_city,我的经纬度是$_latLng',
});
}
}
return messageHistory;

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

@ -10,14 +10,37 @@ import '../../../core/utils/logger.dart';
import '../../../data/models/events/voice_interaction_event.dart';
import '../../../data/services/voice_interaction_service.dart';
import '../../../data/services/asr_service.dart';
import 'package:jieli/jieli.dart';
class TranslationController extends GetxController {
late Jieli jieli;
// 事件订阅
StreamSubscription? _scanSubscription;
StreamSubscription? _connectionSubscription;
StreamSubscription? _wakeupSubscription;
StreamSubscription? _audioSubscription;
StreamSubscription? _commandResultSubscription;
// 状态变量
var isScanning = false.obs;
var deviceInfo = '未获取'.obs;
var connectStatus = '未连接'.obs;
var currentDevice = Rx<BleDevice?>(null);
var scannedDevices = RxList<BleDevice>([]);
var wakeupReceived = false.obs;
var audioDataSize = 0.obs;
var receivedFrames = 0.obs;
var audioDataReceiving = false.obs;
var volumeCommandResult = ''.obs;
// 服务
final AsrService _asrService = Get.find<AsrService>();
final VolcanoTranslationService _translationService = Get.find<VolcanoTranslationService>();
final VolcanoTranslationService _translationService =
Get.find<VolcanoTranslationService>();
final TtsService _ttsService = Get.find<TtsService>();
final LanguageManager _languageManager = Get.find<LanguageManager>();
final GetStorage _storage = GetStorage();
final VoiceInteractionService _voiceInteractionService = Get.find<VoiceInteractionService>();
final VoiceInteractionService _voiceInteractionService =
Get.find<VoiceInteractionService>();
// 存储相关
static const String _historyKey = 'translation_history';
@ -29,75 +52,80 @@ class TranslationController extends GetxController {
final isRecognizing = false.obs;
final isTranslating = false.obs;
final isTtsEnabled = true.obs;
// 语言相关
final sourceLanguage = '中文(简体)'.obs;
final targetLanguage = '英语'.obs;
final sourceLanguageCode = 'zh-CN'.obs;
final targetLanguageCode = 'en-US'.obs;
var detectedLanguageCode = '';
// 翻译历史和当前项
final translationHistory = <TranslationItem>[].obs;
String? currentSessionId;
// 兼容旧版视图的计算属性
Rx<TranslationItem?> get currentItem {
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate) {
if (translationHistory.isNotEmpty &&
translationHistory.last.isIntermediate) {
return Rx<TranslationItem?>(translationHistory.last);
}
return Rx<TranslationItem?>(null);
}
// UI相关
final ScrollController scrollController = ScrollController();
// 其他
StreamSubscription? _recognitionSubscription;
StreamSubscription? _voiceInteractionSubscription;
Timer? _translationDebounceTimer;
// 获取支持的语言列表
Map<String, String> get supportedLanguages => _languageManager.getChineseNameToAsrCodeMap();
Map<String, String> get supportedLanguages =>
_languageManager.getChineseNameToAsrCodeMap();
@override
void onInit() {
super.onInit();
jieli = Jieli();
//_setupEventListeners();
// 加载上次保存的语言设置
_loadSavedLanguages();
// 初始化语言代码
final sourceAsrCode = _languageManager.getAsrCodeByChineseName(sourceLanguage.value);
final sourceAsrCode =
_languageManager.getAsrCodeByChineseName(sourceLanguage.value);
if (sourceAsrCode != null) {
sourceLanguageCode.value = sourceAsrCode;
}
final targetAsrCode = _languageManager.getAsrCodeByChineseName(targetLanguage.value);
final targetAsrCode =
_languageManager.getAsrCodeByChineseName(targetLanguage.value);
if (targetAsrCode != null) {
targetLanguageCode.value = targetAsrCode;
}
// 订阅原生服务事件
_subscribeToVoiceInteractionService();
// 加载历史记录
loadTranslationHistory();
// 初始化服务
_initServices();
// 使用微任务在布局完成后自动滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
});
}
/// 订阅原生服务事件
void _subscribeToVoiceInteractionService() {
if (_voiceInteractionService.eventStream != null) {
_voiceInteractionSubscription = _voiceInteractionService.eventStream!.listen((event) {
_voiceInteractionSubscription =
_voiceInteractionService.eventStream!.listen((event) {
if (event is RecognitionStartedEvent) {
_handleVoiceInteractionStarted();
}
@ -109,35 +137,54 @@ class TranslationController extends GetxController {
void _loadSavedLanguages() {
final savedSourceLanguage = _storage.read(_sourceLanguageKey);
final savedTargetLanguage = _storage.read(_targetLanguageKey);
if (savedSourceLanguage != null && supportedLanguages.containsKey(savedSourceLanguage)) {
if (savedSourceLanguage != null &&
supportedLanguages.containsKey(savedSourceLanguage)) {
sourceLanguage.value = savedSourceLanguage;
}
if (savedTargetLanguage != null && supportedLanguages.containsKey(savedTargetLanguage)) {
if (savedTargetLanguage != null &&
supportedLanguages.containsKey(savedTargetLanguage)) {
targetLanguage.value = savedTargetLanguage;
}
}
// 保存当前语言设置
void _saveLanguageSettings() {
_storage.write(_sourceLanguageKey, sourceLanguage.value);
_storage.write(_targetLanguageKey, targetLanguage.value);
}
// /// 设置事件监听器
// void _setupEventListeners() {
// // 监听扫描结果
// _scanSubscription = jieli.scanResults.listen((devices) {
// scannedDevices.assignAll(devices);
// });
// // 监听唤醒信号
// _wakeupSubscription = jieli.wakeupStream.listen((wakeup) {
// wakeupReceived.value = true;
// Get.snackbar('收到唤醒信号', '耳机发送了唤醒信号');
// });
// }
// 初始化服务
Future<void> _initServices() async {
try {
await _translationService.initialize();
// 初始化 ASR 服务时,明确指定需要支持的语言
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
final List<String> asrSupportedLanguages = [
sourceLanguageCode.value,
targetLanguageCode.value
];
Logger.info('初始化ASR服务,支持语言: $asrSupportedLanguages');
await _asrService.initialize(
supportedLanguages: asrSupportedLanguages,
);
Logger.info('所有服务初始化完成');
} catch (e) {
Logger.error('初始化服务失败: $e');
@ -159,20 +206,26 @@ class TranslationController extends GetxController {
// 开始语音识别
Future<void> startRecognition() async {
Logger.info('开始语音识别');
if (isRecognizing.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try {
Logger.info('开始语音识别1');
// 每次开始识别前检查 ASR 服务是否已配置为当前语言对
final List<String> currentLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
final bool needsReinit = !_asrService.supportedLanguages.toSet().containsAll(currentLanguages);
final List<String> currentLanguages = [
sourceLanguageCode.value,
targetLanguageCode.value
];
final bool needsReinit =
!_asrService.supportedLanguages.toSet().containsAll(currentLanguages);
if (needsReinit) {
Logger.info('识别开始前发现语言变更,重新初始化ASR服务');
await _reinitializeAsrService();
}
currentSessionId = DateTime.now().millisecondsSinceEpoch.toString();
// // 初始化新的翻译项
@ -186,17 +239,25 @@ class TranslationController extends GetxController {
// isFirstInSession: true,
// isIntermediate: true,
// );
// // 添加到历史记录
// translationHistory.add(newItem);
// 开始连续语音识别
final recognitionStream = await _asrService.startContinuousRecognition();
Logger.info('翻译开启,开始监');
// 监听音频数据
_audioSubscription = jieli.audioDataStream.listen((data) async {
// Logger.info('翻译开启,开始监听解码后的数据=${data}');
final pushAudioDataStream = await _asrService.pushAudioData(data);
});
isRecognizing.value = true;
// 监听识别结果
_recognitionSubscription?.cancel();
_recognitionSubscription = recognitionStream.listen(_handleRecognitionEvent);
_recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent);
} catch (e) {
isRecognizing.value = false;
Logger.error('启动语音识别失败: $e');
@ -205,10 +266,12 @@ class TranslationController extends GetxController {
// 处理识别事件
void _handleRecognitionEvent(RecognitionEvent event) {
if (event.type == RecognitionEventType.finalResult && event.text.isNotEmpty) {
if (event.type == RecognitionEventType.finalResult &&
event.text.isNotEmpty) {
detectedLanguageCode = event.detectedLanguage;
handleFinalResult(event.text);
} else if (event.type == RecognitionEventType.intermediateResult && event.text.isNotEmpty) {
} else if (event.type == RecognitionEventType.intermediateResult &&
event.text.isNotEmpty) {
if (event.detectedLanguage.isNotEmpty) {
detectedLanguageCode = event.detectedLanguage;
}
@ -231,21 +294,25 @@ class TranslationController extends GetxController {
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
isRecognizing.value = false;
// 如果当前有中间结果,将其作为最终结果处理
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) {
if (translationHistory.isNotEmpty &&
translationHistory.last.isIntermediate &&
translationHistory.last.sourceText.isNotEmpty) {
// 标记为最终结果
translationHistory.last.isIntermediate = false;
translationHistory.refresh();
// 如果还没有翻译结果,尝试翻译
if (translationHistory.last.translatedText.isEmpty) {
await translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: true);
await translateText(translationHistory.last.sourceText,
translationHistory.last.timestamp,
isFinal: true);
}
saveTranslationHistory();
}
currentSessionId = null;
} catch (e) {
Logger.error('停止语音识别失败: $e');
@ -265,10 +332,11 @@ class TranslationController extends GetxController {
targetLanguageCode: targetLanguageCode.value,
timestamp: DateTime.now(),
sessionId: currentSessionId!,
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId,
isFirstInSession: translationHistory.isEmpty ||
translationHistory.last.sessionId != currentSessionId,
isIntermediate: true,
);
translationHistory.add(newItem);
// 添加新项后滚动到底部
_scrollToBottom();
@ -283,8 +351,12 @@ class TranslationController extends GetxController {
// 防抖处理翻译请求
_translationDebounceTimer?.cancel();
_translationDebounceTimer = Timer(const Duration(milliseconds: 300), () {
if (translationHistory.isNotEmpty && translationHistory.last.isIntermediate && translationHistory.last.sourceText.isNotEmpty) {
translateText(translationHistory.last.sourceText, translationHistory.last.timestamp, isFinal: false);
if (translationHistory.isNotEmpty &&
translationHistory.last.isIntermediate &&
translationHistory.last.sourceText.isNotEmpty) {
translateText(translationHistory.last.sourceText,
translationHistory.last.timestamp,
isFinal: false);
}
});
}
@ -301,7 +373,7 @@ class TranslationController extends GetxController {
// 更新后滚动到底部
_scrollToBottom();
saveTranslationHistory();
// 使用项目的时间戳进行翻译
translateText(text, translationHistory.last.timestamp, isFinal: true);
} else {
@ -312,16 +384,17 @@ class TranslationController extends GetxController {
sourceLanguageCode: sourceLanguageCode.value,
targetLanguageCode: targetLanguageCode.value,
timestamp: DateTime.now(),
sessionId: currentSessionId ?? DateTime.now().millisecondsSinceEpoch.toString(),
isFirstInSession: translationHistory.isEmpty || translationHistory.last.sessionId != currentSessionId,
sessionId: currentSessionId ??
DateTime.now().millisecondsSinceEpoch.toString(),
isFirstInSession: translationHistory.isEmpty ||
translationHistory.last.sessionId != currentSessionId,
isIntermediate: false,
);
translationHistory.add(newItem);
translationHistory.refresh();
_scrollToBottom();
saveTranslationHistory();
// 使用新项目的时间戳进行翻译
translateText(text, newItem.timestamp, isFinal: true);
@ -329,7 +402,8 @@ class TranslationController extends GetxController {
}
// 翻译文本
Future<String?> translateText(String sourceText, DateTime timestamp, {bool isFinal = false}) async {
Future<String?> translateText(String sourceText, DateTime timestamp,
{bool isFinal = false}) async {
if (sourceText.isEmpty) return null;
isTranslating.value = true;
@ -338,8 +412,10 @@ class TranslationController extends GetxController {
try {
// 根据检测到的语言确定源语言和目标语言
final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value;
final detectedSourceLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
final detectedTargetLanguageCode = shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value;
final detectedSourceLanguageCode =
shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
final detectedTargetLanguageCode =
shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value;
// 调用翻译服务
translationResult = await _translationService.translateText(
@ -352,25 +428,28 @@ class TranslationController extends GetxController {
// 查找匹配的项目 - 优先使用timestamp查找
int index = -1;
index = translationHistory.indexWhere((item) =>
item.timestamp.isAtSameMomentAs(timestamp));
index = translationHistory
.indexWhere((item) => item.timestamp.isAtSameMomentAs(timestamp));
if (index != -1) {
// 更新历史项
translationHistory[index].translatedText = translationResult;
translationHistory[index].sourceLanguageCode = detectedSourceLanguageCode;
translationHistory[index].targetLanguageCode = detectedTargetLanguageCode;
translationHistory[index].sourceLanguageCode =
detectedSourceLanguageCode;
translationHistory[index].targetLanguageCode =
detectedTargetLanguageCode;
translationHistory.refresh();
saveTranslationHistory();
// 更新后滚动到底部
_scrollToBottom();
// 播放TTS - 只在最终结果时播放
if (isTtsEnabled.value && isFinal) {
// Logger.info('播放翻译文本: ${translationResult}');
await playTranslatedText(translationResult);
}
} else {
Logger.info('未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}');
Logger.info(
'未找到匹配的历史项 ${sourceText} ${timestamp.toIso8601String() ?? "无时间戳"}');
}
}
} catch (e) {
@ -388,9 +467,12 @@ class TranslationController extends GetxController {
try {
final bool shouldSwap = detectedLanguageCode == targetLanguageCode.value;
final detectedTargetLanguageCode = shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
final voiceName = _languageManager.getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ?? 'en-US-AriaNeural';
final detectedTargetLanguageCode =
shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
final voiceName = _languageManager
.getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ??
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text);
} catch (e) {
@ -404,7 +486,7 @@ class TranslationController extends GetxController {
if (isRecognizing.value) {
await stopRecognition();
}
_translationDebounceTimer?.cancel();
await _ttsService.stop();
} catch (e) {
@ -415,9 +497,11 @@ class TranslationController extends GetxController {
// 播放特定翻译项
Future<void> playTranslationItem(TranslationItem item) async {
if (item.translatedText.isEmpty) return;
try {
final voiceName = _languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ?? 'en-US-AriaNeural';
final voiceName =
_languageManager.getTtsVoiceNameByAsrCode(item.targetLanguageCode) ??
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(item.translatedText);
} catch (e) {
@ -428,12 +512,15 @@ class TranslationController extends GetxController {
// 播放文本
Future<void> playText(String text, {String? languageCode}) async {
if (text.isEmpty) return;
try {
final voiceName = languageCode != null
? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ?? 'en-US-AriaNeural'
: _languageManager.getTtsVoiceNameByAsrCode(targetLanguageCode.value) ?? 'en-US-AriaNeural';
final voiceName = languageCode != null
? _languageManager.getTtsVoiceNameByAsrCode(languageCode) ??
'en-US-AriaNeural'
: _languageManager
.getTtsVoiceNameByAsrCode(targetLanguageCode.value) ??
'en-US-AriaNeural';
await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text);
} catch (e) {
@ -443,7 +530,8 @@ class TranslationController extends GetxController {
// 滚动到底部
void _scrollToBottom() {
if (scrollController.hasClients && (translationHistory.isNotEmpty || currentSessionId != null)) {
if (scrollController.hasClients &&
(translationHistory.isNotEmpty || currentSessionId != null)) {
try {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
@ -460,33 +548,33 @@ class TranslationController extends GetxController {
void setupScrollListener() {
// 简化后的空方法,保留接口兼容性
}
// 底部控制栏高度
double _bottomBarHeight = 0.0;
// 设置底部控制栏高度
void setBottomBarHeight(double height) {
_bottomBarHeight = height;
}
// 获取底部控制栏高度
double get bottomBarHeight => _bottomBarHeight;
// 切换源语言和目标语言
void swapLanguages() {
stopAll();
final tempName = sourceLanguage.value;
sourceLanguage.value = targetLanguage.value;
targetLanguage.value = tempName;
final tempCode = sourceLanguageCode.value;
sourceLanguageCode.value = targetLanguageCode.value;
targetLanguageCode.value = tempCode;
// 保存语言设置
_saveLanguageSettings();
// 更新ASR支持的语言并重新初始化
_reinitializeAsrService();
}
@ -495,17 +583,17 @@ class TranslationController extends GetxController {
void setSourceLanguage(String language) {
if (sourceLanguage.value != language) {
stopAll();
sourceLanguage.value = language;
final asrCode = _languageManager.getAsrCodeByChineseName(language);
if (asrCode != null) {
sourceLanguageCode.value = asrCode;
}
// 保存语言设置
_saveLanguageSettings();
// 更新ASR支持的语言并重新初始化
_reinitializeAsrService();
}
@ -515,35 +603,38 @@ class TranslationController extends GetxController {
void setTargetLanguage(String language) {
if (targetLanguage.value != language) {
stopAll();
targetLanguage.value = language;
final asrCode = _languageManager.getAsrCodeByChineseName(language);
if (asrCode != null) {
targetLanguageCode.value = asrCode;
}
// 保存语言设置
_saveLanguageSettings();
// 更新ASR支持的语言并重新初始化
_reinitializeAsrService();
}
}
// 重新初始化ASR服务以支持新的语言
Future<void> _reinitializeAsrService() async {
try {
// 更新ASR支持的语言
final List<String> asrSupportedLanguages = [sourceLanguageCode.value, targetLanguageCode.value];
final List<String> asrSupportedLanguages = [
sourceLanguageCode.value,
targetLanguageCode.value
];
Logger.info('重新初始化ASR服务,支持语言: $asrSupportedLanguages');
// 重新初始化ASR服务
await _asrService.initialize(
supportedLanguages: asrSupportedLanguages,
);
Logger.info('ASR服务重新初始化成功');
} catch (e) {
Logger.error('重新初始化ASR服务失败: $e');
@ -556,15 +647,17 @@ class TranslationController extends GetxController {
if (historyJson != null) {
try {
final List<dynamic> historyList = jsonDecode(historyJson);
final List<TranslationItem> allHistory = historyList.map((item) => TranslationItem.fromJson(item)).toList();
final List<TranslationItem> allHistory =
historyList.map((item) => TranslationItem.fromJson(item)).toList();
// 只保留最新的10条记录
if (allHistory.length > _maxHistoryItems) {
translationHistory.value = allHistory.sublist(allHistory.length - _maxHistoryItems);
translationHistory.value =
allHistory.sublist(allHistory.length - _maxHistoryItems);
} else {
translationHistory.value = allHistory;
}
// 使用微任务确保在视图完全渲染后滚动到底部
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToBottom();
@ -579,11 +672,13 @@ class TranslationController extends GetxController {
// 保存翻译历史
void saveTranslationHistory() {
if (translationHistory.length > _maxHistoryItems) {
translationHistory.removeRange(0, translationHistory.length - _maxHistoryItems);
translationHistory.removeRange(
0, translationHistory.length - _maxHistoryItems);
}
try {
final List<Map<String, dynamic>> historyJson = translationHistory.map((item) => item.toJson()).toList();
final List<Map<String, dynamic>> historyJson =
translationHistory.map((item) => item.toJson()).toList();
_storage.write(_historyKey, jsonEncode(historyJson));
} catch (e) {
Logger.error('保存翻译历史失败: $e');
@ -644,7 +739,8 @@ class TranslationItem {
sourceLanguageCode: json['sourceLanguageCode'] ?? 'zh-CN',
targetLanguageCode: json['targetLanguageCode'] ?? 'en-US',
timestamp: timestamp,
sessionId: json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(),
sessionId:
json['sessionId'] ?? DateTime.now().millisecondsSinceEpoch.toString(),
isFirstInSession: json['isFirstInSession'] ?? false,
isIntermediate: false, // 从存储加载的都是最终结果
);
@ -662,16 +758,18 @@ class TranslationItem {
// 不保存isIntermediate,因为存储的都是最终结果
};
}
// 获取源语言的中文名称
String get sourceLanguageName {
final languageManager = Get.find<LanguageManager>();
return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ?? '未知语言';
return languageManager.getChineseNameByAsrCode(sourceLanguageCode) ??
'未知语言';
}
// 获取目标语言的中文名称
String get targetLanguageName {
final languageManager = Get.find<LanguageManager>();
return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? '未知语言';
return languageManager.getChineseNameByAsrCode(targetLanguageCode) ??
'未知语言';
}
}

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

@ -28,7 +28,7 @@ class AzureAsrHelper(private val context: Context) {
private var supportedLanguages = arrayOf("zh-CN")
// 是否使用回音消除 - 内部控制常量
private val useEchoCancellation = false
private val useEchoCancellation = true
// 自定义音频处理相关
private var customAudioProcessor: CustomAudioProcessor? = null
@ -36,6 +36,7 @@ class AzureAsrHelper(private val context: Context) {
private var audioConfig: AudioConfig? = null
// 公共方法:允许外部写入音频数据
fun pushAudioData(data: ByteArray) {
FileLogger.d(TAG, "pushAudioData,data=${data},pushStream=${pushStream}")
pushStream?.write(data) // 使用 SDK 的正确方法(如 push)
}
@ -85,6 +86,7 @@ class AzureAsrHelper(private val context: Context) {
// 创建识别器
try {
if (useEchoCancellation) {
FileLogger.d(TAG, "如果使用回音消除,创建自定义音频输入流")
// 如果使用回音消除,创建自定义音频输入流
setupCustomAudioProcessing()
@ -95,13 +97,13 @@ class AzureAsrHelper(private val context: Context) {
recognizer = SpeechRecognizer(speechConfig, audioConfig)
}
} else {
// 使用默认麦克风输入
if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
} else {
recognizer = SpeechRecognizer(speechConfig)
}
// // 使用默认麦克风输入
// if (isAutoDetectLanguage) {
// val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
// recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
// } else {
// recognizer = SpeechRecognizer(speechConfig)
// }
}
FileLogger.d(TAG, "Azure 语音服务初始化成功")
@ -140,13 +142,13 @@ class AzureAsrHelper(private val context: Context) {
recognizer = SpeechRecognizer(speechConfig, audioConfig)
}
} else {
// 使用默认麦克风输入
if (isAutoDetectLanguage) {
val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
} else {
recognizer = SpeechRecognizer(speechConfig)
}
// // 使用默认麦克风输入
// if (isAutoDetectLanguage) {
// val autoDetectConfig = AutoDetectSourceLanguageConfig.fromLanguages(supportedLanguages.toList())
// recognizer = SpeechRecognizer(speechConfig, autoDetectConfig)
// } else {
// recognizer = SpeechRecognizer(speechConfig)
// }
}
return true
} else {

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

@ -4,6 +4,7 @@ import android.content.Context
import android.app.Activity
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.annotation.NonNull
import com.yunqiinnovation.azure_speech.utils.FileLogger
@ -91,6 +92,21 @@ class AzureSpeechPlugin: FlutterPlugin {
} catch (e: Exception) {
result.error("INITIALIZATION_ERROR", e.message, null)
}
}
"pushAudioData" -> {
val audioData = call.argument<ByteArray>("AudioData")
try {
Log.d("","AsrMethodHandler,pushAudioData${audioData}")
if (audioData != null) {
val success = azureAsrHelper.pushAudioData(audioData)
result.success(success)
}
} catch (e: Exception) {
result.error("pushAudioData_ERROR", e.message, null)
}
}
"recognizeOnce" -> {
azureAsrHelper.recognizeOnce(object : AzureAsrHelper.RecognizeCallback {

2
local_plugins/jieli/android/src/main/kotlin/com/yunqiinnovation/jieli/JieliPlugin.kt

@ -92,7 +92,7 @@ class JieliPlugin: FlutterPlugin, MethodCallHandler, ActivityAware, EventChannel
override fun onAudioDataReceived(data: ByteArray) {
pluginScope.launch(Dispatchers.Main) {
Log.d("","数据流onAudioDataReceived${data}")
// Log.d("","数据流onAudioDataReceived${data}")
sendEvent(EVENT_TYPE_AUDIO_DATA, data)
}
}

2
local_plugins/jieli/lib/jieli.dart

@ -127,7 +127,7 @@ class Jieli {
break;
case EventType.audioData:
if (data is Uint8List) {
print('收到ble通道消息: $data');
//print('收到ble通道消息: $data');
_audioController.add(data);
}
break;

2
local_plugins/jl_opus/android/src/main/kotlin/com/yunqiinnovation/jl_opus/JlOpusPlugin.kt

@ -324,7 +324,7 @@ class JlOpusPlugin: FlutterPlugin, MethodCallHandler {
opusManager?.startDecodeStream(option, object : OnDecodeStreamCallback {
override fun onDecodeStream(data: ByteArray?) {
if (data != null) {
Log.d(TAG, "onDecodeStream${data}")
//Log.d(TAG, "onDecodeStream${data}")
callback.onAudioDataReceived1(data)
sendEventOnMainThread(mapOf(
"event" to "onDecodeStream",

2
local_plugins/open_ai_service/android/src/main/kotlin/com/yunqiinnovation/open_ai_service/MCPClient.kt

@ -168,7 +168,7 @@ class MCPSubClient(
private fun processToolsList(toolsResult: ListToolsResult?) {
try {
// 将MCP工具列表转换为ToolUnion列表
tools = toolsResult?.tools?.filter { tool -> config.tools == null || config.tools.contains(tool.name) == true
tools = toolsResult?.tools?.filter { tool -> config.tools.isNullOrEmpty() || config.tools.contains(tool.name)
}?.map { tool ->
// 转换为ToolUnion格式
ToolUnion.ofTool(

BIN
local_plugins/opus/.gradle/buildOutputCleanup/buildOutputCleanup.lock

Binary file not shown.

4
local_plugins/opus/.gradle/buildOutputCleanup/cache.properties

@ -1,2 +1,2 @@
#Tue Apr 15 09:36:50 IST 2025
gradle.version=8.10
#Thu Apr 24 10:52:53 CST 2025
gradle.version=8.9

86
local_plugins/spotify_sdk/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkService.kt → local_plugins/spotify_sdk/android/src/main/kotlin/de/minimalme/spotify_sdk/SpotifySdkHelper.kt

@ -2,7 +2,6 @@ package de.minimalme.spotify_sdk
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.util.Log
import com.spotify.android.appremote.api.ConnectionParams
import com.spotify.android.appremote.api.Connector
@ -20,46 +19,33 @@ import com.spotify.android.appremote.api.error.UserNotAuthorizedException
import com.spotify.sdk.android.auth.AuthorizationClient
import com.spotify.sdk.android.auth.AuthorizationRequest
import com.spotify.sdk.android.auth.AuthorizationResponse
import de.minimalme.spotify_sdk.subscriptions.CapabilitiesChannel
import de.minimalme.spotify_sdk.subscriptions.ConnectionStatusChannel
import de.minimalme.spotify_sdk.subscriptions.PlayerContextChannel
import de.minimalme.spotify_sdk.subscriptions.PlayerStateChannel
import de.minimalme.spotify_sdk.subscriptions.UserStatusChannel
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.PluginRegistry
import kotlinx.event.SetEvent
import kotlinx.event.event
class SpotifySdkService(private val context: Context? = null) {
class SpotifySdkHelper(private val context: Context? = null) {
// application context
private var applicationContext : Context? = null
private var applicationActivity : Activity? = null
// method channel
private lateinit var methodChannel : MethodChannel
private val channelName = "spotify_sdk"
private val loggingTag = "spotify_sdk"
private val loggingTag = "spotify_sdk"
private val errorConnecting = "errorConnecting"
private val errorDisconnecting = "errorDisconnecting"
private val errorConnection = "errorConnection"
private val errorAuthenticationToken = "authenticationTokenError"
private val requestCodeAuthentication = 1337
private var spotifyAppRemote: SpotifyAppRemote? = null
private var playerApi: PlayerApi? = null
//-- Method implementations
private fun connectToSpotify(clientId: String?, redirectUrl: String?, result: MethodChannel.Result) {
fun initialize(clientId: String, redirectUrl: String): Boolean {
getAccessToken(clientId,redirectUrl,"");
connectToSpotify(clientId,redirectUrl)
return true;
}
public fun connectToSpotify(clientId: String?, redirectUrl: String?) {
if (clientId.isNullOrBlank() || redirectUrl.isNullOrBlank()) {
result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "")
// result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "")
} else {
val connectionParams = ConnectionParams.Builder(clientId)
.setRedirectUri(redirectUrl)
@ -75,7 +61,7 @@ class SpotifySdkService(private val context: Context? = null) {
initiallyConnected = true
Log.i(loggingTag, "App Remote successfully connected")
result.success(true)
// result.success(true)
}
override fun onFailure(throwable: Throwable) {
@ -155,25 +141,23 @@ class SpotifySdkService(private val context: Context? = null) {
// )
} else {
// throw exception as the connect method
result.error(errorCode, errorMessage, errorDetails)
// result.error(errorCode, errorMessage, errorDetails)
}
}
})
}
}
private fun getAccessToken(clientId: String?, redirectUrl: String?, scope: String?, result: MethodChannel.Result) {
public fun getAccessToken(clientId: String?, redirectUrl: String?, scope: String?) {
if (applicationActivity == null) {
throw IllegalStateException("getAccessToken needs a foreground activity")
}
if (clientId.isNullOrBlank() || redirectUrl.isNullOrBlank()) {
result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "")
// result.error(errorConnecting, "client id or redirectUrl are not set or have invalid format", "")
} else {
//Convert String? scope to Array. Delimiter set as comma ","
val scopeArray = scope?.split(",")?.toTypedArray()
// methodConnectToSpotify.checkAndSetPendingOperation(result)
val builder = AuthorizationRequest.Builder(
clientId,
AuthorizationResponse.Type.TOKEN,
@ -190,38 +174,18 @@ class SpotifySdkService(private val context: Context? = null) {
}
}
private fun disconnectFromSpotify(result: MethodChannel.Result) {
public fun disconnectFromSpotify() {
if (spotifyAppRemote != null && spotifyAppRemote!!.isConnected) {
SpotifyAppRemote.disconnect(spotifyAppRemote)
// emit connection terminated event
// connStatusEventChannel(
// ConnectionStatusChannel.ConnectionEvent(
// false,
// "Successfully disconnected from Spotify.",
// null,
// null
// )
// )
// method success
result.success(true)
// result.success(true)
} else if (!spotifyAppRemote!!.isConnected) {
result.error(errorDisconnecting, "could not disconnect spotify remote", "you are not connected, no need to disconnect")
// result.error(errorDisconnecting, "could not disconnect spotify remote", "you are not connected, no need to disconnect")
} else {
result.error(errorDisconnecting, "could not disconnect spotify remote", "spotifyAppRemote is not set")
// result.error(errorDisconnecting, "could not disconnect spotify remote", "spotifyAppRemote is not set")
}
}
private fun play(spotifyUri: String?){
// if (playerApi != null && !spotifyUri.isNullOrBlank()) {
// playerApi.play(spotifyUri)
// .setResultCallback { result.success(true) }
// .setErrorCallback { throwable -> result.error(errorPlay, "error when playing uri: $spotifyUri", throwable.toString()) }
// } else if (spotifyUri.isNullOrBlank()) {
// result.error(errorPlay, "spotifyUri has invalid format or is not set", "")
// } else {
// spotifyRemoteAppNotSetError()
// }
public fun play(spotifyUri: String?){
if (playerApi != null && !spotifyUri.isNullOrBlank()) {
playerApi!!.play(spotifyUri)
} else if (spotifyUri.isNullOrBlank()) {
@ -230,14 +194,8 @@ class SpotifySdkService(private val context: Context? = null) {
}
}
private fun pause(){
// if (playerApi != null) {
// playerApi.pause()
// .setResultCallback { result.success(true) }
// .setErrorCallback { throwable -> result.error(errorPause, "error when pausing", throwable.toString()) }
// } else {
// spotifyRemoteAppNotSetError()
// }
public fun pause(){
if (playerApi != null) {
playerApi!!.pause()
.setResultCallback {}
Loading…
Cancel
Save