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
This commit is contained in:
bachinger 2023-05-22 20:44:05 +01:00 committed by tonihei
parent b9a4e614f7
commit 96a4ae7e40
9 changed files with 113 additions and 164 deletions

View File

@ -40,7 +40,9 @@
<activity
android:name=".PlayerActivity"
android:exported="true"/>
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/Theme.AppCompat.NoActionBar"/>
<activity
android:name=".PlayableFolderActivity"

View File

@ -17,7 +17,6 @@ package androidx.media3.demo.session
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
@ -70,9 +69,9 @@ class MainActivity : AppCompatActivity() {
findViewById<ExtendedFloatingActionButton>(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(

View File

@ -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<ExtendedFloatingActionButton>(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()
}
}

View File

@ -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()

View File

@ -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<MediaItem> = mutableListOf()
private lateinit var mediaItemListView: ListView
private lateinit var mediaItemListAdapter: MediaItemListAdapter
private val mediaItemList: MutableList<MediaItem> = 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<ImageView>(R.id.shuffle_switch).setOnClickListener {
val controller = this.controller ?: return@setOnClickListener
controller.shuffleModeEnabled = !controller.shuffleModeEnabled
}
findViewById<ImageView>(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<ImageView>(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<ImageView>(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<TextView>(R.id.video_title).text = title
findViewById<TextView>(R.id.video_album).text = mediaMetadata.albumTitle
findViewById<TextView>(R.id.video_artist).text = mediaMetadata.artist
findViewById<TextView>(R.id.video_genre).text = mediaMetadata.genre
findViewById<TextView>(R.id.media_title).text = title
findViewById<TextView>(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<MediaItem>
@ -201,22 +152,30 @@ class PlayerActivity : AppCompatActivity() {
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
val deleteButton = returnConvertView.findViewById<Button>(R.id.delete_button)
if (position == controller?.currentMediaItemIndex) {
returnConvertView.setBackgroundColor(ContextCompat.getColor(context, R.color.white))
returnConvertView
.findViewById<TextView>(R.id.media_item)
.setTextColor(ContextCompat.getColor(context, R.color.black))
} else {
returnConvertView.setBackgroundColor(ContextCompat.getColor(context, R.color.black))
// Styles for the current media item list item.
returnConvertView.setBackgroundColor(
ContextCompat.getColor(context, R.color.playlist_item_background)
)
returnConvertView
.findViewById<TextView>(R.id.media_item)
.setTextColor(ContextCompat.getColor(context, R.color.white))
}
returnConvertView.findViewById<Button>(R.id.delete_button).setOnClickListener {
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
controller.removeMediaItem(position)
updateCurrentPlaylistUI()
deleteButton.visibility = View.GONE
} else {
// Styles for any other media item list item.
returnConvertView.setBackgroundColor(
ContextCompat.getColor(context, R.color.player_background)
)
returnConvertView
.findViewById<TextView>(R.id.media_item)
.setTextColor(ContextCompat.getColor(context, R.color.white))
deleteButton.visibility = View.VISIBLE
deleteButton.setOnClickListener {
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
controller.removeMediaItem(position)
updateCurrentPlaylistUI()
}
}
return returnConvertView

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -19,7 +19,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:background="@color/player_background"
tools:context=".PlayerActivity">
<androidx.media3.ui.AspectRatioFrameLayout
@ -28,77 +28,47 @@
>
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:background="@color/player_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:use_artwork="true" />
app:default_artwork="@drawable/artwork_placeholder"
app:repeat_toggle_modes="one|all"
app:show_shuffle_button="true"
app:shutter_background_color="@color/player_background" />
</androidx.media3.ui.AspectRatioFrameLayout>
<TextView
android:id="@+id/video_title"
android:id="@+id/media_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:textColor="@color/white"
android:textSize="14sp"/>
<TextView
android:id="@+id/media_title"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:paddingStart="10dp"
android:textColor="@color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/video_album"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:textColor="@color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/video_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:paddingLeft="10dp"
android:paddingBottom="10dp"/>
<TextView
android:id="@+id/video_genre"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:textSize="12sp" />
<LinearLayout
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/shuffle_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/shuffle"
android:src="@drawable/exo_styled_controls_shuffle_off"
android:textColor="@color/white" />
<ImageView
android:layout_margin="@dimen/exo_icon_horizontal_margin"
android:id="@+id/repeat_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/exo_styled_controls_repeat_off"
android:textColor="@color/white"
android:contentDescription="@string/repeat"
/>
</LinearLayout>
<View
android:background="@color/divider"
android:layout_height="1dp"
android:layout_width="match_parent" />
<ListView
android:id="@+id/current_playing_list"
android:layout_width="match_parent"
android:divider="@drawable/divider"
android:dividerHeight="1px"
android:layout_height="wrap_content"/>
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -25,6 +25,8 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="10dp"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/white"
android:paddingEnd="10dp"
android:minHeight="50dp" />
@ -35,6 +37,7 @@
android:layout_width="50dp"
android:layout_height="match_parent"
android:id="@+id/delete_button"
android:backgroundTint="@color/playlist_item_foreground"
android:background="@drawable/baseline_playlist_remove_white_48"
/>

View File

@ -22,4 +22,9 @@
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="grey">#FF999999</color>
<color name="background">#292929</color>
<color name="player_background">#1c1c1c</color>
<color name="playlist_item_background">#363434</color>
<color name="playlist_item_foreground">#635E5E</color>
<color name="divider">#646464</color>
</resources>