Browse Source

上传测试

weicu
liwei1dao 1 year ago
parent
commit
d7d5c7a55d
  1. 215
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  2. 120
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt
  3. 44
      local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt
  4. 73
      local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

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

@ -1,9 +1,11 @@
package com.yunqiinnovation.agent_service
import android.content.Context
import android.content.Intent
import org.json.JSONArray
import org.json.JSONObject
import com.yunqiinnovation.azure_speech.AzureAsrHelper
import com.yunqiinnovation.azure_speech.AzureTtsHelper
import com.yunqiinnovation.chat_api.ChatApiService
import com.yunqiinnovation.chat_api.StreamCallback
import com.yunqiinnovation.chat_storage.ChatStorageHelper
@ -23,6 +25,8 @@ import com.deep_voice.speech.tts.TtsEvent
import com.deep_voice.speech.tts.TtsEventListener
import com.deep_voice.speech.tts.TtsEventType
import com.deep_voice.bytedance_speech.BytedanceTTS
import java.util.UUID
/**
* 代理服务事件监听接口
*/
@ -56,7 +60,7 @@ object AgentService : CoroutineScope {
private var azureAsrHelper: AzureAsrHelper? = null
// 使用BytedanceTTS作为唯一的TTS实现
private var ttsService: BytedanceTTS? = null
private var ttsService: AzureTtsHelper? = null
// ChatAPI服务 - 使用新的ChatApiService
private lateinit var chatApiService: ChatApiService
@ -97,7 +101,7 @@ object AgentService : CoroutineScope {
// AI流生成相关
private var currentAiJob: Job? = null
private var currsessionId:String = ""
// 空闲检测相关
private var idleCheckJob: Job? = null
private val maxIdleSeconds = 10 // 最大空闲秒数
@ -244,14 +248,14 @@ object AgentService : CoroutineScope {
val ttsLanguage = config["ttsLanguage"]?.toString() ?: "zh-CN"
// 创建并初始化BytedanceTTS
val bytedanceTts = BytedanceTTS(context)
ttsService = bytedanceTts
val azureTtsHelper = AzureTtsHelper(context)
ttsService = azureTtsHelper
// 初始化Bytedance TTS
val success = bytedanceTts.initialize(
ttsAppId = ttsAppId,
ttsAppToken = ttsAppToken,
ttsResource = "", // Bytedance TTS不需要resource参数
val success = azureTtsHelper.initialize(
ttsAppId = "", // Azure TTS不需要appId
ttsAppToken = config["azureSpeechKey"]?.toString() ?: "", // Azure需要subscription key
ttsResource = config["azureSpeechRegion"]?.toString() ?: "", // Azure需要region信息
language = ttsLanguage
)
@ -278,13 +282,23 @@ object AgentService : CoroutineScope {
restartIdleCheck()
sendEvent("tts_canceled", mapOf("status" to "canceled"))
}
TtsEventType.PLAYBACK_STARTED -> {
TtsEventType.PLAYBACK_STARTED -> { //开始AI播报内容
restartIdleCheck()
sendEvent("playback_started", mapOf("status" to "playback_started"))
// 发送AI说话开始广播
val intent = Intent("com.yunqiinnovation.music_service.AI_SAY_START").apply {
setPackage(context.packageName)
}
context.sendBroadcast(intent)
}
TtsEventType.PLAYBACK_COMPLETED -> {
TtsEventType.PLAYBACK_COMPLETED -> { //结束AI播报内容
restartIdleCheck()
sendEvent("playback_completed", mapOf("status" to "playback_completed"))
// 发送AI说话开始广播
val intent = Intent("com.yunqiinnovation.music_service.AI_SAY_END").apply {
setPackage(context.packageName)
}
context.sendBroadcast(intent)
}
TtsEventType.ERROR -> {
_isTtsSpeaking.set(false)
@ -666,6 +680,7 @@ object AgentService : CoroutineScope {
// 记录新的AI任务
currentAiJob = launch {
try {
currsessionId = UUID.randomUUID().toString()
// 设置状态为正在流式输出
_isAiStreaming.set(true)
audioPlayer?.playAudio(R.raw.await, true,0.3f)
@ -692,8 +707,8 @@ object AgentService : CoroutineScope {
)
val _systemPrompt = Utils.fillTemplate(systemPrompt,location)
messagesWithSystemPrompt.put(createSystemMessage(_systemPrompt))
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}")
}
// Log.d(TAG, "liwei--------- 系统提示词: ${_systemPrompt}")
}
// 再添加历史消息
for (i in 0 until historyMessages.length()) {
@ -730,61 +745,84 @@ object AgentService : CoroutineScope {
}
// Log.e("ChatApiService", "liwei---------sendMessageStream messages:${messagesWithSystemPrompt}")
chatApiService.sendMessageStream(
currsessionId,
messagesWithSystemPrompt,
object : StreamCallback {
override fun onToken(token: String) {
responseBuilder.append(token)
if (speakResponse && broadcast) {
ttsService?.speakStream(token)
if (token.length > 0){
audioPlayer?.stopAudio()
override fun onToken(sessionid:String,token: String) {
try {
if (sessionid != currsessionId){
return
}
}
if (broadcast){
// 发送流式回复token
sendEvent("assistant_token", mapOf("token" to token))
responseBuilder.append(token)
if (speakResponse && broadcast) {
ttsService?.speakStream(token)
if (token.length > 0) {
audioPlayer?.stopAudio()
}
}
if (broadcast) {
// 发送流式回复token
sendEvent("assistant_token", mapOf("token" to token))
}
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onToken 异常", e)
}
}
override fun onComplete() {
// 视情况决定是否朗读回复
if (speakResponse && broadcast) {
ttsService?.flushStream()
}
val response = responseBuilder.toString()
// 发送完整回复,包含是否有图片的标记
val responseData = mutableMapOf<String, Any>(
"text" to response,
"userInput" to displayText
)
if (hasImage) {
responseData["hasImage"] = true
}
sendEvent("assistant_response", responseData)
if (response.isNotEmpty()) {
// 添加AI回复到历史记录
addToHistoryMessages(createAssistantMessage(response))
override fun onComplete(sessionid:String) {
try {
// 视情况决定是否朗读回复
if (speakResponse && broadcast && sessionid == currsessionId){
ttsService?.flushStream()
}
val response = responseBuilder.toString()
// 发送完整回复,包含是否有图片的标记
val responseData = mutableMapOf<String, Any>(
"text" to response,
"userInput" to displayText
)
if (hasImage) {
responseData["hasImage"] = true
}
sendEvent("assistant_response", responseData)
if (response.isNotEmpty()) {
// 添加AI回复到历史记录
addToHistoryMessages(createAssistantMessage(response))
}
// 保存聊天记录
saveChatMessage(
displayText,
response,
aiMetadata,
userMetadata.toString()
)
// 标记AI流式输出已完成
_isAiStreaming.set(false)
currentAiJob = null
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onComplete 异常", e)
}
// 保存聊天记录
saveChatMessage(displayText, response,aiMetadata,userMetadata.toString())
// 标记AI流式输出已完成
_isAiStreaming.set(false)
currentAiJob = null
}
override fun onError(e: Exception) {
Log.e(TAG, "AI处理出错", e)
sendEvent("error", mapOf(
"code" to "AI_ERROR",
"message" to e.message.toString()
))
// 标记AI流式输出已完成
_isAiStreaming.set(false)
currentAiJob = null
override fun onError(sessionid:String,e: Exception) {
try {
Log.e(TAG, "AI处理出错", e)
sendEvent(
"error", mapOf(
"code" to "AI_ERROR",
"message" to e.message.toString()
)
)
// 标记AI流式输出已完成
_isAiStreaming.set(false)
currentAiJob = null
}catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onError 异常", e)
}
}
override fun onFunctionCall(call: JSONObject) {
override fun onFunctionCall(sessionid:String,call: JSONObject) {
try {
// audioPlayer?.playAudio(R.raw.calling, true)
val name = call.getString("name")
@ -796,28 +834,34 @@ object AgentService : CoroutineScope {
stopRecognition()
}
} catch (e: Exception) {
Log.e(TAG, "处理函数调用事件失败", e)
Log.e(TAG, "liwei--------------- AI Call onFunctionCall 异常", e)
}
}
override fun onFunctionCallResult(functionCall: JSONObject, functionCallResult: JSONObject) {
audioPlayer?.stopAudio()
val name = functionCall.get("name") as String;
val resultmap = autoHandleFcunCallResult(name,functionCallResult);
aiMetadata = resultmap["meta"] as String;
broadcast = resultmap["broadcast"] as Boolean;
sendEvent("function_call_result", mapOf(
"function_call" to functionCall.toString(),
"result" to functionCallResult.toString(),
"meta" to aiMetadata,
))
override fun onFunctionCallResult(sessionid:String,functionCall: JSONObject, functionCallResult: JSONObject) {
try {
audioPlayer?.stopAudio()
val name = functionCall.get("name") as String;
val resultmap = autoHandleFcunCallResult(name, functionCallResult);
aiMetadata = resultmap["meta"] as String;
broadcast = resultmap["broadcast"] as Boolean;
sendEvent(
"function_call_result", mapOf(
"function_call" to functionCall.toString(),
"result" to functionCallResult.toString(),
"meta" to aiMetadata,
)
)
} catch (e: Exception) {
Log.e(TAG, "liwei--------------- AI Call onFunctionCallResult 异常", e)
}
}
}
)
} catch (e: Exception) {
val errorType = if (hasImage) "AI_IMAGE_PROCESS_ERROR" else "AI_PROCESS_ERROR"
Log.e(TAG, "AI处理出错", e)
Log.e(TAG, "liwei--------------- AI处理出错", e)
sendEvent("error", mapOf(
"code" to errorType,
"message" to e.message.toString()
@ -1119,6 +1163,7 @@ object AgentService : CoroutineScope {
*/
class AudioPlayer(private val context: Context) {
private var mediaPlayer: MediaPlayer? = null
private var isInitialized = false
/**
* 播放音频资源
@ -1155,21 +1200,47 @@ object AgentService : CoroutineScope {
release()
}
}
fun stopAudio() {
mediaPlayer?.stop()
try {
mediaPlayer?.apply {
when {
isPlaying -> {
stop()
Log.d(TAG, "音频已停止")
}
else -> {
Log.d(TAG, "音频未在播放状态,无需停止")
}
}
}
} catch (e: IllegalStateException) {
Log.e(TAG, "MediaPlayer 状态异常,无法停止: ${e.message}", e)
// 重置 MediaPlayer
release()
} catch (e: Exception) {
Log.e(TAG, "停止音频播放异常: ${e.message}", e)
}
}
/**
* 释放资源
*/
fun release() {
try {
mediaPlayer?.apply {
if (isPlaying) stop()
if (isPlaying) {
stop()
}
reset()
release()
}
mediaPlayer = null
isInitialized = false
} catch (e: Exception) {
// 忽略异常
Log.e(TAG, "释放音频资源异常: ${e.message}", e)
mediaPlayer = null
isInitialized = false
}
}
}

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

@ -39,27 +39,27 @@ interface StreamCallback {
/**
* 接收到新的token
*/
fun onToken(token: String)
fun onToken(sessionid: String,token: String)
/**
* 流式响应完成
*/
fun onComplete()
fun onComplete(sessionid: String)
/**
* 出现错误
*/
fun onError(error: Exception)
fun onError(sessionid: String,error: Exception)
/**
* 函数调用 - 兼容JSONObject格式
*/
fun onFunctionCall(functionCall: org.json.JSONObject)
fun onFunctionCall(sessionid: String,functionCall: org.json.JSONObject)
/**
* 函数调用结果 - 兼容JSONObject格式
*/
fun onFunctionCallResult(functionCall: org.json.JSONObject, functionCallResult: org.json.JSONObject)
fun onFunctionCallResult(sessionid: String,functionCall: org.json.JSONObject, functionCallResult: org.json.JSONObject)
}
/**
@ -142,7 +142,8 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
private var currentMessages: List<Map<String, Any>> = emptyList()
private var toolCalls: MutableMap<Int, ToolCallInfo> = Collections.synchronizedMap(mutableMapOf())
private var currSessionId = ""
private var currSessionCallback: StreamCallback? = null
// 为每个会话维护独立的回调函数
private val sessionCallbacks: MutableMap<String, StreamCallback> = Collections.synchronizedMap(mutableMapOf())
@ -156,17 +157,18 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
*/
private fun abortCurrentSession() {
if (currSessionId.isNotEmpty()) {
val oldSessionId = currSessionId
// 1. 取消当前的协程任务
currentStreamJob?.cancel()
currentStreamJob = null
// 2. 通知旧会话被中止
// currSessionCallback?.onError(ChatApiException("Session aborted by new request"))
currSessionCallback?.onComplete() // 直接完成当前会话
getSessionCallback(oldSessionId)?.onComplete(currSessionId) // 直接完成当前会话
// 3. 清理状态
currSessionId = ""
currSessionCallback = null
sessionCallbacks.remove(oldSessionId)
toolCalls.clear()
}
}
@ -175,12 +177,20 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
* 清理当前会话
*/
private fun clearCurrentSession() {
val oldSessionId = currSessionId
currSessionId = ""
currSessionCallback = null
sessionCallbacks.remove(oldSessionId)
currentStreamJob = null
toolCalls.clear()
}
/**
* 获取指定会话的回调函数
*/
private fun getSessionCallback(sessionId: String): StreamCallback? {
return if (sessionId == currSessionId) sessionCallbacks[sessionId] else null
}
/**
* 初始化ChatAPI服务
*/
@ -312,17 +322,18 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
/**
* 发送消息(流式输出)- 兼容JSONArray格式
*/
fun sendMessageStream(messages: org.json.JSONArray, callback: StreamCallback) {
fun sendMessageStream(sessionid: String,messages: org.json.JSONArray, callback: StreamCallback) {
// 1. 立即中止旧会话
abortCurrentSession()
// 2. 创建新会话
currSessionId = UUID.randomUUID().toString()
currSessionCallback = callback
// val newSessionId = UUID.randomUUID().toString()
currSessionId = sessionid
sessionCallbacks[sessionid] = callback
// 3. 转换消息并开始处理
val messageList = convertJsonArrayToList(messages)
sendMessageStream(currSessionId, messageList)
sendMessageStream(sessionid, messageList)
}
/**
@ -336,7 +347,11 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
if (!isInitialized || apiKey.isEmpty() || openAI == null) {
Log.e("ChatApiService", "ChatAPI服务未初始化,无法发送消息")
currSessionCallback?.onError(ChatApiException("ChatAPI服务未初始化"))
try {
getSessionCallback(sessionid)?.onError(sessionid,ChatApiException("ChatAPI服务未初始化"))
} catch (e: Exception) {
Log.e(TAG, "onError回调异常: ${e.message}", e)
}
return
}
@ -345,7 +360,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
toolCalls.clear()
// 检查是否包含图片,决定使用哪个模型
val currentModel = if (containsImage(messages)) visionModel else model
// Log.d(TAG, "liwei-------------------------开始AI 对话 0")
currentStreamJob = launch {
try {
// 转换消息格式
@ -355,22 +370,22 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
Log.e("ChatApiService", "转换消息格式失败: ${e.message}", e)
throw e
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 1")
// 直接获取工具列表
val tools = getOpenAiTools()
// Log.d(TAG, "liwei-------------------------开始AI 对话 2")
// 构建请求
if (currentModel.isEmpty()) {
Log.e("ChatApiService", "模型名称为空")
throw IllegalArgumentException("模型名称不能为空")
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 3")
if (chatMessages.isEmpty()) {
Log.e("ChatApiService", "消息列表为空")
throw IllegalArgumentException("消息列表不能为空")
}
// Log.e("ChatApiService", "AI解析工具列表: ${tools}")
// Log.d(TAG, "liwei-------------------------开始AI 对话 4")
val chatsFlow = try {
val chatCompletionRequest = ChatCompletionRequest(
model = ModelId(currentModel),
@ -379,42 +394,49 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
temperature = 0.7,
tools = if (tools.isNotEmpty()) tools else null
)
// Log.d(TAG, "liwei-------------------------开始AI 对话 5")
if (openAI == null) {
Log.e("ChatApiService", "openAI对象为null")
throw IllegalStateException("OpenAI客户端未初始化")
}
// Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}")
val flow = openAI!!.chatCompletions(chatCompletionRequest)
// Log.d(TAG, "liwei-------------------------开始AI 对话 6")
flow
} catch (e: Exception) {
Log.e("ChatApiService", "创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e)
throw e
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 7")
chatsFlow.collect { result ->
// 每次处理前检查是否仍是当前会话
if (sessionid != currSessionId) {
throw CancellationException("Session cancelled")
// throw CancellationException("Session cancelled")
return@collect
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 7-1")
val choice = result.choices.firstOrNull()
if (choice == null) {
Log.w("ChatApiService", "[Session: $sessionid] choice为null")
return@collect
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 7-2")
val delta = choice.delta
if (delta == null) {
Log.w("ChatApiService", "[Session: $sessionid] delta为null")
return@collect
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 7-3")
// 处理普通文本内容
delta.content?.let { content ->
currSessionCallback?.onToken(content)
try {
getSessionCallback(sessionid)?.onToken(sessionid,content)
} catch (e: Exception) {
Log.e(TAG, "onToken回调异常: ${e.message}", e)
// 不重新抛出异常,继续处理后续内容
}
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 7-4")
// 收集工具调用信息
delta.toolCalls?.forEach { toolCall ->
try {
@ -461,23 +483,32 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
}
}
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 8")
// 完成处理
if (sessionid == currSessionId) {
// Log.d(TAG, "liwei-------------------------开始AI 对话 9")
// 检查是否有工具调用需要处理
val hasToolCalls = processToolCalls(sessionid)
if (!hasToolCalls) {
currSessionCallback?.onComplete()
try {
getSessionCallback(sessionid)?.onComplete(sessionid)
} catch (e: Exception) {
Log.e(TAG, "onComplete回调异常: ${e.message}", e)
}
clearCurrentSession()
}
}
// Log.d(TAG, "liwei-------------------------开始AI 对话 10")
} catch (e: CancellationException) {
// 会话被取消,不需要处理
Log.d("ChatApiService", "Session $sessionid cancelled")
Log.d(TAG, "Session $sessionid cancelled")
} catch (e: Exception) {
if (sessionid == currSessionId) {
currSessionCallback?.onError(ChatApiException("流式请求失败: ${e.message}"))
try {
getSessionCallback(sessionid)?.onError(sessionid,ChatApiException("流式请求失败: ${e.message}"))
} catch (ex: Exception) {
Log.e(TAG, "onError回调异常: ${ex.message}", ex)
}
clearCurrentSession()
}
}
@ -517,7 +548,11 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
"id" to firstToolCall.id
)
// 通知上层工具调用事件
currSessionCallback?.onFunctionCall(convertMapToJsonObject(functionCall))
try {
getSessionCallback(sessionid)?.onFunctionCall(sessionid,convertMapToJsonObject(functionCall))
} catch (e: Exception) {
Log.e(TAG, "onFunctionCall回调异常: ${e.message}", e)
}
// 在当前协程作用域内处理工具调用,使用async确保生命周期管理
val toolCallDeferred = async {
try {
@ -564,10 +599,15 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
}
if (sessionid == currSessionId) {
// 处理结果
currSessionCallback?.onFunctionCallResult(
convertMapToJsonObject(functionCall),
convertMapToJsonObject(result)
)
try {
getSessionCallback(sessionid)?.onFunctionCallResult(
sessionid,
convertMapToJsonObject(functionCall),
convertMapToJsonObject(result)
)
} catch (e: Exception) {
Log.e(TAG, "onFunctionCallResult回调异常: ${e.message}", e)
}
// 将结果发送回OpenAI继续对话
sendFunctionCallResultInternal(
sessionid= sessionid,
@ -843,7 +883,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
coroutineContext[Job]?.cancelChildren()
// 清理其他资源
currSessionCallback = null
sessionCallbacks.clear()
currSessionId = ""
currentMessages = emptyList()
toolCalls.clear()
@ -1305,4 +1345,4 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor
return "Function '$functionName' called with arguments: $arguments"
}
}
}
}

44
local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/MCPSubClient.kt

@ -50,6 +50,7 @@ class MCPSubClient(
* 连接到MCP服务器
*/
suspend fun connect(): Boolean = connectionMutex.withLock {
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 1")
if (isConnected) return true
Log.e(TAG, "[$serverId] 开始连接mcp服务器: $serverUrl")
return try {
@ -60,7 +61,7 @@ class MCPSubClient(
version = "1.0.0"
)
)
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 2")
// 根据URL类型选择传输方式
val newTransport = when {
serverUrl.startsWith("http://") || serverUrl.startsWith("https://") -> {
@ -83,15 +84,26 @@ class MCPSubClient(
return false
}
}
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 3")
transport = newTransport
// 连接到服务器
client.connect(newTransport)
try {
// 连接到服务器
withTimeout(10000) { // 10秒超时
client.connect(newTransport)
}
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 4")
} catch (e: TimeoutCancellationException) {
Log.e(TAG, "liwei---------Mcp [$serverId] 连接超时")
return false
} catch (e: Exception) {
Log.e(TAG, "liwei---------Mcp [$serverId] 连接异常: ${e.message}", e)
return false
}
// 获取可用工具列表
try {
val toolsResult = client.listTools()
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 5")
if (toolsResult != null) {
availableTools.clear()
val filtered = toolsResult.tools.filter { tool ->
@ -99,17 +111,19 @@ class MCPSubClient(
}
Log.w(TAG, "[$serverId] [${filterTools}] 获取工具列表: ${filtered} 原始列表:${toolsResult.tools}")
availableTools.addAll(filtered)
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 6")
}
} catch (e: Exception) {
Log.w(TAG, "[$serverId] 获取工具列表失败: ${e.message}")
// 即使获取工具失败,连接也可能是成功的
}
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 7")
mcpClient = client
isConnected = true
retryCount = 0
currentReconnectDelay = initialReconnectDelay
// Log.e(TAG, "liwei---------Mcp [$serverId] 连接 8")
// 启动心跳检测
// startHeartbeat()
Log.e(TAG, "[$serverId] 连接mcp服务器成功: $serverUrl")
@ -162,11 +176,23 @@ class MCPSubClient(
// 先检查连接状态
if (!checkConnection()) {
Log.e(TAG, "无法连接到服务器,工具调用失败")
return null
return mapOf(
"content" to listOf(mapOf(
"type" to "text",
"text" to "Tool call failed: mcp: service not connected"
)),
"isError" to true
)
}
if (!containsTool(name)) {
Log.w(TAG, "此客户端不包含工具: $name")
return null
return mapOf(
"content" to listOf(mapOf(
"type" to "text",
"text" to "Tool call failed: no found tool"
)),
"isError" to true
)
}
return try {
// 创建工具调用请求 - 将Map转换为JsonObject

73
local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt

@ -39,6 +39,9 @@ class MusicService : MediaBrowserServiceCompat() {
private var currentStatus = false
private var playlist = mutableListOf<Map<String,Any>>()
private var currentIndex = 0
private var currentVolume: Float = 1.0f // 当前音量
private var originalVolume: Float = 1.0f // 原始音量,用于恢复
private var isVolumeReduced: Boolean = false // 是否已降低音量
fun isServiceRunning() = isRunning.get()
fun isPlaying() = currentStatus
@ -179,6 +182,8 @@ class MusicService : MediaBrowserServiceCompat() {
addAction("com.yunqiinnovation.music_service.MEDIA_CLOSE")
addAction("com.yunqiinnovation.music_service.MEDIA_PREVIOUS")
addAction("com.yunqiinnovation.music_service.MEDIA_NEXT")
addAction("com.yunqiinnovation.music_service.AI_SAY_START")
addAction("com.yunqiinnovation.music_service.AI_SAY_END")
}
// Android 14+ 需要指定 flag
@ -205,7 +210,7 @@ class MusicService : MediaBrowserServiceCompat() {
val songMap = intent.getSerializableExtra("song") as? Map<String, Any>
songMap?.let { song ->
val songId = song["id"] ?: return@let
val existingIndex = playlist.indexOfFirst { it["id"] == songId }
if (existingIndex == -1) {
playlist.add(song)
@ -213,7 +218,7 @@ class MusicService : MediaBrowserServiceCompat() {
} else {
currentIndex = existingIndex
}
play(song)
}
}
@ -238,6 +243,10 @@ class MusicService : MediaBrowserServiceCompat() {
seekTo(position)
}
}
"setVolume" -> {
val volume = intent.getFloatExtra("volume", 1.0f)
setVolume(volume)
}
}
return START_STICKY
}
@ -267,6 +276,14 @@ class MusicService : MediaBrowserServiceCompat() {
Log.i(TAG, "收到 MEDIA_NEXT 广播")
playNext()
}
"com.yunqiinnovation.music_service.AI_SAY_START" -> {
Log.i(TAG, "收到 AI_SAY_START 广播 降低播放音量")
reduceVolume()
}
"com.yunqiinnovation.music_service.AI_SAY_END" -> {
Log.i(TAG, "收到 AI_SAY_START 广播 降低播放音量")
restoreVolume()
}
// 你也可以扩展支持更多指令
}
}
@ -427,19 +444,7 @@ class MusicService : MediaBrowserServiceCompat() {
return PendingIntent.getBroadcast(this, action.toInt(), intent, flags)
}
// fun mediaButtonPendingIntent(action: Long): PendingIntent {
// val intent = Intent(this, MusicService::class.java).apply {
// this.action = Intent.ACTION_MEDIA_BUTTON
// putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, action.toKeyCode()))
// }
// val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
// } else {
// PendingIntent.FLAG_UPDATE_CURRENT
// }
// return PendingIntent.getService(this, action.toInt(), intent, flags)
// }
// 转换 PlaybackStateCompat.ACTION 到 KeyEvent
fun Long.toKeyCode(): Int = when (this) {
PlaybackStateCompat.ACTION_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY
PlaybackStateCompat.ACTION_PAUSE -> KeyEvent.KEYCODE_MEDIA_PAUSE
@ -447,6 +452,43 @@ class MusicService : MediaBrowserServiceCompat() {
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS -> KeyEvent.KEYCODE_MEDIA_PREVIOUS
else -> KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
}
/**
* 设置播放音量
* @param volume 音量值,范围 0.0f - 1.0f
*/
private fun setVolume(volume: Float) {
val clampedVolume = volume.coerceIn(0.0f, 1.0f)
currentVolume = clampedVolume
mediaPlayer?.setVolume(clampedVolume, clampedVolume)
Log.i(TAG, "设置音量: $clampedVolume")
}
/**
* 降低音量(AI说话时)
*/
private fun reduceVolume() {
if (!isVolumeReduced) {
originalVolume = currentVolume
val reducedVolume = (currentVolume * 0.3f).coerceIn(0.0f, 1.0f) // 降低到30%
setVolume(reducedVolume)
isVolumeReduced = true
Log.i(TAG, "AI说话开始,音量从 $originalVolume 降低到 $reducedVolume")
}
}
/**
* 恢复原始音量(AI说话结束时)
*/
private fun restoreVolume() {
if (isVolumeReduced) {
setVolume(originalVolume)
isVolumeReduced = false
Log.i(TAG, "AI说话结束,音量恢复到 $originalVolume")
}
}
/**
* 设置播放状态
*/
@ -601,3 +643,4 @@ class MusicService : MediaBrowserServiceCompat() {
override fun onBind(intent: Intent?): IBinder? = null
}

Loading…
Cancel
Save