How to get the player variable from MediaSessionService example in docs?

349 Views Asked by At

Maybe it's just me but Media3 Create a MediaController section in android docs is just missing the last part for me which is getting the "player" and using it in a composable.

I want the session for the notification as well and the player will play a url not local media.

I have PlaybackService.kt which is exactly what's in the documents

import android.content.ComponentName
import android.content.Intent
import androidx.media3.common.MediaMetadata
import androidx.media3.common.PlaybackException
import androidx.media3.common.Player
import androidx.media3.session.MediaController
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import androidx.media3.session.SessionToken
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors

class PlaybackService : MediaSessionService() {
    private lateinit var controllerFuture: ListenableFuture<MediaController>
    lateinit var player: MediaController
    private var mediaSession: MediaSession? = null


    override fun onCreate() {
        super.onCreate()
        val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
        controllerFuture =
            MediaController.Builder(this, sessionToken).buildAsync()
        controllerFuture.addListener({
            player = controllerFuture.get()
            initController()

        }, MoreExecutors.directExecutor())
    }


    private fun initController() {
        player.addListener(object : Player.Listener {

            override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
                super.onMediaMetadataChanged(mediaMetadata)
            }

            override fun onIsPlayingChanged(isPlaying: Boolean) {
                super.onIsPlayingChanged(isPlaying)
            }

            override fun onPlaybackStateChanged(playbackState: Int) {
                super.onPlaybackStateChanged(playbackState)
            }

            override fun onPlayerError(error: PlaybackException) {
                super.onPlayerError(error)
            }

            override fun onPlayerErrorChanged(error: PlaybackException?) {
                super.onPlayerErrorChanged(error)
            }
        })
    }

    override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
        mediaSession


    // The user dismissed the app from the recent tasks
    override fun onTaskRemoved(rootIntent: Intent?) {
        if (!player.playWhenReady || (player.mediaItemCount == 0)) {
            // Stop the service if not playing, continue playing in the background
            // otherwise.
            stopSelf()
        }
    }


    override fun onDestroy() {
        player.stop()
        player.release()
        super.onDestroy()
    }
}

and in AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
...
...
<service
   android:name=".PlaybackService"
   android:exported="true"
   android:foregroundServiceType="mediaPlayback">
   <intent-filter>
      <action android:name="androidx.media3.session.MediaSessionService" />
      <action android:name="android.media.browse.MediaBrowserService" />
   </intent-filter>
</service>

now, I just need that player variable in a composable.

Maybe something like sending it to HomeScreen(player: MediaController)

I've tried multiple examples from open source projects but all of them are using MVVM with big projects which over overcomplicated.

I'm still learning, I just need to use something like player.play(url: String) and player.stop() on a button click and that's it.

1

There are 1 best solutions below

3
On BEST ANSWER

I found myself in this same situation about a year ago so I came up with a helper function to deal with it. You can call this with a context. Don't forget to replace the media service class with yours.

@Suppress("BlockingMethodInNonBlockingContext")
@SuppressLint("UnsafeOptInUsageError")
private suspend inline fun <reified T : Service> getMediaControllerForService(context: Context): MediaController =
    suspendCoroutine { continuation ->
        val mediaControllerFuture = MediaController.Builder(
            context,
            SessionToken(context, ComponentName(context, T::class.java))
        ).buildAsync()

        mediaControllerFuture.addListener({
            val controller = mediaControllerFuture.get()
            continuation.resumeWith(Result.success(controller))
        }, Executors.newSingleThreadExecutor())
    }

 // You can use it as

 mediaController = getMediaControllerForService<MediaService>(context = context)

It is also worth stating that you should accept the incoming connection from your MediaService class by attaching a callback to your MediaSession object and implementing the onConnect method

    val mediaSession = MediaSession
        .Builder(this, player)
        .setCallback(object : MediaSession.Callback {
                override fun onConnect(
                    session: MediaSession,
                    controller: MediaSession.ControllerInfo
                ): MediaSession.ConnectionResult {
                    return MediaSession.ConnectionResult.accept(
                        SessionCommands
                            .Builder()
                            .build(),
                        playerCommands
                    )
                }
            }
        ).build()