pull/34/head
parent
7c38657397
commit
7f6302d80d
@ -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) |
|
||||||
} |
|
||||||
} |
|
Loading…
Reference in new issue