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.
This commit is contained in:
OxygenCobalt 2021-05-30 16:42:45 -06:00
parent cd930554bf
commit 1ddff8c6d3
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 103 additions and 10 deletions

View file

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

View file

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

View file

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

View file

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