diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 0b53e4a8f..6f29c8c95 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -61,7 +61,8 @@ android {
manifestPlaceholders.putAll(
mapOf(
"redirectSchemeName" to "spotify-sdk",
- "redirectHostName" to "auth"
+ "redirectHostName" to "auth",
+ "applicationName" to "com.saitong.voitrans.MyApp" // 新增:声明自定义 Application
)
)
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index e1ca0f551..24d99722b 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
xmlns:tools="http://schemas.android.com/tools">
+
@@ -84,8 +85,7 @@
+ Log.d(TAG, "🔗 处理待处理的深链接")
+ handleDeepLink(intent)
+ pendingDeepLink = null
+ }
+ }, 1000) // 延迟1秒确保Flutter完全准备好
+
+ Log.d(TAG, "✅ configureFlutterEngine 完成")
+ }
+
+ // 处理深链接的统一方法
+ private fun handleDeepLink(intent: Intent?) {
+ intent?.let {
+ if (it.action == Intent.ACTION_VIEW && it.data != null) {
+ val uri = it.data
+ Log.d(TAG, "🔗 处理深链接: $uri")
+
+ try {
+ Log.d(TAG, "🔗 开始发送深链接到 Flutter")
+
+ // 通过 MethodChannel 将深链接信息传递给 Flutter
+ flutterEngine?.dartExecutor?.binaryMessenger?.let { messenger ->
+ Log.d(TAG, "🔗 获取到 BinaryMessenger")
+
+ val channel = MethodChannel(
+ messenger,
+ "com.saitong.voitrans/deeplink"
+ )
+
+ val arguments = mapOf(
+ "url" to uri.toString(),
+ "scheme" to uri?.scheme,
+ "host" to uri?.host,
+ "path" to uri?.path,
+ "query" to uri?.query,
+ "queryParameters" to uri?.queryParameterNames?.associateWith {
+ uri.getQueryParameter(it)
+ }
+ )
+
+ Log.d(TAG, "🔗 准备发送参数: $arguments")
+
+ channel.invokeMethod("handleDeepLink", arguments)
+ Log.d(TAG, "✅ 深链接信息已发送到 Flutter")
+ } ?: run {
+ Log.w(TAG, "❌ BinaryMessenger 为空,无法发送深链接")
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "❌ 处理深链接时出错", e)
+ }
+ } else {
+ Log.d(TAG, "🔗 Intent 不是深链接或数据为空")
+ }
+ }
}
override fun onDestroy() {
@@ -112,5 +237,5 @@ class MainActivity: FlutterActivity() {
// 反射方法可能不存在,忽略
}
}
-}
+}
diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MyApplication.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MyApplication.kt
new file mode 100644
index 000000000..17e5ee3b0
--- /dev/null
+++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MyApplication.kt
@@ -0,0 +1,144 @@
+package com.saitong.voitrans
+
+import android.app.ActivityManager
+import android.content.Context
+import android.util.Log
+import io.flutter.app.FlutterApplication
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.embedding.engine.FlutterEngineCache
+import io.flutter.embedding.engine.dart.DartExecutor
+import io.flutter.plugins.GeneratedPluginRegistrant
+
+// Application.kt
+class MyApp : FlutterApplication() {
+
+ companion object {
+ private const val TAG = "MyApp"
+ private const val ENGINE_ID = "main_engine"
+
+ // 标记 Engine 是否已经执行过 Dart 入口点
+ @Volatile
+ private var isDartEntrypointExecuted = false
+
+ // 记录应用启动时间,用于判断是否为新启动
+ @Volatile
+ private var appStartTime = 0L
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ appStartTime = System.currentTimeMillis()
+ Log.d(TAG, "MyApp onCreate - 进程启动")
+
+ val cache = FlutterEngineCache.getInstance()
+ val existingEngine = cache.get(ENGINE_ID)
+
+ if (existingEngine == null) {
+ Log.d(TAG, "创建新的 FlutterEngine")
+ createAndCacheEngine(cache)
+ } else {
+ Log.d(TAG, "FlutterEngine 已存在,检查状态")
+ // 检查现有 Engine 的状态
+ try {
+ val isExecuting = existingEngine.dartExecutor.isExecutingDart
+ Log.d(TAG, "Engine Dart执行状态: $isExecuting")
+
+ if (!isExecuting && isMainProcess()) {
+ Log.d(TAG, "Engine 存在但Dart未执行,重新执行")
+ existingEngine.dartExecutor.executeDartEntrypoint(
+ DartExecutor.DartEntrypoint.createDefault()
+ )
+ isDartEntrypointExecuted = true
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "检查或重新执行 Dart 入口点失败", e)
+ // 如果失败,重新创建 Engine
+ cache.remove(ENGINE_ID)
+ createAndCacheEngine(cache)
+ }
+ }
+ }
+
+ private fun createAndCacheEngine(cache: FlutterEngineCache) {
+ // 在主进程中总是创建并执行Dart入口点
+ if (isMainProcess()) {
+ Log.d(TAG, "✅ 在主进程创建 FlutterEngine 并执行 Dart")
+ val flutterEngine = FlutterEngine(this)
+
+ // 注册插件(只在这里注册一次)
+ Log.d(TAG, "📦 注册 Flutter 插件")
+ GeneratedPluginRegistrant.registerWith(flutterEngine)
+
+ try {
+ Log.d(TAG, "🚀 开始执行 Dart 入口点")
+ flutterEngine.dartExecutor.executeDartEntrypoint(
+ DartExecutor.DartEntrypoint.createDefault()
+ )
+ isDartEntrypointExecuted = true
+ cache.put(ENGINE_ID, flutterEngine)
+ Log.d(TAG, "✅ FlutterEngine 创建并缓存成功")
+ } catch (e: Exception) {
+ Log.e(TAG, "❌ 执行 Dart 入口点失败", e)
+ // 如果执行失败,仍然缓存 Engine,但标记为未执行
+ isDartEntrypointExecuted = false
+ cache.put(ENGINE_ID, flutterEngine)
+ }
+ } else {
+ Log.d(TAG, "⚠️ 在非主进程,仅创建 FlutterEngine")
+ // 非主进程只创建 Engine,不执行 Dart 入口点
+ val flutterEngine = FlutterEngine(this)
+ cache.put(ENGINE_ID, flutterEngine)
+ }
+ }
+
+ private fun isMainProcess(): Boolean {
+ val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ val processes = am.runningAppProcesses ?: return true
+ val mainProcessName = packageName
+ val myPid = android.os.Process.myPid()
+
+ for (process in processes) {
+ if (process.pid == myPid) {
+ return process.processName == mainProcessName
+ }
+ }
+ return true
+ }
+
+ private fun isAppInForeground(): Boolean {
+ val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ val processes = am.runningAppProcesses ?: return false
+ val pkg = packageName
+ for (p in processes) {
+ if (p.processName == pkg &&
+ p.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun isAppBeingLaunched(): Boolean {
+ // 改进的启动检测逻辑
+ val currentTime = System.currentTimeMillis()
+ val isRecentStart = (currentTime - appStartTime) < 3000 // 3秒内认为是新启动
+
+ if (isRecentStart) {
+ Log.d(TAG, "检测到应用新启动")
+ return true
+ }
+
+ // 备用检测方法(兼容旧版本)
+ return try {
+ val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ val tasks = am.getRunningTasks(1)
+ val result = tasks.isNotEmpty() && tasks[0].topActivity?.packageName == packageName
+ Log.d(TAG, "通过 RunningTasks 检测启动状态: $result")
+ result
+ } catch (e: Exception) {
+ Log.w(TAG, "无法获取 RunningTasks,可能缺少权限", e)
+ // 如果无法获取任务信息,默认认为是启动状态
+ true
+ }
+ }
+}
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 1087023b6..ec7521c06 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -91,6 +91,9 @@
google.navigation
googlemaps
comgooglemaps
+ baidumap
+ amapuri
+ maps
LSRequiresIPhoneOS
diff --git a/lib/main.dart b/lib/main.dart
index 6cfa3d668..5a0941bdf 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,37 +1,104 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:flutter_easyloading/flutter_easyloading.dart';
-import 'package:flutter_intro/flutter_intro.dart';
-import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get/get.dart';
-import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_storage/get_storage.dart';
+import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
-import 'core/bindings/initial_binding.dart';
+
import 'routes/app_pages.dart';
import 'routes/app_routes.dart';
import 'core/translations/app_translations.dart';
import 'core/themes/app_theme.dart';
import 'core/themes/theme_service.dart';
-import '../../data/services/login_service.dart';
+import 'core/bindings/initial_binding.dart';
+import 'data/services/login_service.dart';
+import 'dart:ui' as ui;
+import 'modules/splash/controllers/splash_controller.dart';
void main() async {
- // 确保Flutter绑定初始化
WidgetsFlutterBinding.ensureInitialized();
- await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
-
- // 初始化存储 - 在创建应用之前初始化
+ // 初始化存储
await GetStorage.init();
- // 初始化环境变量
- await dotenv.load();
+ // 加载环境变量
+ await dotenv.load(fileName: ".env");
+ print("环境变量加载成功");
+
+ // 初始化登录服务
+ Get.put(LoginService());
+
+ // 延迟初始化深链接处理器,确保Flutter完全准备好
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ Future.delayed(const Duration(milliseconds: 1500), () {
+ _initDeepLinkHandler();
+ });
+ });
+
+ runApp(MainApp());
+}
+
+// 初始化深链接处理器
+void _initDeepLinkHandler() {
+ print("🔗 初始化深链接处理器");
+
+ const platform = MethodChannel('com.saitong.voitrans/deeplink');
+
+ platform.setMethodCallHandler((call) async {
+ print("🔗 Flutter 收到深链接调用: ${call.method}");
+
+ if (call.method == 'handleDeepLink') {
+ final arguments = call.arguments as Map;
+ print("🔗 深链接参数: $arguments");
- // 初始化登录服务(包含Firebase初始化)
- LoginService();
+ final url = arguments['url'] as String?;
+ if (url != null) {
+ print("🔗 处理深链接 URL: $url");
+ await _handleDeepLinkNavigation(url);
+ }
+ }
+ });
- // 运行应用
- runApp(const MainApp());
+ print("✅ 深链接处理器初始化完成");
+}
+
+// 处理深链接导航
+Future _handleDeepLinkNavigation(String url) async {
+ print("🔗 开始处理深链接导航: $url");
+
+ try {
+ final uri = Uri.parse(url);
+ print("🔗 解析URI: scheme=${uri.scheme}, host=${uri.host}, path=${uri.path}");
+
+ if (uri.scheme == 'deepvoice' && uri.host == 'qqmusic') {
+ print("🔗 检测到QQ音乐登录回调");
+
+ // 等待SplashController注册
+ int retryCount = 0;
+ const maxRetries = 10;
+
+ while (retryCount < maxRetries) {
+ try {
+ if (Get.isRegistered()) {
+ print("🔗 找到SplashController,处理QQ音乐登录");
+ final controller = Get.find();
+ controller.handleQQMusicLogin();
+ return;
+ }
+ } catch (e) {
+ print("🔗 获取SplashController失败: $e");
+ }
+
+ retryCount++;
+ print("🔗 等待SplashController注册 ($retryCount/$maxRetries)");
+ await Future.delayed(const Duration(milliseconds: 500));
+ }
+
+ print("❌ 超时等待SplashController注册");
+ }
+ } catch (e) {
+ print("❌ 处理深链接时出错: $e");
+ }
}
class MainApp extends StatelessWidget {
@@ -39,46 +106,27 @@ class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
- // 获取保存的主题设置
- final themeMode = ThemeService().getThemeMode();
+ // 在应用构建时立即初始化深链接处理
+ _initDeepLinkHandler();
+ // 使用 ScreenUtilInit 包装整个应用
return ScreenUtilInit(
- designSize: const Size(375, 812),
+ designSize: const Size(375, 812), // 设计稿尺寸,根据你的设计稿调整
minTextAdapt: true,
splitScreenMode: true,
- builder: (_, child) {
+ builder: (context, child) {
return GetMaterialApp(
- title: 'Voitrans',
+ title: 'VoiTrans',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
- themeMode: themeMode, // 使用提前获取的主题模式
- initialBinding: InitialBinding(),
+ themeMode: ThemeService().getThemeMode(),
initialRoute: Routes.splash,
getPages: AppPages.pages,
- debugShowCheckedModeBanner: false,
+ initialBinding: InitialBinding(),
translations: AppTranslations(),
- localizationsDelegates: const [
- GlobalMaterialLocalizations.delegate,
- GlobalWidgetsLocalizations.delegate,
- GlobalCupertinoLocalizations.delegate,
- ],
- locale: AppTranslations.deviceLocale(),
- fallbackLocale: AppTranslations.getSmartFallbackLocale(),
- supportedLocales: AppTranslations.supportedLocales,
- builder: EasyLoading.init(
- builder: (BuildContext context, Widget? widget) {
- EasyLoading.instance.userInteractions = false;
- return MediaQuery(
- data: MediaQuery.of(context).copyWith(
- textScaler: TextScaler.noScaling,
- ),
- child: Intro(
- padding: EdgeInsets.zero,
- child: widget!,
- ),
- );
- },
- ),
+ locale: Get.deviceLocale,
+ fallbackLocale: const Locale('en', 'US'),
+ debugShowCheckedModeBanner: false,
);
},
);
diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart
index d629117f2..64e353d77 100644
--- a/lib/modules/splash/controllers/splash_controller.dart
+++ b/lib/modules/splash/controllers/splash_controller.dart
@@ -305,6 +305,21 @@ class SplashController extends GetxController {
return false;
}
}
+
+ /// 处理QQ音乐登录回调
+ void handleQQMusicLogin() {
+ try {
+ Logger.i(_tag, '处理QQ音乐登录回调');
+
+ // 这里可以添加具体的QQ音乐登录处理逻辑
+ // 例如:验证登录状态、更新用户信息、导航到相应页面等
+
+ // 如果需要导航到特定页面,可以使用:
+ // Get.toNamed(Routes.qqmusic);
+ } catch (e) {
+ Logger.e(_tag, '处理QQ音乐登录回调失败: $e');
+ }
+ }
}
/// 显示无网络连接对话框并等待用户操作(阻塞版本)
diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
index 99e0ed8e5..c4cab4819 100644
--- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
+++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
@@ -1956,7 +1956,7 @@ object AgentService : CoroutineScope {
)
}
if (playlist.isNotEmpty()) {
- processQQMusicPlayList(playlist)
+// processQQMusicPlayList(playlist)
} else {
Log.w(TAG, "qqmusic_searchandplay_songs 中没有有效的音乐条目")
}
@@ -1969,6 +1969,10 @@ object AgentService : CoroutineScope {
val start = cardNavigation.optString("start", "")
val end = cardNavigation.optString("end", "")
processNavigation(start, end);
+ }else if (toolname == "map_weather") { //导航服务
+ iscard = true
+ }else if (toolname == "maps_weather") { //导航服务
+ iscard = true
}
return mapOf("meta" to metaStr, "broadcast" to broadcast, "notreply" to notreply,"iscard" to iscard);
}
diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
index b294b6a05..6283446dc 100644
--- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
+++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
@@ -81,7 +81,7 @@ class AgentServiceImpl: NSObject {
private let maxIdleSeconds: TimeInterval = 10
var micCapture: MicrophoneCapture!
// 语音识别模式
- private var currentRecognitionMode: String = "normal"
+ public var currentRecognitionMode: String = "normal"
internal var audioPlayer: AudioPlayer?
// private var locationInfo:[String: String]?
@@ -834,6 +834,7 @@ internal func autoHandleFunctionCallResult(toolName: String, functionCallResult:
private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -> [String: Any] {
var broadcast = true
var notreply = false
+ var iscard = false
var metadata: [String: Any] = [:]
metadata[toolName] = jsonDict
@@ -843,7 +844,11 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -
if let _notreply = jsonDict["notreply"] as? Bool {
notreply = _notreply
}
+ if let _iscard = jsonDict["iscard"] as? Bool {
+ iscard = _iscard
+ }
metadata["notreply"] = notreply
+ metadata["iscard"] = iscard
// 处理卡片数据
// if let cardMusic = jsonDict["card_music"] as? [String: Any] {
// processMusicCard(cardMusic)
@@ -854,8 +859,12 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -
processNavigationCard(cardNavigation)
} else if let cardQQMusiclist = jsonDict["data"] as? [[String: Any]], toolName == "qqmusic_searchandplay_songs" {
processQQMusicPlayList(cardQQMusiclist)
+ } else if toolName == "maps_weather" {
+ iscard = true
+ }else if toolName == "map_weather" {
+ iscard = true
}
-
+ metadata["iscard"] = iscard
let metaStr: String
if let jsonData = try? JSONSerialization.data(withJSONObject: metadata, options: []),
let jsonString = String(data: jsonData, encoding: .utf8) {
@@ -1581,9 +1590,14 @@ class AudioPlayer {
}
func playAwaitSound() {
- // 先停止之前的等待音效和定时器
+ //先停止之前的等待音效和定时器
stopAwaitSound()
+ // 修复:通过 agentService 引用访问 currentRecognitionMode
+ if agentService?.currentRecognitionMode == "phone_call" {//电话模式不要嘟嘟嘟。影响回音消除功能
+ return
+ }
+
// 播放等待音效
playLoopSound(named: "await", fileType: "mp3", player: &awaitPlayer,volume:0.2)
diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift
index 30dfd7464..a5857fe4e 100644
--- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift
+++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift
@@ -124,10 +124,33 @@ public class AzureTtsHelper: NSObject, ITtsService {
*/
public func startspeak(sessionid sessionid: String)-> Bool {
self.sessionid = sessionid
-
+ print("startspeak sessionid \(sessionid)")
// 重置当前会话的文本计数
self.currentSessionTextCount = 0
self.pendingTextCount = 0
+
+ do {
+ // 新增:使用 AVAudioSession.sharedInstance 配置为“通话模式 + 扬声器外放”
+ // - 使用 .playAndRecord 以保证通话上下行链路
+ // - 使用 .voiceChat 获得通话处理(AEC 等)
+ // - 使用 .defaultToSpeaker 默认走外放
+ // - 通过 overrideOutputAudioPort(.speaker) 强制切到扬声器
+ let audioSession = AVAudioSession.sharedInstance()
+
+ //try audioSession.setActive(false)
+ try audioSession.setCategory(.playAndRecord,
+ mode: .voiceChat,
+ options: [.defaultToSpeaker,
+ .mixWithOthers,
+ .defaultToSpeaker,
+ .allowBluetoothA2DP,
+ .allowBluetooth])
+ try audioSession.overrideOutputAudioPort(.none)
+ try audioSession.setActive(true)
+ } catch {
+ print("音频会话配置失败: \(error.localizedDescription)")
+ }
+
return true
}
@@ -187,7 +210,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
totalTextCount += text.count
currentSessionTextCount += text.count
- print("流式合成文本\(text.count),总计文本数量:\(totalTextCount),当前会话:\(currentSessionTextCount)")
+ //print("流式合成文本\(text.count),总计文本数量:\(totalTextCount),当前会话:\(currentSessionTextCount)")
// 清理文本并添加到缓冲区
let cleanedText = cleanTextForTTS(text)
diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift
index 857d30041..1d14da379 100644
--- a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift
+++ b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift
@@ -445,30 +445,6 @@ public class MicrophoneCapture: NSObject {
// 安全地处理音频会话
guard let audioSession = audioSession else { return }
- do {
- try audioSession.setActive(false)
-
- if self.hasBluetoothDevices {
- // 设置音频会话参数
- try audioSession.setCategory(.playback,
- mode: .videoChat,
- options: [
- .allowBluetoothA2DP,
- .allowBluetooth
- ]) // 添加音频优先级控制
- } else {
- try audioSession.setCategory(.playback,
- mode: .videoChat,
- options: [
- .mixWithOthers,
- .defaultToSpeaker])
- }
-
- try audioSession.overrideOutputAudioPort(.none)
- try audioSession.setActive(true)
- } catch {
- print("音频会话配置失败: \(error.localizedDescription)")
- }
}
/**
diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift
index 5aef34be7..b37cd56f4 100644
--- a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift
+++ b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift
@@ -193,12 +193,15 @@ public class SimpleAudioReceiver: NSObject {
// 确保正在写入状态
guard self._isWriting else { return }
// 设置音频会话参数
- try audioSession.setCategory(.playAndRecord,
- mode: .videoChat,
- options: [.allowBluetoothA2DP, // 允许蓝牙耳机,不占用hfp链路
- .mixWithOthers,
- .defaultToSpeaker]) // 添加音频优先级控制
- try audioSession.setActive(true)
+ try audioSession.setCategory(.playAndRecord,
+ mode: .voiceChat,
+ options: [.defaultToSpeaker,
+ .mixWithOthers,
+ .defaultToSpeaker,
+ .allowBluetoothA2DP,
+ .allowBluetooth])
+ try audioSession.overrideOutputAudioPort(.none)
+ try audioSession.setActive(true)
try micCapture.startCapture()
try micCapture.audioDataHandler = { data in
self.writeQueue.put(data)
@@ -264,37 +267,7 @@ public class SimpleAudioReceiver: NSObject {
* 强制使用设备内置扬声器或听筒
*/
public func disableBluetoothAudio() {
- // do {
- // print("禁用蓝牙音频功能,切换回正常音频模式")
-
- // // 暂停当前音频引擎
- // if _isWriting {
- // audioEngine?.pause()
- // try audioSession.setActive(false)
- // }
-
- // // 设置音频会话为不允许蓝牙
- // try audioSession.setCategory(
- // .playAndRecord,
- // mode: .videoChat,
- // options: [.defaultToSpeaker, .mixWithOthers] // 移除 .allowBluetooth
- // )
-
- // // 强制使用扬声器
- // try audioSession.overrideOutputAudioPort(.speaker)
-
- // // 重新激活音频会话
- // if _isWriting {
- // try audioSession.setActive(true)
- // try audioEngine?.start()
- // }
-
- // // 更新当前路由状态
- // currentRoute = .speaker
-
- // } catch {
- // print("禁用蓝牙音频失败: \(error.localizedDescription)")
- // }
+
}
/**
@@ -369,57 +342,7 @@ public func restoreOriginalAudioState() {
public func setAudioOutputRoute(_ route: AudioOutputRoute) {
- // do {
- // //
- // if self._isWriting {
- // print("当前正在识别")
- // micCapture.stopCapture()
- // // 先停用以避免冲突
- // try audioSession.setActive(false)
-
- // }
- // print("setAudioOutputRoutecurrent,route=\(route)")
- // switch route {
- // case .speaker:
- // print("setAudioOutputRoutecurrent,speaker")
- // // 使用扬声器时必须用videoChat模式
- // try audioSession.setCategory(
- // .playAndRecord,
- // mode: .videoChat,
- // options: [.defaultToSpeaker,.allowBluetooth,.mixWithOthers]
- // )
- // try audioSession.overrideOutputAudioPort(.speaker)
-
- // case .receiver:
- // // 听筒模式使用voiceChat节省资源
- // try audioSession.setCategory(
- // .playAndRecord,
- // mode: .voiceChat,
- // options: [.allowBluetooth]
- // )
- // try audioSession.overrideOutputAudioPort(.none)
-
- // case .bluetooth:
- // // 完整蓝牙设备支持
- // try audioSession.setCategory(
- // .playAndRecord,
- // mode: .voiceChat,
- // options: [.allowBluetooth, .allowBluetoothA2DP]
- // )
- // try audioSession.overrideOutputAudioPort(.none)
- // // 不需要override,系统自动路由
- // }
- // self.currentRoute = route
- // if self._isWriting {
-
- // // 重新激活
- // try audioSession.setActive(true, options: [.notifyOthersOnDeactivation])
- // try micCapture.startCapture()
-
- // }
- // } catch {
- // print("路由切换失败: \(error)")
- // }
+
}
diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt
index db3684402..a820c23d3 100644
--- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt
+++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt
@@ -124,7 +124,26 @@ private class QQMusicSearchHandler(private val context: Context?) : FunctionHand
qqMusicSingleton.searchSongs(keyword) { searchResult ->
val result = if (searchResult.isSuccess) {
val songs = searchResult.getOrNull() ?: emptyList()
-
+ var playError: Throwable? = null
+ if (songs.isNotEmpty()) {
+ // 从搜索结果中提取歌曲MID列表
+ val songMids = songs.mapNotNull { song ->
+ val songMap = song as Map
+ songMap["mid"] as? String
+ }.filter { it.isNotEmpty() }
+
+ if (songMids.isNotEmpty()) {
+ // 播放搜索到的歌曲列表,从第一首开始播放
+ qqMusicSingleton.playSongs(songMids, 0) { playResult ->
+ Log.d("QQMusicSearchHandler", "播放结果: ${playResult.isSuccess}")
+ if (!playResult.isSuccess) {
+ playError = playResult.exceptionOrNull()
+// Log.e("QQMusicSearchHandler", "播放失败: ${playError?.message}")
+ }
+ }
+ }
+ }
+
// 根据搜索类型处理结果
val processedSongs = when (searchType) {
0 -> { // 歌曲搜索,返回所有结果
@@ -148,7 +167,18 @@ private class QQMusicSearchHandler(private val context: Context?) : FunctionHand
val duration = songMap["duration"] ?: 0
"{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}"
}
- "{\"success\": true,\"iscard\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}"
+
+ if (songs.isNotEmpty()) {
+ if (playError == null){
+ "{\"success\": true, \"iscard\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}"
+ }else{
+ Log.e("QQMusicSearchHandler", "播放失败: ${playError?.message}")
+ "{\"success\": false, \"iscard\": true, \"broadcast\": true, \"message\": \"${playError?.message}\", \"data\": [$songsJson]}"
+ }
+
+ } else {
+ "{\"success\": false, \"message\": \"未找到相关歌曲\"}"
+ }
} else {
val error = searchResult.exceptionOrNull()
"{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"
@@ -163,59 +193,6 @@ private class QQMusicSearchHandler(private val context: Context?) : FunctionHand
}
}
}
-//private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler {
-// override suspend fun handle(arguments: Map): String {
-// return try {
-// val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}"
-// val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲
-// val qqMusicSingleton = QQMusicSingleton.getInstance()
-// var result = ""
-//
-// // 直接调用搜索,内部会自动处理登录检查
-// qqMusicSingleton.searchSongs(keyword) { searchResult ->
-// result = if (searchResult.isSuccess) {
-// val songs = searchResult.getOrNull() ?: emptyList()
-//
-// // 根据搜索类型处理结果
-// val processedSongs = when (searchType) {
-// 0 -> { // 歌曲搜索,只返回第一个结果
-//// if (songs.isNotEmpty()) listOf(songs.first()) else emptyList()
-// songs
-// }
-// else -> { // 专辑、歌手等其他类型,返回所有结果
-// songs
-// }
-// }
-//
-// val songsJson = processedSongs.joinToString(",") { song ->
-// val songMap = song as Map
-// // 添加调试日志
-// Log.d("QQMusicSearchHandler", "原始数据: $songMap")
-// // 修正字段名映射
-// val mid = songMap["mid"] ?: "unknown"
-// val name = songMap["name"] ?: "unknown"
-// val artist = songMap["singer"] ?: "unknown"
-// val album = songMap["album"] ?: ""
-// val pic = songMap["pic"] ?: ""
-// val duration = songMap["duration"] ?: 0
-// "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}"
-// }
-// "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,增在播放\", \"data\": [$songsJson]}"
-// } else {
-// val error = searchResult.exceptionOrNull()
-// "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"
-// }
-// }
-//
-// // 等待回调完成
-// delay(5000)
-// result.ifEmpty { "{\"success\": false, \"message\": \"搜索超时\"}" }
-// } catch (e: Exception) {
-// Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e)
-// "{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}"
-// }
-// }
-//}
private class QQMusicPlaySongsHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map): String {
diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift
index c2e2c3409..9d65f4fdd 100644
--- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift
+++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift
@@ -67,6 +67,7 @@ private class MusicSearchHandler: FunctionHandler {
let result: [String: Any] = [
"success": true,
"broadcast": false,
+ "iscard": true,
"message": "搜索成功,正在播放",
"data": songsJson
]
diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift
index c8c3144aa..2558ca63b 100644
--- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift
+++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift
@@ -162,6 +162,7 @@ private class QQMusicSearchHandler: FunctionHandler {
result = [
"success": true,
"message": "Found \(items.count) songs",
+ "iscard": true,
"broadcast": false,
"data": songs
]
diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt
index ca0969865..077fdf10c 100644
--- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt
+++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt
@@ -1,6 +1,7 @@
package com.yunqiinnovation.qq_music
import android.app.Activity
+import android.app.ActivityManager
import android.content.Context
import android.os.Bundle
import android.os.Handler
@@ -18,12 +19,12 @@ import org.json.JSONTokener
* 负责处理QQ音乐的登录授权流程
*/
class QQMusicLoginManager(private val context: Context) {
-
+
companion object {
private const val TAG = "QQMusicLoginManager"
private const val BASE_SCHEME = "voitrans://"
private const val URI_LOGIN = BASE_SCHEME + "login"
-
+
// 定义错误码常量,替代 LoginExampleActivity 中的常量
const val LOGIN_FAILED = 1
}
@@ -32,7 +33,7 @@ class QQMusicLoginManager(private val context: Context) {
private var qqMusicApi: IQQMusicApi? = null
private lateinit var qqMusicApiWrapper: QQMusicApiWrapper
private val handler = Handler(Looper.getMainLooper())
-
+
/**
* 登录结果回调接口
*/
@@ -51,63 +52,63 @@ class QQMusicLoginManager(private val context: Context) {
*/
fun startLogin(callback: LoginCallback) {
Log.d(TAG, "开始QQ音乐登录流程")
-
+
// 检查配置
if (Config.OPENID_APPID.isEmpty() || Config.OPENID_APP_PRIVATE_KEY.isEmpty()) {
callback.onError("CONFIG_ERROR", "请先配置APPID和私钥")
return
}
-
+
// 初始化绑定助手
qPlayBindHelper = QPlayBindHelper(context)
-
+
// 开始绑定QQ音乐服务
bindQQMusicService(callback)
}
-
+
/**
* 绑定QQ音乐服务
*/
private fun bindQQMusicService(callback: LoginCallback) {
Log.d(TAG, "开始绑定QQ音乐服务")
-
+
qPlayBindHelper.ensureQQMusicBindByStartProcess { success ->
if (success && qPlayBindHelper.isBindQQMusic()) {
Log.d(TAG, "QQ音乐服务绑定成功")
qqMusicApi = qPlayBindHelper.getQQMusicApi()!!
qqMusicApiWrapper = QQMusicApiWrapper(qqMusicApi!!)
-
+
// 检查是否已经授权
checkAuthStatus(callback)
-
+
} else {
Log.e(TAG, "QQ音乐服务绑定失败")
callback.onError("APP_NOT_INSTALLED", "请先安装QQ音乐App才能使用音乐功能")
}
}
}
-
+
/**
* 检查授权状态
*/
private fun checkAuthStatus(callback: LoginCallback) {
Log.d(TAG, "检查授权状态")
-
+
val bundle = Bundle().apply {
putInt(Keys.API_PARAM_KEY_SDK_VERSION, CommonCmd.SDK_VERSION)
putString(Keys.API_PARAM_KEY_PLATFORM_TYPE, Config.BIND_PLATFORM)
}
-
+
try {
val result = qqMusicApi?.execute("hi", bundle)
val code = result?.getInt(Keys.API_RETURN_KEY_CODE)
-
+
when (code) {
ErrorCodes.ERROR_OK -> {
// 已授权,直接请求登录信息
Log.d(TAG, "已授权,直接获取登录信息")
// isBindQQMusicService = true
- startAuthProcess2(callback)
+ startAuthProcess(callback)
}
ErrorCodes.ERROR_API_NO_PERMISSION -> {
// 需要授权
@@ -147,16 +148,46 @@ class QQMusicLoginManager(private val context: Context) {
*/
fun startAuthProcess2(callback: LoginCallback) {
Log.d(TAG, "启动QQ音乐授权流程")
-
+ if (isAppInForeground(context)) {
+ CommonCmd.loginQQMusic(
+ context,
+ context.packageName,
+ BASE_SCHEME
+ )
+// Log.d(TAG, "当前是前台服务")
+ } else {
+ CommonCmd.loginQQMusic(
+ context,
+ context.packageName,
+ BASE_SCHEME,
+ )
+ Log.d(TAG, "当前是后台服务")
+ }
// 启动QQ音乐登录
- CommonCmd.startQQMusicProcess(context,context.packageName)
- callback.onSuccess()
+// CommonCmd.startQQMusicProcess(context,context.packageName)
+ isBindQQMusicService = true
+
+ Handler(Looper.getMainLooper()).postDelayed({
+ callback.onSuccess()
+ }, 1000) // 延迟500ms
+
+
// 等待用户登录完成后进行身份验证
// handler.postDelayed({
// verifyIdentity(callback)
// }, 3000) // 给用户足够的登录时间
}
-
+ private fun isAppInForeground(context: Context): Boolean {
+ val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ val appProcesses = activityManager.runningAppProcesses ?: return false
+ val packageName = context.packageName
+ for (appProcess in appProcesses) {
+ if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName == packageName) {
+ return true
+ }
+ }
+ return false
+ }
/**
* 启动授权流程
*/
diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt
index 5c87e6c94..c69c781cd 100644
--- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt
+++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt
@@ -573,7 +573,8 @@ class QQMusicSingleton private constructor() {
musicManager?.bindQQMusicApiService()
callback(Result.failure(Exception("播放失败: $errorMessage")))
} else if (errorCode == 1030) {
- callback(Result.success(Unit))
+ callback(Result.failure(Exception("播放失败: $errorMessage")))
+// callback(Result.success(Unit))
}else{
callback(Result.failure(Exception("播放失败: $errorMessage")))
}