From d43a6f18652bb009334b0da2942219ee13c6a514 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 8 Nov 2021 17:23:35 +0900 Subject: [PATCH] #115 changed video thumbnail strategy --- .../aves/decoder/VideoThumbnailGlideModule.kt | 16 +++++-------- lib/widgets/common/thumbnail/image.dart | 24 +++++++++++++++---- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt index d0f2138ff..6c548ef44 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/decoder/VideoThumbnailGlideModule.kt @@ -3,7 +3,6 @@ package deckers.thibault.aves.decoder import android.content.Context import android.media.MediaMetadataRetriever import android.net.Uri -import android.os.Build import com.bumptech.glide.Glide import com.bumptech.glide.Priority import com.bumptech.glide.Registry @@ -58,16 +57,13 @@ internal class VideoThumbnailFetcher(private val model: VideoThumbnail) : DataFe try { var bytes = retriever.embeddedPicture if (bytes == null) { - // try to match the thumbnails returned by the content resolver / Media Store - // the following strategies are from empirical evidence from a few test devices: - // - API 29: sync frame closest to the middle - // - API 26/27: default representative frame at any time position + // there is no consistent strategy across devices to match + // the thumbnails returned by the content resolver / Media Store + // so we derive one in an arbitrary way var timeMillis: Long? = null - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - val durationMillis = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() - if (durationMillis != null) { - timeMillis = durationMillis / 2 - } + val durationMillis = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() + if (durationMillis != null) { + timeMillis = if (durationMillis < 15000) 0 else 15000 } val frame = if (timeMillis != null) { retriever.getFrameAtTime(timeMillis * 1000) diff --git a/lib/widgets/common/thumbnail/image.dart b/lib/widgets/common/thumbnail/image.dart index 26af6f3d8..3edba69d8 100644 --- a/lib/widgets/common/thumbnail/image.dart +++ b/lib/widgets/common/thumbnail/image.dart @@ -94,18 +94,34 @@ class _ThumbnailImageState extends State { _lastException = null; _providers.clear(); + + final highQuality = entry.getThumbnail(extent: extent); + ThumbnailProvider? lowQuality; + if (widget.progressive && !entry.isSvg) { + if (entry.isVideo) { + // previously fetched thumbnail + final cached = entry.bestCachedThumbnail; + final lowQualityExtent = cached.key.extent; + if (lowQualityExtent > 0 && lowQualityExtent != extent) { + lowQuality = cached; + } + } else { + // default platform thumbnail + lowQuality = entry.getThumbnail(); + } + } _providers.addAll([ - if (widget.progressive && !entry.isSvg) + if (lowQuality != null) _ConditionalImageProvider( ScrollAwareImageProvider( context: _scrollAwareContext, - imageProvider: entry.getThumbnail(), + imageProvider: lowQuality, ), ), _ConditionalImageProvider( ScrollAwareImageProvider( context: _scrollAwareContext, - imageProvider: entry.getThumbnail(extent: extent), + imageProvider: highQuality, ), _needSizedProvider, ), @@ -233,7 +249,7 @@ class _ThumbnailImageState extends State { if (animate && widget.heroTag != null) { final background = settings.imageBackground; - final backgroundColor = background.isColor? background.color : null; + final backgroundColor = background.isColor ? background.color : null; image = Hero( tag: widget.heroTag!, flightShuttleBuilder: (flight, animation, direction, fromHero, toHero) {