Browse Source

Merge commit '23c3ad422245f76faef1a6975f3739f9bfa9b4f2' into new_dev

weicu
lxm 1 year ago
parent
commit
1ea23cf8a1
  1. 2
      ios/Runner.xcodeproj/project.pbxproj
  2. 2
      ios/Runner/Info.plist
  3. 8
      lib/data/services/novice_guide_service.dart
  4. 26
      lib/modules/permissions/models/permission_group.dart
  5. 43
      lib/modules/permissions/views/permissions_view.dart
  6. 12
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  7. 7
      local_plugins/ble_service/ios/ble_service/Package.swift
  8. 16
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  9. 136
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt
  10. 2
      local_plugins/jl_opus/pubspec.yaml
  11. 37
      local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt
  12. 17
      local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt
  13. 9
      pubspec.yaml

2
ios/Runner.xcodeproj/project.pbxproj

@ -45,7 +45,6 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0AD944112DD9DB5C00557D21 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; }; 0AD944112DD9DB5C00557D21 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
0AD944132DDA0C0D00557D21 /* AccessorySetupKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AccessorySetupKit.framework; path = System/Library/Frameworks/AccessorySetupKit.framework; sourceTree = SDKROOT; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
162D67C21ED243BA396CEF5C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 162D67C21ED243BA396CEF5C /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -109,7 +108,6 @@
1E5DDD8701F6FB27AA502080 /* Frameworks */ = { 1E5DDD8701F6FB27AA502080 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
0AD944132DDA0C0D00557D21 /* AccessorySetupKit.framework */,
EFDA2C0DD7A7D82CA03799B2 /* Pods_Runner.framework */, EFDA2C0DD7A7D82CA03799B2 /* Pods_Runner.framework */,
162D67C21ED243BA396CEF5C /* Pods_RunnerTests.framework */, 162D67C21ED243BA396CEF5C /* Pods_RunnerTests.framework */,
); );

2
ios/Runner/Info.plist

@ -106,6 +106,8 @@
<string>Bluetooth permission is required to connect and manage audio devices</string> <string>Bluetooth permission is required to connect and manage audio devices</string>
<key>NSCalendarsUsageDescription</key> <key>NSCalendarsUsageDescription</key>
<string>Calendar permission is required to schedule and manage your audio meetings and recording tasks</string> <string>Calendar permission is required to schedule and manage your audio meetings and recording tasks</string>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>Calendar permission is required to schedule and manage your audio meetings and recording tasks</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Camera permission is required for QR code scanning and video calling functions</string> <string>Camera permission is required for QR code scanning and video calling functions</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>

8
lib/data/services/novice_guide_service.dart

@ -68,11 +68,11 @@ class NoviceGuideService extends GetxService {
'method': () async { 'method': () async {
//创建会议提醒需要日历权限 //创建会议提醒需要日历权限
await PermissionUtil.instance.requestPermission( await PermissionUtil.instance.requestPermission(
permissionType: Permission.calendarWriteOnly, permissionType: Permission.calendarFullAccess,
permissionName: 'calendarWrite'.tr, // 日历写入 permissionName: 'calendarFullAccess'.tr, // 日历写入
explanationText: 'calendarWritePermissionDesc'.tr, // 用于创建日历事件和提醒 explanationText: 'calendarFullAccessPermissionDesc'.tr, // 用于创建日历事件和提醒
permanentDenialText: permanentDenialText:
'calendarWritePermissionDetailDesc'.tr, // 创建日历事件需要日历写入权限 'calendarFullAccessPermissionDetailDesc'.tr, // 创建日历事件需要日历写入权限
); );
}, },
}, },

26
lib/modules/permissions/models/permission_group.dart

