From d7087fe0b17a7a8531b1cc0a2c37a769ea9b00fb Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Thu, 31 Dec 2020 12:17:50 -0700 Subject: [PATCH] Add option to ignore MediaStore covers Add an option to ignore MediaStore covers and to get higher quality covers, at the cost of slower loading times. --- .../auxio/{music => }/coil/CoilUtils.kt | 57 ++++++++++++++----- .../auxio/{music => }/coil/MosaicFetcher.kt | 10 +--- .../oxycblt/auxio/coil/QualityCoverFetcher.kt | 56 ++++++++++++++++++ .../auxio/playback/NotificationUtils.kt | 2 +- .../oxycblt/auxio/playback/PlaybackService.kt | 2 +- .../auxio/settings/SettingsListFragment.kt | 12 ++++ .../oxycblt/auxio/settings/SettingsManager.kt | 7 +++ app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/prefs_main.xml | 7 +++ 9 files changed, 132 insertions(+), 26 deletions(-) rename app/src/main/java/org/oxycblt/auxio/{music => }/coil/CoilUtils.kt (72%) rename app/src/main/java/org/oxycblt/auxio/{music => }/coil/MosaicFetcher.kt (90%) create mode 100644 app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt diff --git a/app/src/main/java/org/oxycblt/auxio/music/coil/CoilUtils.kt b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt similarity index 72% rename from app/src/main/java/org/oxycblt/auxio/music/coil/CoilUtils.kt rename to app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt index fa2de7dbe..34c893f47 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/coil/CoilUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/CoilUtils.kt @@ -1,4 +1,4 @@ -package org.oxycblt.auxio.music.coil +package org.oxycblt.auxio.coil import android.content.Context import android.graphics.Bitmap @@ -9,12 +9,13 @@ import androidx.databinding.BindingAdapter import coil.Coil import coil.request.ImageRequest import org.oxycblt.auxio.R +import org.oxycblt.auxio.logE import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist +import org.oxycblt.auxio.music.BaseModel import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.Song - -// TODO: Add option to ignore MediaStore +import org.oxycblt.auxio.settings.SettingsManager /** * Get a bitmap for a song. onDone will be called when the bitmap is loaded. @@ -39,9 +40,9 @@ fun getBitmap(context: Context, song: Song, onDone: (Bitmap?) -> Unit) { */ @BindingAdapter("coverArt") fun ImageView.bindCoverArt(song: Song) { - val request = getDefaultRequest(context, this) - .data(song.album.coverUri) + val request = getDefaultRequest() .error(R.drawable.ic_song) + .doCoverSetup(context, song) .build() Coil.imageLoader(context).enqueue(request) @@ -52,9 +53,9 @@ fun ImageView.bindCoverArt(song: Song) { */ @BindingAdapter("coverArt") fun ImageView.bindCoverArt(album: Album) { - val request = getDefaultRequest(context, this) - .data(album.coverUri) + val request = getDefaultRequest() .error(R.drawable.ic_album) + .doCoverSetup(context, album) .build() Coil.imageLoader(context).enqueue(request) @@ -77,7 +78,7 @@ fun ImageView.bindArtistImage(artist: Artist) { val fetcher = MosaicFetcher(context) - request = getDefaultRequest(context, this) + request = getDefaultRequest() .data(uris) .fetcher(fetcher) .error(R.drawable.ic_artist) @@ -86,8 +87,8 @@ fun ImageView.bindArtistImage(artist: Artist) { // Otherwise, just get the first cover and use that // If the artist doesn't have any albums [Which happens], then don't even bother with that. if (artist.albums.isNotEmpty()) { - request = getDefaultRequest(context, this) - .data(artist.albums[0].coverUri) + request = getDefaultRequest() + .doCoverSetup(context, artist.albums[0]) .error(R.drawable.ic_artist) .build() } else { @@ -115,15 +116,15 @@ fun ImageView.bindGenreImage(genre: Genre) { if (genreCovers.size >= 4) { val fetcher = MosaicFetcher(context) - request = getDefaultRequest(context, this) + request = getDefaultRequest() .data(genreCovers.slice(0..3)) .fetcher(fetcher) .error(R.drawable.ic_genre) .build() } else { if (genreCovers.isNotEmpty()) { - request = getDefaultRequest(context, this) - .data(genreCovers[0]) + request = getDefaultRequest() + .doCoverSetup(context, genre.songs[0]) .error(R.drawable.ic_genre) .build() } else { @@ -136,13 +137,39 @@ fun ImageView.bindGenreImage(genre: Genre) { Coil.imageLoader(context).enqueue(request) } +fun ImageRequest.Builder.doCoverSetup(context: Context, data: BaseModel): ImageRequest.Builder { + if (data is Artist || data is Genre) { + logE("doCoverSetup does not support ${data::class.simpleName}") + + return this + } + + if (SettingsManager.getInstance().qualityCovers) { + fetcher(QualityCoverFetcher(context)) + + if (data is Song) { + data(data) + } else if (data is Album) { + data(data.songs[0]) + } + } else { + if (data is Song) { + data(data.album.coverUri) + } else if (data is Album) { + data(data.coverUri) + } + } + + return this +} + /** * Get the base request used by the above functions * @return The base request */ -private fun getDefaultRequest(context: Context, imageView: ImageView): ImageRequest.Builder { +private fun ImageView.getDefaultRequest(): ImageRequest.Builder { return ImageRequest.Builder(context) .crossfade(true) .placeholder(android.R.color.transparent) - .target(imageView) + .target(this) } diff --git a/app/src/main/java/org/oxycblt/auxio/music/coil/MosaicFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/MosaicFetcher.kt similarity index 90% rename from app/src/main/java/org/oxycblt/auxio/music/coil/MosaicFetcher.kt rename to app/src/main/java/org/oxycblt/auxio/coil/MosaicFetcher.kt index 318dfcdd3..5a415b08d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/coil/MosaicFetcher.kt +++ b/app/src/main/java/org/oxycblt/auxio/coil/MosaicFetcher.kt @@ -1,4 +1,4 @@ -package org.oxycblt.auxio.music.coil +package org.oxycblt.auxio.coil import android.content.Context import android.graphics.Bitmap @@ -16,7 +16,6 @@ import coil.fetch.SourceResult import coil.size.Size import okio.buffer import okio.source -import org.oxycblt.auxio.R import java.io.InputStream /** @@ -52,12 +51,7 @@ class MosaicFetcher(private val context: Context) : Fetcher> { dataSource = DataSource.DISK ) } else { - // If ALL the streams failed, then don't even bother with that. - DrawableResult( - drawable = R.drawable.ic_song.toDrawable(), - isSampled = false, - dataSource = DataSource.DISK - ) + error("All streams failed. Not bothering.") } } diff --git a/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt b/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt new file mode 100644 index 000000000..d79fd7c39 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/coil/QualityCoverFetcher.kt @@ -0,0 +1,56 @@ +package org.oxycblt.auxio.coil + +import android.content.Context +import android.graphics.BitmapFactory +import android.media.MediaMetadataRetriever +import androidx.core.graphics.drawable.toDrawable +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.size.Size +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.music.toURI +import java.lang.Exception + +/** + * A [Fetcher] for fetching high-quality embedded covers instead of the compressed covers, albeit + * at the cost of load time & caching. + */ +class QualityCoverFetcher(private val context: Context) : Fetcher { + override suspend fun fetch( + pool: BitmapPool, + data: Song, + size: Size, + options: Options + ): FetchResult { + val extractor = MediaMetadataRetriever() + + extractor.setDataSource(context, data.id.toURI()) + + val bitmap = try { + val bytes = extractor.embeddedPicture + + if (bytes != null) { + BitmapFactory.decodeByteArray(bytes, 0, bytes.size) + } else { + throw Exception() + } + } 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 + ) + } + + override fun key(data: Song): String = data.album.id.toString() +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt b/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt index 94c9a26f5..c6c309e82 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt @@ -13,8 +13,8 @@ import androidx.media.app.NotificationCompat.MediaStyle import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.MainActivity import org.oxycblt.auxio.R +import org.oxycblt.auxio.coil.getBitmap import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.music.coil.getBitmap import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackStateManager diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index 8bad99ccb..7706c72ea 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -43,9 +43,9 @@ import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch +import org.oxycblt.auxio.coil.getBitmap import org.oxycblt.auxio.logD import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.music.coil.getBitmap import org.oxycblt.auxio.music.toURI import org.oxycblt.auxio.playback.state.LoopMode import org.oxycblt.auxio.playback.state.PlaybackMode diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt index 5a9bf6e4e..7359de5be 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -10,6 +10,7 @@ import androidx.preference.PreferenceFragmentCompat import androidx.preference.children import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import coil.Coil import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.customview.customView import org.oxycblt.auxio.BuildConfig @@ -104,6 +105,17 @@ class SettingsListFragment : PreferenceFragmentCompat() { } } + SettingsManager.Keys.KEY_QUALITY_COVERS -> { + onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, _ -> + // Clear out any cached images, before recreating the activity + Coil.imageLoader(requireContext()).bitmapPool.clear() + + requireActivity().recreate() + + true + } + } + SettingsManager.Keys.KEY_DEBUG_SAVE -> { onPreferenceClickListener = Preference.OnPreferenceClickListener { playbackModel.save(requireContext()) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt index 0c7134c54..069d71b97 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsManager.kt @@ -73,6 +73,12 @@ class SettingsManager private constructor(context: Context) : ) ) + /** + * Whether to ignore MediaStore covers + */ + val qualityCovers: Boolean + get() = sharedPrefs.getBoolean(Keys.KEY_QUALITY_COVERS, false) + /** * Whether to do Audio focus. */ @@ -207,6 +213,7 @@ class SettingsManager private constructor(context: Context) : const val KEY_COLORIZE_NOTIFICATION = "KEY_COLOR_NOTIF" const val KEY_USE_ALT_NOTIFICATION_ACTION = "KEY_ALT_NOTIF_ACTION" const val KEY_LIBRARY_DISPLAY_MODE = "KEY_LIBRARY_DISPLAY_MODE" + const val KEY_QUALITY_COVERS = "KEY_QUALITY_COVERS" const val KEY_AUDIO_FOCUS = "KEY_AUDIO_FOCUS" const val KEY_PLUG_MANAGEMENT = "KEY_PLUG_MGT" const val KEY_SONG_PLAYBACK_MODE = "KEY_SONG_PLAY_MODE" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ce1be8c02..f85e38a7b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -64,6 +64,9 @@ Colorize notification] Show album art on notification + Ignore MediaStore covers + Results in higher quality album covers, but causes slower loading times + Use alternate notification action Prefer repeat mode action Prefer shuffle action @@ -73,7 +76,7 @@ Pause when other audio plays [ex. Alarms] Headset plug management - Play/Pause when headset connection changes + Play/Pause when the headset connection changes Behavior When a song is selected diff --git a/app/src/main/res/xml/prefs_main.xml b/app/src/main/res/xml/prefs_main.xml index 70b5bb1f9..59782aad8 100644 --- a/app/src/main/res/xml/prefs_main.xml +++ b/app/src/main/res/xml/prefs_main.xml @@ -34,6 +34,13 @@ app:key="KEY_LIBRARY_DISPLAY_MODE" app:useSimpleSummaryProvider="true" /> + +