diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/ImageDecoder.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/ImageDecoder.kt index aa2a42170..0e2f0dd63 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/ImageDecoder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/ImageDecoder.kt @@ -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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webgpu/WebGpuViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webgpu/WebGpuViewer.kt index 25fffcdae..30f21fc66 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webgpu/WebGpuViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webgpu/WebGpuViewer.kt @@ -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() 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() 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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webgpu/WebGpuViewerContinuous.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webgpu/WebGpuViewerContinuous.kt index 3bee77552..09dc62cd9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webgpu/WebGpuViewerContinuous.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webgpu/WebGpuViewerContinuous.kt @@ -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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 978800faf..c4cd7f997 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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]