mirror of
https://github.com/apognu/otter
synced 2025-02-17 11:20:34 +01:00
Streamline the way the media session is controled across devices.
This commit is contained in:
parent
e7cb5e4c6e
commit
b0640cf1b2
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.github.apognu.otter">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@ -19,7 +20,7 @@
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<activity
|
||||
android:name="com.github.apognu.otter.activities.SplashActivity"
|
||||
android:name=".activities.SplashActivity"
|
||||
android:launchMode="singleInstance"
|
||||
android:noHistory="true">
|
||||
|
||||
@ -33,36 +34,48 @@
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="com.github.apognu.otter.activities.LoginActivity"
|
||||
android:name=".activities.LoginActivity"
|
||||
android:configChanges="screenSize|orientation"
|
||||
android:launchMode="singleInstance" />
|
||||
|
||||
<activity android:name="com.github.apognu.otter.activities.MainActivity" />
|
||||
<activity android:name=".activities.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name="com.github.apognu.otter.activities.SearchActivity"
|
||||
android:name=".activities.SearchActivity"
|
||||
android:launchMode="singleTop" />
|
||||
|
||||
<activity android:name="com.github.apognu.otter.activities.DownloadsActivity" />
|
||||
<activity android:name=".activities.DownloadsActivity" />
|
||||
|
||||
<activity android:name="com.github.apognu.otter.activities.SettingsActivity" />
|
||||
<activity android:name=".activities.SettingsActivity" />
|
||||
|
||||
<activity android:name="com.github.apognu.otter.activities.LicencesActivity" />
|
||||
<activity android:name=".activities.LicencesActivity" />
|
||||
|
||||
<service
|
||||
android:name="com.github.apognu.otter.playback.PlayerService"
|
||||
android:foregroundServiceType="mediaPlayback" />
|
||||
android:name=".playback.PlayerService"
|
||||
android:foregroundServiceType="mediaPlayback">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".playback.PinService"
|
||||
android:exported="false">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.exoplayer.downloadService.action.RESTART" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
|
||||
</service>
|
||||
|
||||
<receiver android:name="com.github.apognu.otter.playback.MediaControlActionReceiver" />
|
||||
<receiver android:name="androidx.media.session.MediaButtonReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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
|
||||
}
|
||||
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
|
||||
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user