Optimize quality cover loading

Make the loading process for higher-quality covers far more efficent and elegant compared to the previous method.
This commit is contained in:
OxygenCobalt 2021-01-01 09:41:56 -07:00
parent bc1992de4e
commit 6c37ba7d3e
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
2 changed files with 42 additions and 22 deletions

View file

@ -1,25 +1,28 @@
package org.oxycblt.auxio.coil package org.oxycblt.auxio.coil
import android.content.Context import android.content.Context
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import androidx.core.graphics.drawable.toDrawable import android.net.Uri
import coil.bitmap.BitmapPool import coil.bitmap.BitmapPool
import coil.decode.DataSource import coil.decode.DataSource
import coil.decode.Options import coil.decode.Options
import coil.fetch.DrawableResult
import coil.fetch.FetchResult import coil.fetch.FetchResult
import coil.fetch.Fetcher import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.size.Size import coil.size.Size
import okio.buffer
import okio.source
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.music.toURI 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 * 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<Song> { class QualityCoverFetcher(private val context: Context) : Fetcher<Song> {
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun fetch( override suspend fun fetch(
pool: BitmapPool, pool: BitmapPool,
data: Song, data: Song,
@ -27,29 +30,46 @@ class QualityCoverFetcher(private val context: Context) : Fetcher<Song> {
options: Options options: Options
): FetchResult { ): FetchResult {
val extractor = MediaMetadataRetriever() 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 { stream = if (cover != null) {
val bytes = extractor.embeddedPicture uri = data.id.toURI()
if (bytes != null) { ByteArrayInputStream(cover)
BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
} else { } 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( // If we are here, the extractor likely failed so instead attempt to return the compressed
drawable = bitmap.toDrawable(context.resources), // cover instead.
isSampled = false, context.contentResolver.openInputStream(data.album.coverUri)?.use { stream ->
dataSource = DataSource.DISK 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 // Group bitmaps by their album so that caching is more efficent

View file

@ -68,7 +68,7 @@
<string name="setting_show_covers_desc">Turn off to save memory usage</string> <string name="setting_show_covers_desc">Turn off to save memory usage</string>
<string name="setting_quality_covers">Ignore MediaStore covers</string> <string name="setting_quality_covers">Ignore MediaStore covers</string>
<string name="setting_quality_covers_desc">Results in higher quality album covers at the cost of slower loading times and higher memory usage</string> <string name="setting_quality_covers_desc">Results in higher quality album covers, but causes slower loading and higher memory usage</string>
<string name="setting_use_alt_action">Use alternate notification action</string> <string name="setting_use_alt_action">Use alternate notification action</string>
<string name="setting_use_alt_loop">Prefer repeat mode action</string> <string name="setting_use_alt_loop">Prefer repeat mode action</string>