mirror of
https://github.com/apognu/otter
synced 2025-02-13 09:30:36 +01:00
Allow media session resuming from media buttons when service is killed.
This commit is contained in:
parent
b14b703f05
commit
b34810d631
@ -34,15 +34,22 @@
|
||||
android:name="com.github.apognu.otter.activities.LoginActivity"
|
||||
android:configChanges="screenSize|orientation"
|
||||
android:launchMode="singleInstance" />
|
||||
|
||||
<activity android:name="com.github.apognu.otter.activities.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name="com.github.apognu.otter.activities.SearchActivity"
|
||||
android:launchMode="singleTop" />
|
||||
|
||||
<activity android:name="com.github.apognu.otter.activities.DownloadsActivity" />
|
||||
|
||||
<activity android:name="com.github.apognu.otter.activities.SettingsActivity" />
|
||||
|
||||
<activity android:name="com.github.apognu.otter.activities.LicencesActivity" />
|
||||
|
||||
<service android:name="com.github.apognu.otter.playback.PlayerService" android:foregroundServiceType="mediaPlayback" />
|
||||
<service
|
||||
android:name="com.github.apognu.otter.playback.PlayerService"
|
||||
android:foregroundServiceType="mediaPlayback" />
|
||||
|
||||
<service
|
||||
android:name=".playback.PinService"
|
||||
|
@ -1,9 +1,8 @@
|
||||
package com.github.apognu.otter
|
||||
|
||||
import android.app.Application
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import android.support.v4.media.session.PlaybackStateCompat
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import com.github.apognu.otter.playback.MediaSession
|
||||
import com.github.apognu.otter.playback.QueueManager
|
||||
import com.github.apognu.otter.utils.*
|
||||
import com.google.android.exoplayer2.database.ExoDatabaseProvider
|
||||
@ -62,25 +61,7 @@ class Otter : Application() {
|
||||
}
|
||||
}
|
||||
|
||||
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 mediaSession: MediaSessionCompat by lazy {
|
||||
MediaSessionCompat(this, applicationContext.packageName).apply {
|
||||
setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
|
||||
setPlaybackState(playbackStateBuilder.build())
|
||||
isActive = true
|
||||
}
|
||||
}
|
||||
val mediaSession = MediaSession(this)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
@ -6,6 +6,7 @@ import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.*
|
||||
@ -43,7 +44,6 @@ import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@ -291,9 +291,18 @@ class MainActivity : AppCompatActivity() {
|
||||
CommandBus.get().collect { command ->
|
||||
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)
|
||||
|
@ -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<KeyEvent>(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) {}
|
||||
}
|
@ -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<KeyEvent>(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) {}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user