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:
parent
bc1992de4e
commit
6c37ba7d3e
2 changed files with 42 additions and 22 deletions
|
@ -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,30 +30,47 @@ 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.use { extractor ->
|
||||||
extractor.setDataSource(context, data.id.toURI())
|
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
|
||||||
} catch (e: Exception) {
|
|
||||||
extractor.release()
|
// Blocking call, but coil is on a background thread so it doesn't matter
|
||||||
error("Likely no album art for this item.")
|
context.contentResolver.openInputStream(data.album.coverUri)
|
||||||
} finally {
|
|
||||||
extractor.release()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DrawableResult(
|
stream?.use {
|
||||||
drawable = bitmap.toDrawable(context.resources),
|
return SourceResult(
|
||||||
isSampled = false,
|
source = stream.source().buffer(),
|
||||||
|
mimeType = context.contentResolver.getType(uri),
|
||||||
dataSource = DataSource.DISK
|
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
|
// Group bitmaps by their album so that caching is more efficent
|
||||||
override fun key(data: Song): String = data.album.id.toString()
|
override fun key(data: Song): String = data.album.id.toString()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue