diff --git a/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt b/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt index eb43c1f9f..481b5ffb1 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/Settings.kt @@ -127,7 +127,7 @@ object Settings : DataStorePreferences(null) { val animateItems = boolPref("animate_items", true) val enableCronet = boolPref("enable_cronet", true) val enableQuic = boolPref("enable_quic", true) - val webpDecoder = boolPref("webp_decoder", false) + val webpDecoder = boolPref("webp_decoder", true) val desktopSite = boolPref("desktop_site", true) val displayName = stringOrNullPref("display_name", null) val avatar = stringOrNullPref("avatar", null) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/AnimatedWebPDrawable.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/AnimatedWebPDrawable.kt index 8dba3455f..e0ada2696 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/AnimatedWebPDrawable.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/AnimatedWebPDrawable.kt @@ -7,14 +7,15 @@ import android.graphics.Paint import android.graphics.PixelFormat import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable +import android.os.SystemClock import androidx.core.graphics.createBitmap +import eu.kanade.tachiyomi.util.system.logcat import java.nio.ByteBuffer -import kotlin.time.TimeSource import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.delay +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.launch // Hold a reference to the buffer as it's used by the decoder @@ -29,7 +30,7 @@ class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animata private var decodeJob: Job? = null private var loopsCompleted = 0 - private var frameDuration: Int + private var timeToShowNextFrame = 0L private var currentFrame: Frame private var nextFrame: Frame @@ -45,7 +46,6 @@ class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animata nativeDestroyDecoder(decoder) "Failed to decode first frame" } - frameDuration = timestamp currentFrame = Frame(bitmap, timestamp) nextFrame = Frame(createBitmap(width, height), 0) } @@ -54,30 +54,42 @@ class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animata override fun getIntrinsicHeight() = height - private fun decodeNextFrame(reset: Boolean) { - nextFrame.timestamp = nativeDecodeNextFrame(decoder, reset, nextFrame.bitmap) - if (nextFrame.timestamp == 0) { - throw CancellationException("Failed to decode next frame") + private val runnable = Runnable { + timeToShowNextFrame = SystemClock.uptimeMillis() + nextFrame.timestamp - currentFrame.timestamp + invalidateSelf() + } + + private fun decodeNextFrame(reset: Boolean) = decodeScope.launch { + val timestamp = nativeDecodeNextFrame(decoder, reset, nextFrame.bitmap) + ensureActive() + check(timestamp != 0) { + decodeJob = null + "Failed to decode next frame" } + if (timestamp <= currentFrame.timestamp) { + if (reset) loopsCompleted = 0 else loopsCompleted++ + currentFrame.timestamp = 0 + } + nextFrame.timestamp = timestamp nextFrame.bitmap.prepareToDraw() + }.apply { + invokeOnCompletion { cause -> + when (cause) { + null -> if (reset) { + runnable.run() + } else { + scheduleSelf(runnable, timeToShowNextFrame) + } + !is CancellationException -> logcat(cause) + } + } } override fun draw(canvas: Canvas) { - if (decodeJob?.isCompleted == true && nextFrame.timestamp != 0) { - val timeMark = TimeSource.Monotonic.markNow() - frameDuration = if (nextFrame.timestamp > currentFrame.timestamp) { - nextFrame.timestamp - currentFrame.timestamp - } else { - loopsCompleted++ - nextFrame.timestamp - } - currentFrame = nextFrame.also { nextFrame = currentFrame } + if (decodeJob?.isCompleted == true) { decodeJob = if (loopCount == 0 || loopsCompleted < loopCount) { - decodeScope.launch { - decodeNextFrame(false) - delay(frameDuration - timeMark.elapsedNow().inWholeMilliseconds) - invalidateSelf() - } + currentFrame = nextFrame.also { nextFrame = currentFrame } + decodeNextFrame(false) } else { null } @@ -110,20 +122,14 @@ class AnimatedWebPDrawable(private val source: ByteBuffer) : Drawable(), Animata override fun start() { if (decodeJob == null) { - val timeMark = TimeSource.Monotonic.markNow() - loopsCompleted = 0 - decodeJob = decodeScope.launch { - val isFirstFrame = currentFrame.timestamp == frameDuration - decodeNextFrame(!isFirstFrame) - delay(frameDuration - timeMark.elapsedNow().inWholeMilliseconds) - invalidateSelf() - } + decodeJob = decodeNextFrame(true) } } override fun stop() { decodeJob?.cancel() decodeJob = null + unscheduleSelf(runnable) } fun dispose() {