Building a lifecycle aware media playback component with Exoplayer API

Rachit M
AndroidPub
Published in
4 min readDec 3, 2018

TLDR; We created an easy to use wrapper on top of exoplayer api and made it lifecycle aware. Find the source code here

For a recent feature, we needed to add audio streaming support to our application.

Media playback isn’t the most complex topic out there and one can easily start with Mediaplayer APIs.

And well for local media files it works really well, except for a few device-specific issues, or when we have to work on managing states (A little complex), also most of the MediaPlayer updates are tied to the OS update.

The alternative to MediaPlayer is Exoplayer a `powerful` media playback library by Google which can be easily customised, ships with our application and can be updated through the play store. Exoplayer also happens to be the defacto suggested library for media playback on Android.

As with MediaPlayer APIs, Exoplayer also requires a bit of customization and resource clearing once the playback is completed.

We can build a small life cycle aware component which handles the customization for us as well it manages its own lifecycle

Creating a life cycle aware component

While building the custom view for playback; we decided to make it life cycle aware to initialise or release resources depending on the activity lifecycle.

We can create a life-cycle aware component with the following steps:

  1. Add the dependencies
implementation 'android.arch.lifecycle:extensions:1.1.1'
kapt "android.arch.lifecycle:compiler:1.1.1"

2. Create a class implementing theLifeCyclerObserver interface with lifecycle as a property. Also, add the current class as lifecycle observer.

class PlayerManager(lifecycle: Lifecycle) : LifecycleObserver {
...
init {
lifecycle.addObserver(this) // Adds this class as lifecycle observer
}
}

3. Add a function annotated with @OnLifecycleEvent which is called once a life event happens.

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
if (!hasMarshmallow()) {
releaseMediaPlayer()
}
}

The annotation @OnLifecycleEvent takes Lifecycle.Event as a parameter and which is an enum specifying the lifecycle states.

4. Last from our activity or fragment we initialise our helper class as

val playerManager = PlayerManager(lifeCycle)

Now everytime our activity has paused the onPause function of our PlayerManager class will be called.

Welcome coco-ui

Coco-ui manages the exoplayer resources by observing activity lifecycle as well as hides the complexity of configuring the Exoplayer. It also gives as LiveData observer to observe media playback events.

The PlayerManager class mentioned above is part of the library and using the life cycle observer functions it auto clears the exoplayer resources once our activity has paused.

The other two classes are:

The MediaPlayerView is a custom view based on PlayerView from the exoplayer library. It holds the configuration objects for the underlying exoplayer implementation.

and MediaPlayerState containing the enum states possible for media playback

enum class MediaPlayerState {
BUFFERING,
READY,
ERROR,
UNKNOWN_MEDIA,
LOW_VOLUME,
COMPLETED
}

Using coco-ui in an activity

The steps to add the library to your project are

1st. Add the exoplayer dependencies, and include the library .aar file from the github repo (till we upload it to maven)

2nd, create the playback layout or use the default layout provided in the library sample.

3rd, Add the MediaPlayerView to the activity which has to play the media and specify the default configs as per Exoplayer library.

<com.rachitmishra.coco.ui.MediaPlayerView
android:id="@+id/mediaPlayerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:auto_show="true"
app:controller_layout_id="@layout/layout_playback_controls"
app:hide_on_touch="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:show_timeout="0"/>

The exoplayer configuration in we are using in above code is

app:auto_show="true" -> Auto show the playback controls
app:hide_on_touch="false" -> Hide playback controls on touch
app:show_timeout="0" -> Hide controls once user has touched it
happ:controller_layout_id="@layout/layout_playback_controls" -> The playback layout

For more detailed configuration settings kindly read the Exoplayer docs here

4th, Initialise the MediaPlayerView from the activity

val mediaUrl: String = ...
val headers: ArrayMap<String, String> = ...
val userAgent: String = ...
mediaPlayerView
.headers(headers)
.userAgent(userAgent)
.from(mediaUrl)
.observe(lifecycle) {
mediaPlayerListener(it)
}
fun mediaPlayerListener(state: MediaPlayerState?) {
when (state) {
MediaPlayerState.LOW_VOLUME ->
MediaPlayerState.ERROR -> {
}
MediaPlayerState.BUFFERING ->
MediaPlayerState.READY -> {
}
MediaPlayerState.COMPLETED ->
MediaPlayerState.UNKNOWN_MEDIA ->
}
}

That’s all folks, 4 steps and now our lifecycle component will auto manage the resources related to exoplayer library while also hiding the complexity of configuring it.

Few learnings from this projects were

  • We shouldn’t use multiple instances of exoplayer API as it requires managing multiple states and become bit complex.
  • While adding playback to a list a good practice would be to initialise the playback in a different view above the list rather than adding playback listeners to each list item and the list items should be responsible only for showing the saved progress positions.
  • We can store the played positions in the list item models and restore the playback state using these values.

Thanks for reading this and I hope this helps or inspires you to build your own lifecycle aware components.

Find more interesting articles on software problems at our Practo Engineering blog.

--

--