Browse Source

Merge commit 'ffd8964eb5608c7d551472b2eb242bf2259d7ed3' into new_dev

newdev_shunjiawei
lxm 1 year ago
parent
commit
6ca15228bd
  1. 3
      android/app/build.gradle.kts
  2. 4
      android/app/src/main/AndroidManifest.xml
  3. 135
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt
  4. 144
      android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MyApplication.kt
  5. 3
      ios/Runner/Info.plist
  6. 140
      lib/main.dart
  7. 15
      lib/modules/splash/controllers/splash_controller.dart
  8. 6
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  9. 20
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  10. 27
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureTtsHelper.swift
  11. 24
      local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift
  12. 99
      local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift
  13. 87
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt
  14. 1
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift
  15. 1
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift
  16. 69
      local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt
  17. 3
      local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt

3
android/app/build.gradle.kts

@ -61,7 +61,8 @@ android {
manifestPlaceholders.putAll( manifestPlaceholders.putAll(
mapOf( mapOf(
"redirectSchemeName" to "spotify-sdk", "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). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).

4
android/app/src/main/AndroidManifest.xml

@ -2,6 +2,7 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<!-- 添加必要的权限 --> <!-- 添加必要的权限 -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!-- Android 13+ 前台服务特定类型权限 --> <!-- Android 13+ 前台服务特定类型权限 -->
@ -84,8 +85,7 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTask"
android:taskAffinity=""
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"

135
android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt

@ -13,22 +13,147 @@ import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import io.flutter.embedding.engine.FlutterEngineCache
import io.flutter.plugin.common.MethodChannel // 添加这行导入
class MainActivity: FlutterActivity() { class MainActivity: FlutterActivity() {
private val TAG = "MainActivity" private val TAG = "MainActivity"
private var pendingDeepLink: Intent? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Log.d(TAG, "🚀 MainActivity onCreate 开始")
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// 检查 Intent 信息
intent?.let {
Log.d(TAG, "Intent action: ${it.action}")
Log.d(TAG, "Intent data: ${it.data}")
Log.d(TAG, "Intent extras: ${it.extras}")
}
// 暂存深链接,等Flutter引擎完全准备好后处理
if (intent?.action == Intent.ACTION_VIEW && intent?.data != null) {
pendingDeepLink = intent
Log.d(TAG, "🔗 暂存深链接,等待Flutter引擎准备完成")
}
// 设置日志级别,减少系统级日志 // 设置日志级别,减少系统级日志
suppressSystemLogs() suppressSystemLogs()
Log.d(TAG, "✅ MainActivity onCreate 完成")
}
// 添加 onNewIntent 方法处理深链接
override fun onNewIntent(intent: Intent) {
Log.d(TAG, "🔗 onNewIntent 被调用")
super.onNewIntent(intent)
setIntent(intent) // 更新当前 Intent
// 检查新的 Intent 信息
Log.d(TAG, "New Intent action: ${intent.action}")
Log.d(TAG, "New Intent data: ${intent.data}")
Log.d(TAG, "New Intent extras: ${intent.extras}")
// 处理深链接
handleDeepLink(intent)
}
override fun provideFlutterEngine(context: Context): FlutterEngine? {
Log.d(TAG, "🔍 provideFlutterEngine 被调用")
val engine = FlutterEngineCache.getInstance().get("main_engine")
if (engine == null) {
Log.w(TAG, "❌ FlutterEngine 缓存为空,这可能导致问题")
// 如果缓存为空,让 Flutter 框架创建默认 Engine
return null
}
// 检查 Engine 状态 - 使用更兼容的方式
try {
// 检查 Dart 执行器是否正在运行
val isExecutorRunning = engine.dartExecutor.isExecutingDart
Log.d(TAG, "FlutterEngine Dart 执行状态: $isExecutorRunning")
// 如果需要,可以检查其他状态
if (!isExecutorRunning) {
Log.w(TAG, "⚠️ FlutterEngine Dart 执行器未运行,可能需要重新初始化")
}
} catch (e: Exception) {
Log.w(TAG, "❌ 检查 FlutterEngine 状态时出错", e)
}
Log.d(TAG, "✅ 使用缓存的 FlutterEngine")
return engine
} }
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) Log.d(TAG, "🔧 configureFlutterEngine 开始")
// 检查插件是否已经注册,避免重复注册
val pluginRegistry = flutterEngine.plugins
// 只有在插件未注册时才调用 GeneratedPluginRegistrant
if (!pluginRegistry.has(io.flutter.plugins.pathprovider.PathProviderPlugin::class.java)) {
Log.d(TAG, "📦 注册 Flutter 插件")
super.configureFlutterEngine(flutterEngine)
GeneratedPluginRegistrant.registerWith(flutterEngine)
} else {
Log.d(TAG, "⚠️ 插件已注册,跳过重复注册")
}
// 注册标准Flutter插件 // Flutter引擎配置完成后,处理待处理的深链接
GeneratedPluginRegistrant.registerWith(flutterEngine) Handler(Looper.getMainLooper()).postDelayed({
pendingDeepLink?.let { intent ->
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() { override fun onDestroy() {
@ -112,5 +237,5 @@ class MainActivity: FlutterActivity() {
// 反射方法可能不存在,忽略 // 反射方法可能不存在,忽略
} }
} }
} }

144
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
}
}
}

3
ios/Runner/Info.plist

@ -91,6 +91,9 @@
<string>google.navigation</string> <string>google.navigation</string>
<string>googlemaps</string> <string>googlemaps</string>
<string>comgooglemaps</string> <string>comgooglemaps</string>
<string>baidumap</string>
<string>amapuri</string>
<string>maps</string>
</array> </array>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>

140
lib/main.dart

@ -1,37 +1,104 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.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:get/get.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'core/bindings/initial_binding.dart';
import 'routes/app_pages.dart'; import 'routes/app_pages.dart';
import 'routes/app_routes.dart'; import 'routes/app_routes.dart';
import 'core/translations/app_translations.dart'; import 'core/translations/app_translations.dart';
import 'core/themes/app_theme.dart'; import 'core/themes/app_theme.dart';
import 'core/themes/theme_service.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 { void main() async {
// 确保Flutter绑定初始化
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); // 初始化存储
// 初始化存储 - 在创建应用之前初始化
await GetStorage.init(); 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<dynamic, dynamic>;
print("🔗 深链接参数: $arguments");
// 初始化登录服务(包含Firebase初始化) final url = arguments['url'] as String?;
LoginService(); if (url != null) {
print("🔗 处理深链接 URL: $url");
await _handleDeepLinkNavigation(url);
}
}
});
// 运行应用 print("✅ 深链接处理器初始化完成");
runApp(const MainApp()); }
// 处理深链接导航
Future<void> _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<SplashController>()) {
print("🔗 找到SplashController,处理QQ音乐登录");
final controller = Get.find<SplashController>();
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 { class MainApp extends StatelessWidget {
@ -39,46 +106,27 @@ class MainApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// 获取保存的主题设置 // 在应用构建时立即初始化深链接处理
final themeMode = ThemeService().getThemeMode(); _initDeepLinkHandler();
// 使用 ScreenUtilInit 包装整个应用
return ScreenUtilInit( return ScreenUtilInit(
designSize: const Size(375, 812), designSize: const Size(375, 812), // 设计稿尺寸,根据你的设计稿调整
minTextAdapt: true, minTextAdapt: true,
splitScreenMode: true, splitScreenMode: true,
builder: (_, child) { builder: (context, child) {
return GetMaterialApp( return GetMaterialApp(
title: 'Voitrans', title: 'VoiTrans',
theme: AppTheme.lightTheme, theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme, darkTheme: AppTheme.darkTheme,
themeMode: themeMode, // 使用提前获取的主题模式 themeMode: ThemeService().getThemeMode(),
initialBinding: InitialBinding(),
initialRoute: Routes.splash, initialRoute: Routes.splash,
getPages: AppPages.pages, getPages: AppPages.pages,
debugShowCheckedModeBanner: false, initialBinding: InitialBinding(),
translations: AppTranslations(), translations: AppTranslations(),
localizationsDelegates: const [ locale: Get.deviceLocale,
GlobalMaterialLocalizations.delegate, fallbackLocale: const Locale('en', 'US'),
GlobalWidgetsLocalizations.delegate, debugShowCheckedModeBanner: false,
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!,
),
);
},
),
); );
}, },
); );

