diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d0689f9eb..7bd159503 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -68,7 +68,7 @@
-
+
diff --git a/app/src/main/java/io/legado/app/help/CrashHandler.kt b/app/src/main/java/io/legado/app/help/CrashHandler.kt
index 35c29304c..a8fe0d05f 100644
--- a/app/src/main/java/io/legado/app/help/CrashHandler.kt
+++ b/app/src/main/java/io/legado/app/help/CrashHandler.kt
@@ -8,7 +8,7 @@ import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
-import io.legado.app.service.ReadAloudService
+import io.legado.app.service.TTSReadAloudService
import java.io.File
import java.io.FileOutputStream
import java.io.PrintWriter
@@ -54,7 +54,7 @@ class CrashHandler : Thread.UncaughtExceptionHandler {
* uncaughtException 回调函数
*/
override fun uncaughtException(thread: Thread, ex: Throwable) {
- ReadAloudService.clearTTS()
+ TTSReadAloudService.clearTTS()
handleException(ex)
mDefaultHandler?.uncaughtException(thread, ex)
}
diff --git a/app/src/main/java/io/legado/app/help/PendingIntentHelp.kt b/app/src/main/java/io/legado/app/help/PendingIntentHelp.kt
index e0725cb27..280469f3e 100644
--- a/app/src/main/java/io/legado/app/help/PendingIntentHelp.kt
+++ b/app/src/main/java/io/legado/app/help/PendingIntentHelp.kt
@@ -3,7 +3,7 @@ package io.legado.app.help
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
-import io.legado.app.service.ReadAloudService
+import io.legado.app.service.TTSReadAloudService
import io.legado.app.ui.readbook.ReadBookActivity
object PendingIntentHelp {
@@ -15,7 +15,7 @@ object PendingIntentHelp {
}
fun aloudServicePendingIntent(context: Context, actionStr: String): PendingIntent {
- val intent = Intent(context, ReadAloudService::class.java)
+ val intent = Intent(context, TTSReadAloudService::class.java)
intent.action = actionStr
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
diff --git a/app/src/main/java/io/legado/app/service/ReadAloudService.kt b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
similarity index 54%
rename from app/src/main/java/io/legado/app/service/ReadAloudService.kt
rename to app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
index b3c864a80..35e3ad6c1 100644
--- a/app/src/main/java/io/legado/app/service/ReadAloudService.kt
+++ b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
@@ -2,40 +2,32 @@ package io.legado.app.service
import android.app.PendingIntent
import android.content.*
+import android.graphics.BitmapFactory
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import android.os.Handler
-import android.speech.tts.TextToSpeech
-import android.speech.tts.UtteranceProgressListener
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
+import androidx.annotation.CallSuper
+import androidx.core.app.NotificationCompat
import io.legado.app.R
import io.legado.app.base.BaseService
import io.legado.app.constant.Action
import io.legado.app.constant.AppConst
import io.legado.app.constant.Bus
import io.legado.app.constant.Status
-import io.legado.app.help.IntentDataHelp
-import io.legado.app.help.IntentHelp
import io.legado.app.help.MediaHelp
+import io.legado.app.help.PendingIntentHelp
import io.legado.app.receiver.MediaButtonReceiver
-import io.legado.app.service.notification.ReadAloudNotification
import io.legado.app.ui.widget.page.TextChapter
-import io.legado.app.utils.getPrefBoolean
-import io.legado.app.utils.getPrefInt
import io.legado.app.utils.postEvent
-import kotlinx.coroutines.launch
-import org.jetbrains.anko.toast
-import java.util.*
-class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
+abstract class BaseReadAloudService : BaseService(),
AudioManager.OnAudioFocusChangeListener {
companion object {
- val tag: String = ReadAloudService::class.java.simpleName
var isRun = false
- var textToSpeech: TextToSpeech? = null
var timeMinute: Int = 0
fun play(
@@ -46,7 +38,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
dataKey: String,
play: Boolean = true
) {
- val readAloudIntent = Intent(context, ReadAloudService::class.java)
+ val readAloudIntent = Intent(context, TTSReadAloudService::class.java)
readAloudIntent.action = Action.play
readAloudIntent.putExtra("title", title)
readAloudIntent.putExtra("subtitle", subtitle)
@@ -58,7 +50,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
fun pause(context: Context) {
if (isRun) {
- val intent = Intent(context, ReadAloudService::class.java)
+ val intent = Intent(context, TTSReadAloudService::class.java)
intent.action = Action.pause
context.startService(intent)
}
@@ -66,7 +58,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
fun resume(context: Context) {
if (isRun) {
- val intent = Intent(context, ReadAloudService::class.java)
+ val intent = Intent(context, TTSReadAloudService::class.java)
intent.action = Action.resume
context.startService(intent)
}
@@ -74,7 +66,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
fun stop(context: Context) {
if (isRun) {
- val intent = Intent(context, ReadAloudService::class.java)
+ val intent = Intent(context, TTSReadAloudService::class.java)
intent.action = Action.stop
context.startService(intent)
}
@@ -82,7 +74,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
fun prevParagraph(context: Context) {
if (isRun) {
- val intent = Intent(context, ReadAloudService::class.java)
+ val intent = Intent(context, TTSReadAloudService::class.java)
intent.action = Action.prevParagraph
context.startService(intent)
}
@@ -90,7 +82,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
fun nextParagraph(context: Context) {
if (isRun) {
- val intent = Intent(context, ReadAloudService::class.java)
+ val intent = Intent(context, TTSReadAloudService::class.java)
intent.action = Action.nextParagraph
context.startService(intent)
}
@@ -98,7 +90,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
fun upTtsSpeechRate(context: Context) {
if (isRun) {
- val intent = Intent(context, ReadAloudService::class.java)
+ val intent = Intent(context, TTSReadAloudService::class.java)
intent.action = Action.upTtsSpeechRate
context.startService(intent)
}
@@ -106,57 +98,46 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
fun setTimer(context: Context, minute: Int) {
if (isRun) {
- val intent = Intent(context, ReadAloudService::class.java)
+ val intent = Intent(context, TTSReadAloudService::class.java)
intent.action = Action.setTimer
intent.putExtra("minute", minute)
context.startService(intent)
}
}
-
- fun clearTTS() {
- textToSpeech?.stop()
- textToSpeech?.shutdown()
- textToSpeech = null
- }
}
private val handler = Handler()
- private var ttsIsSuccess: Boolean = false
private lateinit var audioManager: AudioManager
private lateinit var mFocusRequest: AudioFocusRequest
private var broadcastReceiver: BroadcastReceiver? = null
- private var nowSpeak: Int = 0
- private val contentList = arrayListOf()
- private var readAloudNumber: Int = 0
- private var textChapter: TextChapter? = null
- private var pageIndex = 0
- private val dsRunnable: Runnable? = Runnable { doDs() }
- var mediaSessionCompat: MediaSessionCompat? = null
- var pause = false
+ private var mediaSessionCompat: MediaSessionCompat? = null
+ private var pause = false
var title: String = ""
- var subtitle: String = ""
+ private var subtitle: String = ""
+ val contentList = arrayListOf()
+ var nowSpeak: Int = 0
+ var readAloudNumber: Int = 0
+ var textChapter: TextChapter? = null
+ var pageIndex = 0
+ private val dsRunnable: Runnable? = Runnable { doDs() }
override fun onCreate() {
super.onCreate()
isRun = true
- textToSpeech = TextToSpeech(this, this)
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mFocusRequest = MediaHelp.getFocusRequest(this)
}
initMediaSession()
initBroadcastReceiver()
- upSpeechRate()
- ReadAloudNotification.upNotification(this)
+ upNotification()
}
override fun onDestroy() {
super.onDestroy()
- upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED)
- clearTTS()
+ isRun = false
unregisterReceiver(broadcastReceiver)
postEvent(Bus.ALOUD_STATE, Status.STOP)
- isRun = false
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -184,77 +165,24 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
return super.onStartCommand(intent, flags, startId)
}
- override fun onInit(status: Int) {
- launch {
- if (status == TextToSpeech.SUCCESS) {
- val result = textToSpeech?.setLanguage(Locale.CHINA)
- if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
- toast(R.string.tts_fix)
- IntentHelp.toTTSSetting(this@ReadAloudService)
- stopSelf()
- } else {
- textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener())
- ttsIsSuccess = true
- playTTS()
- }
- } else {
- toast(R.string.tts_init_failed)
- }
- }
- }
+ abstract fun newReadAloud(dataKey: String?, play: Boolean)
- private fun newReadAloud(dataKey: String?, play: Boolean) {
- dataKey?.let {
- textChapter = IntentDataHelp.getData(dataKey) as? TextChapter
- textChapter?.let { textChapter ->
- nowSpeak = 0
- readAloudNumber = textChapter.getReadLength(pageIndex)
- contentList.clear()
- if (getPrefBoolean("readAloudByPage")) {
- for (index in pageIndex..textChapter.lastIndex()) {
- textChapter.page(index)?.text?.split("\n")?.let {
- contentList.addAll(it)
- }
- }
- } else {
- contentList.addAll(textChapter.getUnRead(pageIndex).split("\n"))
- }
- if (play) playTTS()
- } ?: stopSelf()
- } ?: stopSelf()
+ @CallSuper
+ open fun pauseReadAloud(pause: Boolean) {
+ postEvent(Bus.ALOUD_STATE, Status.PAUSE)
+ this.pause = pause
}
- @Suppress("DEPRECATION")
- private fun playTTS() {
- if (contentList.size < 1 || !ttsIsSuccess) {
- return
- }
- if (requestFocus()) {
- upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
- postEvent(Bus.ALOUD_STATE, Status.PLAY)
- ReadAloudNotification.upNotification(this)
- for (i in nowSpeak until contentList.size) {
- if (i == 0) {
- speak(contentList[i], TextToSpeech.QUEUE_FLUSH, AppConst.APP_TAG + i)
- } else {
- speak(contentList[i], TextToSpeech.QUEUE_ADD, AppConst.APP_TAG + i)
- }
- }
- }
+ @CallSuper
+ open fun resumeReadAloud() {
+ pause = false
}
- private fun speak(content: String, queueMode: Int, utteranceId: String) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- textToSpeech?.speak(content, queueMode, null, utteranceId)
- } else {
- @Suppress("DEPRECATION")
- textToSpeech?.speak(
- content,
- queueMode,
- hashMapOf(Pair(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId))
- )
- }
- }
+ abstract fun upSpeechRate(reset: Boolean = false)
+
+ abstract fun prevP()
+
+ abstract fun nextP()
private fun setTimer(minute: Int) {
timeMinute = minute
@@ -262,7 +190,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
handler.removeCallbacks(dsRunnable)
handler.postDelayed(dsRunnable, 60000)
}
- ReadAloudNotification.upNotification(this)
+ upNotification()
}
private fun addTimer() {
@@ -276,7 +204,7 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
handler.postDelayed(dsRunnable, 60000)
}
postEvent(Bus.TTS_DS, timeMinute)
- ReadAloudNotification.upNotification(this)
+ upNotification()
}
/**
@@ -292,47 +220,57 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
}
}
postEvent(Bus.TTS_DS, timeMinute)
- ReadAloudNotification.upNotification(this)
+ upNotification()
}
- /**
- * 更新朗读速度
- */
- private fun upSpeechRate(reset: Boolean = false) {
- if (this.getPrefBoolean("ttsFollowSys", true)) {
- if (reset) {
- clearTTS()
- textToSpeech = TextToSpeech(this, this)
+
+ override fun onAudioFocusChange(focusChange: Int) {
+ when (focusChange) {
+ AudioManager.AUDIOFOCUS_GAIN -> {
+ // 重新获得焦点, 可做恢复播放,恢复后台音量的操作
+ if (!pause) resumeReadAloud()
+ }
+ AudioManager.AUDIOFOCUS_LOSS -> {
+ // 永久丢失焦点除非重新主动获取,这种情况是被其他播放器抢去了焦点, 为避免与其他播放器混音,可将音乐暂停
+ }
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
+ // 暂时丢失焦点,这种情况是被其他应用申请了短暂的焦点,可压低后台音量
+ if (!pause) pauseReadAloud(false)
+ }
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
+ // 短暂丢失焦点,这种情况是被其他应用申请了短暂的焦点希望其他声音能压低音量(或者关闭声音)凸显这个声音(比如短信提示音),
}
- } else {
- textToSpeech?.setSpeechRate((this.getPrefInt("ttsSpeechRate", 5) + 5) / 10f)
}
}
/**
- * 上一段
+ * @return 音频焦点
*/
- private fun prevP() {
- if (nowSpeak > 0) {
- textToSpeech?.stop()
- nowSpeak--
- readAloudNumber -= contentList[nowSpeak].length.minus(1)
- playTTS()
+ fun requestFocus(): Boolean {
+ MediaHelp.playSilentSound(this)
+ val request: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ audioManager.requestAudioFocus(mFocusRequest)
+ } else {
+ @Suppress("DEPRECATION")
+ audioManager.requestAudioFocus(
+ this,
+ AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN
+ )
}
+ return request == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
}
- /**
- * 下一段
- */
- private fun nextP() {
- if (nowSpeak < contentList.size - 1) {
- textToSpeech?.stop()
- readAloudNumber += contentList[nowSpeak].length.plus(1)
- nowSpeak++
- playTTS()
- }
+ fun upMediaSessionPlaybackState(state: Int) {
+ mediaSessionCompat?.setPlaybackState(
+ PlaybackStateCompat.Builder()
+ .setActions(MediaHelp.MEDIA_SESSION_ACTIONS)
+ .setState(state, nowSpeak.toLong(), 1f)
+ .build()
+ )
}
+
/**
* 初始化MediaSession, 注册多媒体按钮
*/
@@ -347,10 +285,10 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
PendingIntent.FLAG_CANCEL_CURRENT
)
- mediaSessionCompat = MediaSessionCompat(this, tag)
+ mediaSessionCompat = MediaSessionCompat(this, TTSReadAloudService.tag)
mediaSessionCompat?.setCallback(object : MediaSessionCompat.Callback() {
override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {
- return MediaButtonReceiver.handleIntent(this@ReadAloudService, mediaButtonEvent)
+ return MediaButtonReceiver.handleIntent(this@BaseReadAloudService, mediaButtonEvent)
}
})
mediaSessionCompat?.setMediaButtonReceiver(mediaButtonReceiverPendingIntent)
@@ -373,110 +311,58 @@ class ReadAloudService : BaseService(), TextToSpeech.OnInitListener,
}
/**
- * 暂停朗读
- */
- private fun pauseReadAloud(pause: Boolean) {
- postEvent(Bus.ALOUD_STATE, Status.PAUSE)
- this.pause = pause
- textToSpeech?.stop()
- upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED)
- ReadAloudNotification.upNotification(this)
- }
-
- /**
- * 恢复朗读
- */
- private fun resumeReadAloud() {
- pause = false
- playTTS()
- }
-
- /**
- * @return 音频焦点
+ * 更新通知
*/
- private fun requestFocus(): Boolean {
- MediaHelp.playSilentSound(this)
- val request: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- audioManager.requestAudioFocus(mFocusRequest)
+ fun upNotification() {
+ var nTitle: String = when {
+ pause -> getString(R.string.read_aloud_pause)
+ timeMinute in 1..60 -> getString(
+ R.string.read_aloud_timer,
+ timeMinute
+ )
+ else -> getString(R.string.read_aloud_t)
+ }
+ nTitle += ": $title"
+ var nSubtitle = subtitle
+ if (subtitle.isEmpty())
+ nSubtitle = getString(R.string.read_aloud_s)
+ val builder = NotificationCompat.Builder(this, AppConst.channelIdReadAloud)
+ .setSmallIcon(R.drawable.ic_volume_up)
+ .setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.icon_read_book))
+ .setOngoing(true)
+ .setContentTitle(nTitle)
+ .setContentText(nSubtitle)
+ .setContentIntent(PendingIntentHelp.readBookActivityPendingIntent(this))
+ if (pause) {
+ builder.addAction(
+ R.drawable.ic_play_24dp,
+ getString(R.string.resume),
+ PendingIntentHelp.aloudServicePendingIntent(this, Action.resume)
+ )
} else {
- @Suppress("DEPRECATION")
- audioManager.requestAudioFocus(
- this,
- AudioManager.STREAM_MUSIC,
- AudioManager.AUDIOFOCUS_GAIN
+ builder.addAction(
+ R.drawable.ic_pause_24dp,
+ getString(R.string.pause),
+ PendingIntentHelp.aloudServicePendingIntent(this, Action.pause)
)
}
- return request == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
- }
-
- private fun upMediaSessionPlaybackState(state: Int) {
- mediaSessionCompat?.setPlaybackState(
- PlaybackStateCompat.Builder()
- .setActions(MediaHelp.MEDIA_SESSION_ACTIONS)
- .setState(state, nowSpeak.toLong(), 1f)
- .build()
+ builder.addAction(
+ R.drawable.ic_stop_black_24dp,
+ getString(R.string.stop),
+ PendingIntentHelp.aloudServicePendingIntent(this, Action.stop)
)
+ builder.addAction(
+ R.drawable.ic_time_add_24dp,
+ getString(R.string.set_timer),
+ PendingIntentHelp.aloudServicePendingIntent(this, Action.addTimer)
+ )
+ builder.setStyle(
+ androidx.media.app.NotificationCompat.MediaStyle()
+ .setMediaSession(mediaSessionCompat?.sessionToken)
+ .setShowActionsInCompactView(0, 1, 2)
+ )
+ builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
+ val notification = builder.build()
+ startForeground(112201, notification)
}
-
- override fun onAudioFocusChange(focusChange: Int) {
- when (focusChange) {
- AudioManager.AUDIOFOCUS_GAIN -> {
- // 重新获得焦点, 可做恢复播放,恢复后台音量的操作
- if (!pause) resumeReadAloud()
- }
- AudioManager.AUDIOFOCUS_LOSS -> {
- // 永久丢失焦点除非重新主动获取,这种情况是被其他播放器抢去了焦点, 为避免与其他播放器混音,可将音乐暂停
- }
- AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
- // 暂时丢失焦点,这种情况是被其他应用申请了短暂的焦点,可压低后台音量
- if (!pause) pauseReadAloud(false)
- }
- AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
- // 短暂丢失焦点,这种情况是被其他应用申请了短暂的焦点希望其他声音能压低音量(或者关闭声音)凸显这个声音(比如短信提示音),
- }
- }
- }
-
- /**
- * 朗读监听
- */
- private inner class TTSUtteranceListener : UtteranceProgressListener() {
-
- override fun onStart(s: String) {
- textChapter?.let {
- if (readAloudNumber + 1 > it.getReadLength(pageIndex + 1)) {
- pageIndex++
- postEvent(Bus.TTS_TURN_PAGE, 1)
- }
- }
- postEvent(Bus.TTS_START, readAloudNumber + 1)
- }
-
- override fun onDone(s: String) {
- readAloudNumber += contentList[nowSpeak].length + 1
- nowSpeak += 1
- if (nowSpeak >= contentList.size) {
- postEvent(Bus.TTS_TURN_PAGE, 2)
- }
- }
-
- 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++
- postEvent(Bus.TTS_TURN_PAGE, 1)
- postEvent(Bus.TTS_START, readAloudNumber + start)
- }
- }
- }
-
- override fun onError(s: String) {
- launch {
- toast(s)
- }
- }
-
- }
-
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
new file mode 100644
index 000000000..195fe4fdf
--- /dev/null
+++ b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
@@ -0,0 +1,218 @@
+package io.legado.app.service
+
+import android.os.Build
+import android.speech.tts.TextToSpeech
+import android.speech.tts.UtteranceProgressListener
+import android.support.v4.media.session.PlaybackStateCompat
+import io.legado.app.R
+import io.legado.app.constant.AppConst
+import io.legado.app.constant.Bus
+import io.legado.app.constant.Status
+import io.legado.app.help.IntentDataHelp
+import io.legado.app.help.IntentHelp
+import io.legado.app.ui.widget.page.TextChapter
+import io.legado.app.utils.getPrefBoolean
+import io.legado.app.utils.getPrefInt
+import io.legado.app.utils.postEvent
+import kotlinx.coroutines.launch
+import org.jetbrains.anko.toast
+import java.util.*
+
+class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener {
+
+ companion object {
+ val tag: String = TTSReadAloudService::class.java.simpleName
+ var textToSpeech: TextToSpeech? = null
+
+ fun clearTTS() {
+ textToSpeech?.stop()
+ textToSpeech?.shutdown()
+ textToSpeech = null
+ }
+ }
+
+ private var ttsIsSuccess: Boolean = false
+
+ override fun onCreate() {
+ super.onCreate()
+ textToSpeech = TextToSpeech(this, this)
+ upSpeechRate()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED)
+ clearTTS()
+ }
+
+ override fun onInit(status: Int) {
+ launch {
+ if (status == TextToSpeech.SUCCESS) {
+ val result = textToSpeech?.setLanguage(Locale.CHINA)
+ if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
+ toast(R.string.tts_fix)
+ IntentHelp.toTTSSetting(this@TTSReadAloudService)
+ stopSelf()
+ } else {
+ textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener())
+ ttsIsSuccess = true
+ playTTS()
+ }
+ } else {
+ toast(R.string.tts_init_failed)
+ }
+ }
+ }
+
+ override fun newReadAloud(dataKey: String?, play: Boolean) {
+ dataKey?.let {
+ textChapter = IntentDataHelp.getData(dataKey) as? TextChapter
+ textChapter?.let { textChapter ->
+ nowSpeak = 0
+ readAloudNumber = textChapter.getReadLength(pageIndex)
+ contentList.clear()
+ if (getPrefBoolean("readAloudByPage")) {
+ for (index in pageIndex..textChapter.lastIndex()) {
+ textChapter.page(index)?.text?.split("\n")?.let {
+ contentList.addAll(it)
+ }
+ }
+ } else {
+ contentList.addAll(textChapter.getUnRead(pageIndex).split("\n"))
+ }
+ if (play) playTTS()
+ } ?: stopSelf()
+ } ?: stopSelf()
+ }
+
+ @Suppress("DEPRECATION")
+ private fun playTTS() {
+ if (contentList.size < 1 || !ttsIsSuccess) {
+ return
+ }
+ if (requestFocus()) {
+ upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)
+ postEvent(Bus.ALOUD_STATE, Status.PLAY)
+ upNotification()
+ for (i in nowSpeak until contentList.size) {
+ if (i == 0) {
+ speak(contentList[i], TextToSpeech.QUEUE_FLUSH, AppConst.APP_TAG + i)
+ } else {
+ speak(contentList[i], TextToSpeech.QUEUE_ADD, AppConst.APP_TAG + i)
+ }
+ }
+ }
+ }
+
+ private fun speak(content: String, queueMode: Int, utteranceId: String) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ textToSpeech?.speak(content, queueMode, null, utteranceId)
+ } else {
+ @Suppress("DEPRECATION")
+ textToSpeech?.speak(
+ content,
+ queueMode,
+ hashMapOf(Pair(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId))
+ )
+ }
+ }
+
+ /**
+ * 更新朗读速度
+ */
+ override fun upSpeechRate(reset: Boolean) {
+ if (this.getPrefBoolean("ttsFollowSys", true)) {
+ if (reset) {
+ clearTTS()
+ textToSpeech = TextToSpeech(this, this)
+ }
+ } else {
+ textToSpeech?.setSpeechRate((this.getPrefInt("ttsSpeechRate", 5) + 5) / 10f)
+ }
+ }
+
+ /**
+ * 上一段
+ */
+ override fun prevP() {
+ if (nowSpeak > 0) {
+ textToSpeech?.stop()
+ nowSpeak--
+ readAloudNumber -= contentList[nowSpeak].length.minus(1)
+ playTTS()
+ }
+ }
+
+ /**
+ * 下一段
+ */
+ override fun nextP() {
+ if (nowSpeak < contentList.size - 1) {
+ textToSpeech?.stop()
+ readAloudNumber += contentList[nowSpeak].length.plus(1)
+ nowSpeak++
+ playTTS()
+ }
+ }
+
+ /**
+ * 暂停朗读
+ */
+ override fun pauseReadAloud(pause: Boolean) {
+ super.pauseReadAloud(pause)
+ textToSpeech?.stop()
+ upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED)
+ upNotification()
+ }
+
+ /**
+ * 恢复朗读
+ */
+ override fun resumeReadAloud() {
+ super.resumeReadAloud()
+ playTTS()
+ }
+
+ /**
+ * 朗读监听
+ */
+ private inner class TTSUtteranceListener : UtteranceProgressListener() {
+
+ override fun onStart(s: String) {
+ textChapter?.let {
+ if (readAloudNumber + 1 > it.getReadLength(pageIndex + 1)) {
+ pageIndex++
+ postEvent(Bus.TTS_TURN_PAGE, 1)
+ }
+ }
+ postEvent(Bus.TTS_START, readAloudNumber + 1)
+ }
+
+ override fun onDone(s: String) {
+ readAloudNumber += contentList[nowSpeak].length + 1
+ nowSpeak += 1
+ if (nowSpeak >= contentList.size) {
+ postEvent(Bus.TTS_TURN_PAGE, 2)
+ }
+ }
+
+ 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++
+ postEvent(Bus.TTS_TURN_PAGE, 1)
+ postEvent(Bus.TTS_START, readAloudNumber + start)
+ }
+ }
+ }
+
+ override fun onError(s: String) {
+ launch {
+ toast(s)
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/service/notification/ReadAloudNotification.kt b/app/src/main/java/io/legado/app/service/notification/ReadAloudNotification.kt
deleted file mode 100644
index d7e0c51ca..000000000
--- a/app/src/main/java/io/legado/app/service/notification/ReadAloudNotification.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-package io.legado.app.service.notification
-
-import android.graphics.BitmapFactory
-import androidx.core.app.NotificationCompat
-import io.legado.app.R
-import io.legado.app.constant.Action
-import io.legado.app.constant.AppConst
-import io.legado.app.help.PendingIntentHelp
-import io.legado.app.service.ReadAloudService
-
-object ReadAloudNotification {
-
- /**
- * 更新通知
- */
- fun upNotification(service: ReadAloudService) {
- var nTitle: String = when {
- service.pause -> service.getString(R.string.read_aloud_pause)
- ReadAloudService.timeMinute in 1..60 -> service.getString(
- R.string.read_aloud_timer,
- ReadAloudService.timeMinute
- )
- else -> service.getString(R.string.read_aloud_t)
- }
- nTitle += ": ${service.title}"
- var nSubtitle = service.subtitle
- if (service.subtitle.isEmpty())
- nSubtitle = service.getString(R.string.read_aloud_s)
- val builder = NotificationCompat.Builder(service, AppConst.channelIdReadAloud)
- .setSmallIcon(R.drawable.ic_volume_up)
- .setLargeIcon(BitmapFactory.decodeResource(service.resources, R.drawable.icon_read_book))
- .setOngoing(true)
- .setContentTitle(nTitle)
- .setContentText(nSubtitle)
- .setContentIntent(PendingIntentHelp.readBookActivityPendingIntent(service))
- if (service.pause) {
- builder.addAction(
- R.drawable.ic_play_24dp,
- service.getString(R.string.resume),
- PendingIntentHelp.aloudServicePendingIntent(service, Action.resume)
- )
- } else {
- builder.addAction(
- R.drawable.ic_pause_24dp,
- service.getString(R.string.pause),
- PendingIntentHelp.aloudServicePendingIntent(service, Action.pause)
- )
- }
- builder.addAction(
- R.drawable.ic_stop_black_24dp,
- service.getString(R.string.stop),
- PendingIntentHelp.aloudServicePendingIntent(service, Action.stop)
- )
- builder.addAction(
- R.drawable.ic_time_add_24dp,
- service.getString(R.string.set_timer),
- PendingIntentHelp.aloudServicePendingIntent(service, Action.addTimer)
- )
- builder.setStyle(
- androidx.media.app.NotificationCompat.MediaStyle()
- .setMediaSession(service.mediaSessionCompat?.sessionToken)
- .setShowActionsInCompactView(0, 1, 2)
- )
- builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
- val notification = builder.build()
- service.startForeground(112201, notification)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt
index da9028d0e..a6da939f5 100644
--- a/app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt
+++ b/app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt
@@ -25,7 +25,7 @@ import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.okButton
import io.legado.app.receiver.TimeElectricityReceiver
-import io.legado.app.service.ReadAloudService
+import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.changesource.ChangeSourceDialog
import io.legado.app.ui.chapterlist.ChapterListActivity
import io.legado.app.ui.readbook.config.*
@@ -190,7 +190,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_rea
when (keyCode) {
KeyEvent.KEYCODE_BACK -> {
if (readAloudStatus == Status.PLAY) {
- ReadAloudService.pause(this)
+ BaseReadAloudService.pause(this)
toast(R.string.read_aloud_pause)
return true
}
@@ -444,14 +444,14 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_rea
* 朗读按钮
*/
private fun onClickReadAloud() {
- if (!ReadAloudService.isRun) {
+ if (!BaseReadAloudService.isRun) {
readAloudStatus = Status.STOP
SystemUtils.ignoreBatteryOptimization(this)
}
when (readAloudStatus) {
Status.STOP -> readAloud()
- Status.PLAY -> ReadAloudService.pause(this)
- Status.PAUSE -> ReadAloudService.resume(this)
+ Status.PLAY -> BaseReadAloudService.pause(this)
+ Status.PAUSE -> BaseReadAloudService.resume(this)
}
}
@@ -464,7 +464,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_rea
if (book != null && textChapter != null) {
val key = System.currentTimeMillis().toString()
IntentDataHelp.putData(key, textChapter)
- ReadAloudService.play(
+ BaseReadAloudService.play(
this, book.name, textChapter.title,
viewModel.durPageIndex, key, play
)
@@ -569,7 +569,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_rea
page_view.upContent()
viewModel.saveRead()
}
- 2 -> if (!moveToNextChapter()) ReadAloudService.stop(this)
+ 2 -> if (!moveToNextChapter()) BaseReadAloudService.stop(this)
-1 -> {
if (viewModel.durPageIndex > 0) {
viewModel.durPageIndex = viewModel.durPageIndex - 1
diff --git a/app/src/main/java/io/legado/app/ui/readbook/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/readbook/ReadBookViewModel.kt
index fe1a00201..14ecccba4 100644
--- a/app/src/main/java/io/legado/app/ui/readbook/ReadBookViewModel.kt
+++ b/app/src/main/java/io/legado/app/ui/readbook/ReadBookViewModel.kt
@@ -11,7 +11,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp
import io.legado.app.model.WebBook
-import io.legado.app.service.ReadAloudService
+import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.widget.page.TextChapter
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
@@ -332,7 +332,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
override fun onCleared() {
super.onCleared()
- ReadAloudService.stop(context)
+ BaseReadAloudService.stop(context)
}
interface CallBack {
diff --git a/app/src/main/java/io/legado/app/ui/readbook/config/ReadAloudDialog.kt b/app/src/main/java/io/legado/app/ui/readbook/config/ReadAloudDialog.kt
index 686c0e7b5..846d6ff15 100644
--- a/app/src/main/java/io/legado/app/ui/readbook/config/ReadAloudDialog.kt
+++ b/app/src/main/java/io/legado/app/ui/readbook/config/ReadAloudDialog.kt
@@ -11,7 +11,7 @@ import androidx.fragment.app.DialogFragment
import io.legado.app.R
import io.legado.app.constant.Bus
import io.legado.app.constant.Status
-import io.legado.app.service.ReadAloudService
+import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.readbook.Help
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_read_aloud.*
@@ -61,8 +61,9 @@ class ReadAloudDialog : DialogFragment() {
callBack?.readAloudStatus?.let {
upPlayState(it)
}
- seek_timer.progress = ReadAloudService.timeMinute
- tv_timer.text = requireContext().getString(R.string.timer_m, ReadAloudService.timeMinute)
+ seek_timer.progress = BaseReadAloudService.timeMinute
+ tv_timer.text =
+ requireContext().getString(R.string.timer_m, BaseReadAloudService.timeMinute)
cb_by_page.isChecked = requireContext().getPrefBoolean("readAloudByPage")
cb_tts_follow_sys.isChecked = requireContext().getPrefBoolean("ttsFollowSys", true)
seek_tts_SpeechRate.isEnabled = !cb_tts_follow_sys.isChecked
@@ -103,7 +104,7 @@ class ReadAloudDialog : DialogFragment() {
override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit
override fun onStopTrackingTouch(seekBar: SeekBar?) {
- ReadAloudService.setTimer(requireContext(), seek_timer.progress)
+ BaseReadAloudService.setTimer(requireContext(), seek_timer.progress)
}
})
}
@@ -111,11 +112,11 @@ class ReadAloudDialog : DialogFragment() {
private fun initOnClick() {
iv_menu.onClick { callBack?.showMenu(); dismiss() }
iv_menu.onLongClick { callBack?.openChapterList(); true }
- iv_stop.onClick { ReadAloudService.stop(requireContext()); dismiss() }
+ iv_stop.onClick { BaseReadAloudService.stop(requireContext()); dismiss() }
iv_play_pause.onClick { postEvent(Bus.READ_ALOUD_BUTTON, true) }
- iv_play_prev.onClick { ReadAloudService.prevParagraph(requireContext()) }
+ iv_play_prev.onClick { BaseReadAloudService.prevParagraph(requireContext()) }
iv_play_prev.onLongClick { postEvent(Bus.TTS_TURN_PAGE, -2); true }
- iv_play_next.onClick { ReadAloudService.nextParagraph(requireContext()) }
+ iv_play_next.onClick { BaseReadAloudService.nextParagraph(requireContext()) }
iv_play_next.onLongClick { postEvent(Bus.TTS_TURN_PAGE, 2); true }
}
@@ -128,10 +129,10 @@ class ReadAloudDialog : DialogFragment() {
}
private fun upTtsSpeechRate() {
- ReadAloudService.upTtsSpeechRate(requireContext())
+ BaseReadAloudService.upTtsSpeechRate(requireContext())
if (callBack?.readAloudStatus == Status.PLAY) {
- ReadAloudService.pause(requireContext())
- ReadAloudService.resume(requireContext())
+ BaseReadAloudService.pause(requireContext())
+ BaseReadAloudService.resume(requireContext())
}
}