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