mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 09:33:33 +00:00
feat: notification-heart-rating
This commit is contained in:
parent
5891ec800c
commit
a940af934c
4 changed files with 286 additions and 79 deletions
|
|
@ -122,4 +122,7 @@ object Constants {
|
||||||
const val CUSTOM_COMMAND_TOGGLE_HEART_ON = "android.media3.session.demo.HEART_ON"
|
const val CUSTOM_COMMAND_TOGGLE_HEART_ON = "android.media3.session.demo.HEART_ON"
|
||||||
const val CUSTOM_COMMAND_TOGGLE_HEART_OFF = "android.media3.session.demo.HEART_OFF"
|
const val CUSTOM_COMMAND_TOGGLE_HEART_OFF = "android.media3.session.demo.HEART_OFF"
|
||||||
const val CUSTOM_COMMAND_TOGGLE_HEART_LOADING = "android.media3.session.demo.HEART_LOADING"
|
const val CUSTOM_COMMAND_TOGGLE_HEART_LOADING = "android.media3.session.demo.HEART_LOADING"
|
||||||
|
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF = "android.media3.session.demo.REPEAT_OFF"
|
||||||
|
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE = "android.media3.session.demo.REPEAT_ONE"
|
||||||
|
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL = "android.media3.session.demo.REPEAT_ALL"
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +88,9 @@
|
||||||
<string name="error_required">Required</string>
|
<string name="error_required">Required</string>
|
||||||
<string name="error_server_prefix">http or https prefix required</string>
|
<string name="error_server_prefix">http or https prefix required</string>
|
||||||
<string name="exo_download_notification_channel_name">Downloads</string>
|
<string name="exo_download_notification_channel_name">Downloads</string>
|
||||||
|
<string name="exo_controls_heart_off_description">Toggle Heart off</string>
|
||||||
|
<string name="exo_controls_heart_on_description">Toggle Heart on</string>
|
||||||
|
<string name="cast_expanded_controller_loading">Loading…</string>
|
||||||
<string name="filter_info_selection">Select two or more filters</string>
|
<string name="filter_info_selection">Select two or more filters</string>
|
||||||
<string name="filter_title">Filter</string>
|
<string name="filter_title">Filter</string>
|
||||||
<string name="filter_artist">Filter artists</string>
|
<string name="filter_artist">Filter artists</string>
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,41 @@ package com.cappielloantonio.tempo.service
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.concurrent.futures.CallbackToFutureAdapter
|
||||||
|
import androidx.media3.common.HeartRating
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.Rating
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.session.CommandButton
|
import androidx.media3.session.CommandButton
|
||||||
import androidx.media3.session.LibraryResult
|
import androidx.media3.session.LibraryResult
|
||||||
|
import androidx.media3.session.MediaConstants
|
||||||
import androidx.media3.session.MediaLibraryService
|
import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import androidx.media3.session.SessionCommand
|
import androidx.media3.session.SessionCommand
|
||||||
|
import androidx.media3.session.SessionError
|
||||||
import androidx.media3.session.SessionResult
|
import androidx.media3.session.SessionResult
|
||||||
|
import com.cappielloantonio.tempo.App
|
||||||
import com.cappielloantonio.tempo.R
|
import com.cappielloantonio.tempo.R
|
||||||
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||||
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.CUSTOM_COMMAND_TOGGLE_HEART_LOADING
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.CUSTOM_COMMAND_TOGGLE_HEART_OFF
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.CUSTOM_COMMAND_TOGGLE_HEART_ON
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.Callback
|
||||||
|
import retrofit2.Response
|
||||||
|
|
||||||
open class MediaLibrarySessionCallback(
|
open class MediaLibrarySessionCallback(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|
@ -28,82 +48,253 @@ open class MediaLibrarySessionCallback(
|
||||||
MediaBrowserTree.initialize(automotiveRepository)
|
MediaBrowserTree.initialize(automotiveRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val shuffleCommandButtons: List<CommandButton> = listOf(
|
private val customCommandToggleShuffleModeOn = CommandButton.Builder()
|
||||||
CommandButton.Builder()
|
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
|
||||||
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
|
.setSessionCommand(
|
||||||
.setSessionCommand(
|
SessionCommand(
|
||||||
SessionCommand(
|
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY
|
||||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY
|
)
|
||||||
)
|
).setIconResId(R.drawable.exo_icon_shuffle_off).build()
|
||||||
).setIconResId(R.drawable.exo_icon_shuffle_off).build(),
|
|
||||||
|
|
||||||
CommandButton.Builder()
|
private val customCommandToggleShuffleModeOff = CommandButton.Builder()
|
||||||
.setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description))
|
.setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description))
|
||||||
.setSessionCommand(
|
.setSessionCommand(
|
||||||
SessionCommand(
|
SessionCommand(
|
||||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY
|
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY
|
||||||
)
|
)
|
||||||
).setIconResId(R.drawable.exo_icon_shuffle_on).build()
|
).setIconResId(R.drawable.exo_icon_shuffle_on).build()
|
||||||
|
|
||||||
|
private val customCommandToggleRepeatModeOff = CommandButton.Builder()
|
||||||
|
.setDisplayName(context.getString(R.string.exo_controls_repeat_off_description))
|
||||||
|
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF, Bundle.EMPTY))
|
||||||
|
.setIconResId(R.drawable.exo_icon_repeat_off)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val customCommandToggleRepeatModeOne = CommandButton.Builder()
|
||||||
|
.setDisplayName(context.getString(R.string.exo_controls_repeat_one_description))
|
||||||
|
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE, Bundle.EMPTY))
|
||||||
|
.setIconResId(R.drawable.exo_icon_repeat_one)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val customCommandToggleRepeatModeAll = CommandButton.Builder()
|
||||||
|
.setDisplayName(context.getString(R.string.exo_controls_repeat_all_description))
|
||||||
|
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL, Bundle.EMPTY))
|
||||||
|
.setIconResId(R.drawable.exo_icon_repeat_all)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val customCommandToggleHeartOn = CommandButton.Builder()
|
||||||
|
.setDisplayName(context.getString(R.string.exo_controls_heart_on_description))
|
||||||
|
.setSessionCommand(
|
||||||
|
SessionCommand(
|
||||||
|
CUSTOM_COMMAND_TOGGLE_HEART_ON, Bundle.EMPTY
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setIconResId(R.drawable.ic_favorite)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val customCommandToggleHeartOff = CommandButton.Builder()
|
||||||
|
.setDisplayName(context.getString(R.string.exo_controls_heart_off_description))
|
||||||
|
.setSessionCommand(
|
||||||
|
SessionCommand(CUSTOM_COMMAND_TOGGLE_HEART_OFF, Bundle.EMPTY)
|
||||||
|
)
|
||||||
|
.setIconResId(R.drawable.ic_favorites_outlined)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Fake Command while waiting for like update command
|
||||||
|
private val customCommandToggleHeartLoading = CommandButton.Builder()
|
||||||
|
.setDisplayName(context.getString(R.string.cast_expanded_controller_loading))
|
||||||
|
.setSessionCommand(
|
||||||
|
SessionCommand(CUSTOM_COMMAND_TOGGLE_HEART_LOADING, Bundle.EMPTY)
|
||||||
|
)
|
||||||
|
.setIconResId(R.drawable.ic_bookmark_sync)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val customLayoutCommandButtons = listOf(
|
||||||
|
customCommandToggleShuffleModeOn,
|
||||||
|
customCommandToggleShuffleModeOff,
|
||||||
|
customCommandToggleRepeatModeOff,
|
||||||
|
customCommandToggleRepeatModeOne,
|
||||||
|
customCommandToggleRepeatModeAll,
|
||||||
|
customCommandToggleHeartOn,
|
||||||
|
customCommandToggleHeartOff,
|
||||||
|
customCommandToggleHeartLoading,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val repeatCommandButtons: List<CommandButton> = listOf(
|
|
||||||
CommandButton.Builder()
|
|
||||||
.setDisplayName(context.getString(R.string.exo_controls_repeat_off_description))
|
|
||||||
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF, Bundle.EMPTY))
|
|
||||||
.setIconResId(R.drawable.exo_icon_repeat_off)
|
|
||||||
.build(),
|
|
||||||
CommandButton.Builder()
|
|
||||||
.setDisplayName(context.getString(R.string.exo_controls_repeat_one_description))
|
|
||||||
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE, Bundle.EMPTY))
|
|
||||||
.setIconResId(R.drawable.exo_icon_repeat_one)
|
|
||||||
.build(),
|
|
||||||
CommandButton.Builder()
|
|
||||||
.setDisplayName(context.getString(R.string.exo_controls_repeat_all_description))
|
|
||||||
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL, Bundle.EMPTY))
|
|
||||||
.setIconResId(R.drawable.exo_icon_repeat_all)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
|
|
||||||
private val customLayoutCommandButtons: List<CommandButton> =
|
|
||||||
shuffleCommandButtons + repeatCommandButtons
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
val mediaNotificationSessionCommands =
|
val mediaNotificationSessionCommands =
|
||||||
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
|
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
|
||||||
.also { builder ->
|
.also { builder ->
|
||||||
(shuffleCommandButtons + repeatCommandButtons).forEach { commandButton ->
|
customLayoutCommandButtons.forEach { commandButton ->
|
||||||
commandButton.sessionCommand?.let { builder.add(it) }
|
commandButton.sessionCommand?.let { builder.add(it) }
|
||||||
}
|
}
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
fun buildCustomLayout(player: Player): ImmutableList<CommandButton> {
|
|
||||||
val shuffle = shuffleCommandButtons[if (player.shuffleModeEnabled) 1 else 0]
|
|
||||||
val repeat = when (player.repeatMode) {
|
|
||||||
Player.REPEAT_MODE_ONE -> repeatCommandButtons[1]
|
|
||||||
Player.REPEAT_MODE_ALL -> repeatCommandButtons[2]
|
|
||||||
else -> repeatCommandButtons[0]
|
|
||||||
}
|
|
||||||
return ImmutableList.of(shuffle, repeat)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
override fun onConnect(
|
override fun onConnect(
|
||||||
session: MediaSession, controller: MediaSession.ControllerInfo
|
session: MediaSession, controller: MediaSession.ControllerInfo
|
||||||
): MediaSession.ConnectionResult {
|
): MediaSession.ConnectionResult {
|
||||||
|
session.player.addListener(object : Player.Listener {
|
||||||
|
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// FIXME: I'm not sure this if is required anymore
|
||||||
if (session.isMediaNotificationController(controller) || session.isAutomotiveController(
|
if (session.isMediaNotificationController(controller) || session.isAutomotiveController(
|
||||||
controller
|
controller
|
||||||
) || session.isAutoCompanionController(controller)
|
) || session.isAutoCompanionController(controller)
|
||||||
) {
|
) {
|
||||||
val customLayout = buildCustomLayout(session.player)
|
|
||||||
|
|
||||||
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||||
.setAvailableSessionCommands(mediaNotificationSessionCommands)
|
.setAvailableSessionCommands(mediaNotificationSessionCommands)
|
||||||
.setCustomLayout(customLayout).build()
|
.setCustomLayout(buildCustomLayout(session.player))
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
|
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the mediaNotification after some changes
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun updateMediaNotificationCustomLayout(
|
||||||
|
session: MediaSession,
|
||||||
|
isRatingPending: Boolean = false
|
||||||
|
) {
|
||||||
|
session.setCustomLayout(
|
||||||
|
session.mediaNotificationControllerInfo!!,
|
||||||
|
buildCustomLayout(session.player, isRatingPending)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildCustomLayout(player: Player, isRatingPending: Boolean = false): ImmutableList<CommandButton> {
|
||||||
|
val customLayout = mutableListOf<CommandButton>()
|
||||||
|
|
||||||
|
//TODO: create a user setting to decide which of the buttons to show on the mini player
|
||||||
|
// // Add shuffle button
|
||||||
|
// customLayout.add(
|
||||||
|
// if (player.shuffleModeEnabled) customCommandToggleShuffleModeOff else customCommandToggleShuffleModeOn
|
||||||
|
// )
|
||||||
|
|
||||||
|
// Add repeat button
|
||||||
|
val repeatButton = when (player.repeatMode) {
|
||||||
|
Player.REPEAT_MODE_ONE -> customCommandToggleRepeatModeOne
|
||||||
|
Player.REPEAT_MODE_ALL -> customCommandToggleRepeatModeAll
|
||||||
|
else -> customCommandToggleRepeatModeOff
|
||||||
|
}
|
||||||
|
|
||||||
|
customLayout.add(repeatButton)
|
||||||
|
|
||||||
|
// HEART_DEBUG logging
|
||||||
|
Log.d("HEART_DEBUG:", "Current media item: ${player.currentMediaItem}")
|
||||||
|
Log.d("HEART_DEBUG:", "User rating: ${player.mediaMetadata.userRating}")
|
||||||
|
Log.d("HEART_DEBUG:", "Is rating pending: $isRatingPending")
|
||||||
|
|
||||||
|
// Add heart button if there's a current media item
|
||||||
|
if (player.currentMediaItem != null) {
|
||||||
|
if (isRatingPending) {
|
||||||
|
customLayout.add(customCommandToggleHeartLoading)
|
||||||
|
} else if ((player.mediaMetadata.userRating as HeartRating?)?.isHeart == true) {
|
||||||
|
customLayout.add(customCommandToggleHeartOff)
|
||||||
|
} else {
|
||||||
|
customLayout.add(customCommandToggleHeartOn)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d("HEART_DEBUG:", "No current media item - skipping heart button")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImmutableList.copyOf(customLayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setting rating without a mediaId will set the currently listened mediaId
|
||||||
|
override fun onSetRating(
|
||||||
|
session: MediaSession,
|
||||||
|
controller: MediaSession.ControllerInfo,
|
||||||
|
rating: Rating
|
||||||
|
): ListenableFuture<SessionResult> {
|
||||||
|
return onSetRating(session, controller, session.player.currentMediaItem!!.mediaId, rating)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSetRating(
|
||||||
|
session: MediaSession,
|
||||||
|
controller: MediaSession.ControllerInfo,
|
||||||
|
mediaId: String,
|
||||||
|
rating: Rating
|
||||||
|
): ListenableFuture<SessionResult> {
|
||||||
|
val isStaring = (rating as HeartRating).isHeart
|
||||||
|
|
||||||
|
val networkCall = if (isStaring)
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.mediaAnnotationClient
|
||||||
|
.star(mediaId, null, null)
|
||||||
|
else
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.mediaAnnotationClient
|
||||||
|
.unstar(mediaId, null, null)
|
||||||
|
|
||||||
|
return CallbackToFutureAdapter.getFuture { completer ->
|
||||||
|
networkCall.enqueue(object : Callback<ApiResponse?> {
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
override fun onResponse(
|
||||||
|
call: Call<ApiResponse?>,
|
||||||
|
response: Response<ApiResponse?>
|
||||||
|
) {
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
|
||||||
|
// Search if the media item in the player should be updated
|
||||||
|
for (i in 0 until session.player.mediaItemCount) {
|
||||||
|
val mediaItem = session.player.getMediaItemAt(i)
|
||||||
|
if (mediaItem.mediaId == mediaId) {
|
||||||
|
val newMetadata = mediaItem.mediaMetadata.buildUpon()
|
||||||
|
.setUserRating(HeartRating(isStaring)).build()
|
||||||
|
session.player.replaceMediaItem(
|
||||||
|
i,
|
||||||
|
mediaItem.buildUpon().setMediaMetadata(newMetadata).build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
completer.set(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
|
} else {
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
completer.set(
|
||||||
|
SessionResult(
|
||||||
|
SessionError(
|
||||||
|
response.code(),
|
||||||
|
response.message()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
override fun onFailure(call: Call<ApiResponse?>, t: Throwable) {
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
completer.set(
|
||||||
|
SessionResult(
|
||||||
|
SessionError(
|
||||||
|
SessionError.ERROR_UNKNOWN,
|
||||||
|
"An error as occurred"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
override fun onCustomCommand(
|
override fun onCustomCommand(
|
||||||
session: MediaSession,
|
session: MediaSession,
|
||||||
|
|
@ -111,9 +302,23 @@ open class MediaLibrarySessionCallback(
|
||||||
customCommand: SessionCommand,
|
customCommand: SessionCommand,
|
||||||
args: Bundle
|
args: Bundle
|
||||||
): ListenableFuture<SessionResult> {
|
): ListenableFuture<SessionResult> {
|
||||||
|
|
||||||
|
val mediaItemId = args.getString(
|
||||||
|
MediaConstants.EXTRA_KEY_MEDIA_ID,
|
||||||
|
session.player.currentMediaItem?.mediaId
|
||||||
|
)
|
||||||
|
|
||||||
when (customCommand.customAction) {
|
when (customCommand.customAction) {
|
||||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
|
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> {
|
||||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
|
session.player.shuffleModeEnabled = true
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
|
}
|
||||||
|
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> {
|
||||||
|
session.player.shuffleModeEnabled = false
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
|
}
|
||||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
|
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF,
|
||||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
|
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL,
|
||||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
|
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
|
||||||
|
|
@ -123,16 +328,31 @@ open class MediaLibrarySessionCallback(
|
||||||
else -> Player.REPEAT_MODE_OFF
|
else -> Player.REPEAT_MODE_OFF
|
||||||
}
|
}
|
||||||
session.player.repeatMode = nextMode
|
session.player.repeatMode = nextMode
|
||||||
|
updateMediaNotificationCustomLayout(session)
|
||||||
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
}
|
}
|
||||||
else -> return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
|
CUSTOM_COMMAND_TOGGLE_HEART_ON,
|
||||||
|
CUSTOM_COMMAND_TOGGLE_HEART_OFF -> {
|
||||||
|
val currentRating = session.player.mediaMetadata.userRating as? HeartRating
|
||||||
|
val isCurrentlyLiked = currentRating?.isHeart ?: false
|
||||||
|
|
||||||
|
val newLikedState = !isCurrentlyLiked
|
||||||
|
|
||||||
|
updateMediaNotificationCustomLayout(
|
||||||
|
session,
|
||||||
|
isRatingPending = true // Show loading state
|
||||||
|
)
|
||||||
|
return onSetRating(session, controller, HeartRating(newLikedState))
|
||||||
|
}
|
||||||
|
else -> return Futures.immediateFuture(
|
||||||
|
SessionResult(
|
||||||
|
SessionError(
|
||||||
|
SessionError.ERROR_NOT_SUPPORTED,
|
||||||
|
customCommand.customAction
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.setCustomLayout(
|
|
||||||
session.mediaNotificationControllerInfo!!,
|
|
||||||
buildCustomLayout(session.player)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetLibraryRoot(
|
override fun onGetLibraryRoot(
|
||||||
|
|
@ -186,17 +406,4 @@ open class MediaLibrarySessionCallback(
|
||||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
return MediaBrowserTree.search(query)
|
return MediaBrowserTree.search(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
|
||||||
"android.media3.session.demo.SHUFFLE_ON"
|
|
||||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
|
||||||
"android.media3.session.demo.SHUFFLE_OFF"
|
|
||||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF =
|
|
||||||
"android.media3.session.demo.REPEAT_OFF"
|
|
||||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE =
|
|
||||||
"android.media3.session.demo.REPEAT_ONE"
|
|
||||||
private const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL =
|
|
||||||
"android.media3.session.demo.REPEAT_ALL"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -219,16 +219,10 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
|
|
||||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||||
Preferences.setShuffleModeEnabled(shuffleModeEnabled)
|
Preferences.setShuffleModeEnabled(shuffleModeEnabled)
|
||||||
mediaLibrarySession.setCustomLayout(
|
|
||||||
librarySessionCallback.buildCustomLayout(player)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||||
Preferences.setRepeatMode(repeatMode)
|
Preferences.setRepeatMode(repeatMode)
|
||||||
mediaLibrarySession.setCustomLayout(
|
|
||||||
librarySessionCallback.buildCustomLayout(player)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue