ExoPlayer is a Google own and operated, open-source, application level media player for Android. Built on top of Android's low level media APIs, ExoPlayer offers a more powerful and more robust alternative to MediaPlayer, with additional features and customization flexibility. It is usable on API 16 and up.
There are many steps required to setting up ExoPlayer, so for this walkthrough, we will cover its most key components to better understand how ExoPlayer works, and then look at a third-party solution for quickly adding an ExoPlayer to your app.
ExoPlayer has several key advantages over MediaPlayer, including:
Note: One key disadvantage is that when using audio only playback, some devices may use more battery than MediaPlayer. It's also worth considering whether or not any of the above features are needed for your app. MediaPlayer can still be a valid option for simple use cases.
Make sure that both the Google and JCenter repositories to the root build.gradle
.
repositories {
google()
jcenter()
}
In your app level build.gradle
, also make sure you have Java 8 support.
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
}
Once these dependencies are added, you can add the version of ExoPlyaer you desire.
implementation 'com.google.android.exoplayer:exoplayer:2.X.X'
If you don't believe you need the full library, a list of individual library modules and extensions can be found here.
Google's implemented version of ExoPlayer is the SimpleExoPlayer
, and can be created using ExoPlayerFactory
.
private void initializePlayer() {
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(context);
}
There are almost a dozen versions of ExoPlayer.newSimpleInstance
in the SimpleExoPlayer class, with differerent parameters depending on what you need your ExoPlayer to have.
Once our player is ready, we can set it to our view. It can either be defined as ExoPlayer's PlayerView
widget, or as a SurfaceView
, TexureView
, SurfaceHolder
, or Surface
.
private void setPlayerView(View playerView, SimpleExoPlayer player) {
if (playerView instanceof PlayerView) {
playerView.setPlayer(player);
} else if (playerView instanceof SurfaceView) {
player.setVideoSurfaceView((SurfaceView) playerView);
} else if (playerView instanceof TextureView) {
player.setVideoTextureView((TextureView) playerView);
} else if (playerView instanceof SurfaceHolder) {
player.setVideoSurfaceHolder((SurfaceHolder) playerView);
} else if (playerView instanceof Surface) {
player.setVideoSurface((Surface) playerView);
}
}
private void initializePlayer() {
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(context);
setPlayerView(playerView, player);
}
Next we need to create a MediaSource
, which defines the media to be played, then loads and reads said media. MediaSource
is constructed from a MediaSourceFactory
, creating an ExoPlayer supoorted MediaSource
. The formats currently available are DASH, SmoothStreaming, HLS, and regular media files.
In the following code example, based on the ExoPlayer demo project, we extract format type from the URI, and create the matching MediaSoruce
:
private MediaSource buildMediaSource(Uri uri) {
@ContentType int type = Util.inferContentType(uri);
DataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context,
Util.getUserAgent(context, "yourApplicationName"));
switch (type) {
case C.TYPE_DASH:
return new DashMediaSource.Factory(dataSourceFactory)
.setManifestParser(
new FilteringManifestParser<>(new DashManifestParser(), getOfflineStreamKeys(uri)))
.createMediaSource(uri);
case C.TYPE_SS:
return new SsMediaSource.Factory(dataSourceFactory)
.setManifestParser(
new FilteringManifestParser<>(new SsManifestParser(), getOfflineStreamKeys(uri)))
.createMediaSource(uri);
case C.TYPE_HLS:
return new HlsMediaSource.Factory(dataSourceFactory)
.setPlaylistParserFactory(
new DefaultHlsPlaylistParserFactory(getOfflineStreamKeys(uri)))
.createMediaSource(uri);
case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
default: {
throw new IllegalStateException("Unsupported type: " + type);
}
}
}
Once the MediaSource
is created, we can call ExoPlayer.prepare(mediaSource)
.
private void initializePlayer() {
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(context);
setPlayerView(playerView, player);
MediaSource mediaSource = buildMediaSource(uri);
player.prepare(mediaSource);
}
The key functions for play, pause, and seek, are setPlayWhenReady(true)
, setPlayWhenReady(false)
, and seekTo(positionInMS)
respectively.
When the instance of the app or activity finishes, we also need to release the player with player.release()
. Failing to do so will cause the player to continue running in the background and not video decoders for use by other applications. Don't forget to release when you're done!
private voide playPlayer() {
if (player != null) {
player.setPlayWhenReady(true);
}
}
private voide pausePlayer() {
if (player != null) {
player.setPlayWhenReady(false);
}
}
private void seekTo(long positionInMS) {
if (player != null) {
player.seekTo(positionInMS);
}
}
private void releasePlayer() {
if (player != null) {
player.release();
}
}
A more detailed look at the ExoPlayer documentation, including using listeners, DRM, tracking, and modified MediaSources can be found here. For full code of the ExoPlayer sample project, see here.
The above is just the high level look at implementing an ExoPlayer in an android app. There are additional steps still required. If you're interested, the linked documentation and demo project show the full requirments for a fully functioning ExoPlayer.
But what if you just want to add a player with a few lines of code, and be good to go? Enter TubiPlayer!
The Tubi TV open source player, built for their streaming needs, has already gone through the leg work to set up and initialize the ExoPlayer, through it's TubiExoPlayer
custom view.
Currently, you need to add the project as its own module. A bintray extension is coming soon.
If all you need is to run ExoPlayer in fullscreen, call the DoubleViewTubiPlayerActivity
, passing in the video url, and other optional fields such as video name, artwork, and subtitles. Pass them into an intent to DoubleViewTubiPlayerActivity
, using a MediaModel
extra mapped to TubiPlayerActivity.TUBI_MEDIA_KEY
, and that's it!
String subs = "http://put_your_own_subtitle.srt";
String artwork = "http://www.put_your_own_art_work.png";
String name = "Example Video Name";
String video_url = "http://put_your_own_video_url.mp4";
Intent intent = new Intent(SelectionActivity.this,
DoubleViewTubiPlayerActivity.class);
intent.putExtra(TubiPlayerActivity.TUBI_MEDIA_KEY,
MediaModel.video(name, video_url, artwork, null));
startActivity(intent);
What if you need an ExoPlayer that's not fullscreen? Then simply create a xml view that includes the TubiExoPlayer
widget, design the layout as desired, and create an activity that extends from DoubleViewTubiPlayerActivity
. Override the initLayout
function and set the mTubiPlayerView
to the xml's TubiExoPlayer
, and it will be fully functional:
@Override
protected void initPlayer() {
super.initPlayer();
mTubiPlayerView = findViewById(R.id.TUBI_PLAYER);
}
Also, don't forget to pass in the same MediaModel
extra mapped to TubiPlayerActivity.TUBI_MEDIA_KEY
for the new activity. After that, you should be good to go with having ExoPlayer running in your app.
If you wish to change any of the player business logic, look through the PlayerModuleDefault
class. This is where nearly every business related dependency is managed, and where you can override the dependencies with your own logic.
TubiPlayer also offers the use of pre-roll and middle-row video ads. To read up on this additional feature, see here.