playback: add event handling to mediasession
Add event handling to the MediaSession. This completes the new PlaybackSessionConnector class and possibly addresses the issue raised in #20.
This commit is contained in:
parent
1ddff8c6d3
commit
ab28fb6323
3 changed files with 80 additions and 81 deletions
|
@ -12,9 +12,7 @@ import android.media.AudioManager
|
|||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import android.support.v4.media.MediaMetadataCompat
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import android.view.KeyEvent
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.ExoPlaybackException
|
||||
import com.google.android.exoplayer2.MediaItem
|
||||
|
@ -35,7 +33,6 @@ import kotlinx.coroutines.flow.conflate
|
|||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.takeWhile
|
||||
import kotlinx.coroutines.launch
|
||||
import org.oxycblt.auxio.coil.loadBitmap
|
||||
import org.oxycblt.auxio.logD
|
||||
import org.oxycblt.auxio.music.Parent
|
||||
import org.oxycblt.auxio.music.Song
|
||||
|
@ -220,8 +217,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
|
|||
player.setMediaItem(MediaItem.fromUri(song.id.toURI()))
|
||||
player.prepare()
|
||||
|
||||
pushMetadataToSession(song)
|
||||
|
||||
notification.setMetadata(
|
||||
this, song, settingsManager.colorizeNotif, ::startForegroundOrNotify
|
||||
)
|
||||
|
@ -354,27 +349,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
|
|||
onSeek(playbackManager.position)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload the song metadata to the [MediaSessionCompat], so that things such as album art
|
||||
* show up on the lock screen.
|
||||
*/
|
||||
private fun pushMetadataToSession(song: Song) {
|
||||
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(this, song) { bitmap ->
|
||||
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
|
||||
mediaSession.setMetadata(builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start polling the position on a coroutine.
|
||||
*/
|
||||
|
@ -449,52 +423,6 @@ class PlaybackService : Service(), Player.Listener, PlaybackStateManager.Callbac
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a media button intent.
|
||||
*/
|
||||
private fun handleMediaButtonEvent(event: Intent): Boolean {
|
||||
val item = event.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
|
||||
|
||||
if (item != null && item.action == KeyEvent.ACTION_DOWN) {
|
||||
return when (item.keyCode) {
|
||||
// Play/Pause if any of the keys are play/pause
|
||||
KeyEvent.KEYCODE_MEDIA_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY,
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_HEADSETHOOK -> {
|
||||
playbackManager.setPlaying(!playbackManager.isPlaying)
|
||||
true
|
||||
}
|
||||
|
||||
// Go to the next song is the key is next
|
||||
KeyEvent.KEYCODE_MEDIA_NEXT -> {
|
||||
playbackManager.next()
|
||||
true
|
||||
}
|
||||
|
||||
// Go to the previous song if the key is back
|
||||
KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {
|
||||
playbackManager.prev()
|
||||
true
|
||||
}
|
||||
|
||||
// Rewind if the key is rewind
|
||||
KeyEvent.KEYCODE_MEDIA_REWIND -> {
|
||||
playbackManager.rewind()
|
||||
true
|
||||
}
|
||||
|
||||
// Stop the service entirely if the key was stop/close
|
||||
KeyEvent.KEYCODE_MEDIA_STOP, KeyEvent.KEYCODE_MEDIA_CLOSE -> {
|
||||
stopSelf()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* A [BroadcastReceiver] for receiving system events from the media notification or the headset.
|
||||
*/
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
package org.oxycblt.auxio.playback.system
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
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.LoopMode
|
||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||
|
||||
class PlaybackSessionConnector(
|
||||
private val context: Context,
|
||||
private val mediaSession: MediaSessionCompat
|
||||
) : PlaybackStateManager.Callback {
|
||||
) : PlaybackStateManager.Callback, MediaSessionCompat.Callback() {
|
||||
private val playbackManager = PlaybackStateManager.getInstance()
|
||||
|
||||
private val emptyMetadata = MediaMetadataCompat.Builder().build()
|
||||
|
@ -20,9 +22,9 @@ class PlaybackSessionConnector(
|
|||
.setActions(ACTIONS)
|
||||
|
||||
private var playerState = PlaybackStateCompat.STATE_NONE
|
||||
private var playerPosition = playbackManager.position
|
||||
|
||||
init {
|
||||
mediaSession.setCallback(this)
|
||||
playbackManager.addCallback(this)
|
||||
|
||||
onSongUpdate(playbackManager.song)
|
||||
|
@ -34,6 +36,62 @@ class PlaybackSessionConnector(
|
|||
playbackManager.removeCallback(this)
|
||||
}
|
||||
|
||||
// --- MEDIASESSION CALLBACKS ---
|
||||
|
||||
override fun onPlay() {
|
||||
playbackManager.setPlaying(true)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
playbackManager.setPlaying(false)
|
||||
}
|
||||
|
||||
override fun onSkipToNext() {
|
||||
playbackManager.next()
|
||||
}
|
||||
|
||||
override fun onSkipToPrevious() {
|
||||
playbackManager.prev()
|
||||
}
|
||||
|
||||
override fun onSeekTo(position: Long) {
|
||||
// Set the state to buffering to prevent weird delays on the duration counter when seeking.
|
||||
// And yes, STATE_PAUSED is the only state that works with this code. Because of course it is.
|
||||
setPlayerState(PlaybackStateCompat.STATE_PAUSED)
|
||||
playbackManager.seekTo(position)
|
||||
setPlayerState(getPlayerState())
|
||||
}
|
||||
|
||||
override fun onRewind() {
|
||||
playbackManager.rewind()
|
||||
}
|
||||
|
||||
override fun onSetRepeatMode(repeatMode: Int) {
|
||||
val mode = when (repeatMode) {
|
||||
PlaybackStateCompat.REPEAT_MODE_ALL -> LoopMode.ALL
|
||||
PlaybackStateCompat.REPEAT_MODE_GROUP -> LoopMode.ALL
|
||||
PlaybackStateCompat.REPEAT_MODE_ONE -> LoopMode.TRACK
|
||||
else -> LoopMode.NONE
|
||||
}
|
||||
|
||||
playbackManager.setLoopMode(mode)
|
||||
}
|
||||
|
||||
override fun onSetShuffleMode(shuffleMode: Int) {
|
||||
playbackManager.setShuffling(
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL ||
|
||||
shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
// Get the service to shut down with the ACTION_EXIT intent
|
||||
context.sendBroadcast(Intent(PlaybackNotification.ACTION_EXIT))
|
||||
}
|
||||
|
||||
// --- PLAYBACKSTATEMANAGER CALLBACKS ---
|
||||
|
||||
override fun onSongUpdate(song: Song?) {
|
||||
if (song == null) {
|
||||
mediaSession.setMetadata(emptyMetadata)
|
||||
|
@ -67,18 +125,31 @@ class PlaybackSessionConnector(
|
|||
)
|
||||
}
|
||||
|
||||
override fun onPositionUpdate(position: Long) {
|
||||
playerPosition = position
|
||||
override fun onSeek(position: Long) {
|
||||
updateState()
|
||||
}
|
||||
|
||||
// --- MISC ---
|
||||
|
||||
private fun setPlayerState(state: Int) {
|
||||
playerState = state
|
||||
updateState()
|
||||
}
|
||||
|
||||
private fun getPlayerState(): Int {
|
||||
if (playbackManager.song == null) {
|
||||
return PlaybackStateCompat.STATE_STOPPED
|
||||
}
|
||||
|
||||
return if (playbackManager.isPlaying) {
|
||||
PlaybackStateCompat.STATE_PLAYING
|
||||
} else {
|
||||
PlaybackStateCompat.STATE_PAUSED
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateState() {
|
||||
state.setState(playerState, playerPosition, 1.0f, SystemClock.elapsedRealtime())
|
||||
state.setState(playerState, playbackManager.position, 1.0f, SystemClock.elapsedRealtime())
|
||||
mediaSession.setPlaybackState(state.build())
|
||||
}
|
||||
|
||||
|
|
|
@ -28,13 +28,13 @@
|
|||
|
||||
<!--
|
||||
These exact flags, in this exact order, in this exact formatting somehow make
|
||||
the dialogs use the nicer material style. Please do not touch them.
|
||||
the dialogs use the nicer material style. Please do not touch this or format it.
|
||||
-->
|
||||
<item name="viewInflaterClass">
|
||||
com.google.android.material.theme.MaterialComponentsViewInflater
|
||||
</item>
|
||||
<!-- @formatter:off -->
|
||||
<item name="viewInflaterClass">com.google.android.material.theme.MaterialComponentsViewInflater</item>
|
||||
<item name="alertDialogTheme">@style/ThemeOverlay.MaterialComponents.Dialog.Alert</item>
|
||||
<item name="materialAlertDialogTheme">@style/Theme.CustomDialog</item>
|
||||
<!-- @formatter:on -->
|
||||
</style>
|
||||
|
||||
<!-- Toolbar theme -->
|
||||
|
|
Loading…
Reference in a new issue