@ -3,17 +3,19 @@ import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
class PermissionGroup { class PermissionGroupModel {
final String titleKey; final String titleKey;
final IconData icon; final IconData icon;
final List<Permission> permissions; final List<Permission> permissions;
const PermissionGroup({ const PermissionGroupModel({
required this.titleKey, required this.titleKey,
required this.icon, required this.icon,
required this.permissions, required this.permissions,
}); });
}
class PermissionGroup {
/// 获取蓝牙权限列表(根据平台和Android版本) /// 获取蓝牙权限列表(根据平台和Android版本)
static Future<List<Permission>> _getBluetoothPermissions() async { static Future<List<Permission>> _getBluetoothPermissions() async {
if (Platform.isAndroid) { if (Platform.isAndroid) {
@ -41,55 +43,55 @@ class PermissionGroup {
} }
/// 获取平台特定的权限组列表(异步方法) /// 获取平台特定的权限组列表(异步方法)
static Future<List<PermissionGroup>> getPlatformGroups() async { static Future<List<PermissionGroupModel>> getPlatformGroups() async {
// 动态获取蓝牙权限列表 // 动态获取蓝牙权限列表
final bluetoothPermissions = await _getBluetoothPermissions(); final bluetoothPermissions = await _getBluetoothPermissions();
return [ return [
// 通讯权限组 - 仅Android(包含联系人权限) // 通讯权限组 - 仅Android(包含联系人权限)
const PermissionGroup( const PermissionGroupModel(
titleKey: 'communicationPermissions', titleKey: 'communicationPermissions',
icon: Icons.phone_android, icon: Icons.phone_android,
permissions: [Permission.phone, Permission.sms, Permission.contacts], permissions: [Permission.phone, Permission.sms, Permission.contacts],
), ),
// 蓝牙权限组 - 根据平台动态获取权限 // 蓝牙权限组 - 根据平台动态获取权限
PermissionGroup( PermissionGroupModel(
titleKey: 'bluetoothPermissions', titleKey: 'bluetoothPermissions',
icon: Icons.bluetooth, icon: Icons.bluetooth,
permissions: bluetoothPermissions, permissions: bluetoothPermissions,
), ),
// 通知权限组 // 通知权限组
const PermissionGroup( const PermissionGroupModel(
titleKey: 'notificationPermissions', titleKey: 'notificationPermissions',
icon: Icons.notifications, icon: Icons.notifications,
permissions: [Permission.notification], permissions: [Permission.notification],
), ),
// 闹钟权限组 - 仅Android // 闹钟权限组 - 仅Android
const PermissionGroup( const PermissionGroupModel(
titleKey: 'alarmPermissions', titleKey: 'alarmPermissions',
icon: Icons.alarm, icon: Icons.alarm,
permissions: [Permission.scheduleExactAlarm], permissions: [Permission.scheduleExactAlarm],
), ),
// 日历权限组 // 日历权限组
const PermissionGroup( const PermissionGroupModel(
titleKey: 'calendarPermissions', titleKey: 'calendarPermissions',
icon: Icons.calendar_today, icon: Icons.calendar_today,
permissions: [Permission.calendarFullAccess], permissions: [Permission.calendarFullAccess],
), ),
// 位置权限组 // 位置权限组
const PermissionGroup( const PermissionGroupModel(
titleKey: 'locationPermissions', titleKey: 'locationPermissions',
icon: Icons.location_on, icon: Icons.location_on,
permissions: [Permission.location], permissions: [Permission.location],
), ),
// 存储权限组 // 存储权限组
const PermissionGroup( const PermissionGroupModel(
titleKey: 'storagePermissions', titleKey: 'storagePermissions',
icon: Icons.storage, icon: Icons.storage,
permissions: [Permission.storage], permissions: [Permission.storage],
), ),
// 麦克风权限组 // 麦克风权限组
const PermissionGroup( const PermissionGroupModel(
titleKey: 'microphonePermissions', titleKey: 'microphonePermissions',
icon: Icons.mic, icon: Icons.mic,
permissions: [Permission.microphone], permissions: [Permission.microphone],

43
lib/modules/permissions/views/permissions_view.dart

@ -4,7 +4,6 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'dart:ui'; import 'dart:ui';
import 'dart:io' show Platform; import 'dart:io' show Platform;
import 'package:device_info_plus/device_info_plus.dart';
import '../controllers/permissions_controller.dart'; import '../controllers/permissions_controller.dart';
import '../models/permission_group.dart'; import '../models/permission_group.dart';
@ -56,23 +55,23 @@ class PermissionsView extends GetView<PermissionsController> {
} }
Widget _buildPermissionGroups(bool isDarkMode) { Widget _buildPermissionGroups(bool isDarkMode) {
return FutureBuilder<List<PermissionGroup>>( return FutureBuilder<List<PermissionGroupModel>>(
future: PermissionGroup.getPlatformGroups(), future: PermissionGroup.getPlatformGroups(),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) { if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator()); return const Center(child: CircularProgressIndicator());
} }
if (snapshot.hasError) { if (snapshot.hasError) {
return Center(child: Text('加载权限组失败: ${snapshot.error}')); return Center(child: Text('加载权限组失败: ${snapshot.error}'));
} }
final groups = snapshot.data ?? []; final groups = snapshot.data ?? [];
final List<Widget> widgets = []; final List<Widget> widgets = [];
for (int i = 0; i < groups.length; i++) { for (int i = 0; i < groups.length; i++) {
final group = groups[i]; final group = groups[i];
// 过滤平台特定权限 // 过滤平台特定权限
if (_shouldShowGroup(group)) { if (_shouldShowGroup(group)) {
widgets.add(_buildPermissionSection( widgets.add(_buildPermissionSection(
@ -82,21 +81,21 @@ class PermissionsView extends GetView<PermissionsController> {
controller.permissionStatuses, controller.permissionStatuses,
isDarkMode, isDarkMode,
)); ));
if (i < groups.length - 1) { if (i < groups.length - 1) {
widgets.add(SizedBox(height: 20.h)); widgets.add(SizedBox(height: 20.h));
} }
} }
} }
return Column(children: widgets); return Column(children: widgets);
}, },
); );
} }
bool _shouldShowGroup(PermissionGroup group) { bool _shouldShowGroup(PermissionGroupModel group) {
// Android特定权限组 // Android特定权限组
if (group.titleKey == 'communicationPermissions' || if (group.titleKey == 'communicationPermissions' ||
group.titleKey == 'alarmPermissions') { group.titleKey == 'alarmPermissions') {
return Platform.isAndroid; return Platform.isAndroid;
} }
@ -162,7 +161,8 @@ class PermissionsView extends GetView<PermissionsController> {
? ImageFilter.blur(sigmaX: 5, sigmaY: 5) ? ImageFilter.blur(sigmaX: 5, sigmaY: 5)
: ImageFilter.blur(sigmaX: 0, sigmaY: 0), : ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Column( child: Column(
children: _buildPermissionItems(permissions, statuses, isDarkMode), children:
_buildPermissionItems(permissions, statuses, isDarkMode),
), ),
), ),
), ),
@ -238,12 +238,13 @@ class PermissionsView extends GetView<PermissionsController> {
), ),
), ),
Obx(() => Switch( Obx(() => Switch(
value: status.isGranted, value: status.isGranted,
onChanged: controller.isRequestingPermission.value onChanged: controller.isRequestingPermission.value
? null ? null
: (value) => _handlePermissionToggle(permission, status, value), : (value) =>
activeColor: Colors.blue[600], _handlePermissionToggle(permission, status, value),
)), activeColor: Colors.blue[600],
)),
], ],
), ),
); );
@ -281,8 +282,8 @@ class PermissionsView extends GetView<PermissionsController> {
} }
void _handlePermissionToggle( void _handlePermissionToggle(
Permission permission, Permission permission,
PermissionStatus status, PermissionStatus status,
bool value, bool value,
) { ) {
if (value) { if (value) {
@ -293,4 +294,4 @@ class PermissionsView extends GetView<PermissionsController> {
controller.openSettings(); controller.openSettings();
} }
} }
} }

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

