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
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<Song> {
@Suppress("BlockingMethodInNonBlockingContext")
override suspend fun fetch(
pool: BitmapPool,
data: Song,
@ -27,29 +30,46 @@ class QualityCoverFetcher(private val context: Context) : Fetcher<Song> {
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

View file

@ -68,7 +68,7 @@
<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_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_loop">Prefer repeat mode action</string>