Files
QieKan-3.0/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
T
2023-08-21 15:22:00 +08:00

212 lines
6.1 KiB
Kotlin

package io.legado.app.service
import android.app.PendingIntent
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import io.legado.app.R
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppLog
import io.legado.app.constant.AppPattern
import io.legado.app.constant.EventBus
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.MediaHelp
import io.legado.app.help.config.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.SelectItem
import io.legado.app.model.ReadAloud
import io.legado.app.model.ReadBook
import io.legado.app.utils.*
import kotlinx.coroutines.ensureActive
/**
* 本地朗读
*/
class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener {
private var textToSpeech: TextToSpeech? = null
private var ttsInitFinish = false
private val ttsUtteranceListener = TTSUtteranceListener()
private var speakJob: Coroutine<*>? = null
override fun onCreate() {
super.onCreate()
initTts()
}
override fun onDestroy() {
super.onDestroy()
clearTTS()
}
@Synchronized
private fun initTts() {
ttsInitFinish = false
val engine = GSON.fromJsonObject<SelectItem<String>>(ReadAloud.ttsEngine).getOrNull()?.value
textToSpeech = if (engine.isNullOrBlank()) {
TextToSpeech(this, this)
} else {
TextToSpeech(this, this, engine)
}
upSpeechRate()
}
@Synchronized
fun clearTTS() {
textToSpeech?.runCatching {
stop()
shutdown()
}
textToSpeech = null
ttsInitFinish = false
}
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
textToSpeech?.let {
it.setOnUtteranceProgressListener(ttsUtteranceListener)
ttsInitFinish = true
play()
}
} else {
toastOnUi(R.string.tts_init_failed)
}
}
@Synchronized
override fun play() {
if (!ttsInitFinish) return
if (!requestFocus()) return
if (contentList.isEmpty()) {
AppLog.putDebug("朗读列表为空")
ReadBook.readAloud()
return
}
super.play()
MediaHelp.playSilentSound(this@TTSReadAloudService)
speakJob?.cancel()
speakJob = execute {
val tts = textToSpeech ?: throw NoStackTraceException("tts is null")
var result = tts.runCatching {
speak("", TextToSpeech.QUEUE_FLUSH, null, null)
}.getOrElse {
AppLog.put("tts出错\n${it.localizedMessage}", it, true)
TextToSpeech.ERROR
}
if (result == TextToSpeech.ERROR) {
clearTTS()
initTts()
return@execute
}
val contentList = contentList
for (i in nowSpeak until contentList.size) {
ensureActive()
val text = contentList[i]
if (text.matches(AppPattern.notReadAloudRegex)) {
continue
}
result = tts.runCatching {
speak(text, TextToSpeech.QUEUE_ADD, null, AppConst.APP_TAG + i)
}.getOrElse {
AppLog.put("tts出错\n${it.localizedMessage}", it, true)
TextToSpeech.ERROR
}
if (result == TextToSpeech.ERROR) {
AppLog.put("tts朗读出错:$text")
}
}
}.onError {
AppLog.put("tts朗读出错", it)
toastOnUi(it.localizedMessage)
}
}
override fun playStop() {
textToSpeech?.runCatching {
stop()
}
}
/**
* 更新朗读速度
*/
override fun upSpeechRate(reset: Boolean) {
if (AppConfig.ttsFlowSys) {
if (reset) {
clearTTS()
initTts()
}
} else {
val speechRate = (AppConfig.ttsSpeechRate + 5) / 10f
textToSpeech?.setSpeechRate(speechRate)
}
}
/**
* 暂停朗读
*/
override fun pauseReadAloud(abandonFocus: Boolean) {
super.pauseReadAloud(abandonFocus)
speakJob?.cancel()
textToSpeech?.runCatching {
stop()
}
}
/**
* 恢复朗读
*/
override fun resumeReadAloud() {
super.resumeReadAloud()
play()
}
/**
* 朗读监听
*/
private inner class TTSUtteranceListener : UtteranceProgressListener() {
override fun onStart(s: String) {
textChapter?.let {
if (readAloudNumber + 1 > it.getReadLength(pageIndex + 1)) {
pageIndex++
ReadBook.moveToNextPage()
}
upTtsProgress(readAloudNumber + 1)
}
}
override fun onDone(s: String) {
//跳过全标点段落
do {
readAloudNumber += contentList[nowSpeak].length + 1
nowSpeak++
if (nowSpeak >= contentList.size) {
nextChapter()
return
}
} while (contentList[nowSpeak].matches(AppPattern.notReadAloudRegex))
}
override fun onRangeStart(utteranceId: String?, start: Int, end: Int, frame: Int) {
super.onRangeStart(utteranceId, start, end, frame)
textChapter?.let {
if (readAloudNumber + start > it.getReadLength(pageIndex + 1)) {
pageIndex++
ReadBook.moveToNextPage()
upTtsProgress(readAloudNumber + start)
}
}
}
@Deprecated("Deprecated in Java")
override fun onError(s: String) {
//nothing
}
}
override fun aloudServicePendingIntent(actionStr: String): PendingIntent? {
return servicePendingIntent<TTSReadAloudService>(actionStr)
}
}