AndroidPub
Published in

AndroidPub

ExoPlayer Components Explained

What is ExoPlayer?

ExoPlayer is an application level media player for Android which provides an alternative to Android’s MediaPlayer. It can play audio/video both locally and over the Internet. Another reason why ExoPlayer is a popular option is that it supports a lot of features, for example, Dynamic Adaptive Streaming over HTTP (DASH) and SmoothStreaming which aren’t supported by MediaPlayer. It is very customizable and provides a lot of other features and abilities which can be found here.

What will I cover?

Like I have mentioned before, ExoPlayer is very customizable and modular. This enables you to include custom implementations of components and fully tailor it to your needs. Unlike Android’s MediaPlayer which literally requires two lines of code to start playing raw audio, ExoPlayer has more moving parts. In this post, I will cover basic information about ExoPlayer, the fundamental components of ExoPlayer, their purpose and a simple ExoPlayer example which will demonstrate all of the mentioned components in action.

Does ExoPlayer have any disadvantages?

ExoPlayer supports Android versions 4.1 (API level 16) and above because it relies on Android’s MediaCodec (added in API level 16). If you don’t decide to support older versions, you would still cover 97,6% of all Android devices (check current percentage).

ExoPlayer’s support for Widevine common encryption requires Android 4.4 (API 19) and above.

Which formats does it support?

For a detailed list of all supported formats click here.

ExoPlayer’s basic components

1.) ExoPlayer

At the core of the ExoPlayer library is the ExoPlayer interface. Instances can be created through ExoPlayerFactory class. For a simple example you should call:

SimpleExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(Context context, TrackSelector trackSelector);

This method will return a SimpleExoPlayer instance. ExoPlayer exposes traditional high-level media player functionality such as getAudioFormat, getCurrentPositon, getDuration, setVolume, seekTo, setPlayWhenReady, stop and much more. Just like I have mentioned before ExoPlayer uses many components to achieve its modularity and customizability. It delegates work to components which have to be injected when we create and prepare an ExoPlayer instance.

It’s important to keep in mind that we have to release the player when it’s no longer needed. This can be done by calling ExoPlayer.release

2.) TrackSelector

As you might have noticed when we call ExoPlayerFactory. newSimpleInstance(…) we have to provide a TrackSelector instance. TrackSelector selects tracks provided by the MediaSource (explained below) to be consumed by each of the available Renderers (explained below). For most use cases you can get an instance by calling new DefaultTrackSelector():

TrackSelector trackSelector = new DefaultTrackSelector();

3.) MediaSource

It defines and provides media to be played by an ExoPlayer. An instance of MediaSource has to be passed to the following method:

 ExoPlayer.prepare(MediaSource mediaSource)

Note that MediaSource instances should not be re-used, meaning they should be passed only once.

They way of getting and MediaSource instance depends on what kind of data you want to play. For regular media files, you would use ExtractorMediaSource, for DASH (DashMediaSource), SmoothStreaming (SsMediaSource) and for HLS (HlsMediaSource).

For a simple MP3 file, you should call:

MediaSource mediaSource = new ExtractorMediaSource.Factory(DataSource.Factory dataSourceFactory)
.createMediaSource(Uri mp3Uri);

7.) DataSource

As you can see above when we create a simple MP3 MediaSource we have to inject a DataSource. For a simple MP3 use case you can create an instance using the DefaultDataSourceFactory:

DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, Util.getUserAgent(this, “yourApplicationName”));

4.) Renderer

Its responsibility is to render individual components of the media. The library provides default implementations for common media types (MediaCodecVideoRenderer, MediaCodecAudioRenderer, TextRenderer and MetadataRenderer). It consumes media from the MediaSource being played. Renderers are injected when the player is created, but in our simple example, we don’t have to pass our own instance.

6.) LoadControl

LoadControl controls when a MediaSource should buffer more media and how much it should buffer. A default implementation DefaultLoadControl is suitable for most cases. Its also injected when the ExoPlayer instance is created, but again we didn’t have to do that in our simple case.

8.) PlayerView

Of course, we have covered only the backend logic so far. If you want to display the video or audio with controls, you can use ExoPlayer’s PlayerView. Just add the following lines to your XML layout:

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

Then in your activity simply bind the ExoPlayer to the PlayerView:

PlayerView playerView ... // Find view
playerView.setPlayer(SimpleExoPlayer exoPlayer);

PlayerView is just like any other component very customizable. You can provide alternative XML layouts to change the look and feel, you can set an artwork (setDefaultArtwork) and much more.

Of course, there are many more components that ExoPlayer uses but I am not going to cover them here. If you’re interested in that or you need a certain class reference you can check the documentation here.

Putting it all together

That’s a lot of parts, compared to Android’s MediaPlayer, that we have to inject to our ExoPlayer instance. So let’s have a look at how a simple MP3 player would look like:

First, make sure to add this dependency to your build.gradle file:

implementation 'com.google.android.exoplayer:exoplayer:2.7.3'

Then in your activity:

// 1. Declare a field SimpleExoPlayer
private SimpleExoPlayer;
...
// In onCreate
// 2. Create a default TrackSelector
TrackSelector trackSelector = new DefaultTrackSelector();
// 3. Create a SimpleExoPlayer instance
player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);

In your XML layout add:

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

In your activity’s onCreate:

// 4. Find the view
PlayerView playerView = findViewById(R.id.player_view);
// 5. Bind the player to the view.
playerView.setPlayer(player);
// 6. Create a DataSource.Factory instance through which media data is loaded.
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
Util.getUserAgent(this, "simpleAudioApp"));
// 6. Create a MediaSource representing the media to be played.
// Note that I have my file in
main/assets
Uri audioSourceUri = Uri.parse("file:///android_asset/hungarian_dance.mp3");
MediaSource audioSource = new ExtractorMediaSource.Factory(dataSourceFactory)
.createMediaSource(audioSourceUri);
// Prepare the player with the source.
player.prepare(audioSource);

Also, make sure to call release in your onDestroy:

@Override
protected void onDestroy() {
super.onDestroy();
player.release();
}

Please keep in mind that this is the most basic example just for demonstration.

Extras

ExoPlayer library provides much more cool features. Here is a few of them just to give you a taste:

Loop

You can loop a video/audio a number of times by simply passing our MediaSource like so:

// Create a media source
MediaSource mediaSource =
new ExtractorMediaSource.Factory(...)
.
createMediaSource(audioUri);
// Plays the video n-times.
LoopingMediaSource loopingSource = new LoopingMediaSource(MediaSource mediaSource, int nTimesToLoop);

But if you want to loop it indefinitely you can simply call setRepeatMode on your ExoPlayer instance:

player.setRepeatMode(Player.REPEAT_MODE_ALL);

Playing a sequence of videos

This is simply achieved by passing two MediaSources to a ConcatenatingMediaSource:

MediaSource firstSource = new ExtractorMediaSource.Factory(...)
.
createMediaSource(firstVideoUri);
MediaSource secondSource = new ExtractorMediaSource.Factory(...)
.
createMediaSource(secondVideoUri);
// Plays the first video, then the second video.
ConcatenatingMediaSource concatenatedSource =
new ConcatenatingMediaSource(firstSource, secondSource);

You can use MergingMediaSource to merge multiple files into a single source for playback, you can add EventListeners and much more. Head over to the official documentation for more information.

That’s it! I hope this post was helpful and please click the clap 👏 button below a few times to show your support!

If you want to follow me on social media:

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store