mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
Notification heart rating (#140)
This commit is contained in:
commit
539920965e
9 changed files with 338 additions and 79 deletions
|
|
@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.model
|
|||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.Keep
|
||||
import androidx.media3.common.HeartRating
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaItem.RequestMetadata
|
||||
import androidx.media3.common.MediaMetadata
|
||||
|
|
@ -243,6 +244,13 @@ class SessionMediaItem() {
|
|||
.setAlbumTitle(album)
|
||||
.setArtist(artist)
|
||||
.setArtworkUri(artworkUri)
|
||||
.setUserRating(HeartRating(starred != null))
|
||||
.setSupportedCommands(
|
||||
listOf(
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_ON,
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_OFF
|
||||
)
|
||||
)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
actionDeleteDownloadStorage();
|
||||
actionKeepScreenOn();
|
||||
actionAutoDownloadLyrics();
|
||||
actionMiniPlayerHeart();
|
||||
|
||||
bindMediaService();
|
||||
actionAppEqualizer();
|
||||
|
|
@ -358,6 +359,21 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
});
|
||||
}
|
||||
|
||||
private void actionMiniPlayerHeart() {
|
||||
SwitchPreference preference = findPreference("mini_shuffle_button_visibility");
|
||||
if (preference == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
preference.setChecked(Preferences.showShuffleInsteadOfHeart());
|
||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
Preferences.setShuffleInsteadOfHeart((Boolean) newValue);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionAutoDownloadLyrics() {
|
||||
SwitchPreference preference = findPreference("auto_download_lyrics");
|
||||
if (preference == null) {
|
||||
|
|
|
|||
|
|
@ -116,4 +116,13 @@ object Constants {
|
|||
const val HOME_SECTOR_RECENTLY_ADDED = "HOME_SECTOR_RECENTLY_ADDED"
|
||||
const val HOME_SECTOR_PINNED_PLAYLISTS = "HOME_SECTOR_PINNED_PLAYLISTS"
|
||||
const val HOME_SECTOR_SHARED = "HOME_SECTOR_SHARED"
|
||||
|
||||
const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON = "android.media3.session.demo.SHUFFLE_ON"
|
||||
const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF = "android.media3.session.demo.SHUFFLE_OFF"
|
||||
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_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"
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import androidx.media3.common.MediaItem;
|
|||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.HeartRating;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
|
|
@ -16,6 +17,7 @@ import com.cappielloantonio.tempo.repository.DownloadRepository;
|
|||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -83,6 +85,13 @@ public class MappingUtil {
|
|||
.setAlbumTitle(media.getAlbum())
|
||||
.setArtist(media.getArtist())
|
||||
.setArtworkUri(artworkUri)
|
||||
.setUserRating(new HeartRating(media.getStarred() != null))
|
||||
.setSupportedCommands(
|
||||
ImmutableList.of(
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_ON,
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_OFF
|
||||
)
|
||||
)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ object Preferences {
|
|||
private const val LAST_INSTANT_MIX = "last_instant_mix"
|
||||
private const val EQUALIZER_ENABLED = "equalizer_enabled"
|
||||
private const val EQUALIZER_BAND_LEVELS = "equalizer_band_levels"
|
||||
private const val MINI_SHUFFLE_BUTTON_VISIBILITY = "mini_shuffle_button_visibility"
|
||||
|
||||
@JvmStatic
|
||||
fun getServer(): String? {
|
||||
|
|
@ -359,6 +360,16 @@ object Preferences {
|
|||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showShuffleInsteadOfHeart(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(MINI_SHUFFLE_BUTTON_VISIBILITY, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setShuffleInsteadOfHeart(enabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(MINI_SHUFFLE_BUTTON_VISIBILITY, enabled).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showServerUnreachableDialog(): Boolean {
|
||||
return App.getInstance().preferences.getLong(
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@
|
|||
<string name="error_required">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_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_title">Filter</string>
|
||||
<string name="filter_artist">Filter artists</string>
|
||||
|
|
@ -332,6 +335,8 @@
|
|||
<string name="settings_queue_syncing_countdown">Sync timer</string>
|
||||
<string name="settings_queue_syncing_summary">If enabled, the user will have the ability to save their play queue and will have the ability to load state when opening the application.</string>
|
||||
<string name="settings_queue_syncing_title">Sync play queue for this user [Not Fully Baked]</string>
|
||||
<string name="settings_show_mini_shuffle_button">Show Shuffle button</string>
|
||||
<string name="settings_show_mini_shuffle_button_summary">If enabled, show the shuffle button, remove the heart in the mini player</string>
|
||||
<string name="settings_radio">Show radio</string>
|
||||
<string name="settings_radio_summary">If enabled, show the radio section. Restart the app for it to take full effect.</string>
|
||||
<string name="settings_auto_download_lyrics">Auto download lyrics</string>
|
||||
|
|
|
|||
|
|
@ -97,6 +97,13 @@
|
|||
android:defaultValue="true"
|
||||
android:summary="@string/settings_music_directory_summary"
|
||||
android:key="music_directory_section_visibility" />
|
||||
|
||||
<SwitchPreference
|
||||
android:title="@string/settings_show_mini_shuffle_button"
|
||||
android:defaultValue="false"
|
||||
android:summary="@string/settings_show_mini_shuffle_button_summary"
|
||||
android:key="mini_shuffle_button_visibility" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/settings_title_data">
|
||||
|
|
|
|||
|
|
@ -2,21 +2,42 @@ package com.cappielloantonio.tempo.service
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.concurrent.futures.CallbackToFutureAdapter
|
||||
import androidx.media3.common.HeartRating
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Rating
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.session.CommandButton
|
||||
import androidx.media3.session.LibraryResult
|
||||
import androidx.media3.session.MediaConstants
|
||||
import androidx.media3.session.MediaLibraryService
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.SessionCommand
|
||||
import androidx.media3.session.SessionError
|
||||
import androidx.media3.session.SessionResult
|
||||
import com.cappielloantonio.tempo.App
|
||||
import com.cappielloantonio.tempo.R
|
||||
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.cappielloantonio.tempo.util.Preferences
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
open class MediaLibrarySessionCallback(
|
||||
context: Context,
|
||||
|
|
@ -28,82 +49,245 @@ open class MediaLibrarySessionCallback(
|
|||
MediaBrowserTree.initialize(automotiveRepository)
|
||||
}
|
||||
|
||||
private val shuffleCommandButtons: List<CommandButton> = listOf(
|
||||
CommandButton.Builder()
|
||||
private val customCommandToggleShuffleModeOn = CommandButton.Builder()
|
||||
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
|
||||
.setSessionCommand(
|
||||
SessionCommand(
|
||||
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))
|
||||
.setSessionCommand(
|
||||
SessionCommand(
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY
|
||||
)
|
||||
).setIconResId(R.drawable.exo_icon_shuffle_on).build()
|
||||
)
|
||||
|
||||
private val repeatCommandButtons: List<CommandButton> = listOf(
|
||||
CommandButton.Builder()
|
||||
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(),
|
||||
CommandButton.Builder()
|
||||
.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(),
|
||||
CommandButton.Builder()
|
||||
.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 customLayoutCommandButtons: List<CommandButton> =
|
||||
shuffleCommandButtons + repeatCommandButtons
|
||||
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,
|
||||
)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
val mediaNotificationSessionCommands =
|
||||
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
|
||||
.also { builder ->
|
||||
(shuffleCommandButtons + repeatCommandButtons).forEach { commandButton ->
|
||||
customLayoutCommandButtons.forEach { commandButton ->
|
||||
commandButton.sessionCommand?.let { builder.add(it) }
|
||||
}
|
||||
}.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)
|
||||
override fun onConnect(
|
||||
session: MediaSession, controller: MediaSession.ControllerInfo
|
||||
): 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(
|
||||
controller
|
||||
) || session.isAutoCompanionController(controller)
|
||||
) {
|
||||
val customLayout = buildCustomLayout(session.player)
|
||||
|
||||
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
|
||||
.setAvailableSessionCommands(mediaNotificationSessionCommands)
|
||||
.setCustomLayout(customLayout).build()
|
||||
.setCustomLayout(buildCustomLayout(session.player))
|
||||
.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>()
|
||||
|
||||
val showShuffle = Preferences.showShuffleInsteadOfHeart()
|
||||
|
||||
if (!showShuffle) {
|
||||
if (player.currentMediaItem != null && !isRatingPending) {
|
||||
// Heart button
|
||||
if ((player.mediaMetadata.userRating as HeartRating?)?.isHeart == true) {
|
||||
customLayout.add(customCommandToggleHeartOff)
|
||||
} else {
|
||||
customLayout.add(customCommandToggleHeartOn)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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)
|
||||
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)
|
||||
override fun onCustomCommand(
|
||||
session: MediaSession,
|
||||
|
|
@ -111,9 +295,23 @@ open class MediaLibrarySessionCallback(
|
|||
customCommand: SessionCommand,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
|
||||
val mediaItemId = args.getString(
|
||||
MediaConstants.EXTRA_KEY_MEDIA_ID,
|
||||
session.player.currentMediaItem?.mediaId
|
||||
)
|
||||
|
||||
when (customCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> {
|
||||
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_ALL,
|
||||
CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE -> {
|
||||
|
|
@ -123,17 +321,32 @@ open class MediaLibrarySessionCallback(
|
|||
else -> Player.REPEAT_MODE_OFF
|
||||
}
|
||||
session.player.repeatMode = nextMode
|
||||
}
|
||||
else -> return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
|
||||
}
|
||||
|
||||
session.setCustomLayout(
|
||||
session.mediaNotificationControllerInfo!!,
|
||||
buildCustomLayout(session.player)
|
||||
)
|
||||
|
||||
updateMediaNotificationCustomLayout(session)
|
||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
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
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetLibraryRoot(
|
||||
session: MediaLibraryService.MediaLibrarySession,
|
||||
|
|
@ -186,17 +399,4 @@ open class MediaLibrarySessionCallback(
|
|||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||
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) {
|
||||
Preferences.setShuffleModeEnabled(shuffleModeEnabled)
|
||||
mediaLibrarySession.setCustomLayout(
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
Preferences.setRepeatMode(repeatMode)
|
||||
mediaLibrarySession.setCustomLayout(
|
||||
librarySessionCallback.buildCustomLayout(player)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue