Media Playback on Android with ExoPlayer: Getting Started

In this tutorial you will learn how to use ExoPlayer to provide media playback in your Android app. By Dean Djermanović.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

Adding a Media Player to the Application

The main screen shows you a list of sample video names that are fetched from Cloudinary.

When you click on a list item VideoViewActivity launches, but shows nothing in the starter project. This is where your video is going to be displayed.

Adding the Dependency

Recall that ExoPlayer is a library, in order to use it you have to add it to the project first. The ExoPlayer library is split into modules to allow developers to import only a subset of the functionality provided by the full library. The benefits of depending on only the modules you need are that you get a smaller APK size and you don’t include the features in your app that you aren’t going to use.

These are the available modules and their purpose:

  • exoplayer-core: Core functionality (required).
  • exoplayer-dash: Support for DASH content.
  • exoplayer-hls: Support for HLS content.
  • exoplayer-smoothstreaming: Support for SmoothStreaming content.
  • exoplayer-ui: UI components and resources for use with ExoPlayer.

It’s still possible to depend on the full library if you prefer which is equivalent to depending on all of the modules individually.

For the sake of simplicity we’ll add the full library.

Open your app module level build.gradle file and add the following dependency to the dependencies block:

implementation 'com.google.android.exoplayer:exoplayer:' + project.ext.exoPlayerVersion

The ExoPlayer version constant is already added to the project level build.gradle file so you can just use that version.

Sync the project after adding the dependency.

Creating the View

Next, you’ll create the view. If you were using Android’s MediaPlayer API you would display videos in a SurfaceView. The ExoPlayer library provides it’s own high level view for media playback. It displays video, subtitles and album art, and also displays playback controls.

To add it, open the activity_video_view.xml layout file from res/layout and replace the contents with the following:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.video.VideoViewActivity">

  <com.google.android.exoplayer2.ui.PlayerView
    android:id="@+id/ep_video_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

Open the VideoViewActivity.kt file in the iu.video package and add a property for the PlayerView:

private lateinit var videoView: PlayerView

Initialize the view in the init() method:

videoView = findViewById(R.id.ep_video_view)

Creating the Player

Since you’re using the MVP pattern in this project, you will decouple the view from the player. Start by creating a new com.raywenderlich.funtime.device.player package.

Inside this package, create a MediaPlayer interface, which is going to describe the behavior for the media player, and a MediaPlayerImpl class, which will contain the concrete implementation of your media player. Make the MediaPlayerImpl class implement the MediaPlayer interface.

Using the MediaPlayer interface makes swapping player implementations a breeze. You might want to explore creating alternate implementations without using ExoPlayer to explore the Android Media APIs more deeply.

Now, open the MediaPlayerImpl class. First, you need to initialize your player.

Add a property called exoPlayer for the player:

private lateinit var exoPlayer: ExoPlayer

Also add a property for the context that you’ll set and use later:

private lateinit var context: Context

Next, add the initializePlayer() method where you’re going to create a new instance of ExoPlayer and assign it to the exoPlayer member variable.

You can create an ExoPlayer instance using ExoPlayerFactory. The factory provides a range of methods for creating ExoPlayer instances with varying levels of customization. But, for most use cases, you should use one of the ExoPlayerFactory.newSimpleInstance methods.

Initialize exoPlayer in the method like this:

private fun initializePlayer() {

  val trackSelector = DefaultTrackSelector()
  val loadControl = DefaultLoadControl()
  val renderersFactory = DefaultRenderersFactory(context)

  exoPlayer = ExoPlayerFactory.newSimpleInstance(
      renderersFactory, trackSelector, loadControl)
}

ExoPlayerFactory.newSimpleInstance() takes three parameters:

  • A RenderersFactory that creates renderer instances for use by ExoPlayer; they render media from some stream.
  • A TrackSelector is responsible for selecting tracks to be consumed by each of the player’s renderers.
  • A LoadControl that controls the buffering of the media.

Don’t worry about the specifics of these classes; using the default classes works perfectly in most use cases.

Awesome, you created an instance of the ExoPlayer!

You want your player to have the ability to play media, so describe that in the MediaPlayer interface by adding the following method:

fun play(url: String)

