Fix page loading for webgpuviewer and bump version (#3823)
This commit is contained in:
@@ -37,7 +37,7 @@ class ImageDecoder(private val resources: ImageSource, private val options: Opti
|
||||
}
|
||||
|
||||
override suspend fun decode(): DecodeResult {
|
||||
val decoder = resources.sourceOrNull()?.use {
|
||||
val decoder = resources.source().use {
|
||||
try {
|
||||
ImageDecoder.new(it.inputStream())
|
||||
} catch (e: ImageDecoder.DecodeException) {
|
||||
|
||||
@@ -49,6 +49,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
@@ -57,12 +58,13 @@ import tachiyomi.core.common.util.system.logcat
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
open class WebGpuViewer(
|
||||
val activity: ReaderActivity,
|
||||
val isReversed: Boolean,
|
||||
val isVertical: Boolean,
|
||||
val pager: ImageView = ImageView(activity, isVertical = isVertical),
|
||||
val pager: ImageView = ImageView(activity, isVertical = isVertical, isReversed = isReversed),
|
||||
) : Viewer {
|
||||
|
||||
open val isContinuous: Boolean = false
|
||||
@@ -198,8 +200,10 @@ open class WebGpuViewer(
|
||||
@Volatile
|
||||
var currentPage: ViewerPage? = null
|
||||
|
||||
val preloadCount = 3
|
||||
open val cacheSize = 9
|
||||
open val preloadAhead = 3
|
||||
open val preloadBehind = 2
|
||||
|
||||
open val cacheSize get() = 1 + preloadAhead + preloadBehind
|
||||
|
||||
/**
|
||||
* Page processing state
|
||||
@@ -227,6 +231,11 @@ open class WebGpuViewer(
|
||||
candidates.find {
|
||||
it is ViewerReaderPage && it.page.chapter.chapter.id == chapterId && it.page.index == nextIndex
|
||||
} ?: candidates.find { it is TransitionPage && it.prevChapter?.chapter?.id == chapterId }
|
||||
?: page.nextChapter?.chapter?.id?.let { nextChapterId ->
|
||||
candidates.find {
|
||||
it is ViewerReaderPage && it.page.chapter.chapter.id == nextChapterId && it.page.index == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is TransitionPage -> {
|
||||
@@ -246,6 +255,14 @@ open class WebGpuViewer(
|
||||
candidates.find {
|
||||
it is ViewerReaderPage && it.page.chapter.chapter.id == chapterId && it.page.index == prevIndex
|
||||
} ?: candidates.find { it is TransitionPage && it.nextChapter?.chapter?.id == chapterId }
|
||||
?: page.prevChapter?.let { prevChapter ->
|
||||
prevChapter.pages?.lastIndex?.let { lastIndex ->
|
||||
candidates.find {
|
||||
it is ViewerReaderPage && it.page.chapter.chapter.id == prevChapter.chapter.id &&
|
||||
it.page.index == lastIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is TransitionPage -> {
|
||||
@@ -315,6 +332,26 @@ open class WebGpuViewer(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks off loading [chapter] and, once its pages actually show up, re-runs
|
||||
* [preloadPages] from the current page - [ReaderActivity]'s viewModel.preload isn't
|
||||
* guaranteed to have finished loading by the time it returns, so a single immediate
|
||||
* retry can race it and silently never queue the adjacent chapter's edge page for
|
||||
* decode. Gives up after 5 seconds if the chapter never finishes loading.
|
||||
*/
|
||||
private fun preloadChapterThenRetry(chapter: ReaderChapter) {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
activity.viewModel.preload(chapter)
|
||||
repeat(25) {
|
||||
if (chapter.state is ReaderChapter.State.Loaded) {
|
||||
currentPage?.let { preloadPages(it) }
|
||||
return@launch
|
||||
}
|
||||
delay(200.milliseconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ViewerPage {
|
||||
abstract val prevChapter: ReaderChapter?
|
||||
abstract val nextChapter: ReaderChapter?
|
||||
@@ -331,10 +368,10 @@ open class WebGpuViewer(
|
||||
inner class TransitionPage(override val prevChapter: ReaderChapter?, override val nextChapter: ReaderChapter?) :
|
||||
ViewerPage() {
|
||||
override val prev: ViewerPage?
|
||||
get() = prevChapter?.pages?.lastOrNull()?.let { getPage(it) }
|
||||
get() = prevChapter?.pages?.lastOrNull()?.let { getPage(it, currentPage) }
|
||||
|
||||
override val next: ViewerPage?
|
||||
get() = nextChapter?.pages?.firstOrNull()?.let { getPage(it) }
|
||||
get() = nextChapter?.pages?.firstOrNull()?.let { getPage(it, currentPage) }
|
||||
}
|
||||
|
||||
inner class ViewerReaderPage(val page: ReaderPage) : ViewerPage() {
|
||||
@@ -357,36 +394,36 @@ open class WebGpuViewer(
|
||||
|
||||
override val prev: ViewerPage?
|
||||
get() = page.chapter.pages?.let { pages ->
|
||||
pages.getOrNull(page.index - 1)?.let { getPage(it) } ?: prevChapter?.let { prevChapter ->
|
||||
pages.getOrNull(page.index - 1)?.let { getPage(it, currentPage) } ?: run {
|
||||
val prevChapter = prevChapter ?: return@run getPage(null, page.chapter, currentPage)
|
||||
|
||||
if (prevChapter.state !is ReaderChapter.State.Loaded) {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
activity.viewModel.preload(prevChapter)
|
||||
currentPage?.let { preloadPages(it) }
|
||||
}
|
||||
preloadChapterThenRetry(prevChapter)
|
||||
}
|
||||
|
||||
if (config.alwaysShowChapterTransition) {
|
||||
getPage(prevChapter, page.chapter)
|
||||
getPage(prevChapter, page.chapter, currentPage)
|
||||
} else {
|
||||
prevChapter.pages?.lastOrNull()?.let { getPage(it) }
|
||||
prevChapter.pages?.lastOrNull()?.let { getPage(it, currentPage) }
|
||||
}
|
||||
} ?: getPage(null, page.chapter)
|
||||
}
|
||||
}
|
||||
|
||||
override val next: ViewerPage?
|
||||
get() = page.chapter.pages?.let { pages ->
|
||||
pages.getOrNull(page.index + 1)?.let { getPage(it) } ?: nextChapter?.let { nextChapter ->
|
||||
pages.getOrNull(page.index + 1)?.let { getPage(it, currentPage) } ?: run {
|
||||
val nextChapter = nextChapter ?: return@run getPage(page.chapter, null, currentPage)
|
||||
|
||||
if (nextChapter.state !is ReaderChapter.State.Loaded) {
|
||||
scope.launch(Dispatchers.Default) {
|
||||
activity.viewModel.preload(nextChapter)
|
||||
currentPage?.let { preloadPages(it) }
|
||||
}
|
||||
preloadChapterThenRetry(nextChapter)
|
||||
}
|
||||
|
||||
if (config.alwaysShowChapterTransition) {
|
||||
getPage(page.chapter, nextChapter)
|
||||
getPage(page.chapter, nextChapter, currentPage)
|
||||
} else {
|
||||
nextChapter.pages?.firstOrNull()?.let { getPage(it) }
|
||||
nextChapter.pages?.firstOrNull()?.let { getPage(it, currentPage) }
|
||||
}
|
||||
} ?: getPage(page.chapter, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -498,18 +535,17 @@ open class WebGpuViewer(
|
||||
init {
|
||||
pager.state.apply {
|
||||
fetchPage = fetch@{ index ->
|
||||
val i = if (isReversed) -index else index
|
||||
val current = currentPage ?: return@fetch null
|
||||
|
||||
// For index 0, return the current spread
|
||||
if (i == 0) {
|
||||
if (index == 0) {
|
||||
return@fetch buildSpreadPage(getSpreadAnchor(current))
|
||||
}
|
||||
|
||||
// Navigate by spreads from current
|
||||
var page = current
|
||||
val step = if (i > 0) 1 else -1
|
||||
repeat(abs(i)) {
|
||||
val step = if (index > 0) 1 else -1
|
||||
repeat(abs(index)) {
|
||||
page = nextPage(page, step) ?: return@fetch null
|
||||
}
|
||||
|
||||
@@ -801,6 +837,7 @@ open class WebGpuViewer(
|
||||
}
|
||||
val errorPage = ImagePage(bitmap, createMipMaps = false).also {
|
||||
it.image?.position = Image.Position.SINGLE
|
||||
it.highQuality = false
|
||||
}
|
||||
synchronized(lock) {
|
||||
if (pageInCache(page) && !page.imagePage.isDecoded && !page.imagePage.destroyed) {
|
||||
@@ -959,6 +996,7 @@ open class WebGpuViewer(
|
||||
|
||||
val imagePage = ImagePage(bitmap, createMipMaps = false)
|
||||
imagePage.image?.position = Image.Position.SINGLE
|
||||
imagePage.highQuality = false
|
||||
|
||||
synchronized(lock) {
|
||||
if (pageInCache(page) && !page.imagePage.isDecoded && !page.imagePage.destroyed) {
|
||||
@@ -1103,7 +1141,7 @@ open class WebGpuViewer(
|
||||
// Add prev pages (lowest priority)
|
||||
val prevPages = mutableListOf<ViewerPage>()
|
||||
var p: ViewerPage? = cachedPage
|
||||
for (i in 0 until preloadCount) {
|
||||
for (i in 0 until preloadBehind) {
|
||||
p = p?.prev ?: break
|
||||
prevPages.add(p)
|
||||
}
|
||||
@@ -1112,7 +1150,7 @@ open class WebGpuViewer(
|
||||
// Add next pages (medium priority)
|
||||
val nextPages = mutableListOf<ViewerPage>()
|
||||
p = cachedPage
|
||||
for (i in 0 until preloadCount) {
|
||||
for (i in 0 until preloadAhead) {
|
||||
p = p?.next ?: break
|
||||
nextPages.add(p)
|
||||
}
|
||||
@@ -1148,13 +1186,12 @@ open class WebGpuViewer(
|
||||
|
||||
// The viewer already showed the page at fetchPage(delta).
|
||||
// We need to update currentPage to match that.
|
||||
val direction = if (isReversed) -delta else delta
|
||||
val current = currentPage ?: return@onPageChange
|
||||
|
||||
// Navigate the same way fetchPage does
|
||||
var page = current
|
||||
val step = if (direction > 0) 1 else -1
|
||||
repeat(abs(direction)) {
|
||||
val step = if (delta > 0) 1 else -1
|
||||
repeat(abs(delta)) {
|
||||
page = nextPage(page, step) ?: return@onPageChange
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -9,6 +9,9 @@ class WebGpuViewerContinuous(activity: ReaderActivity) :
|
||||
|
||||
override val isContinuous: Boolean = true
|
||||
|
||||
override val preloadAhead = 1
|
||||
override val preloadBehind = 1
|
||||
|
||||
private fun scrollByHalfPage(direction: Int) {
|
||||
val state = (pager as ImageViewContinuous).state
|
||||
val totalDistance = direction * state.height / 2f
|
||||
|
||||
@@ -76,7 +76,7 @@ swipe = "1.3.0"
|
||||
tapmoc = "0.4.2"
|
||||
unifile = "08f224c8f9"
|
||||
voyager = "2.2.21-1.10.3"
|
||||
webgpuviewer = "28"
|
||||
webgpuviewer = "31"
|
||||
xmlutil = "1.0.2"
|
||||
|
||||
[libraries]
|
||||
|
||||
Reference in New Issue
Block a user