Skip to content

Commit

Permalink
Use kotlinx-coroutines-guava in session demo app
Browse files Browse the repository at this point in the history
I originally tried switching to `Futures.addCallback` (as a follow-up
to Issue: #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: #890
PiperOrigin-RevId: 638948568
  • Loading branch information
icbaker authored and Copybara-Service committed May 31, 2024
1 parent a652c5b commit 1329821
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 26 deletions.
2 changes: 2 additions & 0 deletions constants.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down
3 changes: 3 additions & 0 deletions demos/session/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)

Expand All @@ -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) {
Expand All @@ -79,36 +96,29 @@ 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,
SessionToken(this, ComponentName(this, PlaybackService::class.java)),
)
.buildAsync()
updateMediaMetadataUI()
controllerFuture.addListener({ setController() }, MoreExecutors.directExecutor())
setController()
}

private fun releaseController() {
MediaController.releaseFuture(controllerFuture)
}

@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()
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand All @@ -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)
Expand All @@ -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()
}
Expand Down

0 comments on commit 1329821

Please sign in to comment.