Changed track metadata reporting method so it could work similarly across devices (notification, ambient display, lockscreen, watches, ...) (#55).

This commit is contained in:
Antoine POPINEAU 2020-07-08 12:46:52 +02:00
parent a3f74af076
commit b0d7ff393d
No known key found for this signature in database
GPG Key ID: A78AC64694F84063
2 changed files with 64 additions and 19 deletions

View File

@ -18,7 +18,9 @@ import com.github.apognu.otter.utils.*
import com.squareup.picasso.Picasso
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class MediaControlsManager(val context: Service, private val scope: CoroutineScope, private val mediaSession: MediaSessionCompat) {
companion object {
@ -30,6 +32,27 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
private var notification: Notification? = null
fun buildTrackMetadata(track: Track?): MediaMetadataCompat {
track?.let {
val coverUrl = maybeNormalizeUrl(track.album.cover.original)
return MediaMetadataCompat.Builder().apply {
putString(MediaMetadataCompat.METADATA_KEY_TITLE, track.title)
putString(MediaMetadataCompat.METADATA_KEY_ARTIST, track.artist.name)
putLong(MediaMetadata.METADATA_KEY_DURATION, (track.bestUpload()?.duration?.toLong() ?: 0L) * 1000)
try {
runBlocking(IO) {
this@apply.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, Picasso.get().load(coverUrl).get())
}
} catch (e: Exception) {
}
}.build()
}
return MediaMetadataCompat.Builder().build()
}
fun updateNotification(track: Track?, playing: Boolean) {
if (notification == null && !playing) return
@ -44,17 +67,6 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
val openPendingIntent = PendingIntent.getActivity(context, 0, openIntent, 0)
val coverUrl = maybeNormalizeUrl(track.album.cover.original)
val cover = coverUrl?.run { Picasso.get().load(coverUrl) }
mediaSession.setMetadata(MediaMetadataCompat.Builder().apply {
putString(MediaMetadata.METADATA_KEY_ARTIST, track.artist.name)
putString(MediaMetadata.METADATA_KEY_TITLE, track.title)
putLong(MediaMetadata.METADATA_KEY_DURATION, (track.bestUpload()?.duration?.toLong() ?: 0L) * 1000)
cover?.let {
try { putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, it.get()) } catch (_: Exception) {}
}
}.build())
notification = NotificationCompat.Builder(
context,
@ -69,11 +81,13 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco
)
.setSmallIcon(R.drawable.ottershape)
.run {
if (cover != null) {
try { setLargeIcon(cover.get()) } catch (_: Exception) {}
coverUrl?.let {
try { setLargeIcon(Picasso.get().load(coverUrl).get()) } catch (_: Exception) {}
this
} else this
return@run this
}
this
}
.setContentTitle(track.title)
.setContentText(track.artist.name)

View File

@ -9,15 +9,15 @@ import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.os.ResultReceiver
import android.support.v4.media.session.MediaSessionCompat
import android.support.v4.media.session.PlaybackStateCompat
import android.view.KeyEvent
import com.github.apognu.otter.R
import com.github.apognu.otter.utils.*
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
@ -84,6 +84,13 @@ class PlayerService : Service() {
mediaSession = MediaSessionCompat(this, applicationContext.packageName).apply {
isActive = true
setPlaybackState(PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY_PAUSE or
PlaybackStateCompat.ACTION_SEEK_TO or
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
).build())
}
mediaControlsManager = MediaControlsManager(this, scope, mediaSession)
@ -97,6 +104,30 @@ class PlayerService : Service() {
MediaSessionConnector(mediaSession).also {
it.setPlayer(this)
it.setMediaMetadataProvider {
mediaControlsManager.buildTrackMetadata(queue.current())
}
it.setQueueNavigator(object : MediaSessionConnector.QueueNavigator {
override fun onSkipToQueueItem(player: Player, controlDispatcher: ControlDispatcher, id: Long) {}
override fun onCurrentWindowIndexChanged(player: Player) {}
override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?) = true
override fun getSupportedQueueNavigatorActions(player: Player): Long {
return PlaybackStateCompat.ACTION_PLAY_PAUSE or PlaybackStateCompat.ACTION_SKIP_TO_NEXT or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
}
override fun onSkipToNext(player: Player, controlDispatcher: ControlDispatcher) {}
override fun getActiveQueueItemId(player: Player?) = 0L
override fun onSkipToPrevious(player: Player, controlDispatcher: ControlDispatcher) {}
override fun onTimelineChanged(player: Player) {}
})
it.setMediaButtonEventHandler { player, _, mediaButtonEvent ->
mediaButtonEvent.extras?.getParcelable<KeyEvent>(Intent.EXTRA_KEY_EVENT)?.let { key ->
if (key.action == KeyEvent.ACTION_UP) {