@ -131,6 +131,9 @@ object AgentService : CoroutineScope {
// 语音识别模式 // 语音识别模式
private var currentRecognitionMode = "normal" // 当前识别模式:normal, ble_wakeup, phone_call, push_to_talk private var currentRecognitionMode = "normal" // 当前识别模式:normal, ble_wakeup, phone_call, push_to_talk
//是否播放音乐中
private var isplaymusic = false;
// 打断防抖相关 // 打断防抖相关
private var lastInterruptTime = 0L private var lastInterruptTime = 0L
private val INTERRUPT_DEBOUNCE_MS = 300L private val INTERRUPT_DEBOUNCE_MS = 300L
@ -358,6 +361,11 @@ object AgentService : CoroutineScope {
setPackage(context.packageName) setPackage(context.packageName)
} }
context.sendBroadcast(intent) context.sendBroadcast(intent)
QQMusicSingleton.getInstance().autopauseMusic { result ->
if (result.isSuccess) {
isplaymusic = true
}
}
} }
TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容 TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容
@ -369,6 +377,10 @@ object AgentService : CoroutineScope {
setPackage(context.packageName) setPackage(context.packageName)
} }
context.sendBroadcast(intent) context.sendBroadcast(intent)
if(isplaymusic){
isplaymusic = false
QQMusicSingleton.getInstance().resumeMusic { }
}
} }
TtsEventType.ERROR -> { TtsEventType.ERROR -> {

7
local_plugins/ble_service/ios/ble_service/Package.swift

@ -21,10 +21,9 @@ let package = Package(
linkerSettings: [ linkerSettings: [
.linkedFramework("AVFoundation"), .linkedFramework("AVFoundation"),
.linkedFramework("AudioToolbox"), .linkedFramework("AudioToolbox"),
.linkedFramework("CoreBluetooth"), .linkedFramework("CoreBluetooth")
.linkedFramework("AccessorySetupKit", .when(platforms: [.iOS], configuration: .release)), // 移除 AccessorySetupKit 链接,因为它只在 iOS 18+ 可用
.linkedFramework("AccessorySetupKit", .when(platforms: [.iOS], configuration: .debug))
] ]
) )
] ]
) )

16
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt

@ -404,12 +404,24 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
throw IllegalStateException("OpenAI客户端未初始化") throw IllegalStateException("OpenAI客户端未初始化")
} }
Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}") Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}")
val flow = openAI!!.chatCompletions(chatCompletionRequest) // 添加超时控制,10秒超时
val flow = withTimeout(10.seconds) {
openAI!!.chatCompletions(chatCompletionRequest)
}
Log.d(TAG, "liwei-------------------------开始AI 对话 6") Log.d(TAG, "liwei-------------------------开始AI 对话 6")
flow flow
} catch (e: Exception) { } catch (e: Exception) {
Log.e("ChatApiService", "创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e) Log.e("ChatApiService", "创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e)
throw e if (sessionid == currSessionId) {
try {
getSessionCallback(sessionid)?.onError(sessionid,ChatApiException("流式请求失败: ${e.message}"))
} catch (ex: Exception) {
Log.e(TAG, "onError回调异常: ${ex.message}", ex)
}
clearCurrentSession()
}
return@launch
// throw e
} }
var replyContext = ""; var replyContext = "";

136
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt

@ -4,6 +4,8 @@ import android.content.Context
import android.util.Log import android.util.Log
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import com.yunqiinnovation.qq_music.QQMusicSingleton import com.yunqiinnovation.qq_music.QQMusicSingleton
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.resume
/** /**
* QQ音乐系统功能处理器 * QQ音乐系统功能处理器
* 负责注册QQ音乐相关的MCP函数 * 负责注册QQ音乐相关的MCP函数
@ -195,60 +197,110 @@ private class QQMusicLoginHandler(private val context: Context?) : FunctionHandl
} }
} }
} }
private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler { private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler {
override suspend fun handle(arguments: Map<String, Any>): String { override suspend fun handle(arguments: Map<String, Any>): String {
return try { return try {
val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}" val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}"
val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲 val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲
val qqMusicSingleton = QQMusicSingleton.getInstance() val qqMusicSingleton = QQMusicSingleton.getInstance()
var result = ""
// 直接调用搜索,内部会自动处理登录检查
qqMusicSingleton.searchSongs(keyword) { searchResult ->
result = if (searchResult.isSuccess) {
val songs = searchResult.getOrNull() ?: emptyList()
// 根据搜索类型处理结果 // 使用 suspendCoroutine 将回调转换为协程
val processedSongs = when (searchType) { suspendCoroutine { continuation ->
0 -> { // 歌曲搜索,只返回第一个结果 qqMusicSingleton.searchSongs(keyword) { searchResult ->
// if (songs.isNotEmpty()) listOf(songs.first()) else emptyList() val result = if (searchResult.isSuccess) {
songs val songs = searchResult.getOrNull() ?: emptyList()
}
else -> { // 专辑、歌手等其他类型,返回所有结果 // 根据搜索类型处理结果
songs val processedSongs = when (searchType) {
0 -> { // 歌曲搜索,返回所有结果
songs
}
else -> { // 专辑、歌手等其他类型,返回所有结果
songs
}
} }
}
val songsJson = processedSongs.joinToString(",") { song -> val songsJson = processedSongs.joinToString(",") { song ->
val songMap = song as Map<String, Any?> val songMap = song as Map<String, Any?>
// 添加调试日志 // 添加调试日志
Log.d("QQMusicSearchHandler", "原始数据: $songMap") Log.d("QQMusicSearchHandler", "原始数据: $songMap")
// 修正字段名映射 // 修正字段名映射
val mid = songMap["mid"] ?: "unknown" val mid = songMap["mid"] ?: "unknown"
val name = songMap["name"] ?: "unknown" val name = songMap["name"] ?: "unknown"
val artist = songMap["singer"] ?: "unknown" val artist = songMap["singer"] ?: "unknown"
val album = songMap["album"] ?: "" val album = songMap["album"] ?: ""
val pic = songMap["pic"] ?: "" val pic = songMap["pic"] ?: ""
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, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}"
} else {
val error = searchResult.exceptionOrNull()
"{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"
} }
"{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,增在播放\", \"data\": [$songsJson]}" // 恢复协程执行
} else { continuation.resume(result)
val error = searchResult.exceptionOrNull()
"{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}"
} }
} }
// 等待回调完成
delay(5000)
result.ifEmpty { "{\"success\": false, \"message\": \"搜索超时\"}" }
} catch (e: Exception) { } catch (e: Exception) {
Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e) Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e)
"{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}" "{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}"
} }
} }
} }
//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 {
@ -310,7 +362,7 @@ private class QQMusicPauseHandler(private val context: Context?) : FunctionHandl
qqMusicSingleton.pauseMusic { pauseResult -> qqMusicSingleton.pauseMusic { pauseResult ->
result = if (pauseResult.isSuccess) { result = if (pauseResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"暂停播放\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"暂停播放\"}"
} else { } else {
val error = pauseResult.exceptionOrNull() val error = pauseResult.exceptionOrNull()
"{\"success\": false, \"message\": \"暂停失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"暂停失败: ${error?.message}\"}"
@ -334,7 +386,7 @@ private class QQMusicResumeHandler(private val context: Context?) : FunctionHand
qqMusicSingleton.resumeMusic { resumeResult -> qqMusicSingleton.resumeMusic { resumeResult ->
result = if (resumeResult.isSuccess) { result = if (resumeResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"恢复播放\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"恢复播放\"}"
} else { } else {
val error = resumeResult.exceptionOrNull() val error = resumeResult.exceptionOrNull()
"{\"success\": false, \"message\": \"恢复播放失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"恢复播放失败: ${error?.message}\"}"
@ -358,7 +410,7 @@ private class QQMusicStopHandler(private val context: Context?) : FunctionHandle
qqMusicSingleton.stopMusic { stopResult -> qqMusicSingleton.stopMusic { stopResult ->
result = if (stopResult.isSuccess) { result = if (stopResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"停止播放\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"停止播放\"}"
} else { } else {
val error = stopResult.exceptionOrNull() val error = stopResult.exceptionOrNull()
"{\"success\": false, \"message\": \"停止失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"停止失败: ${error?.message}\"}"
@ -382,7 +434,7 @@ private class QQMusicNextHandler(private val context: Context?) : FunctionHandle
qqMusicSingleton.skipToNext { nextResult -> qqMusicSingleton.skipToNext { nextResult ->
result = if (nextResult.isSuccess) { result = if (nextResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"切换到下一首\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"切换到下一首\"}"
} else { } else {
val error = nextResult.exceptionOrNull() val error = nextResult.exceptionOrNull()
"{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}"
@ -406,7 +458,7 @@ private class QQMusicPreviousHandler(private val context: Context?) : FunctionHa
qqMusicSingleton.skipToPrevious { previousResult -> qqMusicSingleton.skipToPrevious { previousResult ->
result = if (previousResult.isSuccess) { result = if (previousResult.isSuccess) {
"{\"notreply\": true, \"success\": true, \"message\": \"切换到上一首\"}" "{\"broadcast\": false, \"success\": true, \"message\": \"切换到上一首\"}"
} else { } else {
val error = previousResult.exceptionOrNull() val error = previousResult.exceptionOrNull()
"{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"切换失败: ${error?.message}\"}"
@ -434,7 +486,7 @@ private class QQMusicGetPlaybackStateHandler(private val context: Context?) : Fu
val stateJson = state.entries.joinToString(",") { (key, value) -> val stateJson = state.entries.joinToString(",") { (key, value) ->
"\"$key\": \"$value\"" "\"$key\": \"$value\""
} }
"{\"notreply\": true, \"success\": true, \"message\": \"获取播放状态成功\", \"data\": {$stateJson}}" "{\"broadcast\": false, \"success\": true, \"message\": \"获取播放状态成功\", \"data\": {$stateJson}}"
} else { } else {
val error = stateResult.exceptionOrNull() val error = stateResult.exceptionOrNull()
"{\"success\": false, \"message\": \"获取播放状态失败: ${error?.message}\"}" "{\"success\": false, \"message\": \"获取播放状态失败: ${error?.message}\"}"

2
local_plugins/jl_opus/pubspec.yaml

@ -11,7 +11,7 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
path_provider: ^2.0.0 path_provider: ^2.0.0
permission_handler: ^11.4.0 permission_handler: ^12.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

37
local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicManager.kt

@ -319,6 +319,43 @@ class QQMusicManager(private val activity: Activity) {
} }
} }
fun autopauseMusic(callback: PlayCallback){
if (!isBindQQMusicService || qqmusicApi == null) {
callback.onError(-1, "QQ音乐服务未连接")
return
}
// 获取播放状态
val stateResult = qqmusicApi?.execute("getPlaybackState", null)
val stateCode = stateResult?.getInt(Keys.API_RETURN_KEY_CODE) ?: -1
// 处理播放状态 - 使用正确的常量
if (stateCode == ErrorCodes.ERROR_OK) {
val playState = stateResult?.getInt(Keys.API_RETURN_KEY_DATA) ?: 0
val isPlaying = when (playState) {
PlayState.STARTED -> true
PlayState.PREPARED -> true
PlayState.BUFFERING -> true
PlayState.PAUSED -> false
PlayState.STOPPED -> false
PlayState.ERROR -> false
else -> false
}
if(isPlaying){
val result = qqmusicApi?.execute("pauseMusic", null)
val code = result?.getInt(Keys.API_RETURN_KEY_CODE) ?: -1
if (code == ErrorCodes.ERROR_OK) {
callback.onSuccess()
} else {
callback.onError(code, "暂停失败")
}
}else {
callback.onError(-1, "未播放")
}
} else {
callback.onError(-1, "未播放")
}
}
/** /**
* 暂停音乐 * 暂停音乐
*/ */

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

