From b0640cf1b241dc1af778371f51254e41900ed546 Mon Sep 17 00:00:00 2001 From: Antoine POPINEAU Date: Sun, 12 Jul 2020 18:28:50 +0200 Subject: [PATCH] Streamline the way the media session is controled across devices. --- app/src/main/AndroidManifest.xml | 33 +++++++++----- .../otter/playback/MediaControlsManager.kt | 43 ++++++------------- .../apognu/otter/playback/MediaSession.kt | 33 ++++---------- .../apognu/otter/playback/PlayerService.kt | 31 +++++++------ 4 files changed, 61 insertions(+), 79 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 60518b5..e44ec50 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -19,7 +20,7 @@ android:usesCleartextTraffic="true"> @@ -33,36 +34,48 @@ - + - + - + - + + android:name=".playback.PlayerService" + android:foregroundServiceType="mediaPlayback"> + + + + + + + + - + + + + + diff --git a/app/src/main/java/com/github/apognu/otter/playback/MediaControlsManager.kt b/app/src/main/java/com/github/apognu/otter/playback/MediaControlsManager.kt index eb197c2..e53d201 100644 --- a/app/src/main/java/com/github/apognu/otter/playback/MediaControlsManager.kt +++ b/app/src/main/java/com/github/apognu/otter/playback/MediaControlsManager.kt @@ -3,16 +3,19 @@ package com.github.apognu.otter.playback import android.app.Notification import android.app.PendingIntent import android.app.Service -import android.content.BroadcastReceiver -import android.content.Context import android.content.Intent import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.media.app.NotificationCompat.MediaStyle +import androidx.media.session.MediaButtonReceiver import com.github.apognu.otter.R import com.github.apognu.otter.activities.MainActivity -import com.github.apognu.otter.utils.* +import com.github.apognu.otter.utils.AppContext +import com.github.apognu.otter.utils.Track +import com.github.apognu.otter.utils.log +import com.github.apognu.otter.utils.maybeNormalizeUrl import com.squareup.picasso.Picasso import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.Default @@ -21,14 +24,13 @@ import kotlinx.coroutines.launch class MediaControlsManager(val context: Service, private val scope: CoroutineScope, private val mediaSession: MediaSessionCompat) { companion object { const val NOTIFICATION_ACTION_OPEN_QUEUE = 0 - const val NOTIFICATION_ACTION_PREVIOUS = 1 - const val NOTIFICATION_ACTION_TOGGLE = 2 - const val NOTIFICATION_ACTION_NEXT = 3 } private var notification: Notification? = null fun updateNotification(track: Track?, playing: Boolean) { + "updateNotification".log() + if (notification == null && !playing) return track?.let { @@ -74,19 +76,19 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco .addAction( action( R.drawable.previous, context.getString(R.string.control_previous), - NOTIFICATION_ACTION_PREVIOUS + PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS ) ) .addAction( action( stateIcon, context.getString(R.string.control_toggle), - NOTIFICATION_ACTION_TOGGLE + PlaybackStateCompat.ACTION_PLAY_PAUSE ) ) .addAction( action( R.drawable.next, context.getString(R.string.control_next), - NOTIFICATION_ACTION_NEXT + PlaybackStateCompat.ACTION_SKIP_TO_NEXT ) ) .build() @@ -102,26 +104,9 @@ class MediaControlsManager(val context: Service, private val scope: CoroutineSco } } - private fun action(icon: Int, title: String, id: Int): NotificationCompat.Action { - val intent = Intent(context, MediaControlActionReceiver::class.java).apply { action = id.toString() } - val pendingIntent = PendingIntent.getBroadcast(context, id, intent, 0) - - return NotificationCompat.Action.Builder(icon, title, pendingIntent).build() - } -} - -class MediaControlActionReceiver : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - val command = when (intent?.action) { - MediaControlsManager.NOTIFICATION_ACTION_PREVIOUS.toString() -> Command.PreviousTrack - MediaControlsManager.NOTIFICATION_ACTION_TOGGLE.toString() -> Command.ToggleState - MediaControlsManager.NOTIFICATION_ACTION_NEXT.toString() -> Command.NextTrack - else -> null - } - - command?.let { - CommandBus.send(command) - CommandBus.send(Command.StartService(command)) + private fun action(icon: Int, title: String, id: Long): NotificationCompat.Action { + return MediaButtonReceiver.buildMediaButtonPendingIntent(context, id).run { + NotificationCompat.Action.Builder(icon, title, this).build() } } } diff --git a/app/src/main/java/com/github/apognu/otter/playback/MediaSession.kt b/app/src/main/java/com/github/apognu/otter/playback/MediaSession.kt index 4fcf34e..7c811ae 100644 --- a/app/src/main/java/com/github/apognu/otter/playback/MediaSession.kt +++ b/app/src/main/java/com/github/apognu/otter/playback/MediaSession.kt @@ -6,8 +6,6 @@ import android.os.Bundle 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.Otter import com.github.apognu.otter.utils.Command import com.github.apognu.otter.utils.CommandBus import com.google.android.exoplayer2.ControlDispatcher @@ -15,7 +13,7 @@ import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector class MediaSession(private val context: Context) { - var active: Boolean = false + var active = false private val playbackStateBuilder = PlaybackStateCompat.Builder().apply { setActions( @@ -30,41 +28,28 @@ class MediaSession(private val context: Context) { } val session: MediaSessionCompat by lazy { - active = true - MediaSessionCompat(context, context.packageName).apply { setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS) setPlaybackState(playbackStateBuilder.build()) isActive = true + active = true } } val connector: MediaSessionConnector by lazy { - MediaSessionConnector(Otter.get().mediaSession.session).also { + MediaSessionConnector(session).also { it.setQueueNavigator(OtterQueueNavigator()) - it.setMediaButtonEventHandler { _, _, event -> + it.setMediaButtonEventHandler { _, _, intent -> if (!active) { - event.extras?.getParcelable(Intent.EXTRA_KEY_EVENT)?.let { key -> - if (key.action == KeyEvent.ACTION_UP) { - val command = when (key.keyCode) { - KeyEvent.KEYCODE_MEDIA_PLAY -> Command.ToggleState - KeyEvent.KEYCODE_MEDIA_PAUSE -> Command.ToggleState - KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> Command.ToggleState - KeyEvent.KEYCODE_MEDIA_NEXT -> Command.NextTrack - KeyEvent.KEYCODE_MEDIA_PREVIOUS -> Command.PreviousTrack - else -> null - } + context.startService(Intent(context, PlayerService::class.java).apply { + action = intent.action - command?.let { - CommandBus.send(command) - CommandBus.send(Command.StartService(command)) + intent.extras?.let { extras -> putExtras(extras) } + }) - return@setMediaButtonEventHandler true - } - } - } + return@setMediaButtonEventHandler true } false diff --git a/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt b/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt index 84ec263..903aac0 100644 --- a/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt +++ b/app/src/main/java/com/github/apognu/otter/playback/PlayerService.kt @@ -13,6 +13,7 @@ import android.os.Build import android.os.IBinder import android.support.v4.media.MediaMetadataCompat import androidx.core.app.NotificationManagerCompat +import androidx.media.session.MediaButtonReceiver import com.github.apognu.otter.Otter import com.github.apognu.otter.R import com.github.apognu.otter.utils.* @@ -55,18 +56,14 @@ class PlayerService : Service() { private lateinit var radioPlayer: RadioPlayer override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + intent?.action?.let { + if (it == Intent.ACTION_MEDIA_BUTTON) { + MediaButtonReceiver.handleIntent(Otter.get().mediaSession.session, intent) + } + } + if (!started) { watchEventBus() - - intent?.extras?.getString(INITIAL_COMMAND_KEY)?.let { - when (it) { - Command.SetState(true).toString() -> setPlaybackState(true) - Command.SetState(false).toString() -> setPlaybackState(false) - Command.ToggleState.toString() -> togglePlayback() - Command.NextTrack.toString() -> skipToNextTrack() - Command.PreviousTrack.toString() -> skipToPreviousTrack() - } - } } started = true @@ -98,8 +95,6 @@ class PlayerService : Service() { } } - Otter.get().mediaSession.active = true - mediaControlsManager = MediaControlsManager(this, scope, Otter.get().mediaSession.session) player = SimpleExoPlayer.Builder(this).build().apply { @@ -110,6 +105,8 @@ class PlayerService : Service() { } } + Otter.get().mediaSession.active = true + Otter.get().mediaSession.connector.apply { setPlayer(player) @@ -247,12 +244,12 @@ class PlayerService : Service() { audioManager.abandonAudioFocus(audioFocusChangeListener) }) - Otter.get().mediaSession.active = false - player.removeListener(playerEventListener) setPlaybackState(false) player.release() + Otter.get().mediaSession.active = false + super.onDestroy() } @@ -401,8 +398,10 @@ class PlayerService : Service() { override fun onTracksChanged(trackGroups: TrackGroupArray, trackSelections: TrackSelectionArray) { super.onTracksChanged(trackGroups, trackSelections) - queue.current = player.currentWindowIndex - mediaControlsManager.updateNotification(queue.current(), player.playWhenReady) + if (queue.current != player.currentWindowIndex) { + queue.current = player.currentWindowIndex + mediaControlsManager.updateNotification(queue.current(), player.playWhenReady) + } if (queue.get().isNotEmpty() && queue.current() == queue.get().last() && radioPlayer.isActive()) { scope.launch(IO) {