Load album art from the musikcube server by default, if thumbnails

exist. Otherwise farm out to lastfm.
This commit is contained in:
casey langen 2017-11-13 00:02:49 -08:00
parent 850664a519
commit 35c3e4b534
3 changed files with 101 additions and 77 deletions

View File

@ -3,6 +3,7 @@ package io.casey.musikcube.remote.playback
import android.app.* import android.app.*
import android.content.* import android.content.*
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.media.AudioManager import android.media.AudioManager
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
@ -22,6 +23,7 @@ import com.bumptech.glide.request.transition.Transition
import io.casey.musikcube.remote.Application import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.MainActivity import io.casey.musikcube.remote.MainActivity
import io.casey.musikcube.remote.R import io.casey.musikcube.remote.R
import io.casey.musikcube.remote.data.ITrack
import io.casey.musikcube.remote.ui.extension.fallback import io.casey.musikcube.remote.ui.extension.fallback
import io.casey.musikcube.remote.ui.model.albumart.Size import io.casey.musikcube.remote.ui.model.albumart.Size
import io.casey.musikcube.remote.ui.view.GlideApp import io.casey.musikcube.remote.ui.view.GlideApp
@ -175,10 +177,8 @@ class SystemService : Service() {
private fun updateMediaSessionPlaybackState() { private fun updateMediaSessionPlaybackState() {
var mediaSessionState = PlaybackStateCompat.STATE_STOPPED var mediaSessionState = PlaybackStateCompat.STATE_STOPPED
var title = "-"
var album = "-"
var artist = "-"
var duration = 0 var duration = 0
var playing: ITrack? = null
if (playback != null) { if (playback != null) {
when (playback?.playbackState) { when (playback?.playbackState) {
@ -188,15 +188,14 @@ class SystemService : Service() {
else -> { } else -> { }
} }
val playing = playback!!.playingTrack playing = playback!!.playingTrack
title = fallback(playing.title, "-")
album = fallback(playing.album, "-")
artist = fallback(playing.artist, "-")
duration = ((playback?.duration ?: 0.0) * 1000).toInt() duration = ((playback?.duration ?: 0.0) * 1000).toInt()
} }
updateMetadata(title, artist, album, duration) Log.e(TAG, String.format("updatePlaybackState: %s", playing))
updateNotification(title, artist, album, mediaSessionState)
updateMetadata(playing, duration)
updateNotification(playing, mediaSessionState)
mediaSession?.setPlaybackState(PlaybackStateCompat.Builder() mediaSession?.setPlaybackState(PlaybackStateCompat.Builder()
.setState(mediaSessionState, 0, 0f) .setState(mediaSessionState, 0, 0f)
@ -204,60 +203,73 @@ class SystemService : Service() {
.build()) .build())
} }
private fun downloadAlbumArt(title: String, artist: String, album: String, duration: Int) { private fun downloadAlbumArt(track: ITrack, duration: Int) {
albumArt.reset() if (!albumArt.same(track) || albumArt.request == null) {
albumArt.url = getAlbumArtUrl(artist, album, Size.Mega) if (track.artist.isNotBlank() && track.album.isNotBlank()) {
val url = getAlbumArtUrl(track, Size.Mega)
val originalRequest = GlideApp.with(applicationContext) val originalRequest = GlideApp.with(applicationContext)
.asBitmap().load(albumArt.url).apply(BITMAP_OPTIONS) .asBitmap().load(url).apply(BITMAP_OPTIONS)
albumArt.reset(track)
albumArt.request = originalRequest albumArt.request = originalRequest
albumArt.target = originalRequest.into(object : SimpleTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) { albumArt.target = originalRequest.into(object : SimpleTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
override fun onResourceReady(bitmap: Bitmap?, transition: Transition<in Bitmap>?) { override fun onResourceReady(bitmap: Bitmap?, transition: Transition<in Bitmap>?) {
/* make sure the instance's current request is the same as this request. it's /* make sure the instance's current request is the same as this request. it's
possible we had another download request come in before this one finished */ possible we had another download request come in before this one finished */
if (albumArt.request == originalRequest) { if (albumArt.request == originalRequest) {
albumArt.bitmap = bitmap albumArt.bitmap = bitmap
updateMetadata(title, artist, album, duration)
}
albumArt.request = null albumArt.request = null
updateMetadata(track, duration)
}
}
override fun onLoadFailed(errorDrawable: Drawable?) {
if (albumArt.request == originalRequest) {
albumArt.request = null
}
} }
}) })
} }
}
else {
Log.d(TAG, "downloadAlbumArt already in flight")
}
}
private fun updateMetadata(title: String, artist: String, album: String, duration: Int) { private fun updateMetadata(track: ITrack?, duration: Int) {
var currentImage: Bitmap? = null var currentImage: Bitmap? = null
val albumArtEnabledInSettings = this.prefs.getBoolean( val albumArtEnabledInSettings = this.prefs.getBoolean(
Prefs.Key.ALBUM_ART_ENABLED, Prefs.Default.ALBUM_ART_ENABLED) Prefs.Key.ALBUM_ART_ENABLED, Prefs.Default.ALBUM_ART_ENABLED)
if (albumArtEnabledInSettings) { if (albumArtEnabledInSettings && track != null && albumArt.same(track)) {
val url = getAlbumArtUrl(artist, album, Size.Mega) if (albumArt.bitmap != null) {
if ("-" != artist && "-" != album && url != albumArt.url) {
downloadAlbumArt(title, artist, album, duration)
}
else if (albumArt.url == url) {
if (albumArt.bitmap == null) {
downloadAlbumArt(title, artist, album, duration)
}
currentImage = albumArt.bitmap currentImage = albumArt.bitmap
} }
} }
if (track != null && currentImage == null) {
downloadAlbumArt(track, duration)
}
mediaSession?.setMetadata(MediaMetadataCompat.Builder() mediaSession?.setMetadata(MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist) .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, track?.artist ?: "-")
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album) .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, track?.album ?: "-")
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title) .putString(MediaMetadataCompat.METADATA_KEY_TITLE, track?.title ?: "-")
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration.toLong()) .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration.toLong())
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, currentImage) .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, currentImage)
.build()) .build())
} }
private fun updateNotification(title: String, artist: String, album: String, state: Int) { private fun updateNotification(track: ITrack?, state: Int) {
val contentIntent = PendingIntent.getActivity( val contentIntent = PendingIntent.getActivity(
applicationContext, 1, MainActivity.getStartIntent(this), 0) applicationContext, 1, MainActivity.getStartIntent(this), 0)
val title = fallback(track?.title, "-")
val artist = fallback(track?.artist, "-")
val album = fallback(track?.album, "-")
val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL) val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.ic_notification) .setSmallIcon(R.drawable.ic_notification)
.setContentTitle(title) .setContentTitle(title)
@ -461,19 +473,23 @@ class SystemService : Service() {
} }
private class AlbumArt { private class AlbumArt {
var url: String? = null
var target: SimpleTarget<Bitmap>? = null var target: SimpleTarget<Bitmap>? = null
var request: GlideRequest<Bitmap>? = null var request: GlideRequest<Bitmap>? = null
var bitmap: Bitmap? = null var bitmap: Bitmap? = null
var track: ITrack? = null
fun reset() { fun reset(t: ITrack? = null) {
if (target != null && target?.request != null) { if (target != null && target?.request != null) {
target?.request?.clear() target?.request?.clear()
} }
url = null
bitmap = null bitmap = null
request = null request = null
target = null target = null
track = t
}
fun same(other: ITrack?): Boolean {
return track != null && other != null && other.externalId == track?.externalId
} }
} }

