From de1ac10c5fbe13f14b4057a8fb01941f40007c7c Mon Sep 17 00:00:00 2001
From: fdp <1286779656@qq.com>
Date: Tue, 9 Sep 2025 18:40:54 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=AE=89=E5=8D=93=E5=88=87?=
=?UTF-8?q?=E6=8D=A2=E5=88=B0=E5=90=8E=E5=8F=B0=E4=B8=8D=E8=83=BD=E7=BB=A7?=
=?UTF-8?q?=E7=BB=AD=E5=BD=95=E9=9F=B3=E7=9A=84bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../android/src/main/AndroidManifest.xml | 15 ++-
.../tools/AudioRecordingForegroundService.kt | 125 ++++++++++++++++++
.../azure_speech/tools/SimpleAudioReceiver.kt | 16 ++-
3 files changed, 152 insertions(+), 4 deletions(-)
create mode 100644 local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt
diff --git a/local_plugins/azure_speech/android/src/main/AndroidManifest.xml b/local_plugins/azure_speech/android/src/main/AndroidManifest.xml
index df2a770c6..88f2910c1 100644
--- a/local_plugins/azure_speech/android/src/main/AndroidManifest.xml
+++ b/local_plugins/azure_speech/android/src/main/AndroidManifest.xml
@@ -3,4 +3,17 @@
package="com.yunqiinnovation.azure_speech">
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt
new file mode 100644
index 000000000..4b03f4ec3
--- /dev/null
+++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/AudioRecordingForegroundService.kt
@@ -0,0 +1,125 @@
+package com.yunqiinnovation.azure_speech.tools
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.app.Service
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.IBinder
+import androidx.core.app.NotificationCompat
+import android.util.Log
+
+/**
+ * 音频录制前台服务
+ * 用于在录制音频时保持应用在前台运行,防止系统杀死应用
+ */
+class AudioRecordingForegroundService : Service() {
+
+ companion object {
+ private const val TAG = "AudioRecordingService"
+ private const val NOTIFICATION_ID = 1001
+ private const val CHANNEL_ID = "audio_recording_channel"
+ private const val CHANNEL_NAME = "音频录制服务"
+
+ /**
+ * 启动前台服务
+ * @param context 上下文
+ */
+ fun startService(context: Context) {
+ val intent = Intent(context, AudioRecordingForegroundService::class.java)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ context.startForegroundService(intent)
+ } else {
+ context.startService(intent)
+ }
+ Log.d(TAG, "音频录制前台服务已启动")
+ }
+
+ /**
+ * 停止前台服务
+ * @param context 上下文
+ */
+ fun stopService(context: Context) {
+ val intent = Intent(context, AudioRecordingForegroundService::class.java)
+ context.stopService(intent)
+ Log.d(TAG, "音频录制前台服务已停止")
+ }
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.d(TAG, "AudioRecordingForegroundService onCreate")
+ createNotificationChannel()
+ }
+
+ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+ Log.d(TAG, "AudioRecordingForegroundService onStartCommand")
+
+ // 创建前台通知
+ val notification = createNotification()
+ startForeground(NOTIFICATION_ID, notification)
+
+ // 返回START_STICKY确保服务被系统杀死后会重启
+ return START_STICKY
+ }
+
+ override fun onBind(intent: Intent?): IBinder? {
+ return null
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ Log.d(TAG, "AudioRecordingForegroundService onDestroy")
+ stopForeground(true)
+ }
+
+ /**
+ * 创建通知渠道(Android 8.0及以上需要)
+ */
+ private fun createNotificationChannel() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val channel = NotificationChannel(
+ CHANNEL_ID,
+ CHANNEL_NAME,
+ NotificationManager.IMPORTANCE_LOW
+ ).apply {
+ description = "用于音频录制时保持应用运行"
+ setShowBadge(false)
+ setSound(null, null)
+ enableVibration(false)
+ }
+
+ val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.createNotificationChannel(channel)
+ }
+ }
+
+ /**
+ * 创建前台服务通知
+ * @return 通知对象
+ */
+ private fun createNotification(): Notification {
+ // 创建点击通知时的意图(可以打开应用主界面)
+ val intent = packageManager.getLaunchIntentForPackage(packageName)
+ val pendingIntent = PendingIntent.getActivity(
+ this,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ return NotificationCompat.Builder(this, CHANNEL_ID)
+ .setContentTitle("语音助手正在运行")
+ .setContentText("正在录制音频,点击返回应用")
+ .setSmallIcon(android.R.drawable.ic_btn_speak_now) // 使用系统默认麦克风图标
+ .setContentIntent(pendingIntent)
+ .setOngoing(true) // 设置为持续通知,用户无法滑动删除
+ .setPriority(NotificationCompat.PRIORITY_LOW)
+ .setCategory(NotificationCompat.CATEGORY_SERVICE)
+ .setShowWhen(false)
+ .build()
+ }
+}
\ No newline at end of file
diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt
index 08e5ae668..ee4e9fed0 100644
--- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt
+++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/tools/SimpleAudioReceiver.kt
@@ -17,6 +17,7 @@ import android.media.AudioFocusRequest
import android.media.AudioAttributes
import com.yunqiinnovation.azure_speech.tools.RecordFile
import android.os.Build // 新增:用于获取设备信息
+import com.yunqiinnovation.azure_speech.tools.AudioRecordingForegroundService
/**
* 简单音频接收器类,用于处理音频录制和流传输
@@ -208,6 +209,10 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
Log.d("tag", "存下来的数据: ${writeQueue?.size}")
}
+ // 启动前台服务
+ AudioRecordingForegroundService.startService(context)
+ Log.d(TAG, "前台服务已启动,开始音频录制")
+
when (audioSourceType) {
AudioSourceType.MICROPHONE -> runMicrophoneCapture()
AudioSourceType.EXTERNAL -> runExternalCapture()
@@ -218,10 +223,11 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
pushAudioStream?.close()
pushAudioStream = null
isWriting.set(false)
+ // 停止前台服务
+ AudioRecordingForegroundService.stopService(context)
throw e // 重新抛出异常让调用者处理
}
}
-// ... existing code ...
/**
* 配置通话音频模式以优化回声消除
*/
@@ -392,6 +398,9 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
try {
writeQueue.clear()
isWriting.set(false)
+ // 停止前台服务
+ AudioRecordingForegroundService.stopService(context)
+
// 停止录音
audioRecord?.stop()
@@ -403,7 +412,6 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
Log.e(TAG, "停止麦克风捕获失败: ${e.message}")
}
}
-
/**
* 释放音频资源
*/
@@ -411,7 +419,9 @@ fun startAudioRecord(audioSourceType: AudioSourceType, callback: AudioDataCallba
try {
isRunning.set(false)
isWriting.set(false)
-
+ // 停止前台服务
+ AudioRecordingForegroundService.stopService(context)
+ Log.d(TAG, "前台服务已停止,音频资源已释放")
// 中断并等待捕获线程结束
writeThread?.interrupt()
writeThread?.join(300) // 最多等待300ms