Use kotlinx-coroutines-guava
in session demo app
I originally tried switching to `Futures.addCallback` (as a follow-up to Issue: androidx/media#890), but it seemed like a good chance to go further into Kotlin-ification. Before this change, if the connection to the session failed, the app would hang at the 'waiting' screen with nothing logged (and the music keeps playing). This behaviour is maintained with the `try/catch` around the `.await()` call (with additional logging). Without this, the failed connection causes the `PlayerActivity` to crash and the music in the background stops. The `try/catch` is used to flag to developers who might be using this app as an example that connecting to the session may fail, and they may want to handle that. This change also switches `this.controller` to be `lateinit` instead of nullable. Issue: androidx/media#890 PiperOrigin-RevId: 638948568
This commit is contained in:
parent
a652c5b3f5
commit
1329821a35
@ -29,6 +29,7 @@ project.ext {
|
||||
// Use the same Guava version as the Android repo:
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:external/guava/METADATA
|
||||
guavaVersion = '33.0.0-android'
|
||||
kotlinxCoroutinesVersion = '1.8.1'
|
||||
leakCanaryVersion = '2.10'
|
||||
mockitoVersion = '3.12.4'
|
||||
robolectricVersion = '4.11'
|
||||
@ -46,6 +47,7 @@ project.ext {
|
||||
// Updating this to 1.9.0+ will import Kotlin stdlib [internal ref: b/277891049].
|
||||
androidxCoreVersion = '1.8.0'
|
||||
androidxExifInterfaceVersion = '1.3.6'
|
||||
androidxLifecycleVersion = '2.6.0'
|
||||
androidxMediaVersion = '1.7.0'
|
||||
androidxMultidexVersion = '2.0.1'
|
||||
androidxRecyclerViewVersion = '1.3.0'
|
||||
|
@ -62,9 +62,12 @@ dependencies {
|
||||
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
|
||||
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
||||
implementation 'androidx.lifecycle:lifecycle-common:' + androidxLifecycleVersion
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:' + androidxLifecycleVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:' + kotlinxCoroutinesVersion
|
||||
implementation project(modulePrefix + 'lib-ui')
|
||||
implementation project(modulePrefix + 'lib-session')
|
||||
implementation project(modulePrefix + 'demo-session-service')
|
||||
|
@ -18,6 +18,7 @@ package androidx.media3.demo.session
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -28,6 +29,9 @@ import android.widget.TextView
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.media3.common.C.TRACK_TYPE_TEXT
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
@ -40,13 +44,15 @@ import androidx.media3.session.MediaController
|
||||
import androidx.media3.session.SessionToken
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.guava.await
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val TAG = "PlayerActivity"
|
||||
|
||||
class PlayerActivity : AppCompatActivity() {
|
||||
private lateinit var controllerFuture: ListenableFuture<MediaController>
|
||||
private val controller: MediaController?
|
||||
get() =
|
||||
if (controllerFuture.isDone && !controllerFuture.isCancelled) controllerFuture.get() else null
|
||||
private lateinit var controller: MediaController
|
||||
|
||||
private lateinit var playerView: PlayerView
|
||||
private lateinit var mediaItemListView: ListView
|
||||
@ -57,6 +63,18 @@ class PlayerActivity : AppCompatActivity() {
|
||||
@OptIn(UnstableApi::class) // PlayerView.hideController
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
try {
|
||||
initializeController()
|
||||
awaitCancellation()
|
||||
} finally {
|
||||
playerView.player = null
|
||||
releaseController()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_player)
|
||||
playerView = findViewById(R.id.player_view)
|
||||
|
||||
@ -65,7 +83,6 @@ class PlayerActivity : AppCompatActivity() {
|
||||
mediaItemListView.adapter = mediaItemListAdapter
|
||||
mediaItemListView.setOnItemClickListener { _, _, position, _ ->
|
||||
run {
|
||||
val controller = this.controller ?: return@run
|
||||
if (controller.currentMediaItemIndex == position) {
|
||||
controller.playWhenReady = !controller.playWhenReady
|
||||
if (controller.playWhenReady) {
|
||||
@ -79,18 +96,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
initializeController()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
playerView.player = null
|
||||
releaseController()
|
||||
}
|
||||
|
||||
private fun initializeController() {
|
||||
private suspend fun initializeController() {
|
||||
controllerFuture =
|
||||
MediaController.Builder(
|
||||
this,
|
||||
@ -98,7 +104,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||
)
|
||||
.buildAsync()
|
||||
updateMediaMetadataUI()
|
||||
controllerFuture.addListener({ setController() }, MoreExecutors.directExecutor())
|
||||
setController()
|
||||
}
|
||||
|
||||
private fun releaseController() {
|
||||
@ -106,9 +112,13 @@ class PlayerActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) // PlayerView.setShowSubtitleButton
|
||||
private fun setController() {
|
||||
val controller = this.controller ?: return
|
||||
|
||||
private suspend fun setController() {
|
||||
try {
|
||||
controller = controllerFuture.await()
|
||||
} catch (t: Throwable) {
|
||||
Log.w(TAG, "Failed to connect to MediaController", t)
|
||||
return
|
||||
}
|
||||
playerView.player = controller
|
||||
|
||||
updateCurrentPlaylistUI()
|
||||
@ -137,8 +147,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun updateMediaMetadataUI() {
|
||||
val controller = this.controller
|
||||
if (controller == null || controller.mediaItemCount == 0) {
|
||||
if (!::controller.isInitialized || controller.mediaItemCount == 0) {
|
||||
findViewById<TextView>(R.id.media_title).text = getString(R.string.waiting_for_metadata)
|
||||
findViewById<TextView>(R.id.media_artist).text = ""
|
||||
return
|
||||
@ -152,7 +161,9 @@ class PlayerActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun updateCurrentPlaylistUI() {
|
||||
val controller = this.controller ?: return
|
||||
if (!::controller.isInitialized) {
|
||||
return
|
||||
}
|
||||
mediaItemList.clear()
|
||||
for (i in 0 until controller.mediaItemCount) {
|
||||
mediaItemList.add(controller.getMediaItemAt(i))
|
||||
@ -173,7 +184,7 @@ 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) {
|
||||
if (::controller.isInitialized && position == controller.currentMediaItemIndex) {
|
||||
// Styles for the current media item list item.
|
||||
returnConvertView.setBackgroundColor(
|
||||
ContextCompat.getColor(context, R.color.playlist_item_background)
|
||||
@ -192,7 +203,6 @@ class PlayerActivity : AppCompatActivity() {
|
||||
.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||
deleteButton.visibility = View.VISIBLE
|
||||
deleteButton.setOnClickListener {
|
||||
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
|
||||
controller.removeMediaItem(position)
|
||||
updateCurrentPlaylistUI()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user