@ -25,8 +25,8 @@ class QQMusicSingleton private constructor() {
private const val KEY_EXPIRE_TIME = "expire_time" private const val KEY_EXPIRE_TIME = "expire_time"
private const val LOGIN_WAIT_DELAY_MS = 30000L private const val LOGIN_WAIT_DELAY_MS = 30000L
// 添加搜索重试相关常量 // 添加搜索重试相关常量
private const val SEARCH_TIMEOUT_MS = 50000L private const val SEARCH_TIMEOUT_MS = 10000L
private const val MAX_RETRY_COUNT = 3 private const val MAX_RETRY_COUNT = 1
private const val RETRY_DELAY_MS = 1000L private const val RETRY_DELAY_MS = 1000L
@Volatile @Volatile
@ -194,7 +194,7 @@ class QQMusicSingleton private constructor() {
callback(Result.failure(Exception("未安装QQ音乐软件,请先安装QQ音乐软件"))) callback(Result.failure(Exception("未安装QQ音乐软件,请先安装QQ音乐软件")))
return return
} }
searchWithRetry(keyword, searchType, 0, callback) searchWithRetry(keyword, searchType, 1, callback)
} }
/** /**
@ -413,7 +413,16 @@ class QQMusicSingleton private constructor() {
musicManager.pauseMusic(playCallback) musicManager.pauseMusic(playCallback)
}(callback) }(callback)
} }
/**
* 暂停音乐
*/
fun autopauseMusic(callback: (Result<Unit>) -> Unit) {
executePlaybackAction("自动暂停") { musicManager, playCallback ->
musicManager.autopauseMusic(playCallback)
}(callback)
}
/** /**
* 恢复播放 * 恢复播放
*/ */