15
lib/modules/splash/controllers/splash_controller.dart

@ -305,6 +305,21 @@ class SplashController extends GetxController {
return false; return false;
} }
} }
/// 处理QQ音乐登录回调
void handleQQMusicLogin() {
try {
Logger.i(_tag, '处理QQ音乐登录回调');
// 这里可以添加具体的QQ音乐登录处理逻辑
// 例如:验证登录状态、更新用户信息、导航到相应页面等
// 如果需要导航到特定页面,可以使用:
// Get.toNamed(Routes.qqmusic);
} catch (e) {
Logger.e(_tag, '处理QQ音乐登录回调失败: $e');
}
}
} }
/// 显示无网络连接对话框并等待用户操作(阻塞版本) /// 显示无网络连接对话框并等待用户操作(阻塞版本)

6
local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt

@ -1956,7 +1956,7 @@ object AgentService : CoroutineScope {
) )
} }
if (playlist.isNotEmpty()) { if (playlist.isNotEmpty()) {
processQQMusicPlayList(playlist) // processQQMusicPlayList(playlist)
} else { } else {
Log.w(TAG, "qqmusic_searchandplay_songs 中没有有效的音乐条目") Log.w(TAG, "qqmusic_searchandplay_songs 中没有有效的音乐条目")
} }
@ -1969,6 +1969,10 @@ object AgentService : CoroutineScope {
val start = cardNavigation.optString("start", "") val start = cardNavigation.optString("start", "")
val end = cardNavigation.optString("end", "") val end = cardNavigation.optString("end", "")
processNavigation(start, 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); return mapOf("meta" to metaStr, "broadcast" to broadcast, "notreply" to notreply,"iscard" to iscard);
} }

