mirror of
https://github.com/gedoor/legado.git
synced 2024-07-19 01:17:25 +08:00
优化
This commit is contained in:
parent
4e044df3b2
commit
788a5b5d08
@ -177,6 +177,20 @@ object ReadBook : CoroutineScope by MainScope() {
|
||||
return hasNextPage
|
||||
}
|
||||
|
||||
fun moveToPrevPage(): Boolean {
|
||||
var hasPrevPage = false
|
||||
curTextChapter?.let {
|
||||
val prevPagePos = it.getPrevPageLength(durChapterPos)
|
||||
if (prevPagePos >= 0) {
|
||||
hasPrevPage = true
|
||||
durChapterPos = prevPagePos
|
||||
callBack?.upContent()
|
||||
saveRead()
|
||||
}
|
||||
}
|
||||
return hasPrevPage
|
||||
}
|
||||
|
||||
fun moveToNextChapter(upContent: Boolean): Boolean {
|
||||
if (durChapterIndex < chapterSize - 1) {
|
||||
durChapterPos = 0
|
||||
|
@ -86,6 +86,9 @@ abstract class BaseReadAloudService : BaseService(),
|
||||
BitmapFactory.decodeResource(appCtx.resources, R.drawable.icon_read_book)
|
||||
var pageChanged = false
|
||||
private var ttsProgress = 0
|
||||
private var toLast = false
|
||||
var paragraphStartPos = 0
|
||||
private var readAloudByPage = false
|
||||
|
||||
private val broadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
@ -164,19 +167,40 @@ abstract class BaseReadAloudService : BaseService(),
|
||||
}
|
||||
|
||||
private fun newReadAloud(play: Boolean, pageIndex: Int, startPos: Int) {
|
||||
lifecycleScope.launch(IO) {
|
||||
execute(executeContext = IO) {
|
||||
this@BaseReadAloudService.pageIndex = pageIndex
|
||||
textChapter = ReadBook.curTextChapter
|
||||
val textChapter = textChapter ?: return@launch
|
||||
nowSpeak = 0
|
||||
val textChapter = textChapter ?: return@execute
|
||||
readAloudNumber = textChapter.getReadLength(pageIndex) + startPos
|
||||
val readAloudByPage = getPrefBoolean(PreferKey.readAloudByPage)
|
||||
contentList = textChapter.getNeedReadAloud(pageIndex, readAloudByPage, startPos)
|
||||
readAloudByPage = getPrefBoolean(PreferKey.readAloudByPage)
|
||||
contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0)
|
||||
.split("\n")
|
||||
.filter { it.isNotEmpty() }
|
||||
var pos = startPos
|
||||
val page = textChapter.getPage(pageIndex)!!
|
||||
if (pos > 0) {
|
||||
for (paragraph in page.paragraphs) {
|
||||
val tmp = pos - paragraph.length - 1
|
||||
if (tmp < 0) break
|
||||
pos = tmp
|
||||
}
|
||||
}
|
||||
nowSpeak = textChapter.getParagraphNum(readAloudNumber + 1, readAloudByPage) - 1
|
||||
if (!readAloudByPage && pos == 0) {
|
||||
pos = page.lines.first().chapterPosition -
|
||||
textChapter.paragraphs[nowSpeak].chapterPosition
|
||||
}
|
||||
paragraphStartPos = pos
|
||||
if (toLast) {
|
||||
toLast = false
|
||||
readAloudNumber = textChapter.getLastParagraphPosition()
|
||||
nowSpeak = contentList.lastIndex
|
||||
}
|
||||
launch(Main) {
|
||||
if (play) play() else pageChanged = true
|
||||
}
|
||||
}.onError {
|
||||
AppLog.put("启动朗读出错\n${it.localizedMessage}", it, true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,9 +249,24 @@ abstract class BaseReadAloudService : BaseService(),
|
||||
if (nowSpeak > 0) {
|
||||
playStop()
|
||||
nowSpeak--
|
||||
readAloudNumber -= contentList[nowSpeak].length.minus(1)
|
||||
readAloudNumber -= contentList[nowSpeak].length + 1 + paragraphStartPos
|
||||
paragraphStartPos = 0
|
||||
textChapter?.let {
|
||||
val paragraphs = if (readAloudByPage) {
|
||||
it.pageParagraphs
|
||||
} else {
|
||||
it.paragraphs
|
||||
}
|
||||
if (!paragraphs[nowSpeak].isParagraphEnd) readAloudNumber++
|
||||
if (readAloudNumber < it.getReadLength(pageIndex)) {
|
||||
pageIndex--
|
||||
ReadBook.moveToPrevPage()
|
||||
}
|
||||
}
|
||||
upTtsProgress(readAloudNumber + 1)
|
||||
play()
|
||||
} else {
|
||||
toLast = true
|
||||
ReadBook.moveToPrevChapter(true)
|
||||
}
|
||||
}
|
||||
@ -235,8 +274,10 @@ abstract class BaseReadAloudService : BaseService(),
|
||||
private fun nextP() {
|
||||
if (nowSpeak < contentList.size - 1) {
|
||||
playStop()
|
||||
readAloudNumber += contentList[nowSpeak].length.plus(1)
|
||||
readAloudNumber += contentList[nowSpeak].length.plus(1) - paragraphStartPos
|
||||
paragraphStartPos = 0
|
||||
nowSpeak++
|
||||
upTtsProgress(readAloudNumber + 1)
|
||||
play()
|
||||
} else {
|
||||
nextChapter()
|
||||
|
@ -11,7 +11,6 @@ import com.script.ScriptException
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.constant.AppPattern
|
||||
import io.legado.app.constant.EventBus
|
||||
import io.legado.app.data.entities.HttpTTS
|
||||
import io.legado.app.exception.ConcurrentException
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
@ -22,7 +21,6 @@ import io.legado.app.model.ReadBook
|
||||
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
||||
import io.legado.app.utils.FileUtils
|
||||
import io.legado.app.utils.MD5Utils
|
||||
import io.legado.app.utils.postEvent
|
||||
import io.legado.app.utils.printOnDebug
|
||||
import io.legado.app.utils.servicePendingIntent
|
||||
import io.legado.app.utils.toastOnUi
|
||||
@ -90,10 +88,12 @@ class HttpReadAloudService : BaseReadAloudService(),
|
||||
|
||||
override fun playStop() {
|
||||
exoPlayer.stop()
|
||||
playIndexJob?.cancel()
|
||||
}
|
||||
|
||||
private fun playNext() {
|
||||
readAloudNumber += contentList[nowSpeak].length + 1
|
||||
readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos
|
||||
paragraphStartPos = 0
|
||||
if (nowSpeak < contentList.lastIndex) {
|
||||
nowSpeak++
|
||||
} else {
|
||||
@ -111,10 +111,14 @@ class HttpReadAloudService : BaseReadAloudService(),
|
||||
contentList.forEachIndexed { index, content ->
|
||||
ensureActive()
|
||||
if (index < nowSpeak) return@forEachIndexed
|
||||
val fileName = md5SpeakFileName(content)
|
||||
val speakText = content.replace(AppPattern.notReadAloudRegex, "")
|
||||
var text = content
|
||||
if (paragraphStartPos > 0 && index == nowSpeak) {
|
||||
text = text.substring(paragraphStartPos)
|
||||
}
|
||||
val fileName = md5SpeakFileName(text)
|
||||
val speakText = text.replace(AppPattern.notReadAloudRegex, "")
|
||||
if (speakText.isEmpty()) {
|
||||
AppLog.put("阅读段落内容为空,使用无声音频代替。\n朗读文本:$content")
|
||||
AppLog.put("阅读段落内容为空,使用无声音频代替。\n朗读文本:$text")
|
||||
createSilentSound(fileName)
|
||||
} else if (!hasSpeakFile(fileName)) {
|
||||
runCatching {
|
||||
|
@ -7,7 +7,6 @@ import io.legado.app.R
|
||||
import io.legado.app.constant.AppConst
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.constant.AppPattern
|
||||
import io.legado.app.constant.EventBus
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
import io.legado.app.help.MediaHelp
|
||||
import io.legado.app.help.config.AppConfig
|
||||
@ -100,7 +99,10 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
|
||||
val contentList = contentList
|
||||
for (i in nowSpeak until contentList.size) {
|
||||
ensureActive()
|
||||
val text = contentList[i]
|
||||
var text = contentList[i]
|
||||
if (paragraphStartPos > 0 && i == nowSpeak) {
|
||||
text = text.substring(paragraphStartPos)
|
||||
}
|
||||
if (text.matches(AppPattern.notReadAloudRegex)) {
|
||||
continue
|
||||
}
|
||||
@ -178,7 +180,8 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener
|
||||
override fun onDone(s: String) {
|
||||
//跳过全标点段落
|
||||
do {
|
||||
readAloudNumber += contentList[nowSpeak].length + 1
|
||||
readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos
|
||||
paragraphStartPos = 0
|
||||
nowSpeak++
|
||||
if (nowSpeak >= contentList.size) {
|
||||
nextChapter()
|
||||
|
@ -247,4 +247,16 @@ class RemoteBookActivity : BaseImportBookActivity<RemoteBookViewModel>(),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
override fun addToBookShelfAgain(remoteBook: RemoteBook) {
|
||||
alert(getString(R.string.sure), "是否重新加入书架?") {
|
||||
yesButton {
|
||||
binding.refreshProgressBar.isAutoLoading = true
|
||||
viewModel.addToBookshelf(hashSetOf(remoteBook)) {
|
||||
binding.refreshProgressBar.isAutoLoading = false
|
||||
}
|
||||
}
|
||||
noButton()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -90,6 +90,14 @@ class RemoteBookAdapter(context: Context, val callBack: CallBack) :
|
||||
}
|
||||
}
|
||||
}
|
||||
holder.itemView.setOnLongClickListener {
|
||||
getItem(holder.layoutPosition)?.let { remoteBook ->
|
||||
if (remoteBook.isOnBookShelf) {
|
||||
callBack.addToBookShelfAgain(remoteBook)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun upCheckableCount() {
|
||||
@ -144,5 +152,6 @@ class RemoteBookAdapter(context: Context, val callBack: CallBack) :
|
||||
fun openDir(remoteBook: RemoteBook)
|
||||
fun upCountView()
|
||||
fun startRead(remoteBook: RemoteBook)
|
||||
fun addToBookShelfAgain(remoteBook: RemoteBook)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package io.legado.app.ui.book.manage
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.constant.BookType
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.Book
|
||||
@ -52,22 +53,21 @@ class BookshelfManageViewModel(application: Application) : BaseViewModel(applica
|
||||
batchChangeSourceCoroutine?.cancel()
|
||||
batchChangeSourceCoroutine = execute {
|
||||
books.forEachIndexed { index, book ->
|
||||
batchChangeSourceProcessLiveData.postValue("${index + 1}/${books.size}")
|
||||
batchChangeSourceProcessLiveData.postValue("${index + 1} / ${books.size}")
|
||||
if (book.isLocal) return@forEachIndexed
|
||||
if (book.origin == source.bookSourceUrl) return@forEachIndexed
|
||||
WebBook.preciseSearchAwait(this, source, book.name, book.author)
|
||||
val newBook = WebBook.preciseSearchAwait(this, source, book.name, book.author)
|
||||
.onFailure {
|
||||
context.toastOnUi("获取书籍出错\n${it.localizedMessage}")
|
||||
}.getOrNull()?.let { newBook ->
|
||||
WebBook.getChapterListAwait(source, newBook)
|
||||
.onFailure {
|
||||
context.toastOnUi("获取目录出错\n${it.localizedMessage}")
|
||||
}.getOrNull()?.let { toc ->
|
||||
book.migrateTo(newBook, toc)
|
||||
book.removeType(BookType.updateError)
|
||||
appDb.bookDao.insert(newBook)
|
||||
appDb.bookChapterDao.insert(*toc.toTypedArray())
|
||||
}
|
||||
AppLog.put("获取书籍出错\n${it.localizedMessage}", it, true)
|
||||
}.getOrNull() ?: return@forEachIndexed
|
||||
WebBook.getChapterListAwait(source, newBook)
|
||||
.onFailure {
|
||||
AppLog.put("获取目录出错\n${it.localizedMessage}", it, true)
|
||||
}.getOrNull()?.let { toc ->
|
||||
book.migrateTo(newBook, toc)
|
||||
book.removeType(BookType.updateError)
|
||||
appDb.bookDao.insert(newBook)
|
||||
appDb.bookChapterDao.insert(*toc.toTypedArray())
|
||||
}
|
||||
}
|
||||
}.onStart {
|
||||
|
@ -40,6 +40,38 @@ data class TextChapter(
|
||||
|
||||
val pageSize: Int get() = pages.size
|
||||
|
||||
val paragraphs by lazy {
|
||||
paragraphsInternal
|
||||
}
|
||||
|
||||
val pageParagraphs by lazy {
|
||||
pageParagraphsInternal
|
||||
}
|
||||
|
||||
val paragraphsInternal: ArrayList<TextParagraph>
|
||||
get() {
|
||||
val paragraphs = arrayListOf<TextParagraph>()
|
||||
pages.forEach {
|
||||
it.lines.forEach loop@{ line ->
|
||||
if (line.paragraphNum <= 0) return@loop
|
||||
if (paragraphs.lastIndex < line.paragraphNum - 1) {
|
||||
paragraphs.add(TextParagraph(line.paragraphNum))
|
||||
}
|
||||
paragraphs[line.paragraphNum - 1].textLines.add(line)
|
||||
}
|
||||
}
|
||||
return paragraphs
|
||||
}
|
||||
|
||||
val pageParagraphsInternal: List<TextParagraph>
|
||||
get() = pages.map {
|
||||
it.paragraphs
|
||||
}.flatten().also {
|
||||
it.forEachIndexed { index, textParagraph ->
|
||||
textParagraph.num = index + 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param index 页数
|
||||
* @return 是否是最后一页
|
||||
@ -73,6 +105,18 @@ data class TextChapter(
|
||||
return getReadLength(pageIndex + 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param length 当前页面文字在章节中的位置
|
||||
* @return 上一页位置,如果没有上一页返回-1
|
||||
*/
|
||||
fun getPrevPageLength(length: Int): Int {
|
||||
val pageIndex = getPageIndexByCharIndex(length)
|
||||
if (pageIndex - 1 < 0) {
|
||||
return -1
|
||||
}
|
||||
return getReadLength(pageIndex - 1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内容
|
||||
*/
|
||||
@ -116,6 +160,27 @@ data class TextChapter(
|
||||
return stringBuilder.substring(startPos).toString()
|
||||
}
|
||||
|
||||
fun getParagraphNum(
|
||||
position: Int,
|
||||
pageSplit: Boolean,
|
||||
): Int {
|
||||
val paragraphs = if (pageSplit) {
|
||||
pageParagraphs
|
||||
} else {
|
||||
paragraphs
|
||||
}
|
||||
paragraphs.forEach { paragraph ->
|
||||
if (position in paragraph.chapterIndices) {
|
||||
return paragraph.num
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fun getLastParagraphPosition(): Int {
|
||||
return pageParagraphs.last().chapterPosition
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 根据索引位置获取所在页
|
||||
*/
|
||||
|
@ -18,6 +18,9 @@ data class TextLine(
|
||||
var lineBase: Float = 0f,
|
||||
var lineBottom: Float = 0f,
|
||||
var indentWidth: Float = 0f,
|
||||
var paragraphNum: Int = 0,
|
||||
var chapterPosition: Int = 0,
|
||||
var pagePosition: Int = 0,
|
||||
val isTitle: Boolean = false,
|
||||
var isParagraphEnd: Boolean = false,
|
||||
var isReadAloud: Boolean = false,
|
||||
@ -28,6 +31,7 @@ data class TextLine(
|
||||
val charSize: Int get() = textColumns.size
|
||||
val lineStart: Float get() = textColumns.firstOrNull()?.start ?: 0f
|
||||
val lineEnd: Float get() = textColumns.lastOrNull()?.end ?: 0f
|
||||
val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize
|
||||
|
||||
fun addColumn(column: BaseColumn) {
|
||||
textColumns.add(column)
|
||||
|
@ -36,6 +36,23 @@ data class TextPage(
|
||||
val searchResult = hashSetOf<TextColumn>()
|
||||
var isMsgPage: Boolean = false
|
||||
|
||||
val paragraphs by lazy {
|
||||
paragraphsInternal
|
||||
}
|
||||
|
||||
val paragraphsInternal: ArrayList<TextParagraph> get() {
|
||||
val paragraphs = arrayListOf<TextParagraph>()
|
||||
val lines = textLines.filter { it.paragraphNum > 0 }
|
||||
val offset = lines.first().paragraphNum - 1
|
||||
lines.forEach { line ->
|
||||
if (paragraphs.lastIndex < line.paragraphNum - offset - 1) {
|
||||
paragraphs.add(TextParagraph(0))
|
||||
}
|
||||
paragraphs[line.paragraphNum - offset - 1].textLines.add(line)
|
||||
}
|
||||
return paragraphs
|
||||
}
|
||||
|
||||
fun addLine(line: TextLine) {
|
||||
textLines.add(line)
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package io.legado.app.ui.book.read.page.entities
|
||||
|
||||
@Suppress("unused")
|
||||
data class TextParagraph(
|
||||
var num: Int,
|
||||
val textLines: ArrayList<TextLine> = arrayListOf(),
|
||||
) : Iterable<TextLine> {
|
||||
val text: String get() = textLines.joinToString("") { it.text }
|
||||
val length: Int get() = text.length
|
||||
val chapterIndices: IntRange get() = first().chapterPosition..last().chapterPosition + last().charSize
|
||||
val chapterPosition: Int get() = first().chapterPosition
|
||||
val realNum: Int get() = first().paragraphNum
|
||||
val isParagraphEnd: Boolean get() = last().isParagraphEnd
|
||||
|
||||
override fun iterator(): Iterator<TextLine> {
|
||||
return textLines.iterator()
|
||||
}
|
||||
}
|
@ -417,10 +417,23 @@ object ChapterProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
val sbLength = stringBuilder.length
|
||||
stringBuilder.append(words)
|
||||
if (textLine.isParagraphEnd) {
|
||||
stringBuilder.append("\n")
|
||||
}
|
||||
val lastLine = textPages.last().lines.lastOrNull { it.paragraphNum > 0 }
|
||||
?: textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull { it.paragraphNum > 0 }
|
||||
val paragraphNum = when {
|
||||
lastLine == null -> 1
|
||||
lastLine.isParagraphEnd -> lastLine.paragraphNum + 1
|
||||
else -> lastLine.paragraphNum
|
||||
}
|
||||
textLine.paragraphNum = paragraphNum
|
||||
textLine.chapterPosition = textPages.foldIndexed(sbLength) { index, acc, textPage ->
|
||||
acc + if (index == textPages.lastIndex) 0 else textPage.text.length
|
||||
}
|
||||
textLine.pagePosition = sbLength
|
||||
textPages.last().addLine(textLine)
|
||||
textLine.upTopBottom(durY, textPaint)
|
||||
durY += textPaint.textHeight * lineSpacingExtra
|
||||
|
Loading…
Reference in New Issue
Block a user