ExoPlayer is a media player for Android initially developed by Google for their own applications, but released as a library for any Android Developer to use in their app to play videos from either local storage or live stream from the web.

Replacing the old Android MediaPlayer API, ExoPlayer offers features not currently supported by the old API such as DASH, SmoothStreaming, Playlists, and better customisation options. Add all that and more on top of a much more robust package that works more consistently across different devices.

Do note that if you choose to play from the web, you will need the direct download uri of the video. This means you can’t use ExoPlayer for YouTube, not at least without the YouTube Player API.



 

ExoPlayer Features

ExoPlayer was esentially built to be a better Android Media Player API, and thus offers many features that the old API just doesn’t have.

  • More supported media formats including DASH, SmoothStreaming, HLS, Progressive Container formats, and HDR video playback
  • Features such as Playlists, Gapless Audio Playback, Seeking in Live Streams, and Multi-Period DASH
  • Subtitle support, support for Widevine common encryption, and Google Cast SDK support
  • A very nicely customisable UI, both in its controls and in its look and feel

And that’s just scraping the surface of it. Find the full list of pros and cons here.

Do note that ExoPlayer doesn’t work on Android versions below 4.1 (API level 16). If you’re considering using ExoPlayer, make sure your minimum supported version meets the requirements for all the features you intend to use by checking the supported devices page here.

ExoPlayer Alternatives and Competitors

Although ExoPlayer is very widely adopted in Android apps all over, it does have some successful competition (that can play media from various sources, particularly these libraries:

  • Vitamio
  • LibVLC

How do these libraries compare to ExoPlayer?

Vitamio

It’s arguable which media player is more robust between Vitamio and ExoPlayer, but other devs have conducted tests that proved ExoPlayer has better performance when it comes to loading videos. Vitamio is also a paid service which may put off many developers.

LibVLC

LibVLC is somewhat limited in its functionality as it requires the Android NDK to make use of.

With ExoPlayer being very rich in its features whilst being easy to use, it’s no wonder so many developers choose to use ExoPlayer in their apps.

Getting Started

Start by making sure you have the Google and JCenter repositories in your root build.gradle file.

repositories {
    google()
    jcenter()
}

Then add the ExoPlayer dependency to your app/build.gradle file.

implementation 'com.google.android.exoplayer:exoplayer:2.X.X'

Do find the latest version on the ExoPlayer Github page.

The above dependency includes the full ExoPlayer library, but if you want to optimise even further, you can reduce the size taken by the library by importing only the modules you need.

implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'

Check out the ExoPlayer Github page for more details on individual modules.

Finally, if not already enabled, make sure you have Java 8 support in all files depending on ExoPlayer by adding this code to the android section.

compileOptions {
  targetCompatibility JavaVersion.VERSION_1_8
}

Initialise the Player

In your activity’s XML layout file, add the PlayerView object

<com.google.android.exoplayer2.ui.PlayerView
   android:id="@+id/player_view"
   android:layout_width="match_parent"
   android:layout_height="match_parent"/>

In your activity, declare your player as a global variable and initialise it like so

private var player: ExoPlayer? = null

private fun initialisePlayer() {
    player = SimpleExoPlayer.Builder(this).build()
    player_view.player = player
}

SimpleExoPlayer is an implementation of ExoPlayer that uses default renderer components.

Playing Music/Video

To play audio or video, you need to build a data source. DefaultDataSourceFactory will suit our needs for now, but there are a number of different data source factories we can choose from which include ProgressiveMediaSource, CacheDataSourceFactory, and DashMediaSource among others.

I won’t go into detail about any of them now as that could well be its own article.

private fun buildMediaSource(uri: Uri): MediaSource {
    val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(this, "user-agent")
    return ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri)
}

The user-agent is the user-agent that will be used when making the HTTP request for the media file.

After that, update your initialisePlayer with the following code

private fun initialisePlayer() {
    player = SimpleExoPlayer.Builder(this).build()
    player_view.player = player

    val uri = Uri.parse(getString(R.string.media_url_mp3))
    val mediaSource = buildMediaSource(uri)
}

uri here should be the url of your media file on the web.

If you need a quick one to test with right here, try this:

https://storage.googleapis.com/exoplayer-test-media-0/Jazz_In_Paris.mp3

Or  for video:

https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4

(The above link points to the Big Buck Bunny video provided by the Blender Foundation)

Dealing with the Activity Lifecycle

Audio/Video streaming can be quite memory intensive. ExoPlayer is no different. We need to make sure the app isn’t hogging any resources when they’re not in use.

Override onStart, onResume, onPause, and onStop methods of your activity.

private var playWhenReady = true
private var currentWindow = 0
private var playbackPosition: Long = 0

override fun onStart() {
    super.onStart()
    if (Util.SDK_INT > 23) {
        initializePlayer()
    }
}

override fun onResume() {
    super.onResume()
    hideSystemUi()
    if (Util.SDK_INT <= 23 || player == null) {
        initializePlayer()
    }
}

override fun onPause() {
    super.onPause()
    if (Util.SDK_INT <= 23) {
        releasePlayer()
    }
}

override fun onStop() {
    super.onStop()
    if (Util.SDK_INT > 23) {
        releasePlayer()
    }
}

private fun hideSystemUi() {
    player_view.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
            or View.SYSTEM_UI_FLAG_FULLSCREEN
            or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
            or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
            or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
}

private fun releasePlayer() {
    if (player != null) {
        playbackPosition = player.currentPosition
        currentWindow = player.currentWindowIndex
        playWhenReady = player.playWhenReady
        player.removeListener(playbackStateListener)
        player.release()
        player = null
    }
}

Android API 24 and higher support multiple windows. Your app can be visible but not active whilst in split window mode so you need to initialise the player in onStart.

Below API 24 however, the system requires you to wait as long as possible until you grab resources so you wait until onResume to initialise the player.

hideSystemUi is a helper method that allows you to have a full-screen experience.

releasePlayer is a method that frees up resources, put in either onPause or onStop, based on the same multiwindow-related reasons as initialising the player above.

In case your activity doesn’t get destroyed before the user goes back to it, you save the playWhenReady, currentWindow, and playbackPosition to allow the user to play the audio/video from where they left off.

Final Preparation

All you need to do now is supply the information from the global variables you defined earlier when you initialise your player like so.

private fun initialisePlayer() {
    player = SimpleExoPlayer.Builder(this).build()
    player_view.player = player

    val uri = Uri.parse(getString(R.string.media_url_mp3))
    val mediaSource = buildMediaSource(uri)

    player.setPlayWhenReady(playWhenReady);
    player.seekTo(currentWindow, playbackPosition);
    player.prepare(mediaSource, false, false);
}

setPlayWhenReady tells the player to start playing as soon as the resources for playback have been acquired. We set this to initially be true so playback starts as soon as the activity is launched

seekTo tells the player to seek to a specific position within a specific window. Having both currentWindow and playbackPosition initialised at zero means that playback starts from the start of the video when the activity is launched

prepare tells the player to acquire all the resources for the given mediaSource. The other parameters here are resetPosition and resetState which we set to false because we already handled those in the preceding lines

Now because of the way the player has been set up, once you call prepare, the player will start acquiring resources from the data source and then automatically play your audio / video.

More Coming Up

We only scratched the basics of ExoPlayer. There is so much to cover with this one library with regards to data sources, customising the player UI, adaptive streaming and such.

Expect more ExoPlayer content in the following weeks. If you especially like ExoPlayer, or if you think there are contenders that can tell ExoPlayer to sit down, leave it down in the comments. And as always, happy coding ༼ つ ◕_◕ ༽つ