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:
parent
cd930554bf
commit
1ddff8c6d3
4 changed files with 103 additions and 10 deletions
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue