|
|
@ -25,6 +25,7 @@ import org.json.JSONArray |
|
|
import android.media.MediaPlayer |
|
|
import android.media.MediaPlayer |
|
|
import android.media.AudioAttributes |
|
|
import android.media.AudioAttributes |
|
|
import android.net.Uri |
|
|
import android.net.Uri |
|
|
|
|
|
import com.example.deep_voice.core.utils.FileLogger |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 后台语音交互 Service: |
|
|
* 后台语音交互 Service: |
|
|
@ -71,6 +72,8 @@ class VoiceInteractionService : Service() { |
|
|
// 当前用户输入 |
|
|
// 当前用户输入 |
|
|
private var currentUserInput = "" |
|
|
private var currentUserInput = "" |
|
|
|
|
|
|
|
|
|
|
|
private var testKey = "" |
|
|
|
|
|
|
|
|
// 服务组件 |
|
|
// 服务组件 |
|
|
private lateinit var mediaSession: MediaSessionCompat |
|
|
private lateinit var mediaSession: MediaSessionCompat |
|
|
private lateinit var audioManager: AudioManager |
|
|
private lateinit var audioManager: AudioManager |
|
|
@ -96,12 +99,14 @@ class VoiceInteractionService : Service() { |
|
|
用户用语音和你交互. |
|
|
用户用语音和你交互. |
|
|
""".trimIndent() |
|
|
""".trimIndent() |
|
|
|
|
|
|
|
|
// 添加无声媒体播放器 |
|
|
// 添加媒体播放器 |
|
|
private var silentPlayer: SilentMediaPlayer? = null |
|
|
private var audioPlayer: MiniMediaPlayer? = null |
|
|
|
|
|
|
|
|
override fun onCreate() { |
|
|
override fun onCreate() { |
|
|
super.onCreate() |
|
|
super.onCreate() |
|
|
Log.d(TAG, "onCreate - 语音交互服务正在创建") |
|
|
// 初始化文件日志系统 |
|
|
|
|
|
FileLogger.init(applicationContext) |
|
|
|
|
|
FileLogger.d(TAG, "onCreate - 语音交互服务正在创建") |
|
|
|
|
|
|
|
|
// 初始化状态 |
|
|
// 初始化状态 |
|
|
isRunning.set(true) |
|
|
isRunning.set(true) |
|
|
@ -109,31 +114,25 @@ class VoiceInteractionService : Service() { |
|
|
|
|
|
|
|
|
// 初始化组件 |
|
|
// 初始化组件 |
|
|
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|
|
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|
|
Log.d(TAG, "AudioManager初始化完成") |
|
|
FileLogger.d(TAG, "AudioManager初始化完成") |
|
|
|
|
|
|
|
|
initServices() |
|
|
initServices() |
|
|
initMediaSession() |
|
|
initMediaSession() |
|
|
registerMediaButtonReceiver() |
|
|
registerMediaButtonReceiver() |
|
|
|
|
|
|
|
|
|
|
|
// 创建音频播放器 |
|
|
silentPlayer = SilentMediaPlayer(applicationContext) |
|
|
audioPlayer = MiniMediaPlayer(applicationContext) |
|
|
silentPlayer?.start() |
|
|
audioPlayer?.play(R.raw.silence, 0f) |
|
|
// Log.d(TAG, "无声播放器已启动") |
|
|
|
|
|
|
|
|
|
|
|
// 设置为媒体播放状态 |
|
|
// 设置为媒体播放状态 |
|
|
setPlaybackState(PlaybackStateCompat.STATE_PLAYING) |
|
|
// setPlaybackState(PlaybackStateCompat.STATE_PAUSED) |
|
|
Log.d(TAG, "设置播放状态为PLAYING") |
|
|
// FileLogger.d(TAG, "设置播放状态为STATE_PAUSED") |
|
|
|
|
|
|
|
|
// 启动监控和前台服务 |
|
|
// 启动监控和前台服务 |
|
|
startMonitoring() |
|
|
startMonitoring() |
|
|
startForegroundServiceWithNotification() |
|
|
startForegroundServiceWithNotification() |
|
|
|
|
|
|
|
|
Log.d(TAG, "VoiceInteractionService 初始化完成,等待蓝牙按键事件...") |
|
|
FileLogger.d(TAG, "VoiceInteractionService 初始化完成,等待蓝牙按键事件...") |
|
|
|
|
|
|
|
|
// 延迟3秒后测试按键处理功能 |
|
|
|
|
|
// Handler(Looper.getMainLooper()).postDelayed({ |
|
|
|
|
|
// testMediaButtonEvent() |
|
|
|
|
|
// }, 3000) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -152,31 +151,56 @@ class VoiceInteractionService : Service() { |
|
|
*/ |
|
|
*/ |
|
|
private fun initServices() { |
|
|
private fun initServices() { |
|
|
// 创建新的Azure服务实例 |
|
|
// 创建新的Azure服务实例 |
|
|
Log.d(TAG, "创建新的Azure服务实例") |
|
|
FileLogger.d(TAG, "创建新的Azure服务实例") |
|
|
azureAsrHelper = AzureAsrHelper() |
|
|
azureAsrHelper = AzureAsrHelper(this) |
|
|
azureTtsHelper = AzureTtsHelper() |
|
|
azureTtsHelper = AzureTtsHelper(this) |
|
|
|
|
|
|
|
|
|
|
|
// 尝试从静态变量获取配置 |
|
|
|
|
|
var subscriptionKey = MainActivity.azureSpeechKey |
|
|
|
|
|
var serviceRegion = MainActivity.azureSpeechRegion |
|
|
|
|
|
var volcanoKey = MainActivity.volcanoAiApiKey |
|
|
|
|
|
|
|
|
// 从 MainActivity 获取 Azure 配置 |
|
|
// 如果静态变量中没有配置,尝试从加密存储中加载 |
|
|
val subscriptionKey = MainActivity.azureSpeechKey |
|
|
if (subscriptionKey.isEmpty() || serviceRegion.isEmpty() || volcanoKey.isEmpty()) { |
|
|
val serviceRegion = MainActivity.azureSpeechRegion |
|
|
FileLogger.d(TAG, "静态变量中的配置信息不完整,尝试从加密存储加载") |
|
|
|
|
|
|
|
|
|
|
|
// 从加密存储加载密钥 |
|
|
|
|
|
val keysLoaded = MainActivity.loadKeysFromSecureStorage(applicationContext) |
|
|
|
|
|
|
|
|
|
|
|
if (keysLoaded) { |
|
|
|
|
|
// 更新本地变量 |
|
|
|
|
|
subscriptionKey = MainActivity.azureSpeechKey |
|
|
|
|
|
serviceRegion = MainActivity.azureSpeechRegion |
|
|
|
|
|
volcanoKey = MainActivity.volcanoAiApiKey |
|
|
|
|
|
|
|
|
|
|
|
FileLogger.d(TAG, "已从加密存储加载配置信息") |
|
|
|
|
|
} else { |
|
|
|
|
|
FileLogger.e(TAG, "无法从加密存储加载配置信息") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 初始化语音服务 |
|
|
if (subscriptionKey.isNotEmpty() && serviceRegion.isNotEmpty()) { |
|
|
if (subscriptionKey.isNotEmpty() && serviceRegion.isNotEmpty()) { |
|
|
// 初始化ASR |
|
|
// 初始化ASR |
|
|
azureAsrHelper.initialize(subscriptionKey, serviceRegion, arrayOf("zh-CN")) |
|
|
azureAsrHelper.initialize(subscriptionKey, serviceRegion, arrayOf("zh-CN")) |
|
|
|
|
|
|
|
|
// 初始化TTS |
|
|
// 初始化TTS |
|
|
azureTtsHelper.initialize(subscriptionKey, serviceRegion, "zh-CN") |
|
|
azureTtsHelper.initialize(subscriptionKey, serviceRegion, "zh-CN") |
|
|
|
|
|
|
|
|
|
|
|
FileLogger.d(TAG, "Azure语音服务已初始化") |
|
|
} else { |
|
|
} else { |
|
|
Log.e(TAG, "Azure配置信息不完整,无法初始化Azure服务") |
|
|
FileLogger.e(TAG, "Azure配置信息不完整,无法初始化Azure服务") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 初始化火山AI服务 |
|
|
// 初始化火山AI服务 |
|
|
volcanoAIService = VolcanoAIService() |
|
|
volcanoAIService = VolcanoAIService() |
|
|
// 使用 MainActivity 中的 API key 初始化 |
|
|
|
|
|
val volcanoApiKey = MainActivity.volcanoAiApiKey |
|
|
// 初始化火山AI服务 |
|
|
if (volcanoApiKey.isNotEmpty()) { |
|
|
if (volcanoKey.isNotEmpty()) { |
|
|
volcanoAIService.initialize(volcanoApiKey) |
|
|
volcanoAIService.initialize(volcanoKey) |
|
|
|
|
|
FileLogger.d(TAG, "火山AI服务已初始化") |
|
|
} else { |
|
|
} else { |
|
|
Log.e(TAG, "火山AI配置信息不完整,无法初始化火山AI服务") |
|
|
FileLogger.e(TAG, "火山AI配置信息不完整,无法初始化火山AI服务") |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -184,7 +208,7 @@ class VoiceInteractionService : Service() { |
|
|
* 初始化 MediaSession |
|
|
* 初始化 MediaSession |
|
|
*/ |
|
|
*/ |
|
|
private fun initMediaSession() { |
|
|
private fun initMediaSession() { |
|
|
Log.d(TAG, "开始初始化MediaSession") |
|
|
FileLogger.d(TAG, "开始初始化MediaSession") |
|
|
mediaSession = MediaSessionCompat(applicationContext, "VoiceServiceSession") |
|
|
mediaSession = MediaSessionCompat(applicationContext, "VoiceServiceSession") |
|
|
|
|
|
|
|
|
// 设置标志和状态 |
|
|
// 设置标志和状态 |
|
|
@ -192,91 +216,24 @@ class VoiceInteractionService : Service() { |
|
|
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or |
|
|
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or |
|
|
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS |
|
|
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS |
|
|
) |
|
|
) |
|
|
Log.d(TAG, "MediaSession标志设置完成") |
|
|
FileLogger.d(TAG, "MediaSession标志设置完成") |
|
|
|
|
|
|
|
|
// 设置回调 |
|
|
// 设置回调 |
|
|
mediaSession.setCallback(object : MediaSessionCompat.Callback() { |
|
|
mediaSession.setCallback(object : MediaSessionCompat.Callback() { |
|
|
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { |
|
|
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { |
|
|
Log.d(TAG, "收到媒体按钮事件: $mediaButtonEvent") |
|
|
FileLogger.d(TAG, "initMediaSession收到媒体按钮事件: $mediaButtonEvent") |
|
|
handleDoubleClick() |
|
|
handleDoubleClick() |
|
|
return true |
|
|
return true |
|
|
|
|
|
|
|
|
val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|
|
|
|
|
mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java) |
|
|
|
|
|
} else { |
|
|
|
|
|
@Suppress("DEPRECATION") |
|
|
|
|
|
mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) as? KeyEvent |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (keyEvent != null) { |
|
|
|
|
|
Log.d(TAG, "解析到按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}") |
|
|
|
|
|
|
|
|
|
|
|
if (keyEvent.action == KeyEvent.ACTION_DOWN) { |
|
|
|
|
|
when (keyEvent.keyCode) { |
|
|
|
|
|
KeyEvent.KEYCODE_MEDIA_PLAY -> { |
|
|
|
|
|
Log.d(TAG, "检测到MEDIA_PLAY按键") |
|
|
|
|
|
handleDoubleClick() |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
KeyEvent.KEYCODE_MEDIA_PAUSE -> { |
|
|
|
|
|
Log.d(TAG, "检测到MEDIA_PAUSE按键") |
|
|
|
|
|
handleDoubleClick() |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
KeyEvent.KEYCODE_HEADSETHOOK -> { |
|
|
|
|
|
Log.d(TAG, "检测到HEADSETHOOK按键") |
|
|
|
|
|
// 检测双击 |
|
|
|
|
|
val currentTime = System.currentTimeMillis() |
|
|
|
|
|
val timeDiff = currentTime - lastKeyEventTime |
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "按键时间差: $timeDiff ms, 当前计数: $keyEventCount") |
|
|
|
|
|
|
|
|
|
|
|
if (timeDiff < 500) { // 500毫秒内的按键视为连续按键 |
|
|
|
|
|
keyEventCount++ |
|
|
|
|
|
Log.d(TAG, "连续按键,计数增加到: $keyEventCount") |
|
|
|
|
|
if (keyEventCount == 1) { // 这是第二次按键,即双击 |
|
|
|
|
|
Log.d(TAG, "检测到双击,执行处理") |
|
|
|
|
|
handleDoubleClick() |
|
|
|
|
|
keyEventCount = 0 |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.d(TAG, "非连续按键,重置计数") |
|
|
|
|
|
keyEventCount = 0 |
|
|
|
|
|
} |
|
|
|
|
|
lastKeyEventTime = currentTime |
|
|
|
|
|
|
|
|
|
|
|
// 单击也处理 |
|
|
|
|
|
Log.d(TAG, "单击也处理") |
|
|
|
|
|
handleDoubleClick() |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { |
|
|
|
|
|
Log.d(TAG, "检测到MEDIA_PLAY_PAUSE按键") |
|
|
|
|
|
handleDoubleClick() |
|
|
|
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
else -> { |
|
|
|
|
|
Log.d(TAG, "检测到其他按键: ${keyEvent.keyCode}") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.d(TAG, "非ACTION_DOWN事件: ${keyEvent.action}") |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
Log.d(TAG, "Intent中没有KeyEvent") |
|
|
|
|
|
} |
|
|
|
|
|
return super.onMediaButtonEvent(mediaButtonEvent) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
override fun onPlay() { |
|
|
override fun onPlay() { |
|
|
Log.d(TAG, "收到onPlay回调") |
|
|
FileLogger.d(TAG, "收到onPlay回调") |
|
|
handleDoubleClick() |
|
|
handleDoubleClick() |
|
|
return super.onPlay() |
|
|
return super.onPlay() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
override fun onPause() { |
|
|
override fun onPause() { |
|
|
Log.d(TAG, "收到onPause回调") |
|
|
FileLogger.d(TAG, "收到onPause回调") |
|
|
handleDoubleClick() |
|
|
handleDoubleClick() |
|
|
return super.onPause() |
|
|
return super.onPause() |
|
|
} |
|
|
} |
|
|
@ -284,7 +241,7 @@ class VoiceInteractionService : Service() { |
|
|
|
|
|
|
|
|
// 激活 MediaSession |
|
|
// 激活 MediaSession |
|
|
mediaSession.isActive = true |
|
|
mediaSession.isActive = true |
|
|
Log.d(TAG, "MediaSession已激活") |
|
|
FileLogger.d(TAG, "MediaSession已激活") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -294,9 +251,9 @@ class VoiceInteractionService : Service() { |
|
|
try { |
|
|
try { |
|
|
val mediaButtonReceiver = ComponentName(packageName, MediaButtonReceiver::class.java.name) |
|
|
val mediaButtonReceiver = ComponentName(packageName, MediaButtonReceiver::class.java.name) |
|
|
audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver) |
|
|
audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver) |
|
|
Log.d(TAG, "媒体按钮接收器注册成功: $mediaButtonReceiver") |
|
|
FileLogger.d(TAG, "媒体按钮接收器注册成功: $mediaButtonReceiver") |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "媒体按钮接收器注册失败: ${e.message}", e) |
|
|
FileLogger.e(TAG, "媒体按钮接收器注册失败: ${e.message}", e) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -330,7 +287,7 @@ class VoiceInteractionService : Service() { |
|
|
|
|
|
|
|
|
// 如果超过5秒没有检测到语音,且不在TTS播放中,暂停语音识别 |
|
|
// 如果超过5秒没有检测到语音,且不在TTS播放中,暂停语音识别 |
|
|
if (!hasSpeechDetected && !isTtsSpeaking && elapsedTime >= RECOGNITION_TIMEOUT) { |
|
|
if (!hasSpeechDetected && !isTtsSpeaking && elapsedTime >= RECOGNITION_TIMEOUT) { |
|
|
Log.d(TAG, "超过${RECOGNITION_TIMEOUT/1000}秒未检测到语音,停止识别") |
|
|
FileLogger.d(TAG, "超过${RECOGNITION_TIMEOUT/1000}秒未检测到语音,停止识别") |
|
|
isTimeoutPaused = true |
|
|
isTimeoutPaused = true |
|
|
playNotification("没有听到您说话,已暂停对话。双击耳机按钮可重新开始。") |
|
|
playNotification("没有听到您说话,已暂停对话。双击耳机按钮可重新开始。") |
|
|
|
|
|
|
|
|
@ -348,13 +305,13 @@ class VoiceInteractionService : Service() { |
|
|
* 处理双击事件 |
|
|
* 处理双击事件 |
|
|
*/ |
|
|
*/ |
|
|
private fun handleDoubleClick() { |
|
|
private fun handleDoubleClick() { |
|
|
Log.d(TAG, "处理双击事件") |
|
|
FileLogger.d(TAG, "处理双击事件") |
|
|
|
|
|
|
|
|
//屏蔽短时间重复响应的问题 |
|
|
//屏蔽短时间重复响应的问题 |
|
|
val currentTime = System.currentTimeMillis() |
|
|
val currentTime = System.currentTimeMillis() |
|
|
val timeDiff = currentTime - lastKeyEventTime |
|
|
val timeDiff = currentTime - lastKeyEventTime |
|
|
if (timeDiff < 500) { |
|
|
if (timeDiff < 500) { |
|
|
Log.d(TAG, "短时间重复响应,忽略") |
|
|
FileLogger.d(TAG, "短时间重复响应,忽略") |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
lastKeyEventTime = currentTime |
|
|
lastKeyEventTime = currentTime |
|
|
@ -363,42 +320,28 @@ class VoiceInteractionService : Service() { |
|
|
stopCurrentTTS() |
|
|
stopCurrentTTS() |
|
|
|
|
|
|
|
|
// 播放提示音 |
|
|
// 播放提示音 |
|
|
playPrompt("我在!") |
|
|
// playPrompt("我在!") |
|
|
|
|
|
audioPlayer?.play(R.raw.listening) |
|
|
|
|
|
|
|
|
// 设置为播放状态 |
|
|
// 设置为播放状态 |
|
|
setPlaybackState(PlaybackStateCompat.STATE_PLAYING) |
|
|
// setPlaybackState(PlaybackStateCompat.STATE_PAUSED) |
|
|
|
|
|
|
|
|
// 重置超时暂停标志 |
|
|
// 重置超时暂停标志 |
|
|
isTimeoutPaused = false |
|
|
isTimeoutPaused = false |
|
|
|
|
|
|
|
|
// 启动或重置语音识别 |
|
|
// 启动或重置语音识别 |
|
|
if (!isRecognitionActive) { |
|
|
if (!isRecognitionActive) { |
|
|
Log.d(TAG, "语音识别未激活,开始启动") |
|
|
FileLogger.d(TAG, "语音识别未激活,开始启动") |
|
|
|
|
|
|
|
|
// 如果之前是因为超时暂停,重新初始化语音识别组件 |
|
|
// 如果之前是因为超时暂停,重新初始化语音识别组件 |
|
|
if (isTimeoutPaused) { |
|
|
// if (isTimeoutPaused) { |
|
|
Log.d(TAG, "之前因超时暂停,重新初始化Azure服务") |
|
|
// Log.d(TAG, "之前因超时暂停,重新初始化Azure服务") |
|
|
// 释放旧的实例 |
|
|
// restartAsr() |
|
|
try { |
|
|
// } |
|
|
azureAsrHelper.dispose() |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "释放旧的Azure ASR实例失败: ${e.message}", e) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建新的实例 |
|
|
|
|
|
azureAsrHelper = AzureAsrHelper() |
|
|
|
|
|
|
|
|
|
|
|
// 重新初始化 |
|
|
|
|
|
val subscriptionKey = MainActivity.azureSpeechKey |
|
|
|
|
|
val serviceRegion = MainActivity.azureSpeechRegion |
|
|
|
|
|
if (subscriptionKey.isNotEmpty() && serviceRegion.isNotEmpty()) { |
|
|
|
|
|
azureAsrHelper.initialize(subscriptionKey, serviceRegion, arrayOf("zh-CN")) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
startVoiceRecognition() |
|
|
startVoiceRecognition() |
|
|
} else { |
|
|
} else { |
|
|
Log.d(TAG, "语音识别已激活,更新活动时间") |
|
|
FileLogger.d(TAG, "语音识别已激活,更新活动时间") |
|
|
updateLastActivityTime() |
|
|
updateLastActivityTime() |
|
|
hasSpeechDetected = false |
|
|
hasSpeechDetected = false |
|
|
} |
|
|
} |
|
|
@ -415,7 +358,7 @@ class VoiceInteractionService : Service() { |
|
|
playNotification("需要录音权限,请在设置中授予权限") |
|
|
playNotification("需要录音权限,请在设置中授予权限") |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
// 通知 Flutter 语音识别已启动 |
|
|
// 通知 Flutter 语音识别已启动 |
|
|
notifyRecognitionStarted() |
|
|
notifyRecognitionStarted() |
|
|
|
|
|
|
|
|
isActive = true |
|
|
isActive = true |
|
|
@ -468,6 +411,7 @@ class VoiceInteractionService : Service() { |
|
|
}) |
|
|
}) |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
isRecognitionActive = false |
|
|
isRecognitionActive = false |
|
|
|
|
|
FileLogger.e(TAG, "启动语音识别失败: ${e.message}", e) |
|
|
playNotification("启动语音识别失败") |
|
|
playNotification("启动语音识别失败") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -480,7 +424,7 @@ class VoiceInteractionService : Service() { |
|
|
private fun stopVoiceRecognition() { |
|
|
private fun stopVoiceRecognition() { |
|
|
if (!isRecognitionActive) return |
|
|
if (!isRecognitionActive) return |
|
|
|
|
|
|
|
|
Log.d(TAG, "停止语音识别") |
|
|
FileLogger.d(TAG, "停止语音识别") |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
azureAsrHelper.stopContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { |
|
|
azureAsrHelper.stopContinuousRecognition(object : AzureAsrHelper.ContinuousRecognizeCallback { |
|
|
@ -489,19 +433,19 @@ class VoiceInteractionService : Service() { |
|
|
override fun onSessionStarted() {} |
|
|
override fun onSessionStarted() {} |
|
|
override fun onSessionStopped() { |
|
|
override fun onSessionStopped() { |
|
|
isRecognitionActive = false |
|
|
isRecognitionActive = false |
|
|
Log.d(TAG, "语音识别会话已停止") |
|
|
FileLogger.d(TAG, "语音识别会话已停止") |
|
|
} |
|
|
} |
|
|
override fun onCanceled(reason: String, errorDetails: String) { |
|
|
override fun onCanceled(reason: String, errorDetails: String) { |
|
|
isRecognitionActive = false |
|
|
isRecognitionActive = false |
|
|
Log.d(TAG, "语音识别已取消: $reason") |
|
|
FileLogger.d(TAG, "语音识别已取消: $reason") |
|
|
} |
|
|
} |
|
|
override fun onError(error: String) { |
|
|
override fun onError(error: String) { |
|
|
isRecognitionActive = false |
|
|
isRecognitionActive = false |
|
|
Log.e(TAG, "停止语音识别时出错: $error") |
|
|
FileLogger.e(TAG, "停止语音识别时出错: $error") |
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "停止语音识别异常: ${e.message}", e) |
|
|
FileLogger.e(TAG, "停止语音识别异常: ${e.message}", e) |
|
|
// 确保状态一致性 |
|
|
// 确保状态一致性 |
|
|
isRecognitionActive = false |
|
|
isRecognitionActive = false |
|
|
} |
|
|
} |
|
|
@ -533,7 +477,7 @@ class VoiceInteractionService : Service() { |
|
|
// 同步聊天记录到Flutter端 |
|
|
// 同步聊天记录到Flutter端 |
|
|
notifyChatHistoryUpdated("personal_assistant", text, response) |
|
|
notifyChatHistoryUpdated("personal_assistant", text, response) |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "AI处理出错: ${e.message}") |
|
|
FileLogger.e(TAG, "AI处理出错: ${e.message}") |
|
|
playNotification("AI处理出错") |
|
|
playNotification("AI处理出错") |
|
|
} |
|
|
} |
|
|
}.start() |
|
|
}.start() |
|
|
@ -689,11 +633,11 @@ class VoiceInteractionService : Service() { |
|
|
* 当 Service 被启动时调用 |
|
|
* 当 Service 被启动时调用 |
|
|
*/ |
|
|
*/ |
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
|
|
Log.d(TAG, "onStartCommand: intent=$intent, action=${intent?.action}") |
|
|
FileLogger.d(TAG, "onStartCommand: intent=$intent, action=${intent?.action}") |
|
|
|
|
|
|
|
|
// 处理媒体按键 Intent |
|
|
// 处理媒体按键 Intent |
|
|
if (intent?.action == Intent.ACTION_MEDIA_BUTTON) { |
|
|
if (intent?.action == Intent.ACTION_MEDIA_BUTTON) { |
|
|
Log.d(TAG, "收到媒体按钮Intent: ${intent.extras}") |
|
|
FileLogger.d(TAG, "收到媒体按钮Intent: ${intent.extras}") |
|
|
|
|
|
|
|
|
val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|
|
val keyEvent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { |
|
|
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java) |
|
|
intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java) |
|
|
@ -703,26 +647,26 @@ class VoiceInteractionService : Service() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (keyEvent != null) { |
|
|
if (keyEvent != null) { |
|
|
Log.d(TAG, "从Intent中解析到按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}") |
|
|
FileLogger.d(TAG, "从Intent中解析到按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}") |
|
|
handleKeyEvent(keyEvent) |
|
|
handleKeyEvent(keyEvent) |
|
|
} else { |
|
|
} else { |
|
|
Log.d(TAG, "Intent中没有按键事件,直接处理双击") |
|
|
FileLogger.d(TAG, "Intent中没有按键事件,直接处理双击") |
|
|
handleDoubleClick() |
|
|
handleDoubleClick() |
|
|
} |
|
|
} |
|
|
} else if (intent?.action == ACTION_PAUSE_VOICE_INTERACTION) { |
|
|
} else if (intent?.action == ACTION_PAUSE_VOICE_INTERACTION) { |
|
|
// 处理来自 Flutter 的暂停请求 |
|
|
// 处理来自 Flutter 的暂停请求 |
|
|
Log.d(TAG, "收到暂停请求") |
|
|
FileLogger.d(TAG, "收到暂停请求") |
|
|
pauseVoiceInteraction() |
|
|
pauseVoiceInteraction() |
|
|
} else { |
|
|
} else { |
|
|
Log.d(TAG, "收到其他Intent: action=${intent?.action}") |
|
|
FileLogger.d(TAG, "收到其他Intent: action=${intent?.action}") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 让 MediaSession 处理 Intent |
|
|
// 让 MediaSession 处理 Intent |
|
|
try { |
|
|
try { |
|
|
MediaButtonReceiver.handleIntent(mediaSession, intent) |
|
|
MediaButtonReceiver.handleIntent(mediaSession, intent) |
|
|
Log.d(TAG, "MediaButtonReceiver处理Intent完成") |
|
|
FileLogger.d(TAG, "MediaButtonReceiver处理Intent完成") |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "MediaButtonReceiver处理Intent失败: ${e.message}", e) |
|
|
FileLogger.e(TAG, "MediaButtonReceiver处理Intent失败: ${e.message}", e) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return START_STICKY |
|
|
return START_STICKY |
|
|
@ -732,50 +676,50 @@ class VoiceInteractionService : Service() { |
|
|
* 处理按键事件 |
|
|
* 处理按键事件 |
|
|
*/ |
|
|
*/ |
|
|
private fun handleKeyEvent(keyEvent: KeyEvent) { |
|
|
private fun handleKeyEvent(keyEvent: KeyEvent) { |
|
|
Log.d(TAG, "处理按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}") |
|
|
FileLogger.d(TAG, "处理按键事件: action=${keyEvent.action}, keyCode=${keyEvent.keyCode}") |
|
|
|
|
|
|
|
|
if (keyEvent.action == KeyEvent.ACTION_DOWN) { |
|
|
if (keyEvent.action == KeyEvent.ACTION_DOWN) { |
|
|
when (keyEvent.keyCode) { |
|
|
when (keyEvent.keyCode) { |
|
|
KeyEvent.KEYCODE_MEDIA_PLAY -> { |
|
|
KeyEvent.KEYCODE_MEDIA_PLAY -> { |
|
|
Log.d(TAG, "处理MEDIA_PLAY按键") |
|
|
FileLogger.d(TAG, "处理MEDIA_PLAY按键") |
|
|
handleDoubleClick() |
|
|
handleDoubleClick() |
|
|
} |
|
|
} |
|
|
KeyEvent.KEYCODE_MEDIA_PAUSE -> { |
|
|
KeyEvent.KEYCODE_MEDIA_PAUSE -> { |
|
|
Log.d(TAG, "处理MEDIA_PAUSE按键") |
|
|
FileLogger.d(TAG, "处理MEDIA_PAUSE按键") |
|
|
handleDoubleClick() |
|
|
handleDoubleClick() |
|
|
} |
|
|
} |
|
|
KeyEvent.KEYCODE_HEADSETHOOK -> { |
|
|
KeyEvent.KEYCODE_HEADSETHOOK -> { |
|
|
Log.d(TAG, "处理HEADSETHOOK按键") |
|
|
FileLogger.d(TAG, "处理HEADSETHOOK按键") |
|
|
// 检测双击 |
|
|
// 检测双击 |
|
|
val currentTime = System.currentTimeMillis() |
|
|
val currentTime = System.currentTimeMillis() |
|
|
val timeDiff = currentTime - lastKeyEventTime |
|
|
val timeDiff = currentTime - lastKeyEventTime |
|
|
|
|
|
|
|
|
Log.d(TAG, "按键时间差: $timeDiff ms, 当前计数: $keyEventCount") |
|
|
FileLogger.d(TAG, "按键时间差: $timeDiff ms, 当前计数: $keyEventCount") |
|
|
|
|
|
|
|
|
if (timeDiff < 500) { // 500毫秒内的按键视为连续按键 |
|
|
if (timeDiff < 500) { // 500毫秒内的按键视为连续按键 |
|
|
keyEventCount++ |
|
|
keyEventCount++ |
|
|
Log.d(TAG, "连续按键,计数增加到: $keyEventCount") |
|
|
FileLogger.d(TAG, "连续按键,计数增加到: $keyEventCount") |
|
|
if (keyEventCount == 1) { // 这是第二次按键,即双击 |
|
|
if (keyEventCount == 1) { // 这是第二次按键,即双击 |
|
|
Log.d(TAG, "检测到双击,执行处理") |
|
|
FileLogger.d(TAG, "检测到双击,执行处理") |
|
|
handleDoubleClick() |
|
|
handleDoubleClick() |
|
|
keyEventCount = 0 |
|
|
keyEventCount = 0 |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
Log.d(TAG, "非连续按键,重置计数") |
|
|
FileLogger.d(TAG, "非连续按键,重置计数") |
|
|
keyEventCount = 0 |
|
|
keyEventCount = 0 |
|
|
} |
|
|
} |
|
|
lastKeyEventTime = currentTime |
|
|
lastKeyEventTime = currentTime |
|
|
} |
|
|
} |
|
|
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { |
|
|
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { |
|
|
Log.d(TAG, "处理MEDIA_PLAY_PAUSE按键") |
|
|
FileLogger.d(TAG, "处理MEDIA_PLAY_PAUSE按键") |
|
|
handleDoubleClick() |
|
|
handleDoubleClick() |
|
|
} |
|
|
} |
|
|
else -> { |
|
|
else -> { |
|
|
Log.d(TAG, "处理其他按键: ${keyEvent.keyCode}") |
|
|
FileLogger.d(TAG, "处理其他按键: ${keyEvent.keyCode}") |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
Log.d(TAG, "非ACTION_DOWN事件: ${keyEvent.action}") |
|
|
FileLogger.d(TAG, "非ACTION_DOWN事件: ${keyEvent.action}") |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -783,11 +727,11 @@ class VoiceInteractionService : Service() { |
|
|
override fun onBind(intent: Intent?): IBinder? = null |
|
|
override fun onBind(intent: Intent?): IBinder? = null |
|
|
|
|
|
|
|
|
override fun onDestroy() { |
|
|
override fun onDestroy() { |
|
|
Log.d(TAG, "onDestroy - 语音交互服务正在销毁") |
|
|
FileLogger.d(TAG, "onDestroy - 语音交互服务正在销毁") |
|
|
|
|
|
|
|
|
// 停止无声播放器 |
|
|
// 释放音频播放器 |
|
|
silentPlayer?.release() |
|
|
audioPlayer?.release() |
|
|
silentPlayer = null |
|
|
audioPlayer = null |
|
|
|
|
|
|
|
|
// 停止监控 |
|
|
// 停止监控 |
|
|
stopMonitoring() |
|
|
stopMonitoring() |
|
|
@ -804,13 +748,16 @@ class VoiceInteractionService : Service() { |
|
|
mediaSession.release() |
|
|
mediaSession.release() |
|
|
|
|
|
|
|
|
// 释放Azure服务实例 |
|
|
// 释放Azure服务实例 |
|
|
Log.d(TAG, "释放Azure服务实例") |
|
|
FileLogger.d(TAG, "释放Azure服务实例") |
|
|
azureAsrHelper.dispose() |
|
|
azureAsrHelper.dispose() |
|
|
azureTtsHelper.dispose() |
|
|
azureTtsHelper.dispose() |
|
|
|
|
|
|
|
|
// 更新服务状态 |
|
|
// 更新服务状态 |
|
|
isRunning.set(false) |
|
|
isRunning.set(false) |
|
|
|
|
|
|
|
|
|
|
|
// 关闭日志系统 |
|
|
|
|
|
FileLogger.shutdown() |
|
|
|
|
|
|
|
|
super.onDestroy() |
|
|
super.onDestroy() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -819,7 +766,7 @@ class VoiceInteractionService : Service() { |
|
|
* 用于从 Flutter 端控制服务 |
|
|
* 用于从 Flutter 端控制服务 |
|
|
*/ |
|
|
*/ |
|
|
private fun pauseVoiceInteraction() { |
|
|
private fun pauseVoiceInteraction() { |
|
|
Log.d(TAG, "暂停语音交互(来自Flutter的请求)") |
|
|
FileLogger.d(TAG, "暂停语音交互(来自Flutter的请求)") |
|
|
|
|
|
|
|
|
// 停止当前TTS播放 |
|
|
// 停止当前TTS播放 |
|
|
stopCurrentTTS() |
|
|
stopCurrentTTS() |
|
|
@ -838,9 +785,9 @@ class VoiceInteractionService : Service() { |
|
|
* 通知 Flutter 端聊天记录已更新 |
|
|
* 通知 Flutter 端聊天记录已更新 |
|
|
*/ |
|
|
*/ |
|
|
private fun notifyChatHistoryUpdated(agentId: String, userMessage: String, assistantMessage: String) { |
|
|
private fun notifyChatHistoryUpdated(agentId: String, userMessage: String, assistantMessage: String) { |
|
|
Log.d(TAG, "通知Flutter聊天记录已更新: agentId=$agentId") |
|
|
FileLogger.d(TAG, "通知Flutter聊天记录已更新: agentId=$agentId") |
|
|
Log.d(TAG, "用户消息: ${userMessage.take(50)}...") |
|
|
FileLogger.d(TAG, "用户消息: ${userMessage.take(50)}...") |
|
|
Log.d(TAG, "助手回复: ${assistantMessage.take(50)}...") |
|
|
FileLogger.d(TAG, "助手回复: ${assistantMessage.take(50)}...") |
|
|
|
|
|
|
|
|
// 创建广播 Intent |
|
|
// 创建广播 Intent |
|
|
val intent = Intent(ACTION_CHAT_HISTORY_UPDATED).apply { |
|
|
val intent = Intent(ACTION_CHAT_HISTORY_UPDATED).apply { |
|
|
@ -852,14 +799,14 @@ class VoiceInteractionService : Service() { |
|
|
|
|
|
|
|
|
// 发送广播 |
|
|
// 发送广播 |
|
|
sendBroadcast(intent) |
|
|
sendBroadcast(intent) |
|
|
Log.d(TAG, "已发送聊天记录广播") |
|
|
FileLogger.d(TAG, "已发送聊天记录广播") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 通知 Flutter 端语音识别已启动 |
|
|
* 通知 Flutter 端语音识别已启动 |
|
|
*/ |
|
|
*/ |
|
|
private fun notifyRecognitionStarted() { |
|
|
private fun notifyRecognitionStarted() { |
|
|
Log.d(TAG, "通知Flutter语音识别已启动") |
|
|
FileLogger.d(TAG, "通知Flutter语音识别已启动") |
|
|
|
|
|
|
|
|
// 创建广播 Intent |
|
|
// 创建广播 Intent |
|
|
val intent = Intent(ACTION_RECOGNITION_STARTED).apply { |
|
|
val intent = Intent(ACTION_RECOGNITION_STARTED).apply { |
|
|
@ -871,162 +818,77 @@ class VoiceInteractionService : Service() { |
|
|
sendBroadcast(intent) |
|
|
sendBroadcast(intent) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 测试媒体按钮事件处理 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun testMediaButtonEvent() { |
|
|
|
|
|
Log.d(TAG, "开始测试媒体按钮事件处理") |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 创建一个模拟的HEADSETHOOK按键事件 |
|
|
|
|
|
val keyEvent = KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK) |
|
|
|
|
|
|
|
|
|
|
|
// 创建一个包含按键事件的Intent |
|
|
|
|
|
val intent = Intent(Intent.ACTION_MEDIA_BUTTON).apply { |
|
|
|
|
|
putExtra(Intent.EXTRA_KEY_EVENT, keyEvent) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 直接调用onMediaButtonEvent方法 |
|
|
|
|
|
Log.d(TAG, "直接调用onMediaButtonEvent方法") |
|
|
|
|
|
val result = mediaSession.controller.dispatchMediaButtonEvent(keyEvent) |
|
|
|
|
|
Log.d(TAG, "dispatchMediaButtonEvent结果: $result") |
|
|
|
|
|
|
|
|
|
|
|
// 通过MediaButtonReceiver发送Intent |
|
|
|
|
|
Log.d(TAG, "通过MediaButtonReceiver发送Intent") |
|
|
|
|
|
MediaButtonReceiver.handleIntent(mediaSession, intent) |
|
|
|
|
|
|
|
|
|
|
|
// 直接调用handleKeyEvent方法 |
|
|
|
|
|
Log.d(TAG, "直接调用handleKeyEvent方法") |
|
|
|
|
|
handleKeyEvent(keyEvent) |
|
|
|
|
|
|
|
|
|
|
|
Log.d(TAG, "测试媒体按钮事件处理完成") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "测试媒体按钮事件处理失败: ${e.message}", e) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 无声媒体播放器类 |
|
|
* 简易媒体播放器,用于播放raw中的音频资源 |
|
|
* 用于播放一个1秒的静音音频,确保系统认为app在实际播放声音 |
|
|
|
|
|
*/ |
|
|
*/ |
|
|
inner class SilentMediaPlayer(private val context: Context) { |
|
|
inner class MiniMediaPlayer(private val context: Context) { |
|
|
private var mediaPlayer: MediaPlayer? = null |
|
|
private var mediaPlayer: MediaPlayer? = null |
|
|
private var isPlayingAudio = false |
|
|
|
|
|
|
|
|
|
|
|
init { |
|
|
/** |
|
|
|
|
|
* 播放音频资源 |
|
|
|
|
|
* @param resId 资源ID (R.raw.xxx) |
|
|
|
|
|
* @param volume 音量 0.0-1.0 |
|
|
|
|
|
*/ |
|
|
|
|
|
fun play(resId: Int, volume: Float = 1.0f) { |
|
|
try { |
|
|
try { |
|
|
setupMediaPlayer() |
|
|
// 释放之前的资源 |
|
|
} catch (e: Exception) { |
|
|
release() |
|
|
Log.e(TAG, "初始化无声播放器失败: ${e.message}", e) |
|
|
|
|
|
} |
|
|
// 创建播放器并设置资源 |
|
|
} |
|
|
mediaPlayer = MediaPlayer().apply { |
|
|
|
|
|
// 设置音频资源 |
|
|
private fun setupMediaPlayer() { |
|
|
context.resources.openRawResourceFd(resId)?.use { fd -> |
|
|
try { |
|
|
setDataSource(fd.fileDescriptor, fd.startOffset, fd.length) |
|
|
// 创建一个MediaPlayer |
|
|
} |
|
|
mediaPlayer = MediaPlayer() |
|
|
|
|
|
|
|
|
|
|
|
// 设置一个自带的静音资源 |
|
|
|
|
|
val afd = context.resources.openRawResourceFd(R.raw.silence) |
|
|
|
|
|
if (afd != null) { |
|
|
|
|
|
mediaPlayer?.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length) |
|
|
|
|
|
afd.close() |
|
|
|
|
|
} else { |
|
|
|
|
|
// 如果没有资源,尝试使用一个虚拟数据源 |
|
|
|
|
|
Log.w(TAG, "未找到静音资源,尝试使用虚拟数据源") |
|
|
|
|
|
mediaPlayer?.setDataSource(context, Uri.parse("android.resource://${context.packageName}/raw/silence")) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 设置循环播放 |
|
|
|
|
|
mediaPlayer?.isLooping = false |
|
|
|
|
|
|
|
|
|
|
|
// 设置音量为0 |
|
|
|
|
|
mediaPlayer?.setVolume(0f, 0f) |
|
|
|
|
|
|
|
|
|
|
|
// 设置音频特性 |
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|
|
|
|
|
val audioAttributes = AudioAttributes.Builder() |
|
|
|
|
|
.setUsage(AudioAttributes.USAGE_MEDIA) |
|
|
|
|
|
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) |
|
|
|
|
|
.build() |
|
|
|
|
|
mediaPlayer?.setAudioAttributes(audioAttributes) |
|
|
|
|
|
} else { |
|
|
|
|
|
@Suppress("DEPRECATION") |
|
|
|
|
|
mediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 准备播放 |
|
|
// 设置音量 |
|
|
mediaPlayer?.prepare() |
|
|
setVolume(volume.coerceIn(0f, 1f), volume.coerceIn(0f, 1f)) |
|
|
|
|
|
|
|
|
// 设置完成监听器 - 虽然设置了循环播放,但以防万一 |
|
|
// 播放完成后自动释放 |
|
|
mediaPlayer?.setOnCompletionListener { |
|
|
setOnCompletionListener { release() } |
|
|
Log.d(TAG, "无声播放完成") |
|
|
|
|
|
isPlayingAudio = false |
|
|
|
|
|
release() |
|
|
|
|
|
// start() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 设置错误监听器 |
|
|
// 准备并播放 |
|
|
mediaPlayer?.setOnErrorListener { _, what, extra -> |
|
|
prepare() |
|
|
Log.e(TAG, "无声播放错误: what=$what, extra=$extra") |
|
|
start() |
|
|
isPlayingAudio = false |
|
|
|
|
|
// 尝试重新创建 |
|
|
|
|
|
setupMediaPlayer() |
|
|
|
|
|
true |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Log.d(TAG, "无声播放器初始化成功") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "设置无声播放器失败: ${e.message}", e) |
|
|
release() |
|
|
mediaPlayer?.release() |
|
|
|
|
|
mediaPlayer = null |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun start() { |
|
|
|
|
|
try { |
|
|
|
|
|
if (mediaPlayer == null) { |
|
|
|
|
|
setupMediaPlayer() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (mediaPlayer != null && !isPlayingAudio) { |
|
|
|
|
|
mediaPlayer?.start() |
|
|
|
|
|
isPlayingAudio = true |
|
|
|
|
|
Log.d(TAG, "无声播放开始") |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(TAG, "开始无声播放失败: ${e.message}", e) |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 停止播放 |
|
|
|
|
|
*/ |
|
|
fun stop() { |
|
|
fun stop() { |
|
|
try { |
|
|
try { |
|
|
if (mediaPlayer != null && isPlayingAudio) { |
|
|
mediaPlayer?.apply { |
|
|
mediaPlayer?.pause() |
|
|
if (isPlaying) stop() |
|
|
isPlayingAudio = false |
|
|
|
|
|
Log.d(TAG, "无声播放停止") |
|
|
|
|
|
} |
|
|
} |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "停止无声播放失败: ${e.message}", e) |
|
|
// 忽略异常 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 释放资源 |
|
|
|
|
|
*/ |
|
|
fun release() { |
|
|
fun release() { |
|
|
try { |
|
|
try { |
|
|
mediaPlayer?.stop() |
|
|
mediaPlayer?.apply { |
|
|
mediaPlayer?.release() |
|
|
if (isPlaying) stop() |
|
|
|
|
|
release() |
|
|
|
|
|
} |
|
|
mediaPlayer = null |
|
|
mediaPlayer = null |
|
|
isPlayingAudio = false |
|
|
|
|
|
Log.d(TAG, "无声播放器已释放") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(TAG, "释放无声播放器失败: ${e.message}", e) |
|
|
// 忽略异常 |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fun isPlaying(): Boolean { |
|
|
/** |
|
|
return isPlayingAudio && mediaPlayer != null |
|
|
* 是否正在播放 |
|
|
} |
|
|
*/ |
|
|
|
|
|
fun isPlaying() = mediaPlayer?.isPlaying == true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |