From 6c37ba7d3ecf5ef32638b5562469bdb04f98383d Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Fri, 1 Jan 2021 09:41:56 -0700 Subject: [PATCH] Optimize quality cover loading Make the loading process for higher-quality covers far more efficent and elegant compared to the previous method. --- .../oxycblt/auxio/coil/QualityCoverFetcher.kt | 62 ++++++++++++------- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt index 10a288257..4b5ce9429 100644 --- a/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt @@ -1,25 +1,28 @@ package org.oxycblt.auxio.coil import android.content.Context -import android.graphics.BitmapFactory import android.media.MediaMetadataRetriever -import androidx.core.graphics.drawable.toDrawable +import android.net.Uri import coil.bitmap.BitmapPool import coil.decode.DataSource import coil.decode.Options -import coil.fetch.DrawableResult import coil.fetch.FetchResult import coil.fetch.Fetcher +import coil.fetch.SourceResult import coil.size.Size +import okio.buffer +import okio.source import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.toURI -import java.lang.Exception +import java.io.ByteArrayInputStream +import java.io.InputStream /** * A [Fetcher] for fetching high-quality embedded covers instead of the compressed covers, albeit - * at the cost of load time & caching. + * at the cost of load time & memory usage. */ class QualityCoverFetcher(private val context: Context) : Fetcher { + @Suppress("BlockingMethodInNonBlockingContext") override suspend fun fetch( pool: BitmapPool, data: Song, @@ -27,29 +30,46 @@ class QualityCoverFetcher(private val context: Context) : Fetcher { options: Options ): FetchResult { val extractor = MediaMetadataRetriever() + val stream: InputStream? + val uri: Uri - extractor.setDataSource(context, data.id.toURI()) + extractor.use { extractor -> + extractor.setDataSource(context, data.id.toURI()) + val cover = extractor.embeddedPicture - val bitmap = try { - val bytes = extractor.embeddedPicture + stream = if (cover != null) { + uri = data.id.toURI() - if (bytes != null) { - BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + ByteArrayInputStream(cover) } else { - throw Exception() + // Fallback to the compressed cover if the cover loading failed. + uri = data.album.coverUri + + // Blocking call, but coil is on a background thread so it doesn't matter + context.contentResolver.openInputStream(data.album.coverUri) + } + + stream?.use { + return SourceResult( + source = stream.source().buffer(), + mimeType = context.contentResolver.getType(uri), + dataSource = DataSource.DISK + ) } - } catch (e: Exception) { - extractor.release() - error("Likely no album art for this item.") - } finally { - extractor.release() } - return DrawableResult( - drawable = bitmap.toDrawable(context.resources), - isSampled = false, - dataSource = DataSource.DISK - ) + // If we are here, the extractor likely failed so instead attempt to return the compressed + // cover instead. + context.contentResolver.openInputStream(data.album.coverUri)?.use { stream -> + return SourceResult( + source = stream.source().buffer(), + mimeType = context.contentResolver.getType(uri), + dataSource = DataSource.DISK + ) + } + + // If even that failed, then error out entirely. + error("Likely no bitmap for this song/album.") } // Group bitmaps by their album so that caching is more efficent diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87722a730..0b3b064cb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -68,7 +68,7 @@ Turn off to save memory usage Ignore MediaStore covers - Results in higher quality album covers at the cost of slower loading times and higher memory usage + Results in higher quality album covers, but causes slower loading and higher memory usage Use alternate notification action Prefer repeat mode action