View File

@ -1,10 +1,14 @@
package io.casey.musikcube.remote.ui.model.albumart package io.casey.musikcube.remote.ui.model.albumart
import android.content.Context
import android.content.SharedPreferences
import android.util.LruCache import android.util.LruCache
import io.casey.musikcube.remote.Application
import io.casey.musikcube.remote.data.IAlbum import io.casey.musikcube.remote.data.IAlbum
import io.casey.musikcube.remote.data.ITrack import io.casey.musikcube.remote.data.ITrack
import io.casey.musikcube.remote.util.NetworkUtil import io.casey.musikcube.remote.util.NetworkUtil
import io.casey.musikcube.remote.util.Strings import io.casey.musikcube.remote.util.Strings
import io.casey.musikcube.remote.websocket.Prefs
import okhttp3.* import okhttp3.*
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@ -27,12 +31,35 @@ enum class Size constructor(internal val key: String, internal val order: Int) {
} }
} }
/* used to strip extraneous tags */
private val badPatterns = arrayOf(
Pattern.compile("(?i)^" + Pattern.quote("[") + "CD" + Pattern.quote("]")),
Pattern.compile("(?i)" + Pattern.quote("(") + "disc \\d*" + Pattern.quote(")") + "$"),
Pattern.compile("(?i)" + Pattern.quote("[") + "disc \\d*" + Pattern.quote("]") + "$"),
Pattern.compile("(?i)" + Pattern.quote("(+") + "video" + Pattern.quote(")") + "$"),
Pattern.compile("(?i)" + Pattern.quote("[+") + "video" + Pattern.quote("]") + "$"),
Pattern.compile("(?i)" + Pattern.quote("(") + "explicit" + Pattern.quote(")") + "$"),
Pattern.compile("(?i)" + Pattern.quote("[") + "explicit" + Pattern.quote("]") + "$"),
Pattern.compile("(?i)" + Pattern.quote("[+") + "digital booklet" + Pattern.quote("]") + "$"))
/* http://www.last.fm/group/Last.fm+Web+Services/forum/21604/_/522900 -- it's ok to
put our key in the code */
private val lastFmFormatUrl =
"http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=" +
"502c69bd3f9946e8e0beee4fcb28c4cd&artist=%s&album=%s&format=json&size=%s"
private val urlCache = LruCache<String, String>(500)
private val badUrlCache = LruCache<String, Boolean>(100)
private val inFlight = mutableMapOf<String, CountDownLatch>()
fun getUrl(album: IAlbum, size: Size = Size.Small): String? { fun getUrl(album: IAlbum, size: Size = Size.Small): String? {
return getUrl(album.albumArtist, album.name, size) return getThumbnailUrl(album.thumbnailId)
?: getUrl(album.albumArtist, album.name, size)
} }
fun getUrl(track: ITrack, size: Size = Size.Small): String? { fun getUrl(track: ITrack, size: Size = Size.Small): String? {
return getUrl(track.artist, track.album, size) return getThumbnailUrl(track.thumbnailId)
?: getUrl(track.artist, track.album, size)
} }
fun getUrl(artist: String = "", album: String = "", size: Size = Size.Small): String? { fun getUrl(artist: String = "", album: String = "", size: Size = Size.Small): String? {
@ -172,26 +199,20 @@ fun intercept(req: Request): Request? {
return result return result
} }
/* used to strip extraneous tags */ private val prefs by lazy {
private val badPatterns = arrayOf( Application.instance!!.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE)
Pattern.compile("(?i)^" + Pattern.quote("[") + "CD" + Pattern.quote("]")), }
Pattern.compile("(?i)" + Pattern.quote("(") + "disc \\d*" + Pattern.quote(")") + "$"),
Pattern.compile("(?i)" + Pattern.quote("[") + "disc \\d*" + Pattern.quote("]") + "$"),
Pattern.compile("(?i)" + Pattern.quote("(+") + "video" + Pattern.quote(")") + "$"),
Pattern.compile("(?i)" + Pattern.quote("[+") + "video" + Pattern.quote("]") + "$"),
Pattern.compile("(?i)" + Pattern.quote("(") + "explicit" + Pattern.quote(")") + "$"),
Pattern.compile("(?i)" + Pattern.quote("[") + "explicit" + Pattern.quote("]") + "$"),
Pattern.compile("(?i)" + Pattern.quote("[+") + "digital booklet" + Pattern.quote("]") + "$"))
/* http://www.last.fm/group/Last.fm+Web+Services/forum/21604/_/522900 -- it's ok to private fun getThumbnailUrl(id: Long): String? {
put our key in the code */ if (id > 0) {
private val lastFmFormatUrl = val host = prefs.getString(Prefs.Key.ADDRESS, Prefs.Default.ADDRESS)
"http://ws.audioscrobbler.com/2.0/?method=album.getinfo&api_key=" + val port = prefs.getInt(Prefs.Key.AUDIO_PORT, Prefs.Default.MAIN_PORT)
"502c69bd3f9946e8e0beee4fcb28c4cd&artist=%s&album=%s&format=json&size=%s" val ssl = prefs.getBoolean(Prefs.Key.SSL_ENABLED, Prefs.Default.SSL_ENABLED)
val scheme = if (ssl) "https" else "http"
private val urlCache = LruCache<String, String>(500) return "$scheme://$host:$port/thumbnail/$id"
private val badUrlCache = LruCache<String, Boolean>(100) }
private val inFlight = mutableMapOf<String, CountDownLatch>() return null
}
private fun dejunk(album: String): String { private fun dejunk(album: String): String {
var result = album var result = album

View File

@ -142,7 +142,7 @@ class MainMetadataView : FrameLayout {
setMetadataDisplayMode(DisplayMode.NoArtwork) setMetadataDisplayMode(DisplayMode.NoArtwork)
} }
else { else {
val newUrl = getAlbumArtUrl(artist, album, Size.Mega) ?: "" val newUrl = getAlbumArtUrl(playing, Size.Mega) ?: ""
if (newUrl != loadedAlbumArtUrl) { if (newUrl != loadedAlbumArtUrl) {
updateAlbumArt(newUrl) updateAlbumArt(newUrl)
} }
@ -226,19 +226,6 @@ class MainMetadataView : FrameLayout {
artistAndAlbumWithArt.text = builder artistAndAlbumWithArt.text = builder
} }
// private val thumbnailUrl: String
// get() {
// val playing = playbackService.playingTrack
// if (playing.thumbnailId > 0) {
// val host = prefs.getString(Prefs.Key.ADDRESS, Prefs.Default.ADDRESS)
// val port = prefs.getInt(Prefs.Key.AUDIO_PORT, Prefs.Default.MAIN_PORT)
// val ssl = prefs.getBoolean(Prefs.Key.SSL_ENABLED, Prefs.Default.SSL_ENABLED)
// val scheme = if (ssl) "https" else "http"
// return "$scheme://$host:$port/thumbnail/${playing.thumbnailId}"
// }
// return ""
// }
private fun updateAlbumArt(albumArtUrl: String = "") { private fun updateAlbumArt(albumArtUrl: String = "") {
if (playbackService.playbackState == PlaybackState.Stopped) { if (playbackService.playbackState == PlaybackState.Stopped) {
setMetadataDisplayMode(DisplayMode.NoArtwork) setMetadataDisplayMode(DisplayMode.NoArtwork)