diff --git a/constants.gradle b/constants.gradle index b270a3b067..b4b63909c5 100644 --- a/constants.gradle +++ b/constants.gradle @@ -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' diff --git a/demos/session/build.gradle b/demos/session/build.gradle index 8ec5ed7faf..bd2c1dd7eb 100644 --- a/demos/session/build.gradle +++ b/demos/session/build.gradle @@ -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') 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 aa5c7bc0e8..134405ed9b 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 @@ -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 - 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(R.id.media_title).text = getString(R.string.waiting_for_metadata) findViewById(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(R.id.media_item).text = mediaItem.mediaMetadata.title val deleteButton = returnConvertView.findViewById