diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 602a0c7..b97c382 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,15 +34,22 @@ android:name="com.github.apognu.otter.activities.LoginActivity" android:configChanges="screenSize|orientation" android:launchMode="singleInstance" /> + + + + + - + when (command) { is Command.StartService -> { - startService(Intent(this@MainActivity, PlayerService::class.java).apply { - putExtra(PlayerService.INITIAL_COMMAND_KEY, command.command.toString()) - }) + Build.VERSION_CODES.O.onApi( + { + startForegroundService(Intent(this@MainActivity, PlayerService::class.java).apply { + putExtra(PlayerService.INITIAL_COMMAND_KEY, command.command.toString()) + }) + }, + { + startService(Intent(this@MainActivity, PlayerService::class.java).apply { + putExtra(PlayerService.INITIAL_COMMAND_KEY, command.command.toString()) + }) + } + ) } is Command.RefreshTrack -> refreshCurrentTrack(command.track) 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 new file mode 100644 index 0000000..a01a30e --- /dev/null +++ b/app/src/main/java/com/github/apognu/otter/playback/MediaSession.kt @@ -0,0 +1,103 @@ +package com.github.apognu.otter.playback + +import android.content.Context +import android.content.Intent +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 +import com.google.android.exoplayer2.Player +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector + +class MediaSession(private val context: Context) { + var active: Boolean = false + + private val playbackStateBuilder = PlaybackStateCompat.Builder().apply { + setActions( + PlaybackStateCompat.ACTION_PLAY_PAUSE or + PlaybackStateCompat.ACTION_PLAY or + PlaybackStateCompat.ACTION_PAUSE or + PlaybackStateCompat.ACTION_SKIP_TO_NEXT or + PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or + PlaybackStateCompat.ACTION_SEEK_TO or + PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM + ) + } + + 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 + } + } + + val connector: MediaSessionConnector by lazy { + MediaSessionConnector(Otter.get().mediaSession.session).also { + it.setQueueNavigator(OtterQueueNavigator()) + + it.setMediaButtonEventHandler { _, _, event -> + 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 + } + + command?.let { + CommandBus.send(command) + CommandBus.send(Command.StartService(command)) + + return@setMediaButtonEventHandler true + } + } + } + } + + false + } + } + } +} + +class OtterQueueNavigator : MediaSessionConnector.QueueNavigator { + override fun onSkipToQueueItem(player: Player, controlDispatcher: ControlDispatcher, id: Long) { + CommandBus.send(Command.PlayTrack(id.toInt())) + } + + override fun onCurrentWindowIndexChanged(player: Player) {} + + override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?) = true + + override fun getSupportedQueueNavigatorActions(player: Player): Long { + return PlaybackStateCompat.ACTION_PLAY_PAUSE or + PlaybackStateCompat.ACTION_SKIP_TO_NEXT or + PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or + PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM + } + + override fun onSkipToNext(player: Player, controlDispatcher: ControlDispatcher) { + CommandBus.send(Command.NextTrack) + } + + override fun getActiveQueueItemId(player: Player?) = 0L + + override fun onSkipToPrevious(player: Player, controlDispatcher: ControlDispatcher) { + CommandBus.send(Command.PreviousTrack) + } + + override fun onTimelineChanged(player: Player) {} +} 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 c76e57c..8777857 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 @@ -10,17 +10,15 @@ import android.media.AudioFocusRequest import android.media.AudioManager import android.media.MediaMetadata import android.os.Build -import android.os.Bundle import android.os.IBinder -import android.os.ResultReceiver import android.support.v4.media.MediaMetadataCompat -import android.support.v4.media.session.PlaybackStateCompat -import android.view.KeyEvent import com.github.apognu.otter.Otter import com.github.apognu.otter.R import com.github.apognu.otter.utils.* -import com.google.android.exoplayer2.* -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector +import com.google.android.exoplayer2.C +import com.google.android.exoplayer2.ExoPlaybackException +import com.google.android.exoplayer2.Player +import com.google.android.exoplayer2.SimpleExoPlayer import com.google.android.exoplayer2.source.TrackGroupArray import com.google.android.exoplayer2.trackselection.TrackSelectionArray import com.squareup.picasso.Picasso @@ -61,6 +59,8 @@ class PlayerService : Service() { 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() @@ -97,7 +97,7 @@ class PlayerService : Service() { } } - mediaControlsManager = MediaControlsManager(this, scope, Otter.get().mediaSession) + mediaControlsManager = MediaControlsManager(this, scope, Otter.get().mediaSession.session) player = SimpleExoPlayer.Builder(this).build().apply { playWhenReady = false @@ -105,28 +105,13 @@ class PlayerService : Service() { playerEventListener = PlayerEventListener().also { addListener(it) } + } - MediaSessionConnector(Otter.get().mediaSession).also { - it.setPlayer(this) - it.setQueueNavigator(OtterQueueNavigator()) - it.setMediaMetadataProvider { - buildTrackMetadata(queue.current()) - } + Otter.get().mediaSession.connector.apply { + setPlayer(player) - it.setMediaButtonEventHandler { player, _, mediaButtonEvent -> - mediaButtonEvent.extras?.getParcelable(Intent.EXTRA_KEY_EVENT)?.let { key -> - if (key.action == KeyEvent.ACTION_UP) { - when (key.keyCode) { - KeyEvent.KEYCODE_MEDIA_PLAY -> setPlaybackState(true) - KeyEvent.KEYCODE_MEDIA_PAUSE -> setPlaybackState(false) - KeyEvent.KEYCODE_MEDIA_NEXT -> player.next() - KeyEvent.KEYCODE_MEDIA_PREVIOUS -> skipToPreviousTrack() - } - } - } - - true - } + setMediaMetadataProvider { + buildTrackMetadata(queue.current()) } } @@ -250,6 +235,8 @@ class PlayerService : Service() { audioManager.abandonAudioFocus(audioFocusChangeListener) }) + Otter.get().mediaSession.active = false + player.removeListener(playerEventListener) setPlaybackState(false) player.release() @@ -470,33 +457,4 @@ class PlayerService : Service() { } } } - - inner class OtterQueueNavigator : MediaSessionConnector.QueueNavigator { - override fun onSkipToQueueItem(player: Player, controlDispatcher: ControlDispatcher, id: Long) { - CommandBus.send(Command.PlayTrack(id.toInt())) - } - - override fun onCurrentWindowIndexChanged(player: Player) {} - - override fun onCommand(player: Player, controlDispatcher: ControlDispatcher, command: String, extras: Bundle?, cb: ResultReceiver?) = true - - override fun getSupportedQueueNavigatorActions(player: Player): Long { - return PlaybackStateCompat.ACTION_PLAY_PAUSE or - PlaybackStateCompat.ACTION_SKIP_TO_NEXT or - PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or - PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM - } - - override fun onSkipToNext(player: Player, controlDispatcher: ControlDispatcher) { - skipToNextTrack() - } - - override fun getActiveQueueItemId(player: Player?) = queue.current.toLong() - - override fun onSkipToPrevious(player: Player, controlDispatcher: ControlDispatcher) { - skipToPreviousTrack() - } - - override fun onTimelineChanged(player: Player) {} - } } \ No newline at end of file