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