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:
parent
b9a4e614f7
commit
96a4ae7e40
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
BIN
demos/session/src/main/res/drawable/artwork_placeholder.png
Normal file
BIN
demos/session/src/main/res/drawable/artwork_placeholder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
@ -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>
|
||||
|
@ -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"
|
||||
/>
|
||||
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user