9
pubspec.yaml

@ -28,7 +28,7 @@ dependencies:
htmltopdfwidgets: ^1.1.0 htmltopdfwidgets: ^1.1.0
wifi_iot: ^0.3.19+2 wifi_iot: ^0.3.19+2
flutter_intro: ^3.4.0 flutter_intro: ^3.4.0
permission_handler: ^10.4.5 permission_handler: ^12.0.1
web_socket_channel: ^2.4.0 web_socket_channel: ^2.4.0
flutter_dotenv: ^5.1.0 flutter_dotenv: ^5.1.0
synchronized: ^3.1.0 synchronized: ^3.1.0
@ -53,7 +53,7 @@ dependencies:
flutter_local_notifications: ^18.0.1 flutter_local_notifications: ^18.0.1
device_info_plus: ^11.5.0 device_info_plus: ^11.5.0
uuid: ^4.3.3 uuid: ^4.3.3
record: ^5.2.1 record: ^6.0.0
share_plus: ^11.0.0 share_plus: ^11.0.0
image_picker: ^1.1.2 image_picker: ^1.1.2
audio_waveforms: ^1.3.0 audio_waveforms: ^1.3.0
@ -119,11 +119,6 @@ dev_dependencies:
build_runner: ^2.3.3 # 用于生成代码 flutter pub run build_runner build build_runner: ^2.3.3 # 用于生成代码 flutter pub run build_runner build
json_serializable: ^6.6.0 # 用于自动生成代码 json_serializable: ^6.6.0 # 用于自动生成代码
dependency_overrides:
permission_handler: ^12.0.0
permission_handler_android: ^13.0.0
permission_handler_apple: ^9.4.0
flutter: flutter:
uses-material-design: true uses-material-design: true
shaders: shaders:

Loading…
Cancel
Save