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