20
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -81,7 +81,7 @@ class AgentServiceImpl: NSObject {
private let maxIdleSeconds: TimeInterval = 10 private let maxIdleSeconds: TimeInterval = 10
var micCapture: MicrophoneCapture! var micCapture: MicrophoneCapture!
// 语音识别模式 // 语音识别模式
private var currentRecognitionMode: String = "normal" public var currentRecognitionMode: String = "normal"
internal var audioPlayer: AudioPlayer? internal var audioPlayer: AudioPlayer?
// private var locationInfo:[String: String]? // 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] { private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -> [String: Any] {
var broadcast = true var broadcast = true
var notreply = false var notreply = false
var iscard = false
var metadata: [String: Any] = [:] var metadata: [String: Any] = [:]
metadata[toolName] = jsonDict metadata[toolName] = jsonDict
@ -843,7 +844,11 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -
if let _notreply = jsonDict["notreply"] as? Bool { if let _notreply = jsonDict["notreply"] as? Bool {
notreply = _notreply notreply = _notreply
} }
if let _iscard = jsonDict["iscard"] as? Bool {
iscard = _iscard
}
metadata["notreply"] = notreply metadata["notreply"] = notreply
metadata["iscard"] = iscard
// 处理卡片数据 // 处理卡片数据
// if let cardMusic = jsonDict["card_music"] as? [String: Any] { // if let cardMusic = jsonDict["card_music"] as? [String: Any] {
// processMusicCard(cardMusic) // processMusicCard(cardMusic)
@ -854,8 +859,12 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -
processNavigationCard(cardNavigation) processNavigationCard(cardNavigation)
} else if let cardQQMusiclist = jsonDict["data"] as? [[String: Any]], toolName == "qqmusic_searchandplay_songs" { } else if let cardQQMusiclist = jsonDict["data"] as? [[String: Any]], toolName == "qqmusic_searchandplay_songs" {
processQQMusicPlayList(cardQQMusiclist) processQQMusicPlayList(cardQQMusiclist)
} else if toolName == "maps_weather" {
iscard = true
}else if toolName == "map_weather" {
iscard = true
} }
metadata["iscard"] = iscard
let metaStr: String let metaStr: String
if let jsonData = try? JSONSerialization.data(withJSONObject: metadata, options: []), if let jsonData = try? JSONSerialization.data(withJSONObject: metadata, options: []),
let jsonString = String(data: jsonData, encoding: .utf8) { let jsonString = String(data: jsonData, encoding: .utf8) {
@ -1581,9 +1590,14 @@ class AudioPlayer {
} }
func playAwaitSound() { func playAwaitSound() {
// 先停止之前的等待音效和定时器 //先停止之前的等待音效和定时器
stopAwaitSound() stopAwaitSound()
// 修复:通过 agentService 引用访问 currentRecognitionMode
if agentService?.currentRecognitionMode == "phone_call" {//电话模式不要嘟嘟嘟。影响回音消除功能
return
}
// 播放等待音效 // 播放等待音效
playLoopSound(named: "await", fileType: "mp3", player: &awaitPlayer,volume:0.2) playLoopSound(named: "await", fileType: "mp3", player: &awaitPlayer,volume:0.2)

27
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 { public func startspeak(sessionid sessionid: String)-> Bool {
self.sessionid = sessionid self.sessionid = sessionid
print("startspeak sessionid \(sessionid)")
// 重置当前会话的文本计数 // 重置当前会话的文本计数
self.currentSessionTextCount = 0 self.currentSessionTextCount = 0
self.pendingTextCount = 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 return true
} }
@ -187,7 +210,7 @@ public class AzureTtsHelper: NSObject, ITtsService {
totalTextCount += text.count totalTextCount += text.count
currentSessionTextCount += text.count currentSessionTextCount += text.count
print("流式合成文本\(text.count),总计文本数量:\(totalTextCount),当前会话:\(currentSessionTextCount)") //print("流式合成文本\(text.count),总计文本数量:\(totalTextCount),当前会话:\(currentSessionTextCount)")
// 清理文本并添加到缓冲区 // 清理文本并添加到缓冲区
let cleanedText = cleanTextForTTS(text) let cleanedText = cleanTextForTTS(text)

24
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 } 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)")
}
} }
/** /**

99
local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift

@ -193,12 +193,15 @@ public class SimpleAudioReceiver: NSObject {
// 确保正在写入状态 // 确保正在写入状态
guard self._isWriting else { return } guard self._isWriting else { return }
// 设置音频会话参数 // 设置音频会话参数
try audioSession.setCategory(.playAndRecord, try audioSession.setCategory(.playAndRecord,
mode: .videoChat, mode: .voiceChat,
options: [.allowBluetoothA2DP, // 允许蓝牙耳机,不占用hfp链路 options: [.defaultToSpeaker,
.mixWithOthers, .mixWithOthers,
.defaultToSpeaker]) // 添加音频优先级控制 .defaultToSpeaker,
try audioSession.setActive(true) .allowBluetoothA2DP,
.allowBluetooth])
try audioSession.overrideOutputAudioPort(.none)
try audioSession.setActive(true)
try micCapture.startCapture() try micCapture.startCapture()
try micCapture.audioDataHandler = { data in try micCapture.audioDataHandler = { data in
self.writeQueue.put(data) self.writeQueue.put(data)
@ -264,37 +267,7 @@ public class SimpleAudioReceiver: NSObject {
* 强制使用设备内置扬声器或听筒 * 强制使用设备内置扬声器或听筒
*/ */
public func disableBluetoothAudio() { 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) { 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)")
// }
} }

87
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 -> qqMusicSingleton.searchSongs(keyword) { searchResult ->
val result = if (searchResult.isSuccess) { val result = if (searchResult.isSuccess) {
val songs = searchResult.getOrNull() ?: emptyList() val songs = searchResult.getOrNull() ?: emptyList()
var playError: Throwable? = null
if (songs.isNotEmpty()) {
// 从搜索结果中提取歌曲MID列表
val songMids = songs.mapNotNull { song ->
val songMap = song as Map<String, Any?>
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) { val processedSongs = when (searchType) {
0 -> { // 歌曲搜索,返回所有结果 0 -> { // 歌曲搜索,返回所有结果
@ -148,7 +167,18 @@ private class QQMusicSearchHandler(private val context: Context?) : FunctionHand
val duration = songMap["duration"] ?: 0 val duration = songMap["duration"] ?: 0
"{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}" "{\"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 { } else {
val error = searchResult.exceptionOrNull() val error = searchResult.exceptionOrNull()
"{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" "{\"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, Any>): 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<String, Any?>
// // 添加调试日志
// 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 { private class QQMusicPlaySongsHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { override suspend fun handle(arguments: Map<String, Any>): String {

1
local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift

@ -67,6 +67,7 @@ private class MusicSearchHandler: FunctionHandler {
let result: [String: Any] = [ let result: [String: Any] = [
"success": true, "success": true,
"broadcast": false, "broadcast": false,
"iscard": true,
"message": "搜索成功,正在播放", "message": "搜索成功,正在播放",
"data": songsJson "data": songsJson
] ]

1
local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift

@ -162,6 +162,7 @@ private class QQMusicSearchHandler: FunctionHandler {
result = [ result = [
"success": true, "success": true,
"message": "Found \(items.count) songs", "message": "Found \(items.count) songs",
"iscard": true,
"broadcast": false, "broadcast": false,
"data": songs "data": songs
] ]

69
local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt

@ -1,6 +1,7 @@
package com.yunqiinnovation.qq_music package com.yunqiinnovation.qq_music
import android.app.Activity import android.app.Activity
import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
@ -18,12 +19,12 @@ import org.json.JSONTokener
* 负责处理QQ音乐的登录授权流程 * 负责处理QQ音乐的登录授权流程
*/ */
class QQMusicLoginManager(private val context: Context) { class QQMusicLoginManager(private val context: Context) {
companion object { companion object {
private const val TAG = "QQMusicLoginManager" private const val TAG = "QQMusicLoginManager"
private const val BASE_SCHEME = "voitrans://" private const val BASE_SCHEME = "voitrans://"
private const val URI_LOGIN = BASE_SCHEME + "login" private const val URI_LOGIN = BASE_SCHEME + "login"
// 定义错误码常量,替代 LoginExampleActivity 中的常量 // 定义错误码常量,替代 LoginExampleActivity 中的常量
const val LOGIN_FAILED = 1 const val LOGIN_FAILED = 1
} }
@ -32,7 +33,7 @@ class QQMusicLoginManager(private val context: Context) {
private var qqMusicApi: IQQMusicApi? = null private var qqMusicApi: IQQMusicApi? = null
private lateinit var qqMusicApiWrapper: QQMusicApiWrapper private lateinit var qqMusicApiWrapper: QQMusicApiWrapper
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
/** /**
* 登录结果回调接口 * 登录结果回调接口
*/ */
@ -51,63 +52,63 @@ class QQMusicLoginManager(private val context: Context) {
*/ */
fun startLogin(callback: LoginCallback) { fun startLogin(callback: LoginCallback) {
Log.d(TAG, "开始QQ音乐登录流程") Log.d(TAG, "开始QQ音乐登录流程")
// 检查配置 // 检查配置
if (Config.OPENID_APPID.isEmpty() || Config.OPENID_APP_PRIVATE_KEY.isEmpty()) { if (Config.OPENID_APPID.isEmpty() || Config.OPENID_APP_PRIVATE_KEY.isEmpty()) {
callback.onError("CONFIG_ERROR", "请先配置APPID和私钥") callback.onError("CONFIG_ERROR", "请先配置APPID和私钥")
return return
} }
// 初始化绑定助手 // 初始化绑定助手
qPlayBindHelper = QPlayBindHelper(context) qPlayBindHelper = QPlayBindHelper(context)
// 开始绑定QQ音乐服务 // 开始绑定QQ音乐服务
bindQQMusicService(callback) bindQQMusicService(callback)
} }
/** /**
* 绑定QQ音乐服务 * 绑定QQ音乐服务
*/ */
private fun bindQQMusicService(callback: LoginCallback) { private fun bindQQMusicService(callback: LoginCallback) {
Log.d(TAG, "开始绑定QQ音乐服务") Log.d(TAG, "开始绑定QQ音乐服务")
qPlayBindHelper.ensureQQMusicBindByStartProcess { success -> qPlayBindHelper.ensureQQMusicBindByStartProcess { success ->
if (success && qPlayBindHelper.isBindQQMusic()) { if (success && qPlayBindHelper.isBindQQMusic()) {
Log.d(TAG, "QQ音乐服务绑定成功") Log.d(TAG, "QQ音乐服务绑定成功")
qqMusicApi = qPlayBindHelper.getQQMusicApi()!! qqMusicApi = qPlayBindHelper.getQQMusicApi()!!
qqMusicApiWrapper = QQMusicApiWrapper(qqMusicApi!!) qqMusicApiWrapper = QQMusicApiWrapper(qqMusicApi!!)
// 检查是否已经授权 // 检查是否已经授权
checkAuthStatus(callback) checkAuthStatus(callback)
} else { } else {
Log.e(TAG, "QQ音乐服务绑定失败") Log.e(TAG, "QQ音乐服务绑定失败")
callback.onError("APP_NOT_INSTALLED", "请先安装QQ音乐App才能使用音乐功能") callback.onError("APP_NOT_INSTALLED", "请先安装QQ音乐App才能使用音乐功能")
} }
} }
} }
/** /**
* 检查授权状态 * 检查授权状态
*/ */
private fun checkAuthStatus(callback: LoginCallback) { private fun checkAuthStatus(callback: LoginCallback) {
Log.d(TAG, "检查授权状态") Log.d(TAG, "检查授权状态")
val bundle = Bundle().apply { val bundle = Bundle().apply {
putInt(Keys.API_PARAM_KEY_SDK_VERSION, CommonCmd.SDK_VERSION) putInt(Keys.API_PARAM_KEY_SDK_VERSION, CommonCmd.SDK_VERSION)
putString(Keys.API_PARAM_KEY_PLATFORM_TYPE, Config.BIND_PLATFORM) putString(Keys.API_PARAM_KEY_PLATFORM_TYPE, Config.BIND_PLATFORM)
} }
try { try {
val result = qqMusicApi?.execute("hi", bundle) val result = qqMusicApi?.execute("hi", bundle)
val code = result?.getInt(Keys.API_RETURN_KEY_CODE) val code = result?.getInt(Keys.API_RETURN_KEY_CODE)
when (code) { when (code) {
ErrorCodes.ERROR_OK -> { ErrorCodes.ERROR_OK -> {
// 已授权,直接请求登录信息 // 已授权,直接请求登录信息
Log.d(TAG, "已授权,直接获取登录信息") Log.d(TAG, "已授权,直接获取登录信息")
// isBindQQMusicService = true // isBindQQMusicService = true
startAuthProcess2(callback) startAuthProcess(callback)
} }
ErrorCodes.ERROR_API_NO_PERMISSION -> { ErrorCodes.ERROR_API_NO_PERMISSION -> {
// 需要授权 // 需要授权
@ -147,16 +148,46 @@ class QQMusicLoginManager(private val context: Context) {
*/ */
fun startAuthProcess2(callback: LoginCallback) { fun startAuthProcess2(callback: LoginCallback) {
Log.d(TAG, "启动QQ音乐授权流程") 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音乐登录 // 启动QQ音乐登录
CommonCmd.startQQMusicProcess(context,context.packageName) // CommonCmd.startQQMusicProcess(context,context.packageName)
callback.onSuccess() isBindQQMusicService = true
Handler(Looper.getMainLooper()).postDelayed({
callback.onSuccess()
}, 1000) // 延迟500ms
// 等待用户登录完成后进行身份验证 // 等待用户登录完成后进行身份验证
// handler.postDelayed({ // handler.postDelayed({
// verifyIdentity(callback) // verifyIdentity(callback)
// }, 3000) // 给用户足够的登录时间 // }, 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
}
/** /**
* 启动授权流程 * 启动授权流程
*/ */

3
local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt

@ -573,7 +573,8 @@ class QQMusicSingleton private constructor() {
musicManager?.bindQQMusicApiService() musicManager?.bindQQMusicApiService()
callback(Result.failure(Exception("播放失败: $errorMessage"))) callback(Result.failure(Exception("播放失败: $errorMessage")))
} else if (errorCode == 1030) { } else if (errorCode == 1030) {
callback(Result.success(Unit)) callback(Result.failure(Exception("播放失败: $errorMessage")))
// callback(Result.success(Unit))
}else{ }else{
callback(Result.failure(Exception("播放失败: $errorMessage"))) callback(Result.failure(Exception("播放失败: $errorMessage")))
} }

Loading…
Cancel
Save