|
|
|
@ -22,6 +22,9 @@ import android.os.Handler |
|
|
|
import android.os.Looper |
|
|
|
import java.util.concurrent.atomic.AtomicBoolean |
|
|
|
import org.json.JSONArray |
|
|
|
import android.media.MediaPlayer |
|
|
|
import android.media.AudioAttributes |
|
|
|
import android.net.Uri |
|
|
|
|
|
|
|
/** |
|
|
|
* 后台语音交互 Service: |
|
|
|
@ -93,6 +96,9 @@ class VoiceInteractionService : Service() { |
|
|
|
用户用语音和你交互. |
|
|
|
""".trimIndent() |
|
|
|
|
|
|
|
// 添加无声媒体播放器 |
|
|
|
private var silentPlayer: SilentMediaPlayer? = null |
|
|
|
|
|
|
|
override fun onCreate() { |
|
|
|
super.onCreate() |
|
|
|
Log.d(TAG, "onCreate - 语音交互服务正在创建") |
|
|
|
@ -109,6 +115,11 @@ class VoiceInteractionService : Service() { |
|
|
|
initMediaSession() |
|
|
|
registerMediaButtonReceiver() |
|
|
|
|
|
|
|
|
|
|
|
silentPlayer = SilentMediaPlayer(applicationContext) |
|
|
|
silentPlayer?.start() |
|
|
|
// Log.d(TAG, "无声播放器已启动") |
|
|
|
|
|
|
|
// 设置为媒体播放状态 |
|
|
|
setPlaybackState(PlaybackStateCompat.STATE_PLAYING) |
|
|
|
Log.d(TAG, "设置播放状态为PLAYING") |
|
|
|
@ -440,53 +451,26 @@ class VoiceInteractionService : Service() { |
|
|
|
override fun onSessionStopped() { |
|
|
|
isRecognitionActive = false |
|
|
|
|
|
|
|
// // 如果不是因超时暂停,尝试重新启动 |
|
|
|
// if (isActive && !isTimeoutPaused) { |
|
|
|
// handler.postDelayed({ |
|
|
|
// if (isActive && !isRecognitionActive && !isTtsSpeaking) { |
|
|
|
// startVoiceRecognition() |
|
|
|
// } |
|
|
|
// }, 1000) |
|
|
|
// } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
override fun onCanceled(reason: String, errorDetails: String) { |
|
|
|
isRecognitionActive = false |
|
|
|
|
|
|
|
// // 如果不是因超时暂停,尝试重新启动 |
|
|
|
// if (isActive && !isTimeoutPaused) { |
|
|
|
// handler.postDelayed({ |
|
|
|
// if (isActive && !isRecognitionActive && !isTtsSpeaking) { |
|
|
|
// startVoiceRecognition() |
|
|
|
// } |
|
|
|
// }, 1000) |
|
|
|
// } |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
override fun onError(error: String) { |
|
|
|
isRecognitionActive = false |
|
|
|
playNotification("语音识别出错") |
|
|
|
|
|
|
|
// 如果不是因超时暂停,尝试重新启动 |
|
|
|
// if (isActive && !isTimeoutPaused) { |
|
|
|
// handler.postDelayed({ |
|
|
|
// if (isActive && !isRecognitionActive && !isTtsSpeaking) { |
|
|
|
// startVoiceRecognition() |
|
|
|
// } |
|
|
|
// }, 2000) |
|
|
|
// } |
|
|
|
|
|
|
|
} |
|
|
|
}) |
|
|
|
} catch (e: Exception) { |
|
|
|
isRecognitionActive = false |
|
|
|
playNotification("启动语音识别失败") |
|
|
|
|
|
|
|
// 延迟重试 |
|
|
|
// handler.postDelayed({ |
|
|
|
// if (isActive && !isRecognitionActive && !isTtsSpeaking) { |
|
|
|
// startVoiceRecognition() |
|
|
|
// } |
|
|
|
// }, 3000) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -588,6 +572,11 @@ class VoiceInteractionService : Service() { |
|
|
|
*/ |
|
|
|
private fun playNotification(message: String) { |
|
|
|
isTtsSpeaking = true |
|
|
|
// 更新播放状态为播放中,增加接收蓝牙按键事件的几率 |
|
|
|
setPlaybackState(PlaybackStateCompat.STATE_PLAYING) |
|
|
|
// 确保媒体会话处于活跃状态 |
|
|
|
mediaSession.isActive = true |
|
|
|
|
|
|
|
azureTtsHelper.speakText(message, object : AzureTtsHelper.TTSCallback { |
|
|
|
override fun onSuccess(message: String) { |
|
|
|
isTtsSpeaking = false |
|
|
|
@ -628,10 +617,20 @@ class VoiceInteractionService : Service() { |
|
|
|
PlaybackStateCompat.ACTION_PAUSE or |
|
|
|
PlaybackStateCompat.ACTION_PLAY_PAUSE or |
|
|
|
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or |
|
|
|
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS |
|
|
|
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or |
|
|
|
PlaybackStateCompat.ACTION_STOP or |
|
|
|
PlaybackStateCompat.ACTION_SEEK_TO or |
|
|
|
PlaybackStateCompat.ACTION_FAST_FORWARD or |
|
|
|
PlaybackStateCompat.ACTION_REWIND |
|
|
|
) |
|
|
|
.setState(state, 0, 1.0f) |
|
|
|
// 使用更高的播放速度,让系统认为是真正的播放 |
|
|
|
.setState(state, 0, 1.0f, System.currentTimeMillis()) |
|
|
|
mediaSession.setPlaybackState(playbackStateBuilder.build()) |
|
|
|
|
|
|
|
// 确保MediaSession激活 |
|
|
|
if (!mediaSession.isActive) { |
|
|
|
mediaSession.isActive = true |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
@ -786,6 +785,10 @@ class VoiceInteractionService : Service() { |
|
|
|
override fun onDestroy() { |
|
|
|
Log.d(TAG, "onDestroy - 语音交互服务正在销毁") |
|
|
|
|
|
|
|
// 停止无声播放器 |
|
|
|
silentPlayer?.release() |
|
|
|
silentPlayer = null |
|
|
|
|
|
|
|
// 停止监控 |
|
|
|
stopMonitoring() |
|
|
|
|
|
|
|
@ -901,4 +904,128 @@ class VoiceInteractionService : Service() { |
|
|
|
Log.e(TAG, "测试媒体按钮事件处理失败: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 无声媒体播放器类 |
|
|
|
* 用于播放一个1秒的静音音频,确保系统认为app在实际播放声音 |
|
|
|
*/ |
|
|
|
inner class SilentMediaPlayer(private val context: Context) { |
|
|
|
private var mediaPlayer: MediaPlayer? = null |
|
|
|
private var isPlayingAudio = false |
|
|
|
|
|
|
|
init { |
|
|
|
try { |
|
|
|
setupMediaPlayer() |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "初始化无声播放器失败: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private fun setupMediaPlayer() { |
|
|
|
try { |
|
|
|
// 创建一个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() |
|
|
|
|
|
|
|
// 设置完成监听器 - 虽然设置了循环播放,但以防万一 |
|
|
|
mediaPlayer?.setOnCompletionListener { |
|
|
|
Log.d(TAG, "无声播放完成") |
|
|
|
isPlayingAudio = false |
|
|
|
// start() |
|
|
|
} |
|
|
|
|
|
|
|
// 设置错误监听器 |
|
|
|
mediaPlayer?.setOnErrorListener { _, what, extra -> |
|
|
|
Log.e(TAG, "无声播放错误: what=$what, extra=$extra") |
|
|
|
isPlayingAudio = false |
|
|
|
// 尝试重新创建 |
|
|
|
setupMediaPlayer() |
|
|
|
true |
|
|
|
} |
|
|
|
|
|
|
|
Log.d(TAG, "无声播放器初始化成功") |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "设置无声播放器失败: ${e.message}", e) |
|
|
|
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() { |
|
|
|
try { |
|
|
|
if (mediaPlayer != null && isPlayingAudio) { |
|
|
|
mediaPlayer?.pause() |
|
|
|
isPlayingAudio = false |
|
|
|
Log.d(TAG, "无声播放停止") |
|
|
|
} |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "停止无声播放失败: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fun release() { |
|
|
|
try { |
|
|
|
mediaPlayer?.stop() |
|
|
|
mediaPlayer?.release() |
|
|
|
mediaPlayer = null |
|
|
|
isPlayingAudio = false |
|
|
|
Log.d(TAG, "无声播放器已释放") |
|
|
|
} catch (e: Exception) { |
|
|
|
Log.e(TAG, "释放无声播放器失败: ${e.message}", e) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
fun isPlaying(): Boolean { |
|
|
|
return isPlayingAudio && mediaPlayer != null |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |