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:
parent
5b2e0dc0f4
commit
d7087fe0b1
9 changed files with 132 additions and 26 deletions
|
@ -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)
|
||||
}
|
|
@ -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.")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue