优化
This commit is contained in:
@@ -146,6 +146,7 @@ object PreferKey {
|
|||||||
const val recordHeapDump = "recordHeapDump"
|
const val recordHeapDump = "recordHeapDump"
|
||||||
const val optimizeRender = "optimizeRender"
|
const val optimizeRender = "optimizeRender"
|
||||||
const val updateToVariant = "updateToVariant"
|
const val updateToVariant = "updateToVariant"
|
||||||
|
const val streamReadAloudAudio = "streamReadAloudAudio"
|
||||||
|
|
||||||
const val cPrimary = "colorPrimary"
|
const val cPrimary = "colorPrimary"
|
||||||
const val cAccent = "colorAccent"
|
const val cAccent = "colorAccent"
|
||||||
|
|||||||
@@ -473,6 +473,8 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
|
|
||||||
val updateToVariant get() = appCtx.getPrefString(PreferKey.updateToVariant, "default_version")
|
val updateToVariant get() = appCtx.getPrefString(PreferKey.updateToVariant, "default_version")
|
||||||
|
|
||||||
|
val streamReadAloudAudio get() = appCtx.getPrefBoolean(PreferKey.streamReadAloudAudio, false)
|
||||||
|
|
||||||
val doublePageHorizontal: String?
|
val doublePageHorizontal: String?
|
||||||
get() = appCtx.getPrefString(PreferKey.doublePageHorizontal)
|
get() = appCtx.getPrefString(PreferKey.doublePageHorizontal)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package io.legado.app.help.exoplayer
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.datasource.BaseDataSource
|
||||||
|
import androidx.media3.datasource.DataSpec
|
||||||
|
import java.io.EOFException
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
class InputStreamDataSource(private val supplier: () -> InputStream) : BaseDataSource(false) {
|
||||||
|
private var dataSpec: DataSpec? = null
|
||||||
|
private var bytesRemaining: Long = 0
|
||||||
|
private var opened = false
|
||||||
|
private val inputStream by lazy {
|
||||||
|
supplier.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun open(dataSpec: DataSpec): Long {
|
||||||
|
this.dataSpec = dataSpec
|
||||||
|
transferInitializing(dataSpec)
|
||||||
|
|
||||||
|
inputStream.skip(dataSpec.position)
|
||||||
|
|
||||||
|
if (dataSpec.length == C.LENGTH_UNSET.toLong()) {
|
||||||
|
bytesRemaining = inputStream.available().toLong()
|
||||||
|
if (bytesRemaining == 0L) bytesRemaining = C.LENGTH_UNSET.toLong()
|
||||||
|
} else {
|
||||||
|
bytesRemaining = dataSpec.length
|
||||||
|
}
|
||||||
|
|
||||||
|
opened = true
|
||||||
|
transferStarted(dataSpec)
|
||||||
|
return bytesRemaining
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getUri(): Uri? = dataSpec?.uri
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun read(buffer: ByteArray, offset: Int, readLength: Int): Int {
|
||||||
|
if (readLength == 0) {
|
||||||
|
return 0
|
||||||
|
} else if (bytesRemaining == 0L) {
|
||||||
|
return C.RESULT_END_OF_INPUT
|
||||||
|
}
|
||||||
|
|
||||||
|
val bytesToRead =
|
||||||
|
if (bytesRemaining == C.LENGTH_UNSET.toLong()) readLength
|
||||||
|
else min(bytesRemaining, readLength.toLong()).toInt()
|
||||||
|
|
||||||
|
val bytesRead = inputStream.read(buffer, offset, bytesToRead)
|
||||||
|
|
||||||
|
if (bytesRead == -1) {
|
||||||
|
if (bytesRemaining != C.LENGTH_UNSET.toLong()) {
|
||||||
|
// End of stream reached having not read sufficient data.
|
||||||
|
throw EOFException()
|
||||||
|
}
|
||||||
|
return C.RESULT_END_OF_INPUT
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesRemaining != C.LENGTH_UNSET.toLong()) {
|
||||||
|
bytesRemaining -= bytesRead.toLong()
|
||||||
|
bytesTransferred(bytesRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesRead
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun close() {
|
||||||
|
if (!opened) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
inputStream.close()
|
||||||
|
} finally {
|
||||||
|
opened = false
|
||||||
|
transferEnded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -113,7 +113,8 @@ abstract class BaseReadAloudService : BaseService(),
|
|||||||
var pageChanged = false
|
var pageChanged = false
|
||||||
private var toLast = false
|
private var toLast = false
|
||||||
var paragraphStartPos = 0
|
var paragraphStartPos = 0
|
||||||
private var readAloudByPage = false
|
var readAloudByPage = false
|
||||||
|
private set
|
||||||
|
|
||||||
private val broadcastReceiver = object : BroadcastReceiver() {
|
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
package io.legado.app.service
|
package io.legado.app.service
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.media3.common.C
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.database.StandaloneDatabaseProvider
|
||||||
|
import androidx.media3.datasource.DataSource
|
||||||
|
import androidx.media3.datasource.cache.CacheDataSink
|
||||||
|
import androidx.media3.datasource.cache.CacheDataSource
|
||||||
|
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
|
||||||
|
import androidx.media3.datasource.cache.SimpleCache
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.offline.DefaultDownloaderFactory
|
||||||
|
import androidx.media3.exoplayer.offline.DownloadRequest
|
||||||
|
import androidx.media3.exoplayer.offline.Downloader
|
||||||
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
|
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy
|
||||||
|
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy
|
||||||
import com.script.ScriptException
|
import com.script.ScriptException
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
@@ -15,6 +30,8 @@ import io.legado.app.data.entities.HttpTTS
|
|||||||
import io.legado.app.exception.NoStackTraceException
|
import io.legado.app.exception.NoStackTraceException
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
|
import io.legado.app.help.exoplayer.InputStreamDataSource
|
||||||
|
import io.legado.app.help.http.okHttpClient
|
||||||
import io.legado.app.model.ReadAloud
|
import io.legado.app.model.ReadAloud
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
||||||
@@ -26,13 +43,16 @@ import io.legado.app.utils.toastOnUi
|
|||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers.Main
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.mozilla.javascript.WrappedException
|
import org.mozilla.javascript.WrappedException
|
||||||
|
import splitties.init.appCtx
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
@@ -42,6 +62,7 @@ import kotlin.coroutines.coroutineContext
|
|||||||
/**
|
/**
|
||||||
* 在线朗读
|
* 在线朗读
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
class HttpReadAloudService : BaseReadAloudService(),
|
class HttpReadAloudService : BaseReadAloudService(),
|
||||||
Player.Listener {
|
Player.Listener {
|
||||||
|
|
||||||
@@ -51,6 +72,24 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
private val ttsFolderPath: String by lazy {
|
private val ttsFolderPath: String by lazy {
|
||||||
cacheDir.absolutePath + File.separator + "httpTTS" + File.separator
|
cacheDir.absolutePath + File.separator + "httpTTS" + File.separator
|
||||||
}
|
}
|
||||||
|
private val cache by lazy {
|
||||||
|
SimpleCache(
|
||||||
|
File(cacheDir, "httpTTS_cache"),
|
||||||
|
LeastRecentlyUsedCacheEvictor((50 * 1024 * 1024).toLong()),
|
||||||
|
StandaloneDatabaseProvider(appCtx)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val cacheDataSinkFactory by lazy {
|
||||||
|
CacheDataSink.Factory()
|
||||||
|
.setCache(cache)
|
||||||
|
}
|
||||||
|
private val loadErrorHandlingPolicy by lazy {
|
||||||
|
object : DefaultLoadErrorHandlingPolicy(0) {
|
||||||
|
override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo): Long {
|
||||||
|
return C.TIME_UNSET
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private var speechRate: Int = AppConfig.speechRatePlay + 5
|
private var speechRate: Int = AppConfig.speechRatePlay + 5
|
||||||
private var downloadTask: Coroutine<*>? = null
|
private var downloadTask: Coroutine<*>? = null
|
||||||
private var playIndexJob: Job? = null
|
private var playIndexJob: Job? = null
|
||||||
@@ -67,6 +106,7 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
downloadTask?.cancel()
|
downloadTask?.cancel()
|
||||||
exoPlayer.release()
|
exoPlayer.release()
|
||||||
|
cache.release()
|
||||||
Coroutine.async {
|
Coroutine.async {
|
||||||
removeCacheFile()
|
removeCacheFile()
|
||||||
}
|
}
|
||||||
@@ -81,7 +121,11 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
ReadBook.readAloud()
|
ReadBook.readAloud()
|
||||||
} else {
|
} else {
|
||||||
super.play()
|
super.play()
|
||||||
downloadAndPlayAudios()
|
if (AppConfig.streamReadAloudAudio) {
|
||||||
|
downloadAndPlayAudiosStream()
|
||||||
|
} else {
|
||||||
|
downloadAndPlayAudios()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,12 +193,150 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
preDownloadAudios(httpTts)
|
||||||
}
|
}
|
||||||
}.onError {
|
}.onError {
|
||||||
AppLog.put("朗读下载出错\n${it.localizedMessage}", it, true)
|
AppLog.put("朗读下载出错\n${it.localizedMessage}", it, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun preDownloadAudios(httpTts: HttpTTS) {
|
||||||
|
val textChapter = ReadBook.nextTextChapter ?: return
|
||||||
|
val contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0, 1)
|
||||||
|
.splitToSequence("\n")
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.take(10)
|
||||||
|
.toList()
|
||||||
|
contentList.forEach { content ->
|
||||||
|
coroutineContext.ensureActive()
|
||||||
|
val fileName = md5SpeakFileName(content)
|
||||||
|
val speakText = content.replace(AppPattern.notReadAloudRegex, "")
|
||||||
|
if (speakText.isEmpty()) {
|
||||||
|
createSilentSound(fileName)
|
||||||
|
} else if (!hasSpeakFile(fileName)) {
|
||||||
|
runCatching {
|
||||||
|
val inputStream = getSpeakStream(httpTts, speakText)
|
||||||
|
if (inputStream != null) {
|
||||||
|
createSpeakFile(fileName, inputStream)
|
||||||
|
} else {
|
||||||
|
createSilentSound(fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun downloadAndPlayAudiosStream() {
|
||||||
|
exoPlayer.clearMediaItems()
|
||||||
|
downloadTask?.cancel()
|
||||||
|
downloadTask = execute {
|
||||||
|
downloadTaskActiveLock.withLock {
|
||||||
|
ensureActive()
|
||||||
|
val httpTts = ReadAloud.httpTTS ?: throw NoStackTraceException("tts is null")
|
||||||
|
val downloaderChannel = Channel<Downloader>()
|
||||||
|
launch {
|
||||||
|
for (downloader in downloaderChannel) {
|
||||||
|
downloader.download(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentList.forEachIndexed { index, content ->
|
||||||
|
ensureActive()
|
||||||
|
if (index < nowSpeak) return@forEachIndexed
|
||||||
|
var text = content
|
||||||
|
if (paragraphStartPos > 0 && index == nowSpeak) {
|
||||||
|
text = text.substring(paragraphStartPos)
|
||||||
|
}
|
||||||
|
val speakText = text.replace(AppPattern.notReadAloudRegex, "")
|
||||||
|
if (speakText.isEmpty()) {
|
||||||
|
AppLog.put("阅读段落内容为空,使用无声音频代替。\n朗读文本:$speakText")
|
||||||
|
}
|
||||||
|
val fileName = md5SpeakFileName(text)
|
||||||
|
val dataSourceFactory = createDataSourceFactory(httpTts, speakText)
|
||||||
|
val downloader = createDownloader(dataSourceFactory, fileName)
|
||||||
|
downloaderChannel.send(downloader)
|
||||||
|
val mediaSource = createMediaSource(dataSourceFactory, fileName)
|
||||||
|
launch(Main) {
|
||||||
|
if (exoPlayer.playbackState == Player.STATE_ENDED) {
|
||||||
|
exoPlayer.stop()
|
||||||
|
exoPlayer.clearMediaItems()
|
||||||
|
}
|
||||||
|
exoPlayer.addMediaSource(mediaSource)
|
||||||
|
if (!exoPlayer.isPlaying) {
|
||||||
|
exoPlayer.playWhenReady = !pause
|
||||||
|
exoPlayer.prepare()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preDownloadAudiosStream(httpTts, downloaderChannel)
|
||||||
|
}
|
||||||
|
}.onError {
|
||||||
|
AppLog.put("朗读下载出错\n${it.localizedMessage}", it, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun preDownloadAudiosStream(
|
||||||
|
httpTts: HttpTTS,
|
||||||
|
downloaderChannel: Channel<Downloader>
|
||||||
|
) {
|
||||||
|
val textChapter = ReadBook.nextTextChapter ?: return
|
||||||
|
val contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0, 1)
|
||||||
|
.splitToSequence("\n")
|
||||||
|
.filter { it.isNotEmpty() }
|
||||||
|
.take(10)
|
||||||
|
.toList()
|
||||||
|
contentList.forEach { content ->
|
||||||
|
coroutineContext.ensureActive()
|
||||||
|
val fileName = md5SpeakFileName(content)
|
||||||
|
val speakText = content.replace(AppPattern.notReadAloudRegex, "")
|
||||||
|
val dataSourceFactory = createDataSourceFactory(httpTts, speakText)
|
||||||
|
val downloader = createDownloader(dataSourceFactory, fileName)
|
||||||
|
downloaderChannel.send(downloader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDataSourceFactory(
|
||||||
|
httpTts: HttpTTS,
|
||||||
|
speakText: String
|
||||||
|
): CacheDataSource.Factory {
|
||||||
|
val upstreamFactory = DataSource.Factory {
|
||||||
|
InputStreamDataSource {
|
||||||
|
if (speakText.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
kotlin.runCatching {
|
||||||
|
runBlocking {
|
||||||
|
getSpeakStream(httpTts, speakText)
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
when (it) {
|
||||||
|
is InterruptedException -> Unit
|
||||||
|
else -> pauseReadAloud()
|
||||||
|
}
|
||||||
|
}.getOrThrow()
|
||||||
|
} ?: resources.openRawResource(R.raw.silent_sound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val factory = CacheDataSource.Factory()
|
||||||
|
.setCache(cache)
|
||||||
|
.setUpstreamDataSourceFactory(upstreamFactory)
|
||||||
|
.setCacheWriteDataSinkFactory(cacheDataSinkFactory)
|
||||||
|
return factory
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDownloader(factory: CacheDataSource.Factory, fileName: String): Downloader {
|
||||||
|
val uri = Uri.parse(fileName)
|
||||||
|
val request = DownloadRequest.Builder(fileName, uri).build()
|
||||||
|
return DefaultDownloaderFactory(factory, okHttpClient.dispatcher.executorService)
|
||||||
|
.createDownloader(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createMediaSource(factory: DataSource.Factory, fileName: String): MediaSource {
|
||||||
|
return DefaultMediaSourceFactory(this)
|
||||||
|
.setDataSourceFactory(factory)
|
||||||
|
.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)
|
||||||
|
.createMediaSource(MediaItem.fromUri(fileName))
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getSpeakStream(
|
private suspend fun getSpeakStream(
|
||||||
httpTts: HttpTTS,
|
httpTts: HttpTTS,
|
||||||
speakText: String
|
speakText: String
|
||||||
@@ -329,7 +511,11 @@ class HttpReadAloudService : BaseReadAloudService(),
|
|||||||
downloadTask?.cancel()
|
downloadTask?.cancel()
|
||||||
exoPlayer.stop()
|
exoPlayer.stop()
|
||||||
speechRate = AppConfig.speechRatePlay + 5
|
speechRate = AppConfig.speechRatePlay + 5
|
||||||
downloadAndPlayAudios()
|
if (AppConfig.streamReadAloudAudio) {
|
||||||
|
downloadAndPlayAudiosStream()
|
||||||
|
} else {
|
||||||
|
downloadAndPlayAudios()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ import io.legado.app.lib.theme.backgroundColor
|
|||||||
import io.legado.app.lib.theme.primaryColor
|
import io.legado.app.lib.theme.primaryColor
|
||||||
import io.legado.app.model.ReadAloud
|
import io.legado.app.model.ReadAloud
|
||||||
import io.legado.app.service.BaseReadAloudService
|
import io.legado.app.service.BaseReadAloudService
|
||||||
import io.legado.app.utils.*
|
import io.legado.app.utils.GSON
|
||||||
|
import io.legado.app.utils.StringUtils
|
||||||
|
import io.legado.app.utils.fromJsonObject
|
||||||
|
import io.legado.app.utils.postEvent
|
||||||
|
import io.legado.app.utils.setEdgeEffectColor
|
||||||
|
import io.legado.app.utils.setLayout
|
||||||
|
import io.legado.app.utils.showDialogFragment
|
||||||
|
|
||||||
class ReadAloudConfigDialog : DialogFragment() {
|
class ReadAloudConfigDialog : DialogFragment() {
|
||||||
private val readAloudPreferTag = "readAloudPreferTag"
|
private val readAloudPreferTag = "readAloudPreferTag"
|
||||||
@@ -103,7 +109,7 @@ class ReadAloudConfigDialog : DialogFragment() {
|
|||||||
key: String?
|
key: String?
|
||||||
) {
|
) {
|
||||||
when (key) {
|
when (key) {
|
||||||
PreferKey.readAloudByPage -> {
|
PreferKey.readAloudByPage, PreferKey.streamReadAloudAudio -> {
|
||||||
if (BaseReadAloudService.isRun) {
|
if (BaseReadAloudService.isRun) {
|
||||||
postEvent(EventBus.MEDIA_BUTTON, false)
|
postEvent(EventBus.MEDIA_BUTTON, false)
|
||||||
}
|
}
|
||||||
@@ -117,6 +123,7 @@ class ReadAloudConfigDialog : DialogFragment() {
|
|||||||
val index = preference.findIndexOfValue(value)
|
val index = preference.findIndexOfValue(value)
|
||||||
preference.summary = if (index >= 0) preference.entries[index] else null
|
preference.summary = if (index >= 0) preference.entries[index] else null
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
preference?.summary = value
|
preference?.summary = value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,10 +177,15 @@ data class TextChapter(
|
|||||||
* @param pageSplit 是否分页
|
* @param pageSplit 是否分页
|
||||||
* @param startPos 从当前页什么地方开始朗读
|
* @param startPos 从当前页什么地方开始朗读
|
||||||
*/
|
*/
|
||||||
fun getNeedReadAloud(pageIndex: Int, pageSplit: Boolean, startPos: Int): String {
|
fun getNeedReadAloud(
|
||||||
|
pageIndex: Int,
|
||||||
|
pageSplit: Boolean,
|
||||||
|
startPos: Int,
|
||||||
|
pageEndIndex: Int = pages.lastIndex
|
||||||
|
): String {
|
||||||
val stringBuilder = StringBuilder()
|
val stringBuilder = StringBuilder()
|
||||||
if (pages.isNotEmpty()) {
|
if (pages.isNotEmpty()) {
|
||||||
for (index in pageIndex..pages.lastIndex) {
|
for (index in pageIndex..min(pageEndIndex, pages.lastIndex)) {
|
||||||
stringBuilder.append(pages[index].text)
|
stringBuilder.append(pages[index].text)
|
||||||
if (pageSplit && !stringBuilder.endsWith("\n")) {
|
if (pageSplit && !stringBuilder.endsWith("\n")) {
|
||||||
stringBuilder.append("\n")
|
stringBuilder.append("\n")
|
||||||
|
|||||||
@@ -1159,4 +1159,6 @@
|
|||||||
<string name="official_version">正式版</string>
|
<string name="official_version">正式版</string>
|
||||||
<string name="beta_release_version">测试版</string>
|
<string name="beta_release_version">测试版</string>
|
||||||
<string name="beta_releaseA_version">共存版</string>
|
<string name="beta_releaseA_version">共存版</string>
|
||||||
|
<string name="stream_read_aloud_audio">流式播放音频</string>
|
||||||
|
<string name="stream_read_aloud_audio_summary">即边下边播,网络不好时播放会断断续续,仅TTS源有效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1162,4 +1162,6 @@
|
|||||||
<string name="official_version">正式版</string>
|
<string name="official_version">正式版</string>
|
||||||
<string name="beta_release_version">测试版</string>
|
<string name="beta_release_version">测试版</string>
|
||||||
<string name="beta_releaseA_version">共存版</string>
|
<string name="beta_releaseA_version">共存版</string>
|
||||||
|
<string name="stream_read_aloud_audio">流式播放音频</string>
|
||||||
|
<string name="stream_read_aloud_audio_summary">即边下边播,网络不好时播放会断断续续,仅TTS源有效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1162,4 +1162,6 @@
|
|||||||
<string name="official_version">正式版</string>
|
<string name="official_version">正式版</string>
|
||||||
<string name="beta_release_version">测试版</string>
|
<string name="beta_release_version">测试版</string>
|
||||||
<string name="beta_releaseA_version">共存版</string>
|
<string name="beta_releaseA_version">共存版</string>
|
||||||
|
<string name="stream_read_aloud_audio">流式播放音频</string>
|
||||||
|
<string name="stream_read_aloud_audio_summary">即边下边播,网络不好时播放会断断续续,仅TTS源有效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1158,4 +1158,6 @@ Còn </string>
|
|||||||
<string name="official_version">正式版</string>
|
<string name="official_version">正式版</string>
|
||||||
<string name="beta_release_version">测试版</string>
|
<string name="beta_release_version">测试版</string>
|
||||||
<string name="beta_releaseA_version">共存版</string>
|
<string name="beta_releaseA_version">共存版</string>
|
||||||
|
<string name="stream_read_aloud_audio">流式播放音频</string>
|
||||||
|
<string name="stream_read_aloud_audio_summary">即边下边播,网络不好时播放会断断续续,仅TTS源有效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1159,4 +1159,6 @@
|
|||||||
<string name="official_version">正式版</string>
|
<string name="official_version">正式版</string>
|
||||||
<string name="beta_release_version">测试版</string>
|
<string name="beta_release_version">测试版</string>
|
||||||
<string name="beta_releaseA_version">共存版</string>
|
<string name="beta_releaseA_version">共存版</string>
|
||||||
|
<string name="stream_read_aloud_audio">流式播放音频</string>
|
||||||
|
<string name="stream_read_aloud_audio_summary">即边下边播,网络不好时播放会断断续续,仅TTS源有效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1161,4 +1161,6 @@
|
|||||||
<string name="official_version">正式版</string>
|
<string name="official_version">正式版</string>
|
||||||
<string name="beta_release_version">测试版</string>
|
<string name="beta_release_version">测试版</string>
|
||||||
<string name="beta_releaseA_version">共存版</string>
|
<string name="beta_releaseA_version">共存版</string>
|
||||||
|
<string name="stream_read_aloud_audio">流式播放音频</string>
|
||||||
|
<string name="stream_read_aloud_audio_summary">即边下边播,网络不好时播放会断断续续,仅TTS源有效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1161,4 +1161,6 @@
|
|||||||
<string name="official_version">正式版</string>
|
<string name="official_version">正式版</string>
|
||||||
<string name="beta_release_version">测试版</string>
|
<string name="beta_release_version">测试版</string>
|
||||||
<string name="beta_releaseA_version">共存版</string>
|
<string name="beta_releaseA_version">共存版</string>
|
||||||
|
<string name="stream_read_aloud_audio">流式播放音频</string>
|
||||||
|
<string name="stream_read_aloud_audio_summary">即边下边播,网络不好时播放会断断续续,仅TTS源有效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1162,4 +1162,6 @@
|
|||||||
<string name="official_version">正式版</string>
|
<string name="official_version">正式版</string>
|
||||||
<string name="beta_release_version">测试版</string>
|
<string name="beta_release_version">测试版</string>
|
||||||
<string name="beta_releaseA_version">共存版</string>
|
<string name="beta_releaseA_version">共存版</string>
|
||||||
|
<string name="stream_read_aloud_audio">流式播放音频</string>
|
||||||
|
<string name="stream_read_aloud_audio_summary">即边下边播,网络不好时播放会断断续续,仅TTS源有效</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -35,6 +35,13 @@
|
|||||||
android:key="readAloudByPage"
|
android:key="readAloudByPage"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
<io.legado.app.lib.prefs.SwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:title="@string/stream_read_aloud_audio"
|
||||||
|
android:summary="@string/stream_read_aloud_audio_summary"
|
||||||
|
android:key="streamReadAloudAudio"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<io.legado.app.lib.prefs.Preference
|
<io.legado.app.lib.prefs.Preference
|
||||||
android:title="@string/speak_engine"
|
android:title="@string/speak_engine"
|
||||||
android:summary="TTS"
|
android:summary="TTS"
|
||||||
|
|||||||
Reference in New Issue
Block a user