From 96a4ae7e40732dccd2fdded4cb8d0cc7f25f8cdd Mon Sep 17 00:00:00 2001 From: bachinger Date: Mon, 22 May 2023 20:44:05 +0100 Subject: [PATCH] Minor session demo app improvements Basically this change removes a bug that makes video playback stuck when a video is playing and the user taps the UMO notification to get to the player activity. - Use `launchMode="singleTop"` for `PlayerActivity` - Change session activity to a back stacked activity on service `onDestroy`. Using a back stacked activity `onDestroy()` will be useful once this demo app implements playback resumption. The rest of the changes are aesthetic: - clean up and optimize screen space usage in UI of `PlayerActivity` - changed some colors, paddings and spacings - adds a default artwork for the `PlayerView` PiperOrigin-RevId: 534152052 --- demos/session/src/main/AndroidManifest.xml | 4 +- .../media3/demo/session/MainActivity.kt | 7 +- .../demo/session/PlayableFolderActivity.kt | 10 +- .../media3/demo/session/PlaybackService.kt | 35 +++-- .../media3/demo/session/PlayerActivity.kt | 131 ++++++------------ .../main/res/drawable/artwork_placeholder.png | Bin 0 -> 40772 bytes .../src/main/res/layout/activity_player.xml | 82 ++++------- .../src/main/res/layout/playlist_items.xml | 3 + demos/session/src/main/res/values/colors.xml | 5 + 9 files changed, 113 insertions(+), 164 deletions(-) create mode 100644 demos/session/src/main/res/drawable/artwork_placeholder.png diff --git a/demos/session/src/main/AndroidManifest.xml b/demos/session/src/main/AndroidManifest.xml index 90557fc6c7..d3f9ba1620 100644 --- a/demos/session/src/main/AndroidManifest.xml +++ b/demos/session/src/main/AndroidManifest.xml @@ -40,7 +40,9 @@ + android:exported="true" + android:launchMode="singleTop" + android:theme="@style/Theme.AppCompat.NoActionBar"/> (R.id.open_player_floating_button) .setOnClickListener { - // display the playing media items - val intent = Intent(this, PlayerActivity::class.java) - startActivity(intent) + // Start the session activity that shows the playback activity. The System UI uses the same + // intent in the same way to start the activity from the notification. + browser?.sessionActivity?.send() } onBackPressedDispatcher.addCallback( diff --git a/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt b/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt index 9c6f3e5b4a..f0dbec48fc 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt @@ -51,6 +51,7 @@ class PlayableFolderActivity : AppCompatActivity() { companion object { private const val MEDIA_ITEM_ID_KEY = "MEDIA_ITEM_ID_KEY" + fun createIntent(context: Context, mediaItemID: String): Intent { val intent = Intent(context, PlayableFolderActivity::class.java) intent.putExtra(MEDIA_ITEM_ID_KEY, mediaItemID) @@ -77,8 +78,7 @@ class PlayableFolderActivity : AppCompatActivity() { browser.shuffleModeEnabled = false browser.prepare() browser.play() - val intent = Intent(this, PlayerActivity::class.java) - startActivity(intent) + browser.sessionActivity?.send() } } @@ -104,9 +104,9 @@ class PlayableFolderActivity : AppCompatActivity() { findViewById(R.id.open_player_floating_button) .setOnClickListener { - // display the playing media items - val intent = Intent(this, PlayerActivity::class.java) - startActivity(intent) + // Start the session activity that shows the playback activity. The System UI uses the same + // intent in the same way to start the activity from the notification. + browser?.sessionActivity?.send() } } diff --git a/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt b/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt index eff81c8beb..26d396a7d6 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt @@ -18,6 +18,7 @@ package androidx.media3.demo.session import android.annotation.SuppressLint import android.app.NotificationChannel import android.app.NotificationManager +import android.app.PendingIntent import android.app.PendingIntent.* import android.app.TaskStackBuilder import android.content.Intent @@ -55,6 +56,7 @@ class PlaybackService : MediaLibraryService() { "android.media3.session.demo.SHUFFLE_OFF" private const val NOTIFICATION_ID = 123 private const val CHANNEL_ID = "demo_session_notification_channel_id" + private val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0 } override fun onCreate() { @@ -78,14 +80,15 @@ class PlaybackService : MediaLibraryService() { } override fun onTaskRemoved(rootIntent: Intent?) { - if (!player.playWhenReady) { + if (!player.playWhenReady || player.mediaItemCount == 0) { stopSelf() } } override fun onDestroy() { - player.release() + mediaLibrarySession.setSessionActivity(getBackStackedActivity()) mediaLibrarySession.release() + player.release() clearListener() super.onDestroy() } @@ -235,18 +238,9 @@ class PlaybackService : MediaLibraryService() { .build() MediaItemTree.initialize(assets) - val sessionActivityPendingIntent = - TaskStackBuilder.create(this).run { - addNextIntent(Intent(this@PlaybackService, MainActivity::class.java)) - addNextIntent(Intent(this@PlaybackService, PlayerActivity::class.java)) - - val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0 - getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT) - } - mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback) - .setSessionActivity(sessionActivityPendingIntent) + .setSessionActivity(getSingleTopActivity()) .setBitmapLoader(CacheBitmapLoader(DataSourceBitmapLoader(/* context= */ this))) .build() if (!customLayout.isEmpty()) { @@ -255,6 +249,23 @@ class PlaybackService : MediaLibraryService() { } } + private fun getSingleTopActivity(): PendingIntent { + return getActivity( + this, + 0, + Intent(this, PlayerActivity::class.java), + immutableFlag or FLAG_UPDATE_CURRENT + ) + } + + private fun getBackStackedActivity(): PendingIntent { + return TaskStackBuilder.create(this).run { + addNextIntent(Intent(this@PlaybackService, MainActivity::class.java)) + addNextIntent(Intent(this@PlaybackService, PlayerActivity::class.java)) + getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT) + } + } + private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton { val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON return CommandButton.Builder() diff --git a/demos/session/src/main/java/androidx/media3/demo/session/PlayerActivity.kt b/demos/session/src/main/java/androidx/media3/demo/session/PlayerActivity.kt index 452b901da5..b769bd3967 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/PlayerActivity.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/PlayerActivity.kt @@ -19,7 +19,6 @@ import android.content.ComponentName import android.content.Context import android.os.Bundle import android.view.LayoutInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter @@ -46,41 +45,33 @@ class PlayerActivity : AppCompatActivity() { get() = if (controllerFuture.isDone) controllerFuture.get() else null private lateinit var playerView: PlayerView - private lateinit var mediaList: ListView - private lateinit var mediaListAdapter: PlayingMediaItemArrayAdapter - private val subItemMediaList: MutableList = mutableListOf() + private lateinit var mediaItemListView: ListView + private lateinit var mediaItemListAdapter: MediaItemListAdapter + private val mediaItemList: MutableList = mutableListOf() + private var lastMediaItemId: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_player) playerView = findViewById(R.id.player_view) - mediaList = findViewById(R.id.current_playing_list) - mediaListAdapter = PlayingMediaItemArrayAdapter(this, R.layout.folder_items, subItemMediaList) - mediaList.adapter = mediaListAdapter - mediaList.setOnItemClickListener { _, _, position, _ -> + mediaItemListView = findViewById(R.id.current_playing_list) + mediaItemListAdapter = MediaItemListAdapter(this, R.layout.folder_items, mediaItemList) + mediaItemListView.adapter = mediaItemListAdapter + mediaItemListView.setOnItemClickListener { _, _, position, _ -> run { val controller = this.controller ?: return@run - controller.seekToDefaultPosition(/* windowIndex= */ position) - mediaListAdapter.notifyDataSetChanged() + if (controller.currentMediaItemIndex == position) { + controller.playWhenReady = !controller.playWhenReady + if (controller.playWhenReady) { + playerView.hideController() + } + } else { + controller.seekToDefaultPosition(/* mediaItemIndex= */ position) + mediaItemListAdapter.notifyDataSetChanged() + } } } - - findViewById(R.id.shuffle_switch).setOnClickListener { - val controller = this.controller ?: return@setOnClickListener - controller.shuffleModeEnabled = !controller.shuffleModeEnabled - } - - findViewById(R.id.repeat_switch).setOnClickListener { - val controller = this.controller ?: return@setOnClickListener - when (controller.repeatMode) { - Player.REPEAT_MODE_ALL -> controller.repeatMode = Player.REPEAT_MODE_OFF - Player.REPEAT_MODE_OFF -> controller.repeatMode = Player.REPEAT_MODE_ONE - Player.REPEAT_MODE_ONE -> controller.repeatMode = Player.REPEAT_MODE_ALL - } - } - - supportActionBar!!.setDisplayHomeAsUpEnabled(true) } override fun onStart() { @@ -94,14 +85,6 @@ class PlayerActivity : AppCompatActivity() { releaseController() } - override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == android.R.id.home) { - onBackPressed() - return true - } - return super.onOptionsItemSelected(item) - } - private fun initializeController() { controllerFuture = MediaController.Builder( @@ -123,8 +106,6 @@ class PlayerActivity : AppCompatActivity() { updateCurrentPlaylistUI() updateMediaMetadataUI(controller.mediaMetadata) - updateShuffleSwitchUI(controller.shuffleModeEnabled) - updateRepeatSwitchUI(controller.repeatMode) playerView.setShowSubtitleButton(controller.currentTracks.isTypeSupported(TRACK_TYPE_TEXT)) controller.addListener( @@ -133,14 +114,6 @@ class PlayerActivity : AppCompatActivity() { updateMediaMetadataUI(mediaItem?.mediaMetadata ?: MediaMetadata.EMPTY) } - override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) { - updateShuffleSwitchUI(shuffleModeEnabled) - } - - override fun onRepeatModeChanged(repeatMode: Int) { - updateRepeatSwitchUI(repeatMode) - } - override fun onTracksChanged(tracks: Tracks) { playerView.setShowSubtitleButton(tracks.isTypeSupported(TRACK_TYPE_TEXT)) } @@ -148,48 +121,26 @@ class PlayerActivity : AppCompatActivity() { ) } - private fun updateShuffleSwitchUI(shuffleModeEnabled: Boolean) { - val resId = - if (shuffleModeEnabled) R.drawable.exo_styled_controls_shuffle_on - else R.drawable.exo_styled_controls_shuffle_off - findViewById(R.id.shuffle_switch) - .setImageDrawable(ContextCompat.getDrawable(this, resId)) - } - - private fun updateRepeatSwitchUI(repeatMode: Int) { - val resId: Int = - when (repeatMode) { - Player.REPEAT_MODE_OFF -> R.drawable.exo_styled_controls_repeat_off - Player.REPEAT_MODE_ONE -> R.drawable.exo_styled_controls_repeat_one - Player.REPEAT_MODE_ALL -> R.drawable.exo_styled_controls_repeat_all - else -> R.drawable.exo_styled_controls_repeat_off - } - findViewById(R.id.repeat_switch) - .setImageDrawable(ContextCompat.getDrawable(this, resId)) - } - private fun updateMediaMetadataUI(mediaMetadata: MediaMetadata) { - val title: CharSequence = mediaMetadata.title ?: getString(R.string.no_item_prompt) + val title: CharSequence = mediaMetadata.title ?: "getString(R.string.no_item_prompt)" - findViewById(R.id.video_title).text = title - findViewById(R.id.video_album).text = mediaMetadata.albumTitle - findViewById(R.id.video_artist).text = mediaMetadata.artist - findViewById(R.id.video_genre).text = mediaMetadata.genre + findViewById(R.id.media_title).text = title + findViewById(R.id.media_artist).text = mediaMetadata.artist // Trick to update playlist UI - mediaListAdapter.notifyDataSetChanged() + mediaItemListAdapter.notifyDataSetChanged() } private fun updateCurrentPlaylistUI() { val controller = this.controller ?: return - subItemMediaList.clear() + mediaItemList.clear() for (i in 0 until controller.mediaItemCount) { - subItemMediaList.add(controller.getMediaItemAt(i)) + mediaItemList.add(controller.getMediaItemAt(i)) } - mediaListAdapter.notifyDataSetChanged() + mediaItemListAdapter.notifyDataSetChanged() } - private inner class PlayingMediaItemArrayAdapter( + private inner class MediaItemListAdapter( context: Context, viewID: Int, mediaItemList: List @@ -201,22 +152,30 @@ class PlayerActivity : AppCompatActivity() { returnConvertView.findViewById(R.id.media_item).text = mediaItem.mediaMetadata.title + val deleteButton = returnConvertView.findViewById