Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

newdev_shunjiawei
lxm 1 year ago
parent
commit
f7c27c09d2
  1. 81
      local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt
  2. 3
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  3. 203
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift

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

@ -2,6 +2,8 @@ package com.yunqiinnovation.agent_service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Handler
import android.os.Looper
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import com.yunqiinnovation.azure_speech.AzureAsrHelper import com.yunqiinnovation.azure_speech.AzureAsrHelper
@ -38,6 +40,7 @@ import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.Date import java.util.Date
/** /**
* 代理服务事件监听接口 * 代理服务事件监听接口
*/ */
@ -771,6 +774,9 @@ object AgentService : CoroutineScope {
// 通知ChatAPI服务终止当前流式请求 // 通知ChatAPI服务终止当前流式请求
chatApiService.cancelCurrentStream() chatApiService.cancelCurrentStream()
//结束等待音
audioPlayer?.stopAudio()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "停止AI流输出异常", e) Log.e(TAG, "停止AI流输出异常", e)
// 确保状态被重置,即使发生异常 // 确保状态被重置,即使发生异常
@ -873,7 +879,7 @@ object AgentService : CoroutineScope {
// 设置状态为正在流式输出 // 设置状态为正在流式输出
_isAiStreaming.set(true) _isAiStreaming.set(true)
Log.d(TAG, "processWithChatApiInternal") Log.d(TAG, "processWithChatApiInternal")
audioPlayer?.playAudio(R.raw.await, true, 0.3f) audioPlayer?.playAudio(R.raw.await, true, 0.3f,true)
// 使用历史记录作为上下文发送到OpenAI // 使用历史记录作为上下文发送到OpenAI
val responseBuilder = StringBuilder() val responseBuilder = StringBuilder()
var aiMetadata: String = "" var aiMetadata: String = ""
@ -970,8 +976,6 @@ object AgentService : CoroutineScope {
responseBuilder.append(token) responseBuilder.append(token)
if (speakResponse && reply && broadcast && token.isNotEmpty()) { if (speakResponse && reply && broadcast && token.isNotEmpty()) {
ttsService?.speakStream(token) ttsService?.speakStream(token)
// 在开始流式TTS时立即停止气泡音
audioPlayer?.stopAudio()
} }
if (reply && token.isNotEmpty()) { if (reply && token.isNotEmpty()) {
// 发送流式回复token // 发送流式回复token
@ -1584,6 +1588,11 @@ object AgentService : CoroutineScope {
private var mediaPlayer: MediaPlayer? = null private var mediaPlayer: MediaPlayer? = null
private var isInitialized = false private var isInitialized = false
private val playbackLock = Object() // 添加同步锁 private val playbackLock = Object() // 添加同步锁
// 新增:超时相关属性
private var timeoutHandler: Handler? = null
private var timeoutRunnable: Runnable? = null
private var timeoutSeconds: Long = 30000L // 默认10秒超时
/** /**
* 安全检查 MediaPlayer 是否正在播放 * 安全检查 MediaPlayer 是否正在播放
@ -1601,17 +1610,21 @@ object AgentService : CoroutineScope {
} }
/** /**
* 播放音频资源 * 播放音频资源(带超时控制)
* @param resId 资源ID * @param resId 资源ID
* @param isLooping 是否循环播放 * @param isLooping 是否循环播放
* @param volume 音量大小,范围0.0-1.0,默认1.0 * @param volume 音量大小,范围0.0-1.0,默认1.0
* @param enableTimeout 是否启用超时自动停止,默认false
*/ */
fun playAudio(resId: Int, isLooping: Boolean = false, volume: Float = 1.0f) { fun playAudio(resId: Int, isLooping: Boolean = false, volume: Float = 1.0f, enableTimeout: Boolean = false) {
synchronized(playbackLock) { synchronized(playbackLock) {
try { try {
Log.d(TAG, "playAudio${isLooping}") Log.d(TAG, "playAudio isLooping=$isLooping, enableTimeout=$enableTimeout")
// 强制停止并释放之前的资源 // 强制停止并释放之前的资源
forceStop() forceStop()
// 停止之前的超时定时器
stopTimeout()
// 创建播放器并设置资源 // 创建播放器并设置资源
mediaPlayer = MediaPlayer().apply { mediaPlayer = MediaPlayer().apply {
@ -1627,11 +1640,14 @@ object AgentService : CoroutineScope {
if (!isLooping) { if (!isLooping) {
release() release()
} }
// 播放完成时停止超时定时器
stopTimeout()
} }
setOnErrorListener { _, what, extra -> setOnErrorListener { _, what, extra ->
Log.e(TAG, "MediaPlayer 错误: what=$what, extra=$extra") Log.e(TAG, "MediaPlayer 错误: what=$what, extra=$extra")
forceRelease() forceRelease()
stopTimeout()
true // 返回true表示错误已处理 true // 返回true表示错误已处理
} }
@ -1640,9 +1656,15 @@ object AgentService : CoroutineScope {
Log.d(TAG, "开始播放音频资源: $resId, 循环: $isLooping") Log.d(TAG, "开始播放音频资源: $resId, 循环: $isLooping")
} }
isInitialized = true isInitialized = true
// 如果启用超时且是循环播放,启动超时定时器
if (enableTimeout && isLooping) {
startTimeout()
}
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "播放音频资源异常: ${e.message}", e) Log.e(TAG, "播放音频资源异常: ${e.message}", e)
forceRelease() forceRelease()
stopTimeout()
} }
} }
} }
@ -1654,6 +1676,9 @@ object AgentService : CoroutineScope {
synchronized(playbackLock) { synchronized(playbackLock) {
try { try {
Log.d(TAG, "stopAudio") Log.d(TAG, "stopAudio")
// 停止超时定时器
stopTimeout()
mediaPlayer?.apply { mediaPlayer?.apply {
if (isMediaPlayerPlaying()) { if (isMediaPlayerPlaying()) {
stop() stop()
@ -1672,6 +1697,42 @@ object AgentService : CoroutineScope {
} }
} }
/**
* 启动超时定时器
*/
private fun startTimeout() {
stopTimeout() // 先停止之前的定时器
timeoutHandler = Handler(Looper.getMainLooper())
timeoutRunnable = Runnable {
Log.d(TAG, "音频播放超时,自动停止 - 超时时间: ${timeoutSeconds}ms")
stopAudio()
}
timeoutHandler?.postDelayed(timeoutRunnable!!, timeoutSeconds)
Log.d(TAG, "启动音频播放超时定时器,超时时间: ${timeoutSeconds}ms")
}
/**
* 停止超时定时器
*/
private fun stopTimeout() {
timeoutRunnable?.let { runnable ->
timeoutHandler?.removeCallbacks(runnable)
}
timeoutHandler = null
timeoutRunnable = null
}
/**
* 设置超时时间
* @param seconds 超时秒数,最小2秒
*/
fun setTimeoutSeconds(seconds: Long) {
timeoutSeconds = maxOf(2000L, seconds * 1000L) // 最小2秒,转换为毫秒
Log.d(TAG, "设置音频播放超时时间: ${timeoutSeconds}ms")
}
/** /**
* 强制停止播放(不释放资源) * 强制停止播放(不释放资源)
*/ */
@ -1703,6 +1764,9 @@ object AgentService : CoroutineScope {
fun release() { fun release() {
synchronized(playbackLock) { synchronized(playbackLock) {
try { try {
// 停止超时定时器
stopTimeout()
mediaPlayer?.apply { mediaPlayer?.apply {
// 安全停止播放 // 安全停止播放
if (isMediaPlayerPlaying()) { if (isMediaPlayerPlaying()) {
@ -1733,6 +1797,7 @@ object AgentService : CoroutineScope {
// 确保变量被清理 // 确保变量被清理
mediaPlayer = null mediaPlayer = null
isInitialized = false isInitialized = false
stopTimeout()
} }
} }
} }
@ -1743,6 +1808,9 @@ object AgentService : CoroutineScope {
private fun forceRelease() { private fun forceRelease() {
synchronized(playbackLock) { synchronized(playbackLock) {
try { try {
// 停止超时定时器
stopTimeout()
mediaPlayer?.apply { mediaPlayer?.apply {
try { try {
reset() reset()
@ -1760,6 +1828,7 @@ object AgentService : CoroutineScope {
} finally { } finally {
mediaPlayer = null mediaPlayer = null
isInitialized = false isInitialized = false
stopTimeout()
} }
} }
} }

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

@ -486,7 +486,7 @@ audioStream.saveAudioDataTo(data: audioData)
if interrupted { if interrupted {
sendEvent(name: "response_interrupted", data: ["status": "interrupted"]) sendEvent(name: "response_interrupted", data: ["status": "interrupted"])
} }
audioPlayer?.stopAwaitSound()
return interrupted return interrupted
} }
@ -1256,7 +1256,6 @@ class ChatApiStreamCallback: StreamCallback {
if speakResponse && reply && broadcast{ if speakResponse && reply && broadcast{
try agentService.azureTtsHelper?.speakStream(token) try agentService.azureTtsHelper?.speakStream(token)
// 在开始流式TTS时立即停止气泡音 // 在开始流式TTS时立即停止气泡音
// try agentService.audioPlayer?.stopAwaitSound()
} }
if (reply && token != "") { if (reply && token != "") {
try agentService.sendEvent(name: "assistant_token", data: ["token": token]) try agentService.sendEvent(name: "assistant_token", data: ["token": token])

203
local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift

@ -447,14 +447,14 @@ private class GetCurrentLocationHandler: FunctionHandler {
return await withCheckedContinuation { continuation in return await withCheckedContinuation { continuation in
// 设置超时 // 设置超时
let timeoutTask = Task { let timeoutTask = Task {
try await Task.sleep(seconds: 15) // 15秒 try await Task.sleep(nanoseconds: 15_000_000_000) // 15秒 (15 * 1_000_000_000 nanoseconds)
continuation.resume(returning: self.createErrorResponse(message: "位置获取超时,请检查网络连接后重试")) continuation.resume(returning: self.createErrorResponse(message: "位置获取超时,请检查网络连接后重试"))
} }
// 检查位置服务是否可用 // 检查位置服务是否可用
guard CLLocationManager.locationServicesEnabled() else { guard CLLocationManager.locationServicesEnabled() else {
locationCompletion?(Result(success: false, message: "位置服务未启用。请前往 设置 > 隐私与安全 > 定位服务 启用定位服务")) timeoutTask.cancel()
locationCompletion = nil continuation.resume(returning: self.createErrorResponse(message: "位置服务未启用。请前往 设置 > 隐私与安全 > 定位服务 启用定位服务"))
return return
} }
@ -729,85 +729,37 @@ private class SetReminderHandler: FunctionHandler {
} }
private func setReminder(title: String, content: String, time: String) async -> Result { private func setReminder(title: String, content: String, time: String) async -> Result {
Log.d(TAG, "设置提醒: $title, 时间: $timeStr") guard let eventStore = eventStore else {
return Result(success: false, message: "事件存储未初始化")
}
try { // 请求日历权限
// 解析时间字符串 let status = await requestCalendarAccess()
val time = try { guard status == .authorized else {
val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm", java.util.Locale.getDefault()) return Result(success: false, message: "日历权限被拒绝")
dateFormat.parse(timeStr)?.time ?: System.currentTimeMillis() }
} catch (e: Exception) {
Log.e(TAG, "解析时间失败: $timeStr, ${e.message}") // 解析时间
return Result( let formatter = DateFormatter()
success = false, formatter.dateFormat = "yyyy-MM-dd HH:mm"
message = "提醒时间格式无效,请使用'yyyy-MM-dd HH:mm'格式" guard let date = formatter.date(from: time) else {
) return Result(success: false, message: "时间格式错误")
} }
// 检查日历权限 // 创建提醒
if (!checkPermissionAndGuide(Manifest.permission.WRITE_CALENDAR, "日历写入", "设置提醒")) { let reminder = EKReminder(eventStore: eventStore)
return Result( reminder.title = title
success = false, reminder.notes = content
message = "需要日历权限才能创建提醒,请在权限管理中开启日历权限" reminder.calendar = eventStore.defaultCalendarForNewReminders()
)
} let alarm = EKAlarm(absoluteDate: date)
reminder.addAlarm(alarm)
// 直接使用ContentProvider写入日历
val endTime = time + 3600000 // 默认1小时后结束 do {
try eventStore.save(reminder, commit: true)
// 获取默认日历ID return Result(success: true, message: "提醒设置成功")
val calendarId = getDefaultCalendarId() } catch {
if (calendarId == -1L) { return Result(success: false, message: "设置提醒失败:\(error.localizedDescription)")
Log.w(TAG, "未找到默认日历")
return tryCreateAlarm(title, content, timeStr)
}
// 创建事件
val eventValues = ContentValues().apply {
put(CalendarContract.Events.CALENDAR_ID, calendarId)
put(CalendarContract.Events.TITLE, title)
put(CalendarContract.Events.DESCRIPTION, content)
put(CalendarContract.Events.DTSTART, time)
put(CalendarContract.Events.DTEND, endTime)
put(CalendarContract.Events.HAS_ALARM, 1)
put(CalendarContract.Events.EVENT_TIMEZONE, java.util.TimeZone.getDefault().id)
}
val eventUri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, eventValues)
if (eventUri == null) {
Log.e(TAG, "插入日历事件失败")
return tryCreateAlarm(title, content, timeStr)
}
// 获取事件ID
val eventId = ContentUris.parseId(eventUri)
// 添加提醒(15分钟前提醒)
val reminderValues = ContentValues().apply {
put(CalendarContract.Reminders.EVENT_ID, eventId)
put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT)
put(CalendarContract.Reminders.MINUTES, 15) // 提前15分钟提醒
}
val reminderUri = context.contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, reminderValues)
if (reminderUri != null) {
return Result(
success = true,
message = "已成功创建提醒: $title, 时间: $timeStr"
)
} else {
// 仍然创建了事件,但没有提醒
return Result(
success = true,
message = "已创建日历事件,但未设置提醒: $title"
)
}
} catch (e: Exception) {
Log.e(TAG, "设置提醒失败: ${e.message}", e)
return Result(
success = false,
message = "设置提醒失败: ${e.message}"
)
} }
} }
@ -822,95 +774,6 @@ private class SetReminderHandler: FunctionHandler {
} }
// /**
// * 设置提醒
// * @param title 提醒标题
// * @param content 提醒内容(可选)
// * @param timeStr 提醒时间,格式为"yyyy-MM-dd HH:mm"
// * @return 操作结果
// */
// fun setReminder(title: String, content: String, timeStr: String): Result {
// Log.d(TAG, "设置提醒: $title, 时间: $timeStr")
// try {
// // 解析时间字符串
// val time = try {
// val dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm", java.util.Locale.getDefault())
// dateFormat.parse(timeStr)?.time ?: System.currentTimeMillis()
// } catch (e: Exception) {
// Log.e(TAG, "解析时间失败: $timeStr, ${e.message}")
// return Result(
// success = false,
// message = "提醒时间格式无效,请使用'yyyy-MM-dd HH:mm'格式"
// )
// }
// // 检查日历权限
// if (!checkPermissionAndGuide(Manifest.permission.WRITE_CALENDAR, "日历写入", "设置提醒")) {
// return Result(
// success = false,
// message = "需要日历权限才能创建提醒,请在权限管理中开启日历权限"
// )
// }
// // 直接使用ContentProvider写入日历
// val endTime = time + 3600000 // 默认1小时后结束
// // 获取默认日历ID
// val calendarId = getDefaultCalendarId()
// if (calendarId == -1L) {
// Log.w(TAG, "未找到默认日历")
// return tryCreateAlarm(title, content, timeStr)
// }
// // 创建事件
// val eventValues = ContentValues().apply {
// put(CalendarContract.Events.CALENDAR_ID, calendarId)
// put(CalendarContract.Events.TITLE, title)
// put(CalendarContract.Events.DESCRIPTION, content)
// put(CalendarContract.Events.DTSTART, time)
// put(CalendarContract.Events.DTEND, endTime)
// put(CalendarContract.Events.HAS_ALARM, 1)
// put(CalendarContract.Events.EVENT_TIMEZONE, java.util.TimeZone.getDefault().id)
// }
// val eventUri = context.contentResolver.insert(CalendarContract.Events.CONTENT_URI, eventValues)
// if (eventUri == null) {
// Log.e(TAG, "插入日历事件失败")
// return tryCreateAlarm(title, content, timeStr)
// }
// // 获取事件ID
// val eventId = ContentUris.parseId(eventUri)
// // 添加提醒(15分钟前提醒)
// val reminderValues = ContentValues().apply {
// put(CalendarContract.Reminders.EVENT_ID, eventId)
// put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT)
// put(CalendarContract.Reminders.MINUTES, 15) // 提前15分钟提醒
// }
// val reminderUri = context.contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, reminderValues)
// if (reminderUri != null) {
// return Result(
// success = true,
// message = "已成功创建提醒: $title, 时间: $timeStr"
// )
// } else {
// // 仍然创建了事件,但没有提醒
// return Result(
// success = true,
// message = "已创建日历事件,但未设置提醒: $title"
// )
// }
// } catch (e: Exception) {
// Log.e(TAG, "设置提醒失败: ${e.message}", e)
// return Result(
// success = false,
// message = "设置提醒失败: ${e.message}"
// )
// }
// }
/// 获取当前时间处理器 /// 获取当前时间处理器
private class GetCurrentTimeHandler: FunctionHandler { private class GetCurrentTimeHandler: FunctionHandler {

Loading…
Cancel
Save