我正在开发一个应用程序,需要在屏幕关闭后继续朗读文本。为了实现这个目标,我将文本转语音(TTS)代码放在前台服务中,这样当屏幕关闭时,TTS 可以继续运行。
以前在我的手机上运行得很好。但是当我将手机从 Android 11 升级到 Android 12 后,TTS 在屏幕关闭一段时间后(通常是几分钟后)停止工作。
通常,TTS 说完一句话后,它会调用
onDone
的 UtteranceProgressListener
方法,这样我就可以在那里让 TTS 说出下一句话。 TTS 停止工作的原因是,在屏幕关闭一段时间后,onDone
方法停止被调用。它不会立即停止,而是几分钟后停止,有时更长,有时更短。
编辑:
一开始我关闭了整个系统的电池优化,但是不起作用。然后我关闭单个应用程序的电池优化。我需要转到单个应用程序的设置并将其关闭,或者以编程方式执行此操作,如下所示:
关闭单个应用的电池优化后,这个问题得到了很大改善。然而,TTS 仍然会时不时地停止一次,大约一次持续几个小时。我还注意到,即使电池优化打开,应用程序“T2S”也可以继续运行。我该怎么做才能让 TTS 在电池优化开启时继续运行,就像“T2S”一样,或者至少在电池优化关闭后不让它停止?
补充 Denny Hsu 的答案:
每个 tts 引擎都有一次合成为语音的最大字符数。您可以通过以下方式找到这个最大数量:
TextToSpeech.getMaxSpeechInputLength()
例如,我认为 Google TTS 引擎的最大长度是 4000 个字符。
您可以通过阻止用户尝试说出超过此限制的文本,或者在此限制处或之前将文本分成单独的字符串,然后按顺序发送来解决此问题。
在后一种情况下,最好找到限制之前的最后一个句子的结尾,并将其分开以确保保持正确的发音。
即使关闭电池优化,TTS也会偶尔停止运行,根本原因是
speak()
功能的输入文本太长。
顺便说一下,Android 13 似乎已经解决了这个问题。无需打开电池优化即可让 TTS 在屏幕关闭时继续运行。
即使应用程序处于后台,此代码也可以在 Android 12 中运行
class TTS : Service(), OnInitListener {
private var tts: TextToSpeech? = null
private lateinit var spokenText: String
private var isInit: Boolean = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if(intent?.extras != null) {
spokenText = intent.getStringExtra("text").toString()
}
else {
spokenText = ""
}
Log.d(TAG, "onStartCommand: $spokenText")
return START_NOT_STICKY
}
override fun onCreate() {
tts = TextToSpeech(this, this)
Log.d(TAG, "onCreate: CREATING AGAIN !!")
}
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
Log.d(TAG, "onInit: TextToSpeech Success")
val result = tts!!.setLanguage(Locale("hi", "IN"))
if (result != TextToSpeech.LANG_MISSING_DATA && result != TextToSpeech.LANG_NOT_SUPPORTED) {
Log.d(TAG, "onInit: speaking........")
addAudioAttributes()
isInit = true
}
}
else {
Log.d(TAG, "onInit: TTS initialization failed")
Toast.makeText(
applicationContext,
"Your device don't support text to speech.\n Visit app to download!!",
Toast.LENGTH_SHORT
).show()
}
}
private fun addAudioAttributes() {
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val audioAttributes = AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
tts?.setAudioAttributes(audioAttributes)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val focusRequest =
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build()
)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener { focus ->
when (focus) {
AudioManager.AUDIOFOCUS_GAIN -> {
}
else -> stopSelf()
}
}.build()
when (audioManager.requestAudioFocus(focusRequest)) {
AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> speak(audioManager, focusRequest)
AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> stopSelf()
AudioManager.AUDIOFOCUS_REQUEST_FAILED -> stopSelf()
}
} else {
val result = audioManager.requestAudioFocus( { focusChange: Int ->
when(focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {
}
else -> stopSelf()
}
},
AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
)
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
speak(audioManager, null)
}
}
}
private fun speak(audioManager: AudioManager, focusRequest: AudioFocusRequest?) {
val speechListener = object : UtteranceProgressListener() {
override fun onStart(utteranceId: String?) {
Log.d(TAG, "onStart: Started syntheses.....")
}
override fun onDone(utteranceId: String?) {
Log.d(TAG, "onDone: Completed synthesis ")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && focusRequest != null) {
audioManager.abandonAudioFocusRequest(focusRequest)
}
stopSelf()
}
override fun onError(utteranceId: String?) {
Log.d(TAG, "onError: Error synthesis")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && focusRequest != null) {
audioManager.abandonAudioFocusRequest(focusRequest)
}
stopSelf()
}
}
val paramsMap: HashMap<String, String> = HashMap()
paramsMap[TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID] = "tts_service"
tts?.speak(spokenText, TextToSpeech.QUEUE_ADD, paramsMap)
tts?.setOnUtteranceProgressListener(speechListener)
}
override fun onDestroy() {
if (tts != null) {
Log.d(TAG, "onDestroy: destroyed tts")
tts?.stop()
tts?.shutdown()
}
super.onDestroy()
}
override fun onBind(arg0: Intent?): IBinder? {
return null
}
companion object {
private const val TAG = "TTS_Service"
}
}