diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 05d554d39..75a82d84d 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -59,6 +59,8 @@ 193d0a2b1757c41cfa557ea8e8f21c33 LSApplicationQueriesSchemes + App-Prefs + prefs weixin weixinULAPI weixinURLParamsAPI diff --git a/lib/modules/devices/controllers/devices_controller.dart b/lib/modules/devices/controllers/devices_controller.dart index 1beadea7a..66ff69211 100644 --- a/lib/modules/devices/controllers/devices_controller.dart +++ b/lib/modules/devices/controllers/devices_controller.dart @@ -57,6 +57,7 @@ class DevicesController extends GetxController { User.instance.removeUserDevice(device.id); _bleManager.clearPairedDevices(); _bleManager.disconnect(); + await Future.delayed(const Duration(seconds: 1)); Get.toNamed(Routes.pairing); return; } else { diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index d267211f3..dedb8fbbb 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -16,6 +16,7 @@ import 'dart:io'; import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/ble_manager.dart'; import '../../../core/utils/permission_util.dart'; +import 'package:url_launcher/url_launcher.dart'; /// 配对状态枚举 - 简化版本 enum PairingState { @@ -202,7 +203,7 @@ class PairingController extends GetxController { case PairingState.waitingClassic: if (device != null) { // 有设备连接,进入验证阶段 - Logger.i(_tag, '检测到经典蓝牙设备连接,开始验证'); + Logger.d(_tag, '检测到经典蓝牙设备连接,开始验证'); _transition(PairingState.verifying); _verifyDevice(device); } @@ -623,7 +624,7 @@ class PairingController extends GetxController { ); product = match.isNotEmpty ? match.first : null; } - + final currentBleState = await _bleManager.getConnectionState(); final names = AppConfig.allproductNames(); // 检查是否已有绑定的设备 print("device.address${device.address}"); @@ -645,6 +646,17 @@ class PairingController extends GetxController { // 已经绑定 return 9; } + } else { + //未绑定设备 + + Logger.i(_tag, '验证时BLE状态: $currentBleState'); + if (currentBleState == 2) { + //已连接 清理 + // BleConnectionState.connected + Logger.i(_tag, 'BLE已连接,断开并清理缓存'); + _bleManager.clearPairedDevices(); + _bleManager.disconnect(); + } } if (Platform.isAndroid) { @@ -672,11 +684,15 @@ class PairingController extends GetxController { print( '获取到 deviceName: $deviceName address: ${device.address} names:$names'); if (names.contains(deviceName)) { + if (currentBleState == 2) { + //已连接直接返回 + return 9; + } await _bleManager.startScan(deviceName); await Future.delayed(const Duration(seconds: 1)); // 尝试最多读取 3 次设备信息,每次间隔 1 秒 List>? deviceinfos; - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 15; i++) { // 使用设备名称获取设备列表 deviceinfos = await _bleManager.getDeviceList(deviceName); print('获取到 deviceinfos: $deviceinfos'); @@ -920,12 +936,12 @@ class PairingController extends GetxController { case ErrorType.bluetoothOff: // 引导用户去开启蓝牙 Logger.i(_tag, '引导用户开启蓝牙'); - AppSettings.openAppSettings(type: AppSettingsType.bluetooth); + _openBluetoothSettings(); break; case ErrorType.noClassicDevice: // 引导用户去蓝牙设置连接设备 Logger.i(_tag, '引导用户去蓝牙设置'); - AppSettings.openAppSettings(type: AppSettingsType.bluetooth); + _openBluetoothSettings(); break; default: // 其他错误情况,刷新并重试 @@ -936,7 +952,7 @@ class PairingController extends GetxController { } else if (_currentState.value == PairingState.waitingClassic) { // 等待经典蓝牙连接状态,引导去设置 Logger.i(_tag, '等待经典蓝牙连接,引导用户去蓝牙设置'); - AppSettings.openAppSettings(type: AppSettingsType.bluetooth); + _openBluetoothSettings(); } else { // 其他情况,刷新设备连接 _bluetoothManager.refreshDeviceConnection(); @@ -994,7 +1010,85 @@ class PairingController extends GetxController { /// 打开蓝牙设置 void openBluetoothSettings() { - AppSettings.openAppSettings(type: AppSettingsType.bluetooth); + _openBluetoothSettings(); + } + + /// 打开蓝牙设置 - 简化版本 + void _openBluetoothSettings() async { + if (Platform.isIOS) { + // iOS平台:直接显示用户指导对话框 + _showBluetoothGuideDialog(); + } else { + // Android平台使用原有方式 + AppSettings.openAppSettings(type: AppSettingsType.bluetooth); + } + } + + /// 显示蓝牙设置指导对话框 + void _showBluetoothGuideDialog() { + Get.dialog( + AlertDialog( + title: Text('需要手动开启蓝牙'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('请按照以下步骤操作:'), + SizedBox(height: 16), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('1. ', style: TextStyle(fontWeight: FontWeight.bold)), + Expanded(child: Text('开启蓝牙:设置 → 蓝牙 → 开启')), + ], + ), + SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('2. ', style: TextStyle(fontWeight: FontWeight.bold)), + Expanded(child: Text('连接设备:在"我的设备"中找到您的设备并点击连接')), + ], + ), + SizedBox(height: 8), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('3. ', style: TextStyle(fontWeight: FontWeight.bold)), + Expanded(child: Text('返回应用继续配对流程')), + ], + ), + SizedBox(height: 16), + Container( + padding: EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.blue.withOpacity(0.3)), + ), + child: Row( + children: [ + Icon(Icons.info_outline, color: Colors.blue, size: 20), + SizedBox(width: 8), + Expanded( + child: Text( + '提示:也可以从控制中心长按蓝牙图标快速进入蓝牙设置', + style: TextStyle(fontSize: 12, color: Colors.blue[700]), + ), + ), + ], + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text('我知道了'), + ), + ], + ), + ); } /// 清除设备关联(在需要重新配对时调用) 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 1437d4982..7a7f18b10 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 @@ -815,16 +815,11 @@ object AgentService : CoroutineScope { // 先添加系统提示词 if (systemPrompt.isNotEmpty()) { -// val location = LocationService.getCachedLocation() ?: mapOf( -// "user_location" to "定位未开启", -// "user_lat" to "定位未开启", -// "user_lng" to "定位未开启", -// ) val mapdata = UserPortrait.getAll() - Log.d(TAG, "liwei--------- 用户画像: ${mapdata}") + // Log.d(TAG, "liwei--------- 用户画像: ${mapdata}") val _systemPrompt = Utils.fillTemplate(systemPrompt, mapdata) messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt)) - Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}") + // Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}") } // 再添加历史消息 @@ -893,6 +888,7 @@ object AgentService : CoroutineScope { responseBuilder.append(token) if (speakResponse && reply && broadcast) { ttsService?.speakStream(token) + audioPlayer?.stopAudio() } if (reply && token.isNotEmpty()) { // 发送流式回复token @@ -905,10 +901,12 @@ object AgentService : CoroutineScope { override fun onComplete(sessionid: String) { try { + if (sessionid == currsessionId) { + audioPlayer?.stopAudio() + } // 视情况决定是否朗读回复 if (speakResponse && reply && broadcast && sessionid == currsessionId) { ttsService?.flushStream() - audioPlayer?.stopAudio() } val response = responseBuilder.toString() // 发送完整回复,包含是否有图片的标记 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 d1f5c4c1e..7e54e2c4e 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,6 +81,7 @@ class AgentServiceImpl: NSObject { // chatStorageHelper 现在是单例,不需要初始化 UserPortrait.shared.initialize() BleService.shared.setDelegate(self) + AudioSessionHub.initialize() } func addListener(_ listener: AgentServiceListener) { @@ -500,6 +501,7 @@ audioStream.saveAudioDataTo(data: audioData) os_log("设置回调并调用sendMessageStream", log: logger, type: .info) chatApiService.setStreamCallback(currsessionId,callback) chatApiService.sendMessageStream(currsessionId,messages: messages) + audioPlayer?.playAwaitSound() os_log("sendMessageStream调用完成", log: logger, type: .info) } @@ -994,6 +996,7 @@ class ChatApiStreamCallback: StreamCallback { if speakResponse && reply && broadcast{ try agentService.azureTtsHelper?.speakStream(token) + try agentService.audioPlayer?.stopAwaitSound() } if (reply && token != "") { try agentService.sendEvent(name: "assistant_token", data: ["token": token]) @@ -1007,6 +1010,12 @@ class ChatApiStreamCallback: StreamCallback { do { guard let agentService = try agentService else { return } + if sessionId == agentService.currsessionId { + // 停止等待音效 + agentService.audioPlayer?.stopAwaitSound() + } + + if speakResponse && reply && broadcast && sessionId == agentService.currsessionId{ agentService.azureTtsHelper?.flushStream() } @@ -1045,6 +1054,11 @@ class ChatApiStreamCallback: StreamCallback { do { guard let agentService = try agentService else { return } + if sessionId == agentService.currsessionId { + // 停止等待音效 + agentService.audioPlayer?.stopAwaitSound() + } + os_log("ChatAPI错误: %{public}@", log: agentService.logger, type: .error, error.localizedDescription) if (sessionId != agentService.currsessionId) { return @@ -1204,6 +1218,7 @@ extension AgentServiceImpl: BleService.Callback { class AudioPlayer { private var player: AVAudioPlayer? private var callingPlayer: AVAudioPlayer? + private var awaitPlayer: AVAudioPlayer? // 新增:等待音效播放器 private let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AudioPlayer") private let audioQueue = DispatchQueue(label: "agent.audio.player", qos: .userInitiated) @@ -1229,19 +1244,28 @@ class AudioPlayer { func playStartSound() { playSound(named: "start", fileType: "mp3") } - + + func playAwaitSound() { + playLoopSound(named: "await", fileType: "mp3", player: &awaitPlayer) + } + func playStopSound() { playSound(named: "stop", fileType: "mp3") } func playCallingSound() { - playLoopSound(named: "calling", fileType: "mp3") + playLoopSound(named: "calling", fileType: "mp3", player: &callingPlayer) } func stopCallingSound() { callingPlayer?.stop() callingPlayer = nil } + + func stopAwaitSound() { + awaitPlayer?.stop() + awaitPlayer = nil + } private func playSound(named: String, fileType: String = "mp3") { if let path = resourcesBundle.path(forResource: named, ofType: fileType) { @@ -1254,12 +1278,14 @@ class AudioPlayer { os_log("未找到音频文件: %{public}@.%{public}@ in bundle %{public}@", log: logger, type: .error, named, fileType, resourcesBundle.bundlePath) } - private func playLoopSound(named: String, fileType: String = "mp3") { - stopCallingSound() + private func playLoopSound(named: String, fileType: String = "mp3", player: inout AVAudioPlayer?) { + // 停止当前播放的循环音频 + player?.stop() + player = nil if let path = resourcesBundle.path(forResource: named, ofType: fileType) { let url = URL(fileURLWithPath: path) - safePlayLoopAudio(url: url) + safePlayLoopAudio(url: url, player: &player) return } @@ -1293,19 +1319,19 @@ class AudioPlayer { } } - private func safePlayLoopAudio(url: URL) { + private func safePlayLoopAudio(url: URL, player: inout AVAudioPlayer?) { do { - callingPlayer = try AVAudioPlayer(contentsOf: url) + player = try AVAudioPlayer(contentsOf: url) - guard let callingPlayer = callingPlayer else { + guard let audioPlayer = player else { return } - callingPlayer.numberOfLoops = -1 - callingPlayer.volume = 0.5 - callingPlayer.prepareToPlay() + audioPlayer.numberOfLoops = -1 // 无限循环 + audioPlayer.volume = 0.5 + audioPlayer.prepareToPlay() - let playResult = callingPlayer.play() + let playResult = audioPlayer.play() } catch { os_log("播放循环音频失败: %{public}@", log: logger, type: .error, error.localizedDescription) diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift index 1c8a6062c..0c0ef5703 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift @@ -98,6 +98,10 @@ public class AgentServicePlugin: NSObject, FlutterPlugin { let success = impl.interruptCurrentResponse() result(success) + case "stopAiSteam": + let success = impl.interruptCurrentResponse() + result(success) + case "dispose": let success = impl.dispose() result(success) @@ -172,4 +176,4 @@ extension AgentServiceEventHandler: AgentServiceListener { } } } -} +} diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift index cf2e9ac40..b549e5f1a 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift @@ -1,5 +1,7 @@ import AVFoundation import os.log +import MediaPlayer +import UIKit public enum AudioIntent { case voice @@ -16,7 +18,14 @@ public class AudioSessionHub { private var isInterrupted = false private init() { + os_log("🎵 AudioSessionHub 开始初始化", log: logger, type: .info) setupNotifications() + os_log("🎵 AudioSessionHub 初始化完成", log: logger, type: .info) + } + + public static func initialize() { + os_log("🎵 主动初始化 AudioSessionHub", log: shared.logger, type: .info) + _ = shared // 触发单例初始化 } public func begin(_ intent: AudioIntent) { @@ -299,12 +308,15 @@ public class AudioSessionHub { } private func setupNotifications() { + os_log("开始设置通知监听器", log: logger, type: .info) + NotificationCenter.default.addObserver( self, selector: #selector(handleAudioSessionInterruption), name: AVAudioSession.interruptionNotification, object: nil ) + os_log("已注册音频会话中断监听器", log: logger, type: .info) NotificationCenter.default.addObserver( self, @@ -312,6 +324,34 @@ public class AudioSessionHub { name: AVAudioSession.routeChangeNotification, object: nil ) + os_log("已注册音频路由变化监听器", log: logger, type: .info) + + // 添加音频会话控制通知监听 + NotificationCenter.default.addObserver( + self, + selector: #selector(handleAudioSessionBeginPlayback), + name: Notification.Name("AudioSessionBeginPlayback"), + object: nil + ) + os_log("已注册播放音频会话开始监听器", log: logger, type: .info) + + NotificationCenter.default.addObserver( + self, + selector: #selector(handleAudioSessionBeginVoice), + name: Notification.Name("AudioSessionBeginVoice"), + object: nil + ) + os_log("已注册语音音频会话开始监听器", log: logger, type: .info) + + NotificationCenter.default.addObserver( + self, + selector: #selector(handleVolumeControlRequest), + name: Notification.Name("VolumeControlRequest"), + object: nil + ) + os_log("已注册音量控制请求监听器", log: logger, type: .info) + + os_log("所有通知监听器设置完成", log: logger, type: .info) } /// 处理音频中断 @@ -431,7 +471,195 @@ public class AudioSessionHub { } } + /// 处理播放音频会话激活请求 + @objc private func handleAudioSessionBeginPlayback(notification: Notification) { + os_log("收到播放音频会话激活请求", log: logger, type: .info) + begin(.playback) + } + + /// 处理语音音频会话激活请求 + @objc private func handleAudioSessionBeginVoice(notification: Notification) { + os_log("收到语音音频会话激活请求", log: logger, type: .info) + begin(.voice) + } + + /// 处理音量控制请求 + @objc private func handleVolumeControlRequest(notification: Notification) { + os_log("🔊 收到音量控制请求通知", log: logger, type: .info) + os_log("收到音量控制请求", log: logger, type: .info) + + guard let volumeInfo = notification.object as? [String: Any], + let actionString = volumeInfo["action"] as? String else { + os_log("❌ 音量控制请求数据格式错误", log: logger, type: .error) + return + } + + // 将字符串转换为 VolumeControlAction + let action: VolumeControlAction + switch actionString { + case "increase": + action = .increase + case "decrease": + action = .decrease + case "mute": + action = .mute + case "setMax": + action = .setMax + default: + os_log("未知的音量控制动作: %{public}@", log: logger, type: .error, actionString) + return + } + + // 获取 value(如果有的话) + let value: Float? + if let valueObj = volumeInfo["value"], !(valueObj is NSNull) { + value = valueObj as? Float + } else { + value = nil + } + + os_log("音量控制详情: %{public}@", log: logger, type: .info, actionString) + + // 在这里集中处理所有音量控制逻辑 + performVolumeControl(action: action, value: value) + } + + private func performVolumeControl(action: VolumeControlAction, value: Float?) { + DispatchQueue.main.async { + self.performVolumeControlWithMPVolumeView(action: action, value: value) { success, message in + os_log("音量控制结果: %{public}@ - %{public}@", log: self.logger, type: .info, + success ? "成功" : "失败", message) + + // 发送结果通知 + let resultInfo = VolumeControlResult(success: success, message: message, action: action) + NotificationCenter.default.post( + name: Notification.Name("VolumeControlResult"), + object: resultInfo + ) + } + } + } + + private func performVolumeControlWithMPVolumeView(action: VolumeControlAction, value: Float?, completion: @escaping (Bool, String) -> Void) { + // 创建更大的 frame,有助于子视图正确初始化 + let volumeView = MPVolumeView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) + volumeView.showsVolumeSlider = true + volumeView.showsRouteButton = false + volumeView.alpha = 0.01 // 几乎透明 + + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first else { + completion(false, "无法获取窗口") + return + } + + window.addSubview(volumeView) + volumeView.setNeedsLayout() + volumeView.layoutIfNeeded() + + var retryCount = 0 + let maxRetries = 5 + + func attemptVolumeControl() { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + os_log("尝试第 %d 次音量控制", log: self.logger, type: .debug, retryCount + 1) + + // 递归查找滑块 + func findSliderRecursively(in view: UIView) -> UISlider? { + if let slider = view as? UISlider { + return slider + } + for subview in view.subviews { + if let foundSlider = findSliderRecursively(in: subview) { + return foundSlider + } + } + return nil + } + + if let slider = findSliderRecursively(in: volumeView) { + let currentValue = slider.value + let newValue: Float + + switch action { + case .increase: + newValue = min(currentValue + 0.1, 1.0) + case .decrease: + newValue = max(currentValue - 0.1, 0.0) + case .mute: + newValue = 0.0 + case .setMax: + newValue = 1.0 + case .setValue(let val): + newValue = max(0.0, min(val, 1.0)) + } + + os_log("设置音量从 %.2f 到 %.2f", log: self.logger, type: .info, currentValue, newValue) + + slider.value = newValue + slider.sendActions(for: .valueChanged) + slider.sendActions(for: .touchUpInside) + + let message = self.getVolumeActionMessage(action: action) + completion(true, message) + volumeView.removeFromSuperview() + } else { + retryCount += 1 + if retryCount < maxRetries { + os_log("音量控制重试 %d/%d", log: self.logger, type: .debug, retryCount, maxRetries) + attemptVolumeControl() + } else { + os_log("所有重试都失败,使用备选方案", log: self.logger, type: .error) + volumeView.removeFromSuperview() + + // 备选方案:至少记录当前音量状态 + let currentVolume = self.audioSession.outputVolume + let message = "音量控制请求已处理(当前音量: \(Int(currentVolume * 100))%)" + completion(true, message) + } + } + } + } + + attemptVolumeControl() + } + + private func getVolumeActionMessage(action: VolumeControlAction) -> String { + switch action { + case .increase: + return "音量增加成功" + case .decrease: + return "音量降低成功" + case .mute: + return "静音设置成功" + case .setMax: + return "最大音量设置成功" + case .setValue: + return "音量设置成功" + } + } + deinit { NotificationCenter.default.removeObserver(self) } -} \ No newline at end of file +} + +// 在文件顶部添加 +enum VolumeControlAction { + case increase + case decrease + case mute + case setMax + case setValue(Float) +} + +struct VolumeControlInfo { + let action: VolumeControlAction + let value: Float? +} + +struct VolumeControlResult { + let success: Bool + let message: String + let action: VolumeControlAction +} diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/Resources/await.mp3 b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/Resources/await.mp3 new file mode 100644 index 000000000..ae1bfcc9d Binary files /dev/null and b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/Resources/await.mp3 differ diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift index 716ea30ae..97343fa99 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift @@ -648,7 +648,7 @@ public class AzureAsrHelper: NSObject { guard let dataToWrite = self.writeQueue.take() else { continue } - print("写入数据长度: \(dataToWrite.count)") + // print("写入数据长度: \(dataToWrite.count)") do { try self.pushAudioStream?.write(dataToWrite) self.onAudioData?(dataToWrite) @@ -670,12 +670,12 @@ public class AzureAsrHelper: NSObject { if audioSourceType != .external { return } - print("外部data=\(data)") + // print("外部data=\(data)") // 放入队列,由写线程写入 writeQueue.put(data) } - // 私有方法:启动麦克风捕获 +// 私有方法:启动麦克风捕获 /** * 启动麦克风捕获 * 优化:复用已初始化的音频引擎,减少启动延迟 diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt index 0e43d9188..f8f7b3db9 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt @@ -174,7 +174,7 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - Log.d(TAG, "Method call: ${call.method}") + // Log.d(TAG, "Method call: ${call.method}") when (call.method) { "isConnected" -> { diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift index c9d6b2609..e44bd6c61 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift @@ -108,7 +108,7 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin { case "startScan": let arguments = call.arguments as? [String: Any] - let timeout = (arguments?["timeout"] as? Double) ?? 30.0 + let timeout = (arguments?["timeout"] as? Double) ?? 15.0 os_log("🔍 开始传统BLE扫描,超时: %.1f秒", log: logger, type: .info, timeout) BleService.shared.startScan(timeout: timeout) diff --git a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift index 1f594bd63..a07e91f72 100644 --- a/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift +++ b/local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftOpusAudioProcessor.swift @@ -237,4 +237,4 @@ class SwiftOpusAudioProcessor: NSObject { func getCurrentConfig() -> (sampleRate: Int32, channels: Int32) { return (sampleRate, channels) } -} \ No newline at end of file +} diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift index a2dc14ec2..98773d41f 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift @@ -6,6 +6,32 @@ import MessageUI import location_service import user_portrait // 添加这行 +import MediaPlayer +import AVFoundation + +// 在文件顶部添加通知名称定义 +extension Notification.Name { + static let audioSessionBeginPlayback = Notification.Name("AudioSessionBeginPlayback") + static let audioSessionBeginVoice = Notification.Name("AudioSessionBeginVoice") + static let volumeControlRequest = Notification.Name("VolumeControlRequest") +} + +// 音量控制请求的数据结构 +// 移除结构体定义,直接使用 map 传输 +// 删除以下代码: +// struct VolumeControlInfo { +// let action: VolumeAction +// let value: Float? +// +// enum VolumeAction { +// case increase +// case decrease +// case mute +// case setMax +// case setValue(Float) +// } +// } + /// 结果结构体 public struct Result { let success: Bool @@ -66,6 +92,13 @@ public class SystemFunctionHandler: NSObject { // 注册打开录音机功能 registerOpenRecorderFunction(client: client) + // 注册音量控制函数 + // 在registerOpenRecorderFunction方法之后添加以下方法 + registerAddVolumeFunction(client: client) + registerReduceVolumeFunction(client: client) + registerMuteVolumeFunction(client: client) + registerMaxVolumeFunction(client: client) + // 注册用户画像更新功能 registerSetUserProfileFieldFunction(client: client) @@ -311,6 +344,90 @@ public class SystemFunctionHandler: NSObject { handler: handler ) } + + + /// 注册增加音量函数 + private func registerAddVolumeFunction(client: MCPClient) { + client.registerLocalFunction( + name: "add_volume", + description: "增大音量", + parameters: [ + "type": "object", + "properties": [:] as [String: Any], + "required": [] as [String] + ], + handler: AddVolumeHandler() + ) + } + + /// 注册减少音量函数 + private func registerReduceVolumeFunction(client: MCPClient) { + client.registerLocalFunction( + name: "reduce_volume", + description: "降低音量", + parameters: [ + "type": "object", + "properties": [:] as [String: Any], + "required": [] as [String] + ], + handler: ReduceVolumeHandler() + ) + } + + /// 注册静音函数 + private func registerMuteVolumeFunction(client: MCPClient) { + client.registerLocalFunction( + name: "mute_volume", + description: "设置静音", + parameters: [ + "type": "object", + "properties": [:] as [String: Any], + "required": [] as [String] + ], + handler: MuteVolumeHandler() + ) + } + + /// 注册最大音量函数 + private func registerMaxVolumeFunction(client: MCPClient) { + client.registerLocalFunction( + name: "max_volume", + description: "设置最大音量", + parameters: [ + "type": "object", + "properties": [:] as [String: Any], + "required": [] as [String] + ], + handler: MaxVolumeHandler() + ) + } + + /// 注册设置用户画像字段函数 + private func registerSetUserProfileFieldFunction(client: MCPClient) { + let parameters: [String: Any] = [ + "type": "object", + "properties": [ + "key": [ + "type": "string", + "description": "画像字段名,例如:age、tone、工作经历、device_preference" + ], + "value": [ + "type": "string", + "description": "字段对应的值" + ] + ], + "required": ["key", "value"] + ] + + let handler = SetUserProfileFieldHandler() + _ = client.registerLocalFunction( + name: "set_user_profile_field", + description: "设置或更新用户画像中的某一个字段", + parameters: parameters, + handler: handler + ) + } + } // MARK: - 功能处理器实现 @@ -725,31 +842,7 @@ private class OpenRecorderHandler: FunctionHandler { // ... // } - /// 注册设置用户画像字段函数 - private func registerSetUserProfileFieldFunction(client: MCPClient) { - let parameters: [String: Any] = [ - "type": "object", - "properties": [ - "key": [ - "type": "string", - "description": "画像字段名,例如:age、tone、工作经历、device_preference" - ], - "value": [ - "type": "string", - "description": "字段对应的值" - ] - ], - "required": ["key", "value"] - ] - - let handler = SetUserProfileFieldHandler() - _ = client.registerLocalFunction( - name: "set_user_profile_field", - description: "设置或更新用户画像中的某一个字段", - parameters: parameters, - handler: handler - ) - } + /// 设置用户画像字段处理器 private class SetUserProfileFieldHandler: FunctionHandler { func handle(arguments: [String: Any]) async throws -> String { @@ -799,3 +892,67 @@ private class SetUserProfileFieldHandler: FunctionHandler { } } } + +private class AddVolumeHandler: FunctionHandler { + func handle(arguments: [String: Any]) async throws -> String { + // 使用 map 传输数据 + let volumeInfo: [String: Any] = [ + "action": "increase", + "value": NSNull() + ] + NotificationCenter.default.post( + name: .volumeControlRequest, + object: volumeInfo + ) + + return "{\"result\": \"音量增加请求已发送\"}" + } +} + +/// 减少音量处理器 +private class ReduceVolumeHandler: FunctionHandler { + func handle(arguments: [String: Any]) async throws -> String { + let volumeInfo: [String: Any] = [ + "action": "decrease", + "value": NSNull() + ] + NotificationCenter.default.post( + name: .volumeControlRequest, + object: volumeInfo + ) + + return "{\"result\": \"音量降低请求已发送\"}" + } +} + +/// 静音处理器 +private class MuteVolumeHandler: FunctionHandler { + func handle(arguments: [String: Any]) async throws -> String { + let volumeInfo: [String: Any] = [ + "action": "mute", + "value": NSNull() + ] + NotificationCenter.default.post( + name: .volumeControlRequest, + object: volumeInfo + ) + + return "{\"result\": \"静音请求已发送\"}" + } +} + +/// 最大音量处理器 +private class MaxVolumeHandler: FunctionHandler { + func handle(arguments: [String: Any]) async throws -> String { + let volumeInfo: [String: Any] = [ + "action": "setMax", + "value": NSNull() + ] + NotificationCenter.default.post( + name: .volumeControlRequest, + object: volumeInfo + ) + + return "{\"result\": \"最大音量请求已发送\"}" + } +} diff --git a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt index 4c23455b1..3c90cfabc 100644 --- a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt +++ b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt @@ -25,7 +25,7 @@ class LocationServicePlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Pl } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - Log.d(TAG, "Method call: ${call.method}") + // Log.d(TAG, "Method call: ${call.method}") when (call.method) { "location" -> LocationService.getCurrentLocation(result) "requestPermission" -> LocationService.requestLocationPermission(activityBinding?.activity, result) diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt index 9d5f1043b..3bfaa1e1b 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt @@ -24,7 +24,7 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - Log.d(TAG, "Method call: ${call.method}") + // Log.d(TAG, "Method call: ${call.method}") when (call.method) { "play" -> { val song = call.argument>("song") diff --git a/local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift b/local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift index f4c99cd67..462a3f53d 100644 --- a/local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift +++ b/local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift @@ -13,7 +13,7 @@ public class MusicServicePlugin: NSObject, FlutterPlugin { } public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - print("Method call: \(call.method)") + // print("Method call: \(call.method)") switch call.method { case "play":