pull/34/head
kunfei 5 years ago
parent 7c38657397
commit 7f6302d80d
  1. 2
      app/src/main/AndroidManifest.xml
  2. 4
      app/src/main/java/io/legado/app/help/CrashHandler.kt
  3. 4
      app/src/main/java/io/legado/app/help/PendingIntentHelp.kt
  4. 368
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  5. 218
      app/src/main/java/io/legado/app/service/TTSReadAloudService.kt
  6. 68
      app/src/main/java/io/legado/app/service/notification/ReadAloudNotification.kt
  7. 14
      app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt
  8. 4
      app/src/main/java/io/legado/app/ui/readbook/ReadBookViewModel.kt
  9. 21
      app/src/main/java/io/legado/app/ui/readbook/config/ReadAloudDialog.kt

@ -68,7 +68,7 @@
<service android:name=".service.CheckSourceService" />
<service android:name=".service.DownloadService" />
<service android:name=".service.ReadAloudService" />
<service android:name=".service.TTSReadAloudService" />
<service android:name=".service.UpdateService" />
<service android:name=".service.WebService" />

@ -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)
}

@ -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)
}

@ -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<String>()
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<String>()
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()
}
} else {
textToSpeech?.setSpeechRate((this.getPrefInt("ttsSpeechRate", 5) + 5) / 10f)
AudioManager.AUDIOFOCUS_LOSS -> {
// 永久丢失焦点除非重新主动获取,这种情况是被其他播放器抢去了焦点, 为避免与其他播放器混音,可将音乐暂停
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
// 暂时丢失焦点,这种情况是被其他应用申请了短暂的焦点,可压低后台音量
if (!pause) pauseReadAloud(false)
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
// 短暂丢失焦点,这种情况是被其他应用申请了短暂的焦点希望其他声音能压低音量(或者关闭声音)凸显这个声音(比如短信提示音),
}
/**
* 上一段
*/
private fun prevP() {
if (nowSpeak > 0) {
textToSpeech?.stop()
nowSpeak--
readAloudNumber -= contentList[nowSpeak].length.minus(1)
playTTS()
}
}
/**
* 下一段
* @return 音频焦点
*/
private fun nextP() {
if (nowSpeak < contentList.size - 1) {
textToSpeech?.stop()
readAloudNumber += contentList[nowSpeak].length.plus(1)
nowSpeak++
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
}
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)
}
}
}
}

@ -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)
}
}
}
}

@ -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)
}
}

@ -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<ReadBookViewModel>(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<ReadBookViewModel>(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<ReadBookViewModel>(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<ReadBookViewModel>(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

@ -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 {

@ -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())
}
}

Loading…
Cancel
Save