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.
This commit is contained in:
OxygenCobalt 2020-12-31 12:17:50 -07:00
parent 5b2e0dc0f4
commit d7087fe0b1
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
9 changed files with 132 additions and 26 deletions

View file

@ -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)
}

View file

@ -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<List<Uri>> {
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.")
}
}

View file

@ -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<Song> {
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()
}

View file

@ -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

View file

@ -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

View file

@ -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())

View file

@ -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"

View file

@ -64,6 +64,9 @@
<string name="setting_color_notif">Colorize notification</string>]
<string name="setting_color_desc">Show album art on notification</string>
<string name="setting_quality_covers">Ignore MediaStore covers</string>
<string name="setting_quality_covers_desc">Results in higher quality album covers, but causes slower loading times</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_shuffle">Prefer shuffle action</string>
@ -73,7 +76,7 @@
<string name="setting_playback_focus_desc">Pause when other audio plays [ex. Alarms]</string>
<string name="setting_playback_plug_mgt">Headset plug management</string>
<string name="setting_playback_plug_mgt_desc">Play/Pause when headset connection changes</string>
<string name="setting_playback_plug_mgt_desc">Play/Pause when the headset connection changes</string>
<string name="setting_behavior">Behavior</string>
<string name="setting_behavior_song_playback_mode">When a song is selected</string>

View file

@ -34,6 +34,13 @@
app:key="KEY_LIBRARY_DISPLAY_MODE"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
android:title="@string/setting_quality_covers"
app:summary="@string/setting_quality_covers_desc"
app:iconSpaceReserved="false"
app:key="KEY_QUALITY_COVERS"
app:defaultValue="false" />
<SwitchPreferenceCompat
android:title="@string/setting_color_notif"
app:defaultValue="true"