Card image cap

Table of Contents:



Preface

Note that the contents of this blog post are referring to ExoPlayer version 2.8.0. That is, the dependency in your build.gradle file will look like this:

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

For reference, the code for this version is available here: ExoPlayer 2.8.0


Introduction

When it comes to playing video or audio files in your Android projects, there's basically two options:

  1. The standard MediaPlayer library that's included in the Android SDK
  2. Or the ExoPlayer library.

For those of you who've never heard of ExoPlayer, you've stumbled across a golden egg. ExoPlayer is a truly superior media player in all regards. It provides an alternative to Android’s MediaPlayer API for playing audio and video both locally and over the Internet. ExoPlayer supports features not currently supported by Android’s MediaPlayer API, including DASH and SmoothStreaming adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and can be updated through Play Store application updates. You can even build custom renderers if a media type does not currently exist in the library. For example some video type that isn't commonly used.


MediaPlayer vs ExoPlayer

In essence, ExoPlayer is just plain-old better than MediaPlayer.
  1. It's more robust
  2. More customizable
  3. There's a metric-boatload of more functionality and features (for example playing ads on videos)
  4. It comes with a built-in player UI so playing videos and controlling playback is simple
  5. It loads data more quickly (I've tested this by comparing load times)
  6. It's hip and cool

FYI for those of you who are wondering about the tests I did when coming the load times, I'm going to be writing another blog post including that data soon. But essentially I compared the time it took to begin playing when I passed a url from the internet. On average ExoPlayer took 350ms to buffer and begin playing, whereas the MediaPlayer took around 1400ms.


ExoPlayer Setup

Exoplayer is advertised as "a super simple but highly customizable library." At the core of this simplicity is the ExoPlayerFactory class. The ExoPlayerFactory class is a factory class (surprise), that makes it easy for you to instantiate a new ExoPlayer instance. If you take a look at the class in the link I provided above, there's essentially two sets of methods.

  1. A set for creating a SimpleExoPlayer instance
  2. And a set for creating an ExoPlayer instance

For the vast majority of use cases one of the ExoPlayerFactory.newSimpleInstance methods should be used. These methods return SimpleExoPlayer, which extends ExoPlayer to add additional high level player functionality.

ExoPlayer is extremely customizable. It has the ability to customize and extend the player to suit your use case. ExoPlayer is designed to allow many components to be replaced with custom implementations. Because of this high degree of customizability, the developers of ExoPlayer have given us some common use-cases we can use in our projects. If you look at the ExoPlayerFactory class it has around a dozen different implementations of the SimpleExoPlayer instance. Each of these implementations have different constructors, or in other-words, different examples of how you could potentially configure your ExoPlayer.SimpleExoPlayer instances are basically pre-configured examples of how you could potentially use ExoPlayer. The developers of the ExoPlayer library pre-packaged these for common use-cases. I think the best way to describe the difference between a SimpleExoPlayer instance and an ExoPlayer instance, is with building blocks. Every SimpleExoPlayer implementation will have an ExoPlayer instance inside it. But the SimpleExoPlayer instances differ with respect to how they are configured.

  • One might have a custom Renderer for various media types and a custom track selector. (Fig 1)
  • While another might have a custom track selector, analytics collector, a custom renderer, load control, and a bandwidth meter. (Fig 2)

Here's an example of instantiating a new SimpleExoPlayer instance: (One of many ways)

TrackSelector trackSelector = new DefaultTrackSelector();

SimpleExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector);



Components Common to All ExoPlayer Instances


MediaSource
A MediaSource defines the media to be played, loads the media, and provides a location from which loaded media can be read. A MediaSource is injected via ExoPlayer.prepare at the start of playback.

Renderer
Renderers that render individual components of the media (Video or Audio). If you take a look at the RenderersFactory class, there's 4 types of renderers:
  1. Video Renderers
  2. Audio Renderers
  3. Text Renderers
  4. and Meta data Renderers
In terms of customizability, you can build renderers for data types that aren't currently supported. For example a video format that isn't supported. Renderers are injected when the player is created.

TrackSelector
A TrackSelector selects tracks provided by the MediaSource to be consumed by each of the available Renderers. A TrackSelector is injected when the player is created.

LoadControl
LoadControl controls when the MediaSource buffers more media, and how much media is buffered. A LoadControl is injected when the player is created.

Basic Example
Here is a basic example implementing a MediaSource, Renderer, TrackSelector, and LoadControl. It's playing an audio file.

TrackSelector trackSelector = new DefaultTrackSelector();

DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this);

DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "AudioStreamer"));

MediaSource audioSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource("https://goo.gl/dHcRSD");

ExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector, new DefaultLoadControl());

exoPlayer.prepare(audioSource);

exoPlayer.setPlayWhenReady(true);



Main Thread or Background Thread?

One of the great things about using ExoPlayer is that you don't need to worry about freezing your UI when buffering, preparing, or playing files. ExoPlayer has an internal playback thread that's responsible for these operations. That being said, ExoPlayer instances must be accessed from a single application thread. For the vast majority of cases this should be the application’s main thread. Using the application’s main thread is also a requirement when using ExoPlayer’s UI components or the IMA extension (The IMA extension is an AdsLoader implementation wrapping the Interactive Media Ads SDK for Android. You can use it to insert ads alongside content).

Here's how the threading mechanism works:
When the application performs an operation on the player (for example a seek) a message is delivered to the internal playback thread via a message queue (This is nothing incredible. Just a classic Thread/Handler relationship passing data to a message queue). The internal playback thread consumes messages from the queue and performs the corresponding operations. Similarly, when a playback event occurs on the internal playback thread, a message is delivered to the application thread via a second message queue (Once again nothing special here, just a Thread/Handler relationship passing messages). The application thread consumes messages from the queue, updating the application visible state and calling corresponding listener methods.


Emulators and ExoPlayer

Some Android emulators do not properly implement components of Android’s media stack, and as a result do not support ExoPlayer. This is an issue with the emulator, not with ExoPlayer. Android’s official emulator (“Virtual Devices” in Android Studio) support ExoPlayer provided that the system image has an API level of at least 23. For this reason the ExoPlayer developers recommend testing media applications on physical devices rather than emulators.


Playing an Audio File

Here is a simple use case of how you might play an audio file in your android project using ExoPlayer:

TrackSelector trackSelector = new DefaultTrackSelector();

DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "AudioStreamer"));

MediaSource audioSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource("https://goo.gl/dHcRSD");

ExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector);

exoPlayer.prepare(audioSource);

exoPlayer.setPlayWhenReady(true);



Playing an Video File

Here is a simple use case of how you might play a video file in your android project using ExoPlayer:

TrackSelector trackSelector = new DefaultTrackSelector();

SimpleExoPlayer exoPlayer = ExoPlayerFactory.newSimpleInstance(this, trackSelector);

PlayerView simpleExoPlayerView = findViewById(R.id.player_view);

simpleExoPlayerView.setPlayer(exoPlayer);

exoPlayer.setPlayWhenReady(true);

DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "VideoPlayer"));

MediaSource videoSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse("https://goo.gl/PyKasq"));

exoPlayer.prepare(videoSource);



Monitoring Playback Progress

If you're playing video using ExoPlayer, monitoring the playback progress is a non-issue. This is a built-in function of the PlayerView class. But if you're playing audio and not using the default players provided with the library, you'll have to build something custom. For example if you're building an audio streaming application with a custom UI, you'll need to implement your own system for tracking the playback progress. The developers of the ExoPlayer recommend using a Runnable and Handler - leveraging the handler.postDelayed method (see recommendation reference here). That way you can control exactly how frequently you want to get playback updates. Here's an example of what you might do:
final Handler handler = new Handler();
final Runnable runnable = new Runnable(){
    @Override
    public void run() {
      if(mIsTrackingPlayback){
        updateUiWithProgress(mExoPlayer.getContentPosition(), mExoPlayer.getDuration());
        handler.postDelayed(this, 300);
      }
    }
  };
  handler.postDelayed(runnable, 300);


When the Player Is No Longer Needed

It’s important to release the player when it’s no longer needed, so as to free up limited resources such as video decoders for use by other applications. This can be done by calling ExoPlayer.release:
exoPlayer().release();


Final Thoughts

I'm currently in the process of developing an audio streaming application using ExoPlayer. I'll be publishing a course outlining the entire process by mid December 2018.


Staying in the Loop


If you want to stay in the loop and get an email when I write new blog posts, Follow me on Instagram or join the CodingWithMitch community on my website. It only takes about 30 seconds to register.





Authors


Mitch Tabian

codingwithmitch.com

Software Dev


Create an Account



Have an account? Log In

CodingWithMitch Members

Unlimited access to all courses and videos

Step by step guides to build real projects

Video downloads for offline viewing

Members can vote on what kind of content they want to see

Access to a private chat with other communnity members & Mitch

Become a Member

CodingWithMitch Members

Unlimited access to all courses and videos

Step by step guides to build real projects

Video downloads for offline viewing

Members can vote on what kind of content they want to see

Access to a private chat with other communnity members & Mitch

Become a Member

Comments