playback: cleanup components
Fix miscellanious bugs and clean the component code. Currently the components are in a strange state. They are a big ball of mud with inconsistent lifecycles and callbacks. I want to find a way to unify them under a single lifecycle, but the competing nature of them makes this extremely difficult.
This commit is contained in:
parent
6adc5f8715
commit
d57f980148
4 changed files with 107 additions and 135 deletions
|
@ -23,38 +23,50 @@ import android.os.SystemClock
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
import android.support.v4.media.session.PlaybackStateCompat
|
||||||
|
import androidx.media.session.MediaButtonReceiver
|
||||||
import com.google.android.exoplayer2.Player
|
import com.google.android.exoplayer2.Player
|
||||||
import org.oxycblt.auxio.coil.loadBitmap
|
import org.oxycblt.auxio.coil.loadBitmap
|
||||||
import org.oxycblt.auxio.music.MusicParent
|
import org.oxycblt.auxio.music.MusicParent
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Nightmarish class that coordinates communication between [MediaSessionCompat], [Player], and
|
|
||||||
* [PlaybackStateManager].
|
|
||||||
*/
|
*/
|
||||||
class PlaybackSessionConnector(
|
class MediaSessionComponent(private val context: Context, private val player: Player) :
|
||||||
private val context: Context,
|
PlaybackStateManager.Callback,
|
||||||
private val player: Player,
|
Player.Listener,
|
||||||
private val mediaSession: MediaSessionCompat
|
SettingsManager.Callback,
|
||||||
) : PlaybackStateManager.Callback, Player.Listener, MediaSessionCompat.Callback() {
|
MediaSessionCompat.Callback() {
|
||||||
private val playbackManager = PlaybackStateManager.getInstance()
|
private val playbackManager = PlaybackStateManager.getInstance()
|
||||||
private val emptyMetadata = MediaMetadataCompat.Builder().build()
|
private val settingsManager = SettingsManager.getInstance()
|
||||||
|
|
||||||
|
private val mediaSession = MediaSessionCompat(context, context.packageName)
|
||||||
|
|
||||||
|
val token: MediaSessionCompat.Token
|
||||||
|
get() = mediaSession.sessionToken
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mediaSession.setCallback(this)
|
mediaSession.setCallback(this)
|
||||||
playbackManager.addCallback(this)
|
playbackManager.addCallback(this)
|
||||||
|
settingsManager.addCallback(this)
|
||||||
player.addListener(this)
|
player.addListener(this)
|
||||||
|
|
||||||
onSongChanged(playbackManager.song)
|
onSongChanged(playbackManager.song)
|
||||||
onPlayingChanged(playbackManager.isPlaying)
|
onPlayingChanged(playbackManager.isPlaying)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun handleMediaButtonIntent(intent: Intent) {
|
||||||
|
MediaButtonReceiver.handleIntent(mediaSession, intent)
|
||||||
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
playbackManager.removeCallback(this)
|
playbackManager.removeCallback(this)
|
||||||
|
settingsManager.removeCallback(this)
|
||||||
player.removeListener(this)
|
player.removeListener(this)
|
||||||
|
mediaSession.release()
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MEDIASESSION CALLBACKS ---
|
// --- MEDIASESSION CALLBACKS ---
|
||||||
|
@ -111,10 +123,6 @@ class PlaybackSessionConnector(
|
||||||
onSongChanged(playbackManager.song)
|
onSongChanged(playbackManager.song)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueueChanged(index: Int, queue: List<Song>) {
|
|
||||||
onSongChanged(playbackManager.song)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
||||||
onSongChanged(playbackManager.song)
|
onSongChanged(playbackManager.song)
|
||||||
}
|
}
|
||||||
|
@ -152,18 +160,42 @@ class PlaybackSessionConnector(
|
||||||
invalidateSessionState()
|
invalidateSessionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- EXOPLAYER CALLBACKS ---
|
override fun onRepeatChanged(repeatMode: RepeatMode) {
|
||||||
|
mediaSession.setRepeatMode(
|
||||||
|
when (repeatMode) {
|
||||||
|
RepeatMode.NONE -> PlaybackStateCompat.REPEAT_MODE_NONE
|
||||||
|
RepeatMode.TRACK -> PlaybackStateCompat.REPEAT_MODE_ONE
|
||||||
|
RepeatMode.ALL -> PlaybackStateCompat.REPEAT_MODE_ALL
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun onEvents(player: Player, events: Player.Events) {
|
override fun onShuffledChanged(isShuffled: Boolean) {
|
||||||
if (events.containsAny(
|
mediaSession.setShuffleMode(
|
||||||
Player.EVENT_POSITION_DISCONTINUITY,
|
if (isShuffled) {
|
||||||
Player.EVENT_PLAYBACK_STATE_CHANGED,
|
PlaybackStateCompat.SHUFFLE_MODE_ALL
|
||||||
Player.EVENT_PLAY_WHEN_READY_CHANGED,
|
} else {
|
||||||
Player.EVENT_IS_PLAYING_CHANGED,
|
PlaybackStateCompat.SHUFFLE_MODE_NONE
|
||||||
Player.EVENT_REPEAT_MODE_CHANGED,
|
})
|
||||||
Player.EVENT_PLAYBACK_PARAMETERS_CHANGED)) {
|
}
|
||||||
invalidateSessionState()
|
|
||||||
}
|
// -- SETTINGSMANAGER CALLBACKS --
|
||||||
|
|
||||||
|
override fun onShowCoverUpdate(showCovers: Boolean) {
|
||||||
|
onSongChanged(playbackManager.song)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQualityCoverUpdate(doQualityCovers: Boolean) {
|
||||||
|
onSongChanged(playbackManager.song)
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- EXOPLAYER CALLBACKS --
|
||||||
|
|
||||||
|
override fun onPositionDiscontinuity(
|
||||||
|
oldPosition: Player.PositionInfo,
|
||||||
|
newPosition: Player.PositionInfo,
|
||||||
|
reason: Int
|
||||||
|
) {
|
||||||
|
invalidateSessionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MISC ---
|
// --- MISC ---
|
||||||
|
@ -205,6 +237,8 @@ class PlaybackSessionConnector(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private val emptyMetadata = MediaMetadataCompat.Builder().build()
|
||||||
|
|
||||||
const val ACTIONS =
|
const val ACTIONS =
|
||||||
PlaybackStateCompat.ACTION_PLAY or
|
PlaybackStateCompat.ACTION_PLAY or
|
||||||
PlaybackStateCompat.ACTION_PAUSE or
|
PlaybackStateCompat.ACTION_PAUSE or
|
|
@ -27,24 +27,38 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.media.app.NotificationCompat.MediaStyle
|
import androidx.media.app.NotificationCompat.MediaStyle
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
|
import org.oxycblt.auxio.IntegerTable
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.coil.loadBitmap
|
import org.oxycblt.auxio.coil.loadBitmap
|
||||||
import org.oxycblt.auxio.music.MusicParent
|
import org.oxycblt.auxio.music.MusicParent
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
|
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||||
import org.oxycblt.auxio.util.newBroadcastIntent
|
import org.oxycblt.auxio.util.newBroadcastIntent
|
||||||
import org.oxycblt.auxio.util.newMainIntent
|
import org.oxycblt.auxio.util.newMainIntent
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The unified notification for [PlaybackService]. This is not self-sufficient, updates have to be
|
* The unified notification for [PlaybackService]. Due to the nature of how this notification is
|
||||||
* delivered manually.
|
* used, it is *not self-sufficient*. Updates have to be delivered manually, as to prevent state
|
||||||
|
* inconsistency when the foreground state is started.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
class PlaybackNotification
|
class NotificationComponent(private val context: Context, sessionToken: MediaSessionCompat.Token) :
|
||||||
private constructor(private val context: Context, mediaToken: MediaSessionCompat.Token) :
|
|
||||||
NotificationCompat.Builder(context, CHANNEL_ID) {
|
NotificationCompat.Builder(context, CHANNEL_ID) {
|
||||||
|
private val notificationManager = context.getSystemServiceSafe(NotificationManager::class)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val channel =
|
||||||
|
NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
context.getString(R.string.info_channel_name),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT)
|
||||||
|
|
||||||
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
setSmallIcon(R.drawable.ic_auxio)
|
setSmallIcon(R.drawable.ic_auxio)
|
||||||
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
setShowWhen(false)
|
setShowWhen(false)
|
||||||
|
@ -58,11 +72,11 @@ private constructor(private val context: Context, mediaToken: MediaSessionCompat
|
||||||
addAction(buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next))
|
addAction(buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next))
|
||||||
addAction(buildAction(context, PlaybackService.ACTION_EXIT, R.drawable.ic_exit))
|
addAction(buildAction(context, PlaybackService.ACTION_EXIT, R.drawable.ic_exit))
|
||||||
|
|
||||||
setStyle(MediaStyle().setMediaSession(mediaToken).setShowActionsInCompactView(1, 2, 3))
|
setStyle(MediaStyle().setMediaSession(sessionToken).setShowActionsInCompactView(1, 2, 3))
|
||||||
|
}
|
||||||
|
|
||||||
// Don't connect to PlaybackStateManager here. This is because it's possible for this
|
fun renotify() {
|
||||||
// notification to not be updated by PlaybackStateManager before PlaybackService pushes
|
notificationManager.notify(IntegerTable.NOTIFICATION_CODE, build())
|
||||||
// the notification, resulting in invalid metadata.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- STATE FUNCTIONS ---
|
// --- STATE FUNCTIONS ---
|
||||||
|
@ -71,13 +85,15 @@ private constructor(private val context: Context, mediaToken: MediaSessionCompat
|
||||||
* Set the metadata of the notification using [song].
|
* Set the metadata of the notification using [song].
|
||||||
* @param onDone What to do when the loading of the album art is finished
|
* @param onDone What to do when the loading of the album art is finished
|
||||||
*/
|
*/
|
||||||
fun setMetadata(song: Song, onDone: () -> Unit) {
|
fun updateMetadata(song: Song, parent: MusicParent?, onDone: () -> Unit) {
|
||||||
setContentTitle(song.resolveName(context))
|
setContentTitle(song.resolveName(context))
|
||||||
setContentText(song.resolveIndividualArtistName(context))
|
setContentText(song.resolveIndividualArtistName(context))
|
||||||
|
|
||||||
// On older versions of android [API <24], show the song's album on the subtext instead of
|
// Starting in API 24, the subtext field changed semantics from being below the content
|
||||||
// the current mode, as that makes more sense for the old style of media notifications.
|
// text to being above the title.
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
setSubText(parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs))
|
||||||
|
} else {
|
||||||
setSubText(song.resolveName(context))
|
setSubText(song.resolveName(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,28 +106,20 @@ private constructor(private val context: Context, mediaToken: MediaSessionCompat
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the playing icon on the notification */
|
/** Set the playing icon on the notification */
|
||||||
fun setPlaying(isPlaying: Boolean) {
|
fun updatePlaying(isPlaying: Boolean) {
|
||||||
mActions[2] = buildPlayPauseAction(context, isPlaying)
|
mActions[2] = buildPlayPauseAction(context, isPlaying)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update the first action to reflect the [repeatMode] given. */
|
/** Update the first action to reflect the [repeatMode] given. */
|
||||||
fun setRepeatMode(repeatMode: RepeatMode) {
|
fun updateRepeatMode(repeatMode: RepeatMode) {
|
||||||
mActions[0] = buildRepeatAction(context, repeatMode)
|
mActions[0] = buildRepeatAction(context, repeatMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update the first action to reflect whether the queue is shuffled or not */
|
/** Update the first action to reflect whether the queue is shuffled or not */
|
||||||
fun setShuffled(isShuffled: Boolean) {
|
fun updateShuffled(isShuffled: Boolean) {
|
||||||
mActions[0] = buildShuffleAction(context, isShuffled)
|
mActions[0] = buildShuffleAction(context, isShuffled)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Apply the current [parent] to the header of the notification. */
|
|
||||||
fun setParent(parent: MusicParent?) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return
|
|
||||||
|
|
||||||
// A blank parent always means that the mode is ALL_SONGS
|
|
||||||
setSubText(parent?.resolveName(context) ?: context.getString(R.string.lbl_all_songs))
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- NOTIFICATION ACTION BUILDERS ---
|
// --- NOTIFICATION ACTION BUILDERS ---
|
||||||
|
|
||||||
private fun buildPlayPauseAction(
|
private fun buildPlayPauseAction(
|
||||||
|
@ -161,24 +169,5 @@ private constructor(private val context: Context, mediaToken: MediaSessionCompat
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CHANNEL_ID = BuildConfig.APPLICATION_ID + ".channel.PLAYBACK"
|
const val CHANNEL_ID = BuildConfig.APPLICATION_ID + ".channel.PLAYBACK"
|
||||||
|
|
||||||
/** Build a new instance of [PlaybackNotification]. */
|
|
||||||
fun from(
|
|
||||||
context: Context,
|
|
||||||
notificationManager: NotificationManager,
|
|
||||||
mediaSession: MediaSessionCompat
|
|
||||||
): PlaybackNotification {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
val channel =
|
|
||||||
NotificationChannel(
|
|
||||||
CHANNEL_ID,
|
|
||||||
context.getString(R.string.info_channel_name),
|
|
||||||
NotificationManager.IMPORTANCE_DEFAULT)
|
|
||||||
|
|
||||||
notificationManager.createNotificationChannel(channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
return PlaybackNotification(context, mediaSession.sessionToken)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,19 +17,13 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.system
|
package org.oxycblt.auxio.playback.system
|
||||||
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.ServiceInfo
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.Build
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
|
||||||
import androidx.media.session.MediaButtonReceiver
|
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
import com.google.android.exoplayer2.MediaItem
|
import com.google.android.exoplayer2.MediaItem
|
||||||
|
@ -56,7 +50,6 @@ import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
import org.oxycblt.auxio.settings.SettingsManager
|
import org.oxycblt.auxio.settings.SettingsManager
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.widgets.WidgetController
|
import org.oxycblt.auxio.widgets.WidgetController
|
||||||
import org.oxycblt.auxio.widgets.WidgetProvider
|
import org.oxycblt.auxio.widgets.WidgetProvider
|
||||||
|
@ -71,25 +64,16 @@ import org.oxycblt.auxio.widgets.WidgetProvider
|
||||||
* This service relies on [PlaybackStateManager.Callback] and [SettingsManager.Callback], so
|
* This service relies on [PlaybackStateManager.Callback] and [SettingsManager.Callback], so
|
||||||
* therefore there's no need to bind to it to deliver commands.
|
* therefore there's no need to bind to it to deliver commands.
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*
|
|
||||||
* TODO: Move all external exposal from passing around PlaybackStateManager to passing around the
|
|
||||||
* MediaMetadata instance. Generally makes it easier to encapsulate this class.
|
|
||||||
*
|
|
||||||
* TODO: Move hasPlayed to here as well.
|
|
||||||
*/
|
*/
|
||||||
class PlaybackService :
|
class PlaybackService :
|
||||||
Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
|
Service(), Player.Listener, PlaybackStateManager.Callback, SettingsManager.Callback {
|
||||||
// Player components
|
// Player components
|
||||||
private lateinit var player: ExoPlayer
|
private lateinit var player: ExoPlayer
|
||||||
private lateinit var mediaSession: MediaSessionCompat
|
|
||||||
private lateinit var connector: PlaybackSessionConnector
|
|
||||||
private val replayGainProcessor = ReplayGainAudioProcessor()
|
private val replayGainProcessor = ReplayGainAudioProcessor()
|
||||||
|
|
||||||
// Notification components
|
|
||||||
private lateinit var notification: PlaybackNotification
|
|
||||||
private lateinit var notificationManager: NotificationManager
|
|
||||||
|
|
||||||
// System backend components
|
// System backend components
|
||||||
|
private lateinit var notificationComponent: NotificationComponent
|
||||||
|
private lateinit var mediaSessionComponent: MediaSessionComponent
|
||||||
private lateinit var widgets: WidgetController
|
private lateinit var widgets: WidgetController
|
||||||
private val systemReceiver = PlaybackReceiver()
|
private val systemReceiver = PlaybackReceiver()
|
||||||
|
|
||||||
|
@ -126,8 +110,8 @@ class PlaybackService :
|
||||||
// --- SYSTEM SETUP ---
|
// --- SYSTEM SETUP ---
|
||||||
|
|
||||||
widgets = WidgetController(this)
|
widgets = WidgetController(this)
|
||||||
mediaSession = MediaSessionCompat(this, packageName).apply { isActive = true }
|
mediaSessionComponent = MediaSessionComponent(this, player)
|
||||||
connector = PlaybackSessionConnector(this, player, mediaSession)
|
notificationComponent = NotificationComponent(this, mediaSessionComponent.token)
|
||||||
|
|
||||||
// Then the notification/headset callbacks
|
// Then the notification/headset callbacks
|
||||||
IntentFilter().apply {
|
IntentFilter().apply {
|
||||||
|
@ -145,11 +129,6 @@ class PlaybackService :
|
||||||
registerReceiver(systemReceiver, this)
|
registerReceiver(systemReceiver, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- NOTIFICATION SETUP ---
|
|
||||||
|
|
||||||
notificationManager = getSystemServiceSafe(NotificationManager::class)
|
|
||||||
notification = PlaybackNotification.from(this, notificationManager, mediaSession)
|
|
||||||
|
|
||||||
// --- PLAYBACKSTATEMANAGER SETUP ---
|
// --- PLAYBACKSTATEMANAGER SETUP ---
|
||||||
|
|
||||||
playbackManager.addCallback(this)
|
playbackManager.addCallback(this)
|
||||||
|
@ -168,7 +147,7 @@ class PlaybackService :
|
||||||
if (intent.action == Intent.ACTION_MEDIA_BUTTON) {
|
if (intent.action == Intent.ACTION_MEDIA_BUTTON) {
|
||||||
// Workaround to get GadgetBridge and other apps that blindly query for
|
// Workaround to get GadgetBridge and other apps that blindly query for
|
||||||
// ACTION_MEDIA_BUTTON working.
|
// ACTION_MEDIA_BUTTON working.
|
||||||
MediaButtonReceiver.handleIntent(mediaSession, intent)
|
mediaSessionComponent.handleMediaButtonIntent(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
|
@ -188,8 +167,7 @@ class PlaybackService :
|
||||||
|
|
||||||
serviceJob.cancel()
|
serviceJob.cancel()
|
||||||
player.release()
|
player.release()
|
||||||
connector.release()
|
mediaSessionComponent.release()
|
||||||
mediaSession.release()
|
|
||||||
widgets.release()
|
widgets.release()
|
||||||
|
|
||||||
playbackManager.removeCallback(this)
|
playbackManager.removeCallback(this)
|
||||||
|
@ -273,7 +251,8 @@ class PlaybackService :
|
||||||
logD("Setting player to ${song.rawName}")
|
logD("Setting player to ${song.rawName}")
|
||||||
player.setMediaItem(MediaItem.fromUri(song.uri))
|
player.setMediaItem(MediaItem.fromUri(song.uri))
|
||||||
player.prepare()
|
player.prepare()
|
||||||
notification.setMetadata(song, ::startForegroundOrNotify)
|
notificationComponent.updateMetadata(
|
||||||
|
song, playbackManager.parent, ::startForegroundOrNotify)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,20 +264,20 @@ class PlaybackService :
|
||||||
|
|
||||||
override fun onPlayingChanged(isPlaying: Boolean) {
|
override fun onPlayingChanged(isPlaying: Boolean) {
|
||||||
player.playWhenReady = isPlaying
|
player.playWhenReady = isPlaying
|
||||||
notification.setPlaying(isPlaying)
|
notificationComponent.updatePlaying(isPlaying)
|
||||||
startForegroundOrNotify()
|
startForegroundOrNotify()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRepeatChanged(repeatMode: RepeatMode) {
|
override fun onRepeatChanged(repeatMode: RepeatMode) {
|
||||||
if (!settingsManager.useAltNotifAction) {
|
if (!settingsManager.useAltNotifAction) {
|
||||||
notification.setRepeatMode(repeatMode)
|
notificationComponent.updateRepeatMode(repeatMode)
|
||||||
startForegroundOrNotify()
|
startForegroundOrNotify()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShuffledChanged(isShuffled: Boolean) {
|
override fun onShuffledChanged(isShuffled: Boolean) {
|
||||||
if (settingsManager.useAltNotifAction) {
|
if (settingsManager.useAltNotifAction) {
|
||||||
notification.setShuffled(isShuffled)
|
notificationComponent.updateShuffled(isShuffled)
|
||||||
startForegroundOrNotify()
|
startForegroundOrNotify()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -309,18 +288,11 @@ class PlaybackService :
|
||||||
|
|
||||||
// --- SETTINGSMANAGER OVERRIDES ---
|
// --- SETTINGSMANAGER OVERRIDES ---
|
||||||
|
|
||||||
override fun onColorizeNotifUpdate(doColorize: Boolean) {
|
|
||||||
playbackManager.song?.let { song ->
|
|
||||||
connector.onSongChanged(song)
|
|
||||||
notification.setMetadata(song, ::startForegroundOrNotify)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNotifActionUpdate(useAltAction: Boolean) {
|
override fun onNotifActionUpdate(useAltAction: Boolean) {
|
||||||
if (useAltAction) {
|
if (useAltAction) {
|
||||||
notification.setShuffled(playbackManager.isShuffled)
|
notificationComponent.updateShuffled(playbackManager.isShuffled)
|
||||||
} else {
|
} else {
|
||||||
notification.setRepeatMode(playbackManager.repeatMode)
|
notificationComponent.updateRepeatMode(playbackManager.repeatMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
startForegroundOrNotify()
|
startForegroundOrNotify()
|
||||||
|
@ -328,14 +300,15 @@ class PlaybackService :
|
||||||
|
|
||||||
override fun onShowCoverUpdate(showCovers: Boolean) {
|
override fun onShowCoverUpdate(showCovers: Boolean) {
|
||||||
playbackManager.song?.let { song ->
|
playbackManager.song?.let { song ->
|
||||||
connector.onSongChanged(song)
|
notificationComponent.updateMetadata(
|
||||||
notification.setMetadata(song, ::startForegroundOrNotify)
|
song, playbackManager.parent, ::startForegroundOrNotify)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQualityCoverUpdate(doQualityCovers: Boolean) {
|
override fun onQualityCoverUpdate(doQualityCovers: Boolean) {
|
||||||
playbackManager.song?.let { song ->
|
playbackManager.song?.let { song ->
|
||||||
notification.setMetadata(song, ::startForegroundOrNotify)
|
notificationComponent.updateMetadata(
|
||||||
|
song, playbackManager.parent, ::startForegroundOrNotify)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -398,20 +371,12 @@ class PlaybackService :
|
||||||
logD("Starting foreground/notifying")
|
logD("Starting foreground/notifying")
|
||||||
|
|
||||||
if (!isForeground) {
|
if (!isForeground) {
|
||||||
// Specify that this is a media service, if supported.
|
startForeground(IntegerTable.NOTIFICATION_CODE, notificationComponent.build())
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
startForeground(
|
|
||||||
IntegerTable.NOTIFICATION_CODE,
|
|
||||||
notification.build(),
|
|
||||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
|
|
||||||
} else {
|
|
||||||
startForeground(IntegerTable.NOTIFICATION_CODE, notification.build())
|
|
||||||
}
|
|
||||||
|
|
||||||
isForeground = true
|
isForeground = true
|
||||||
} else {
|
} else {
|
||||||
// If we are already in foreground just update the notification
|
// If we are already in foreground just update the notification
|
||||||
notificationManager.notify(IntegerTable.NOTIFICATION_CODE, notification.build())
|
notificationComponent.renotify()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -424,18 +389,6 @@ class PlaybackService :
|
||||||
saveScope.launch { playbackManager.saveStateToDatabase(this@PlaybackService) }
|
saveScope.launch { playbackManager.saveStateToDatabase(this@PlaybackService) }
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Metadata(
|
|
||||||
val title: String,
|
|
||||||
val album: String,
|
|
||||||
val artist: String,
|
|
||||||
val album_artist: String,
|
|
||||||
val genre: String,
|
|
||||||
val parent: String,
|
|
||||||
val year: Int,
|
|
||||||
val track: Int?,
|
|
||||||
val albumCover: Bitmap
|
|
||||||
)
|
|
||||||
|
|
||||||
/** A [BroadcastReceiver] for receiving general playback events from the system. */
|
/** A [BroadcastReceiver] for receiving general playback events from the system. */
|
||||||
private inner class PlaybackReceiver : BroadcastReceiver() {
|
private inner class PlaybackReceiver : BroadcastReceiver() {
|
||||||
private var initialHeadsetPlugEventHandled = false
|
private var initialHeadsetPlugEventHandled = false
|
||||||
|
|
|
@ -66,10 +66,6 @@ class WidgetController(private val context: Context) :
|
||||||
widget.update(context, playbackManager)
|
widget.update(context, playbackManager)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onQueueChanged(index: Int, queue: List<Song>) {
|
|
||||||
widget.update(context, playbackManager)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
override fun onNewPlayback(index: Int, queue: List<Song>, parent: MusicParent?) {
|
||||||
widget.update(context, playbackManager)
|
widget.update(context, playbackManager)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue