Building a video player app in Android (Part 1 / 5)

With ExoPlayer, Playlists, MediaSession, Audio Focus, PIP

Nazmul Idris (Naz)
Android Developers
6 min readMar 9, 2018

--

The Android Media APIs allow you to create rich media experiences that immerse your users in the audio or video content of your app. They also provide controls for externally controlled by Bluetooth headphones, car audio systems, wired headsets, and even the Google Assistant and Android Auto.

The goal of this series of articles is to get you started with using ExoPlayer and MediaSession to build a simple, but feature rich, video player app.

This app will support the following features:

  • Playback of video (or even audio files) from local storage (assets folder on your APK) or remote HTTP sources.
  • Support for playlists, so that you can string together a list of videos to play one after the other and skip between them.
  • Support for MediaSession, so that external Bluetooth headphones can control your media (play, pause, skip to next, etc) and see what media is currently playing (like on your car Bluetooth head unit).
  • Support for Audio Focus, so that you respect Android’s audio focus system and pause playback if something else is playing, or the user receives a phone call.
  • Support for picture in picture (PIP) mode on Android Oreo, so that your app’s video playback can continue in a minimized window while you use other apps..

We will use Kotlin for this app, and, believe it or not, this entire app will be built from 4 moderately sized Kotlin files! 😃

This is the first part of a 5 part series that includes:

Overview of using ExoPlayer

In order to use ExoPlayer to play video files over the network or from your APK, you will need to create a SimpleExoPlayer instance and a MediaSource. You will also need an Activity contains a PlayerView which will actually render the video content that’s loaded by the player.

The Player

At a minimum, in order to create a SimpleExoPlayer you will need to provide a track selector (which chooses which track of audio, video, or text to load from your media source, based on bandwidth, devices, capabilities, language, etc).

What to play?

You will need to tell the player what media to load, and where to load it from. MediaSource allows you to do this. Sources of media are expressed as Uris which can point to files (mp3, mp4, webm, mkv, etc) that are in the assets folder in your APK or over HTTP. The Uris that you provide are used by the MediaSource to actually load and prepare the content for playback. ExtractorMediaSource (which implements MediaSource) allows you to handle these sources and formats.

Note — For adaptive formats, you can use DashMediaSource (for DASH sources), SsMediaSource (for SmoothStreaming sources), and HlsMediaSource (for HLS sources).

If you are loading media files over HTTP then the following permission needs to be added to your AndroidManifest.xml file. No permissions need to be added to access files that are in your APK (assets folder).

<uses-permission android:name=”android.permission.INTERNET” />

Where to render playback?

Then you have to attach the player to a PlayerView, which renders the video to your UI, and also provides UI controls for audio / video playback. It’s best to do this before you start to prepare the source (which is the next step). If you attach the view to the player, after you’ve prepared the source, then the media will be loaded and potentially played before the player has a surface to output to.

You must also prepare the player, which tells it to start loading the data (as it might take time to buffer data over the network before it can start playback). You also have to set playWhenReady to true, which tells ExoPlayer to play.

playWhenReady = true  -> PLAY (start after enough data is buffered)
playWhenReady = false -> PAUSE

To add support for ExoPlayer to your app, make sure to add the following line to the build.gradle file. For more information on including only specific ExoPlayer components please refer to the Developer Guide and this medium article.

ext { ver = “2.7.0” }
dependencies {
implementation “com.google.android.exoplayer:exoplayer-core:$ver”
implementation “com.google.android.exoplayer:exoplayer-ui:$ver”
}

Adding the ExoPlayer dependency doesn’t significantly increase the size of your APK. Before running Proguard you can expect to see something in the order of a few 100K added to the size of your APK. After applying ProGuard, you can expect this size to decrease.

Create the ExoPlayer instance

This code shows how you might create a player with the default options, and attach it to a PlayerView (that has to be declared in the XML layout of the Activity that you want to display the video in).

The PlayerView should be declared in your Activity’s layout XML. For example:

Loading files locally from APK using ExoPlayer

DefaultDataSource allows local files to be loaded via the following Uris:

  • file:///
  • asset:///
  • content:///
  • rtmp
  • data
  • http(s)

You can load files from assets in the following ways (you can create nested folders under the assets folder):

  • Uri.parse(“file:///android_asset/video/video.mp4”)
  • Uri.parse(“asset:///video/video.mp4”)

Please note that:

  • ExoPlayer doesn’t allow loading files from the res folder using Uri.parse(“android.resource://${packageName}/${R.raw.id}”). Also, Android doesn’t allow you to add folders in the res folder (unlike assets).
  • You can use RawResourceDataSource to build a Uri that points to a resource id in the res folder, eg: RawResourceDataSource.buildRawResourceUri(R.raw.my_media_file).

Release ExoPlayer resources

When you’re done with playback, be sure to stop the player, since it consumes resources like network, memory, and system codecs. Codecs are a globally shared resource on the phone, and there might be a limited number of them available depending on the specific phone and OS version, so it’s important to release them when not using them. The following code allows you to reuse the same ExoPlayer instance again, if you would like.

This will release all the resources (codecs, MediaSources, etc) held by the player. In order to use the player again, call prepare(MediaSource) and set playWhenReady as show in the gists above.

ExoPlayer.release() is a method that does the same thing as stop() with one exception: it also stops the playback thread, preventing the the ExoPlayer instance from being reused. Release should be called when the player will no longer be used, such as in Service.onDestroy() or Activity.onDestroy().

Activity Lifecycle Integration

You have to integrate with the Android Activity lifecycle in order to create, use, and release the player. Here’s a simple example of this.

Saving player state between onStart() and onStop()

The PlayerState data class (shown below) is used to load the player’s state information before it’s been run the first time. When the player is released, some of the player’s state is saved to an object of this class. When a new player is created, this simple state object is used to configure the player to resume where the instance had previously left off.

From a UX standpoint, this means that when you run the app, play some media, and hit the home button, the player resources are released. When you switch back to that app, the player is initialized again, and the previous state information should be restored, so that the user can resume playback where they left off (position of previous playback if any, and the item in the playlist that they were consuming, which is a window index).

Before the player’s resources are released, the player’s currentWindowIndex, currentPosition, playWhenReady, and playlist or media item information are saved to the PlayerState object. The state is then restored once the player is reinitialized. Here are methods from PlayerHolder class that demonstrate how this can be done.

A little more control over player creation

You can also use a different signature of ExoPlayerFactor.newSimpleInstance(…) factory method to customize your player. For example, even while using the DefaultLoadControl class you can change the buffering policy of ExoPlayer to better suit your needs.

For more complex use cases, you can provide your own implementations of all the arguments that are passed to the ExoPlayerFactory.newSimpleInstance(…) factory method, which gives you a great deal of flexibility in what you can do with ExoPlayer.

Source code on GitHub

By following these 5 articles, you should be able to create a video player app that is similar to this sample we have created (which you can get from Android Studio as well).

Resources for further learning

ExoPlayer

MediaSession, Audio Focus

DASH, HLS

Picture in Picture

--

--

Nazmul Idris (Naz)
Android Developers

Google SWE, entrepreneur, leader, designer, dancer, TaiChi'er, Yogi, racer, healer, storyteller. I ❤️ leadership, authenticity, empowerment, & lifelong learning