#TECH

Getting started with ExoPlayer

ExoPlayer is an open source application which was introduced in Google I/O 2014. It’s built on top of the Android low-level MediaPlayer.

Audio and video playing is a frequently used action in Android. That’s why we have a wide range of audio and video players on Android. ExoPlayer is also one such audio and video player. It is a custom open source media product developed by the Google team. It is used for playing audio and video locally or streaming over the Internet. ExoPlayer is very handy, you can use it by writing just a few lines of code. It has some cool features like streaming with dynamic adaptive bitrate (DASH) or Live streaming over HTTP protocol.

Exo Player

So let’s explore some advantages and disadvantages of ExoPlayer first and then we will learn about how to configure and use ExoPlayer in our project.

Advantages

  • It’s highly customizable and designed in a way that you can replace many components with your own custom components. For e.g MediaController.
  • It supports dynamic adaptive streaming over HTTP or HLS which is something the low-level Android MediaPlayer does not support.
  • It supports almost all audio and video formats like MP4, MP3, AAC, WEBM, M4A, MPEG and much more.
  • It’s a lightweight library which can easily be used to replace the Android MediaPlayer. It can be embedded in your APK. So you can manage and update the versions of the player accordingly.
  • It supports DRM (Digital Right Management) protected content.
  • ExoPlayer supports advanced HLS features, such as correct handling of #EXT-X-DISCONTINUITY tags. #EXT-X-DISCONTINUITY indicates a discontinuity between the media segment that follows it and the one that precedes it.

Disadvantages

  • The audio and video content used by ExoPlayer is dependent on the MediaCodec which was introduced in Android 4.1. That’s why we can’t use ExoPlayer on versions lower than 4.1.

Now let’s take a look at the ExoPlayer components-

  • Media Source: A media source is responsible for loading the media and it can be played after reading. You have to pass this parameter to ExoPlayer.prepare(). The media source can be of different types like ExtractorMediaSource, ConcatenatingMediaSource, DashMediaSource.
  • Rendered: It creates a renderer for timestamp synchronized rendering of video, audio, and text (subtitles).
  • Track Selector: A track selector is responsible for selecting from the list of tracks (audio, video or text) passed to ExoPlayer via media source.
  • Load Control: It is responsible for controlling the media buffer, i.e how much media is buffered. A LoadControl is injected when the player is created.

Now let’s try to understand the working of ExoPlayer through a component diagram. I have shown 3 diagrams for simple media source as well as the adaptive media source.

Figure 1


Figure 2


Figure 3

Figure 1 explains the workflow of sample source whereas figure 2 explains the workflow of extractor media source and figure 3 depicts adaptive media source.

Gradle setup

There are 5 different Gradle modules which can be used in your project depending upon your requirement for the ExoPlayer.

compile 'com.google.android.exoplayer:exoplayer-core:r2.4.0'
compile 'com.google.android.exoplayer:exoplayer-dash:r2.4.0'
compile 'com.google.android.exoplayer:exoplayer-ui:r2.4.0'
compile 'com.google.android.exoplayer:exoplayer-hls:r2.4.0'
compile 'com.google.android.exoplayer:exoplayer-smoothstreaming:r2.4.0'
  • exoplayer-core: Core functionality(required)
  • exoplayer-dash: Support for DASH content
  • exoplayer-hls : Support for HLS content
  • exoplayer-smoothstreaming: Support for smooth streaming content
  • exoplayer-ui: Support for UI component modification in ExoPlayer

The current ExoPlayer version is r2.5.1 which you can use.

Add SimpleExoPlayerView

Open your layout XML file and add a SimpleExoPlayerView element to your XML file.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.quo.exoplayer.activities.AudioPlayerActivity">

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

</FrameLayout>

 

Initialize your player

To initialize your ExoPlayer you can use ExoPlayerFactory class and its static method newSimpleInstance which takes RenderersFactory, TrackSelector, and LoadControl as a parameter. I have already described these components in the starting of this blog-

private void initializePlayer() {
        mSimpleExoPlayer = ExoPlayerFactory.newSimpleInstance(
                new DefaultRenderersFactory(this),
                new DefaultTrackSelector(), new DefaultLoadControl());
        mAudioSEPV.setPlayer(mSimpleExoPlayer);
        mSimpleExoPlayer.setPlayWhenReady(mPlayWhenReady);
        mSimpleExoPlayer.seekTo(mCurrentWindow, mPlaybackPosition);
        Uri uri = Uri.parse(getString(R.string.media_url_mp3));
        MediaSource mediaSource = buildMediaSource(uri);
        mSimpleExoPlayer.prepare(mediaSource, true, false);
    }

 

Creating your media source

To play any media through ExoPlayer you need to pass a media source to your ExoPlayer. For that, you need to build a media source depending upon your media type and your requirement. Here I am showing you a few examples of building a media source through code snippet.

  • ExtractorMediaSource: ExtractorMediaSource is suitable for regular media files (mp4, webm, mkv etc). We can use it as default media source.

private MediaSource buildMediaSource(Uri uri) {
        return new ExtractorMediaSource(uri,
                new DefaultHttpDataSourceFactory("ua"),
                new DefaultExtractorsFactory(), null, null);
    }

 

  •  ConcatenatingMediaSource: ConcatenatingMediaSource can combine more than one media source into a single unit. You can pass any number of MediaSource object to its constructor.  It plays all the MediaSource in the same sequence you pass it into a constructor. This class has one other constructor in which you can pass a boolean for repeat mode if you want it to be turned on.

private MediaSource buildMediaSource(Uri uri) {
        // these are reused for both media sources we create below
        DefaultExtractorsFactory extractorsFactory =
                new DefaultExtractorsFactory();
        DefaultHttpDataSourceFactory dataSourceFactory =
                new DefaultHttpDataSourceFactory("user-agent");
        ExtractorMediaSource videoSource =
                new ExtractorMediaSource(uri, dataSourceFactory,
                        extractorsFactory, null, null);
        Uri audioUri = Uri.parse(getString(R.string.media_url_mp3));
        ExtractorMediaSource audioSource =
                new ExtractorMediaSource(audioUri, dataSourceFactory,
                        extractorsFactory, null, null);
        return new ConcatenatingMediaSource(audioSource, videoSource);
    }

 

  • LoopingMediaSource: If you want to play the same MediaSource multiple times then you can use LoopingMediaSource. It takes a MediaSource and loop count (number of times we want to play the same MediaSource) as a parameter. If you don’t pass count it will play the MediaSource infinitely.

private MediaSource buildMediaSource(Uri uri) {
        // these are reused for both media sources we create below
        DefaultExtractorsFactory extractorsFactory =
                new DefaultExtractorsFactory();
        DefaultHttpDataSourceFactory dataSourceFactory =
                new DefaultHttpDataSourceFactory("user-agent");
        ExtractorMediaSource videoSource =
                new ExtractorMediaSource(uri, dataSourceFactory,
                        extractorsFactory, null, null);
        Uri audioUri = Uri.parse(getString(R.string.media_url_mp3));
        ExtractorMediaSource audioSource =
                new ExtractorMediaSource(audioUri, dataSourceFactory,
                        extractorsFactory, null, null);
        return new LoopingMediaSource(audioSource, 5);
    }

 

  • MergingMediaSource: MergingMediaSource also takes multiple MediaSource as a parameter. You can merge more than one MediaSource at a time but the timelines of the sources being merged must have the same number of periods, and must not have any dynamic windows.

private MediaSource buildMediaSource(Uri uri) {
        // these are reused for both media sources we create below
        DefaultExtractorsFactory extractorsFactory =
                new DefaultExtractorsFactory();
        DefaultHttpDataSourceFactory dataSourceFactory =
                new DefaultHttpDataSourceFactory("user-agent");
        ExtractorMediaSource videoSource =
                new ExtractorMediaSource(uri, dataSourceFactory,
                        extractorsFactory, null, null);
        Uri audioUri = Uri.parse(getString(R.string.media_url_mp3));
        ExtractorMediaSource audioSource =
                new ExtractorMediaSource(audioUri, dataSourceFactory,
                        extractorsFactory, null, null);
        return new MergingMediaSource(audioSource,videoSource);
    }

 

  • DashMediaSource: We use DashMediaSource for adaptive streaming. It divided the video, audio files into multiple chunks for the giving duration. ExoPlayer then links them while playing. It downloads the chunks on behalf of the network bandwidth. After starting with a low-quality chunk to be able to render the first frame as quickly as possible, the player switches to a better quality for the second chunk if sufficient bandwidth is available (for instance on wifi vs. mobile).
private MediaSource buildMediaSource(Uri uri) {
        DataSource.Factory dataSourceFactory =
                new DefaultHttpDataSourceFactory("ua", BANDWIDTH_METER);
        DashChunkSource.Factory dashChunkSourceFactory =
                new DefaultDashChunkSource.Factory(dataSourceFactory);
        return new DashMediaSource(uri, dataSourceFactory,
                dashChunkSourceFactory, null, null);
    }

 

In case of DashMediaSource, we need to create an instance of ExoPlayer differently. You need to use the adaptive track selection factory instance with bandwidth meter (create a member variable of DefaultBandwidthMeter in your class) which needs to be passed in DefaultTrackSelector() instance. Here is the code snippet:

private static final DefaultBandwidthMeter BANDWIDTH_METER =
            new DefaultBandwidthMeter();

private void initializePlayer() {
        if (mSimpleExoPlayer == null) {
            TrackSelection.Factory adaptiveTrackSelectionFactory =
                    new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
            mSimpleExoPlayer = ExoPlayerFactory.newSimpleInstance(
                    new DefaultRenderersFactory(this),
                    new DefaultTrackSelector(adaptiveTrackSelectionFactory),
                    new DefaultLoadControl());

        }
        mAudioSEPV.setPlayer(mSimpleExoPlayer);
        Uri uri = Uri.parse(getString(R.string.media_url_dash));
        MediaSource mediaSource = buildMediaSource(uri);
        mSimpleExoPlayer.prepare(mediaSource, true, false);
    }
All done

Now we are all done and ready to play a media file through ExoPlayer. You just need to call the initializeMediaPlayer() method when you want to play it via ExoPlayer.

Release media player

Last but not the least, you need to write the code for releasing the MediaPlayer. Make sure you release the instance of the player and mark it as null once you are done or when activity gets destroyed. It is recommended to release the media player immediately once you are done so that the resources used by it are released and can be made available for use by other applications. In order to do so, you just need to call mediaplayer.release() method. It guarantees that all the objects allocated by media player get released. So here is the code to release the MediaPlayer:

 private void releasePlayer() {
        if (mSimpleExoPlayer != null) {
            mPlaybackPosition = mSimpleExoPlayer.getCurrentPosition();
            mCurrentWindow = mSimpleExoPlayer.getCurrentWindowIndex();
            mPlayWhenReady = mSimpleExoPlayer.getPlayWhenReady();
            mSimpleExoPlayer.release();
            mSimpleExoPlayer = null;
        }
    }

 

That’s all for this blog. In my next blog, I will explain how to detect different ExoPlayer events and how to debug the quality assurance of the ExoPlayer. I will also be writing on how to customize UI components of ExoPlayer i.e. PlayerControlView.

Thank you for reading. Please feel free to drop any comments.