Android TTS stops reading aloud after the screen is turned off for a while

Viewed 212

I am working on an app that needs to keep reading aloud text after the screen is turned off. To achieve this goal, I put the Text-to-speech (TTS) code in the foreground service, so the TTS can keep running when the screen is off.

It worked well on my phone before. But after I upgraded my phone from Android 11 to Android 12, the TTS stops working after the screen is turned off for a while, usually after several minutes.

Normally, after the TTS finishes speaking one sentence, it will call the onDone method of the UtteranceProgressListener, so I can make the TTS speak next sentence there. The reason the TTS stops working is that the onDone method stops getting called after the screen is turned off for a while. It doesn't stop immediately, but stops after a few minutes, sometimes longer, sometimes shorter.

Edited:

At the beginning I turned off the battery optimization for the whole system, but it doesn't work. Then I turned off the battery optimization for a specific app. I need to go to the settings for a single app and turn it off, or do it programmatically like this:

Check if battery optimization is enabled or not for an app

This issue is greatly improved after I turn off the battery optimization for a single app. However, the TTS still stops about once for several hours. I also noticed that the app "T2S" can keep running TTS even when its battery optimization is turned on. What can I do to let TTS keep running when the battery optimization is turned on, just like "T2S" does, or at least never stop after battery optimization is turned off?

1 Answers

This code is working in Android 12 even app is background

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"
}

}

Related