212 lines
6.1 KiB
Kotlin
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)
|
|
}
|
|
|
|
} |