Implement that method in the MediaPlayerImpl class.

This is how you play the media with ExoPlayer:

override fun play(url: String) {
  //1
  val userAgent = Util.getUserAgent(context, context.getString(R.string.app_name))
  //2
  val mediaSource = ExtractorMediaSource
      .Factory(DefaultDataSourceFactory(context, userAgent))
      .setExtractorsFactory(DefaultExtractorsFactory())
      .createMediaSource(Uri.parse(url))
  //3
  exoPlayer.prepare(mediaSource)
  //4
  exoPlayer.playWhenReady = true
}

Going through this step by step:

  1. A UserAgent is just a string that is generated for you based on the given application name and library version. You’ll use it in next step.
  2. In ExoPlayer, every piece of media is represented by a MediaSource. To play a piece of media, you must first create a corresponding MediaSource. Again, there’s a factory for media source creation that takes a data source factory as a parameter. Data source is a component from which streams of data can be read. You have to set the ExtractorsFactory, which just returns the array of extractors. An Extractor extracts media data from a container format. Don’t worry about the specifics of these classes, since using the default classes works perfectly in most use cases. What’s important here is the createMediaSource() method which takes a Uri of the media that you want to play. In this case you’ll play the media from a remote server.
  3. You need to call the prepare() method on the ExoPlayer instance. This method prepares the player to play the provided media source.
  4. Finally, by setting the playWhenReady variable to true or false, you actually tell the player to play the media when it’s ready. If the player is already in the ready state, then this method can be used to pause and resume playback.

You have now initialized the player and you have the view. What’s next?

Attaching the Player to a View

Attaching the player to the view is very straightforward. You just set the ExoPlayer instance on the player view that you added to the xml by calling the setPlayer(...) method.

Since you’re using MVP and you’re decoupling the concrete player implementation from the view, you need a way to get the underlying player implementation.

Add a method to the MediaPlayer interface that will give you access to the underlying implementation:

fun getPlayerImpl(context: Context): ExoPlayer

Implement that method in MediaPlayerImpl class:

override fun getPlayerImpl(context: Context): ExoPlayer {
  this.context = context
  initializePlayer()
  return exoPlayer
}

Now, you have access to the ExoPlayer implementation. VideoViewActivity will get the ExoPlayer instance through VideoViewPresenter.

Add a getPlayer() method to the VideoViewContract.Presenter interface in the ui.video package, which returns a MediaPlayer instance:

fun getPlayer(): MediaPlayer

Add a media player property to the VideoViewPresenter:

private val mediaPlayer = MediaPlayerImpl()

Implement the getPlayer() method, which will just return the media player instance:

override fun getPlayer() = mediaPlayer

In VideoViewActivity, set the player on the view inside the init() method by calling:

videoView.player = presenter.getPlayer().getPlayerImpl(this)

To actually play the video, add a play() method to the VideoViewContract.Presenter interface and pass in the media url:

fun play(url: String)

Now, implement that method in the VideoViewPresenter. This method just delegates media playing to media player.

override fun play(url: String) = mediaPlayer.play(url)

Great, now you’re ready to play the video.

At the end of VideoViewActivity’s init() method, tell the presenter to play the video:

presenter.play(videoUrl)

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

Add a releasePlayer() method to the MediaPlayer interface:

fun releasePlayer()

And implement it in the MediaPlayerImpl class:

override fun releasePlayer() {
  exoPlayer.stop()
  exoPlayer.release()
}

Add the releasePlayer() method to the VideoViewContract.Presenter, as well, and implement it in the VideoViewPresenter class:

override fun releasePlayer() = mediaPlayer.releasePlayer()

You need to make sure that VideoViewActivity releases the player when it is no longer the active Activity.

To do this, you release the player in onPause() if on Android Marshmallow and below:

override fun onPause() {
  super.onPause()
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
    presenter.releasePlayer()
 }
}

Or release in onStop if on Android Nougat and above because of the multi window support that was added in Android N:

override fun onStop() {
  super.onStop()
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    presenter.releasePlayer()
 }
}

Build and run your app to see what happens.

Click on any item in the list and you will get a screen like this:

Playing Video

Now you can play video. Awesome. :]

Guess we’re done here, right? Not yet.