From 1ddff8c6d3ad328ec723d0bd3738aaaf9deb2d06 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 30 May 2021 16:42:45 -0600 Subject: [PATCH] playback: create custom mediasession connector Create a custom session connector that connects between PlaybackStateManager and MediaSession. The previous session connector that was part of the exoplayer library turned out to cause bugs that could cause the covert art not to show on the lock screen and to not be recognized as proper player controls. This new connector [once complete] should fix these issues. --- app/build.gradle | 1 - .../playback/system/PlaybackNotification.kt | 2 +- .../auxio/playback/system/PlaybackService.kt | 14 ++- .../system/PlaybackSessionConnector.kt | 96 +++++++++++++++++++ 4 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt diff --git a/app/build.gradle b/app/build.gradle index 9d441b48c..c8a7e92bb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -93,7 +93,6 @@ dependencies { // ExoPlayer def exoplayer_version = "2.14.0" implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version" - implementation "com.google.android.exoplayer:extension-mediasession:$exoplayer_version" // Image loading implementation "io.coil-kt:coil:1.2.1" diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt index 6cd9e956e..ccea6fa25 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt @@ -40,7 +40,7 @@ class PlaybackNotification private constructor( setSmallIcon(R.drawable.ic_song) setCategory(NotificationCompat.CATEGORY_SERVICE) setShowWhen(false) - setNotificationSilent() + setSilent(true) setContentIntent(mainActivityIntent) setVisibility(NotificationCompat.VISIBILITY_PUBLIC) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 028f57124..4029812ce 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -23,7 +23,6 @@ import com.google.android.exoplayer2.RenderersFactory import com.google.android.exoplayer2.SimpleExoPlayer import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory import com.google.android.exoplayer2.mediacodec.MediaCodecSelector import com.google.android.exoplayer2.source.DefaultMediaSourceFactory @@ -60,6 +59,7 @@ import org.oxycblt.auxio.ui.getSystemServiceSafe class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback { private lateinit var player: SimpleExoPlayer private lateinit var mediaSession: MediaSessionCompat + private lateinit var connector: PlaybackSessionConnector private lateinit var notification: PlaybackNotification private lateinit var notificationManager: NotificationManager @@ -113,15 +113,10 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac // Set up the media button callbacks mediaSession = MediaSessionCompat(this, packageName).apply { isActive = true - - MediaSessionConnector(this).apply { - setPlayer(player) - setMediaButtonEventHandler { _, _, mediaButtonEvent -> - handleMediaButtonEvent(mediaButtonEvent) - } - } } + connector = PlaybackSessionConnector(this, mediaSession) + // Then the notif/headset callbacks IntentFilter().apply { addAction(PlaybackNotification.ACTION_LOOP) @@ -167,6 +162,7 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac unregisterReceiver(systemReceiver) player.release() + connector.release() mediaSession.release() audioReactor.release() releaseWakelock() @@ -305,6 +301,8 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac override fun onShowCoverUpdate(showCovers: Boolean) { playbackManager.song?.let { song -> + connector.onSongUpdate(song) + notification.setMetadata( this, song, settingsManager.colorizeNotif, ::startForegroundOrNotify ) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt new file mode 100644 index 000000000..3be251e6c --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackSessionConnector.kt @@ -0,0 +1,96 @@ +package org.oxycblt.auxio.playback.system + +import android.content.Context +import android.os.SystemClock +import android.support.v4.media.MediaMetadataCompat +import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat +import org.oxycblt.auxio.coil.loadBitmap +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.state.PlaybackStateManager + +class PlaybackSessionConnector( + private val context: Context, + private val mediaSession: MediaSessionCompat +) : PlaybackStateManager.Callback { + private val playbackManager = PlaybackStateManager.getInstance() + + private val emptyMetadata = MediaMetadataCompat.Builder().build() + private val state = PlaybackStateCompat.Builder() + .setActions(ACTIONS) + + private var playerState = PlaybackStateCompat.STATE_NONE + private var playerPosition = playbackManager.position + + init { + playbackManager.addCallback(this) + + onSongUpdate(playbackManager.song) + onPlayingUpdate(playbackManager.isPlaying) + onPositionUpdate(playbackManager.position) + } + + fun release() { + playbackManager.removeCallback(this) + } + + override fun onSongUpdate(song: Song?) { + if (song == null) { + mediaSession.setMetadata(emptyMetadata) + setPlayerState(PlaybackStateCompat.STATE_STOPPED) + return + } + + val builder = MediaMetadataCompat.Builder() + .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.name) + .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, song.name) + .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.album.artist.name) + .putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, song.album.artist.name) + .putString(MediaMetadataCompat.METADATA_KEY_COMPOSER, song.album.artist.name) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, song.album.artist.name) + .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.album.name) + .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) + + loadBitmap(context, song) { bitmap -> + builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap) + mediaSession.setMetadata(builder.build()) + } + } + + override fun onPlayingUpdate(isPlaying: Boolean) { + setPlayerState( + if (playbackManager.isPlaying) { + PlaybackStateCompat.STATE_PLAYING + } else { + PlaybackStateCompat.STATE_PAUSED + } + ) + } + + override fun onPositionUpdate(position: Long) { + playerPosition = position + updateState() + } + + private fun setPlayerState(state: Int) { + playerState = state + updateState() + } + + private fun updateState() { + state.setState(playerState, playerPosition, 1.0f, SystemClock.elapsedRealtime()) + mediaSession.setPlaybackState(state.build()) + } + + companion object { + const val ACTIONS = PlaybackStateCompat.ACTION_PLAY or + PlaybackStateCompat.ACTION_PAUSE or + PlaybackStateCompat.ACTION_PLAY_PAUSE or + PlaybackStateCompat.ACTION_SET_REPEAT_MODE or + PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE or + PlaybackStateCompat.ACTION_SKIP_TO_NEXT or + PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or + PlaybackStateCompat.ACTION_SEEK_TO or + PlaybackStateCompat.ACTION_STOP + } +}