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ć.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Media Playback on Android with ExoPlayer: Getting Started
25 mins
- Media Playback on the Android Framework
- Audio and Video
- Playing Media on Android
- Getting Started
- Project Structure
- Adding a Media Player to the Application
- Adding the Dependency
- Creating the View
- Creating the Player
- Attaching the Player to a View
- Customizing ExoPlayer
- Changing the Appearance
- Pros and Cons of ExoPlayer
- Where to Go From Here?
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:
- 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. - In
ExoPlayer
, every piece of media is represented by aMediaSource
. To play a piece of media, you must first create a correspondingMediaSource
. 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 theExtractorsFactory
, 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 thecreateMediaSource()
method which takes aUri
of the media that you want to play. In this case you’ll play the media from a remote server. - You need to call the
prepare()
method on theExoPlayer
instance. This method prepares the player to play the provided media source. - Finally, by setting the
playWhenReady
variable totrue
orfalse
, 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:
Now you can play video. Awesome. :]
Guess we’re done here, right? Not yet.