Browse Source

上传音量和蓝牙绑定优化

weicu
liwei1dao 1 year ago
parent
commit
559bf4b7cd
  1. 2
      ios/Runner/Info.plist
  2. 1
      lib/modules/devices/controllers/devices_controller.dart
  3. 106
      lib/modules/pairing/controllers/pairing_controller.dart
  4. 14
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  5. 48
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  6. 4
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServicePlugin.swift
  7. 228
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AudioSessionHub.swift
  8. BIN
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/Resources/await.mp3
  9. 6
      local_plugins/azure_speech/ios/azure_speech/Sources/azure_speech/AzureAsrHelper.swift
  10. 2
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt
  11. 2
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift
  12. 205
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift
  13. 2
      local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt
  14. 2
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt
  15. 2
      local_plugins/music_service/ios/music_service/Sources/music_service/MusicServicePlugin.swift

2
ios/Runner/Info.plist

@ -59,6 +59,8 @@
<string>193d0a2b1757c41cfa557ea8e8f21c33</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>App-Prefs</string>
<string>prefs</string>
<string>weixin</string>
<string>weixinULAPI</string>
<string>weixinURLParamsAPI</string>

1
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 {

106
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<Map<String, String>>? 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,8 +1010,86 @@ class PairingController extends GetxController {
/// 打开蓝牙设置
void openBluetoothSettings() {
_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('我知道了'),
),
],
),
);
}
/// 清除设备关联(在需要重新配对时调用)
Future<void> clearDeviceAssociation() async {

14
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()
// 发送完整回复,包含是否有图片的标记

48
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)
@ -1230,12 +1245,16 @@ class AudioPlayer {
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() {
@ -1243,6 +1262,11 @@ class AudioPlayer {
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) {
let url = URL(fileURLWithPath: path)
@ -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)

4
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)

228
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)
}
}
// 在文件顶部添加
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
}

BIN
local_plugins/agent_service/ios/agent_service/Sources/agent_service/Resources/await.mp3

Binary file not shown.

6
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)
}
// 私有方法:启动麦克风捕获
// 私有方法:启动麦克风捕获
/**
* 启动麦克风捕获
* 优化:复用已初始化的音频引擎,减少启动延迟

2
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" -> {

2
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)

205
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\": \"最大音量请求已发送\"}"
}
}

2
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)

2
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<Map<String, Any>>("song")

2
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":

Loading…
Cancel
Save