From ed6067760893a21077b90dc7cfa8a965d393abc4 Mon Sep 17 00:00:00 2001 From: antonio Date: Thu, 21 Dec 2023 09:52:00 +0100 Subject: [PATCH] feat: cleaned up MediaService class, added support for Android Auto repository --- .../service/MediaLibraryServiceCallback.kt | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 app/src/tempo/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt diff --git a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt new file mode 100644 index 00000000..2cf347de --- /dev/null +++ b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaLibraryServiceCallback.kt @@ -0,0 +1,241 @@ +package com.cappielloantonio.tempo.service + +import android.content.Context +import android.os.Bundle +import androidx.annotation.OptIn +import androidx.media3.common.MediaItem +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.CommandButton +import androidx.media3.session.LibraryResult +import androidx.media3.session.MediaLibraryService +import androidx.media3.session.MediaSession +import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionResult +import com.cappielloantonio.tempo.R +import com.cappielloantonio.tempo.repository.AutomotiveRepository +import com.google.common.collect.ImmutableList +import com.google.common.util.concurrent.Futures +import com.google.common.util.concurrent.ListenableFuture + +open class MediaLibrarySessionCallback( + context: Context, + automotiveRepository: AutomotiveRepository +) : + MediaLibraryService.MediaLibrarySession.Callback { + private val TAG = "MediaLibraryServiceCall" + + init { + MediaBrowserTree.initialize(context, automotiveRepository) + } + + private val customLayoutCommandButtons: List = listOf( + 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(), + + 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() + ) + + @OptIn(UnstableApi::class) + val mediaNotificationSessionCommands = + MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon() + .also { builder -> + customLayoutCommandButtons.forEach { commandButton -> + commandButton.sessionCommand?.let { builder.add(it) } + } + }.build() + + @OptIn(UnstableApi::class) + override fun onConnect( + session: MediaSession, controller: MediaSession.ControllerInfo + ): MediaSession.ConnectionResult { + if (session.isMediaNotificationController(controller) || session.isAutomotiveController( + controller + ) || session.isAutoCompanionController(controller) + ) { + val customLayout = + customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0] + + return MediaSession.ConnectionResult.AcceptedResultBuilder(session) + .setAvailableSessionCommands(mediaNotificationSessionCommands) + .setCustomLayout(ImmutableList.of(customLayout)).build() + } + + return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build() + } + + @OptIn(UnstableApi::class) + override fun onCustomCommand( + session: MediaSession, + controller: MediaSession.ControllerInfo, + customCommand: SessionCommand, + args: Bundle + ): ListenableFuture { + if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) { + session.player.shuffleModeEnabled = true + session.setCustomLayout( + session.mediaNotificationControllerInfo!!, + ImmutableList.of(customLayoutCommandButtons[1]) + ) + + return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) + } else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) { + session.player.shuffleModeEnabled = false + session.setCustomLayout( + session.mediaNotificationControllerInfo!!, + ImmutableList.of(customLayoutCommandButtons[0]) + ) + + return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) + } + + return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED)) + } + + override fun onGetLibraryRoot( + session: MediaLibraryService.MediaLibrarySession, + browser: MediaSession.ControllerInfo, + params: MediaLibraryService.LibraryParams? + ): ListenableFuture> { + return Futures.immediateFuture(LibraryResult.ofItem(MediaBrowserTree.getRootItem(), params)) + } + + override fun onGetChildren( + session: MediaLibraryService.MediaLibrarySession, + browser: MediaSession.ControllerInfo, + parentId: String, + page: Int, + pageSize: Int, + params: MediaLibraryService.LibraryParams? + ): ListenableFuture>> { + return MediaBrowserTree.getChildren(parentId, params) + } + + /* override fun onGetItem( + session: MediaLibraryService.MediaLibrarySession, + browser: MediaSession.ControllerInfo, + mediaId: String + ): ListenableFuture> { + Log.d(TAG, "onGetItem()") + + return MediaBrowserTree.getItem(mediaId) + } */ + + /* override fun onAddMediaItems( + mediaSession: MediaSession, + controller: MediaSession.ControllerInfo, + mediaItems: List + ): ListenableFuture> { + Log.d(TAG, "onAddMediaItems()") + + return Futures.immediateFuture(mediaItems) + } + + @OptIn(UnstableApi::class) + override fun onSetMediaItems( + mediaSession: MediaSession, + browser: MediaSession.ControllerInfo, + mediaItems: List, + startIndex: Int, + startPositionMs: Long + ): ListenableFuture { + Log.d(TAG, "onSetMediaItems()") + + val mediaItemss: MutableList = ArrayList() + + val mediaMetadata = MediaMetadata.Builder() + .setTitle("Titolo") + .setAlbumTitle("Titolo album") + .setArtist("Artista") + .setIsBrowsable(false) + .setIsPlayable(true) + .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) + .build() + + val mediaItem = MediaItem.Builder() + .setMediaId(mediaItems.get(0).mediaId) + .setMediaMetadata(mediaMetadata) + .setUri(MusicUtil.getStreamUri(mediaItems.get(0).mediaId)) + .build() + + mediaItemss.add(mediaItem) + + return Futures.immediateFuture( + MediaSession.MediaItemsWithStartPosition( + mediaItemss, 0, 0 + ) + ) + } */ + + /* @OptIn(UnstableApi::class) // MediaSession.MediaItemsWithStartPosition + private fun maybeExpandSingleItemToPlaylist( + mediaItem: MediaItem, startIndex: Int, startPositionMs: Long + ): MediaSession.MediaItemsWithStartPosition? { + var playlist = listOf() + var indexInPlaylist = startIndex + + MediaBrowserTree.getItem(mediaItem.mediaId)?.apply { + if (mediaMetadata.isBrowsable == true) { + playlist = MediaBrowserTree.getChildren(mediaId) + } else if (requestMetadata.searchQuery == null) { + MediaBrowserTree.getParentId(mediaId)?.let { + playlist = MediaBrowserTree.getChildren(it).map { mediaItem -> + if (mediaItem.mediaId == mediaId) MediaBrowserTree.expandItem(mediaItem)!! else mediaItem + } + + indexInPlaylist = MediaBrowserTree.getIndexInMediaItems(mediaId, playlist) + } + } + } + + if (playlist.isNotEmpty()) { + return MediaSession.MediaItemsWithStartPosition( + playlist, indexInPlaylist, startPositionMs + ) + } + + return null + } */ + + /* override fun onSearch( + session: MediaLibraryService.MediaLibrarySession, + browser: MediaSession.ControllerInfo, + query: String, + params: MediaLibraryService.LibraryParams? + ): ListenableFuture> { + session.notifySearchResultChanged(browser, query, MediaBrowserTree.search(query).size, params) + return Futures.immediateFuture(LibraryResult.ofVoid()) + } */ + + /* override fun onGetSearchResult( + session: MediaLibraryService.MediaLibrarySession, + browser: MediaSession.ControllerInfo, + query: String, + page: Int, + pageSize: Int, + params: MediaLibraryService.LibraryParams? + ): ListenableFuture>> { + return Futures.immediateFuture( + LibraryResult.ofItemList( + MediaBrowserTree.search(query), params + ) + ) + } */ + + 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" + } +} \ No newline at end of file