Categories
android blog kotlin

Building a podcast app series: 4. Player service

How do we make sure that our app can run in the background and play a podcast without worrying about Android shutting the app down to claim resources? A foreground service to the rescue!

MediaBrowserServiceCompat

What the heck is a MediaBrowserServiceCompat? It’s a service that ExoPlayer ships with, and it can simplify the task of creating a background service for the player. Since Android O, any service that wants to be alive for extended periods of time should also show a notification and mark itself as a foreground service. To accomplish this we need to make use of PlayerNotificationManager class, also shipped with ExoPlayer, that given a media session token for our player will display a notification which will be in sync with the ExoPlayer. This is the most complicated part of the whole ExoPlayer setup because it works with a variety of Android APIs, and we all know how easy is it to use Android APIs.

PlayerNotificationManager

In order to utilize PlayerNotificationManager class, we need to create an instance of it and pass in a context, notification channel id, notification id, and a MediaDescriptionAdapter. The MediaDescriptionAdapter is how we tell the PlayerNotificationManager what is the title and image resource for our currently playing episode. Once we create an instance of this class, we can customize it in various ways. The most important method call after we create an instance is setPlayer(exoPlayerInstance). This is super important, given an ExoPlayer instance, PlayerNotificationManager will listen to that ExoPlayer instance changes and change the notification UI accordingly. This is a simple configuration of the PlayerNotificationManager

val playerNotificationManager = PlayerNotificationManager(context, channelId, notificationId, adapter)
playerNotificationManager.setPlayer(exoPlayer)
playerNotificationManager.setMediaSessionToken(it)

This leaves us with a question, who will supply an ExoPlayer instance? There can be a singleton that always returns a single instance, or even better, dependency injection can be used to provide the ExoPlyaer instance to different classes. That is what we will focus on the next, and it will be the last article in the series.

Notification channels

One thing we can do in this player service is to create a notification channel that PlayerNotificationManager will use to actually show a notification. Here is a simple way to create a new channel in case it is not created. Android O requires channels to be created before notifications can be presented to the user. We want to set the priority to low here to avoid any sounds or vibrations coming from the player notification when the user presses any action. Let’s look at an example

    private fun shouldCreateNowPlayingChannel(notificationManager: NotificationManagerCompat) =
        Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !nowPlayingChannelExists(
            notificationManager
        )

    @RequiresApi(Build.VERSION_CODES.O)
    private fun nowPlayingChannelExists(notificationManager: NotificationManagerCompat) =
        notificationManager.getNotificationChannel(nowPlayingChannelId) != null

    @RequiresApi(Build.VERSION_CODES.O)
    private fun createNowPlayingChannel(notificationManager: NotificationManagerCompat) {
        val notificationChannel = NotificationChannel(
            nowPlayingChannelId,
            getString(R.string.notification_channel),
            NotificationManager.IMPORTANCE_LOW
        ).apply {
           description = getString(R.string.notification_channel_description)
        }
         notificationManager.createNotificationChannel(notificationChannel)
    }

Checkout this Github link for the full player service code.

Stay tuned for the next article where we will explore a simple yet powerful dependency injection framework called Koin (technically it is a service locator but it enough to what we need for this app)

Leave a Reply

Your email address will not be published. Required fields are marked *