Migrating from ExoPlayer 1.x to 2.0

Evan
3 min readNov 2, 2016

--

In my previous article, I walked through how I architected an audio app on Android. I wagered that all of the work I would need to do would be contained to the LocalMediaPlayer class. I was correct. Here is how I migrated from ExoPlayer 1.x to 2.0.

I updated my app’s module build.gradle:

compile ‘com.google.android.exoplayer:exoplayer:r2.0.4’

Sync the Gradle changes so the updated ExoPlayer dependency is pulled down. Congratulations, PremoFM no longer compiles.

An aside, the ExoPlayer import is the only dependency I’ve seen that prepends the version numbers with an ‘r’. 🤔

Now in the LocalMediaPlayer class, there should be errors everywhere. Let’s fix them.

Starting in the constructor:

mHandler = new Handler();
TrackSelector trackSelector = new DefaultTrackSelector(mHandler);
DefaultLoadControl loadControl = new DefaultLoadControl();
mMediaPlayer = ExoPlayerFactory.newSimpleInstance(mContext, trackSelector, loadControl);
mMediaPlayer.addListener(this);

There’s a lot to unpack here, so let me explain. I need to create a new Handler object so that ExoPlayer can pass messages to my listeners (the LocalMediaPlayer class implements the EventListener interface). I create and use default implementations of the load control and track selector classes. Finally, I grab an instance of the ExoPlayer class by calling the newSimpleInstance method on the ExoPlayerFactory class. This method class obfuscates a lot of complexity for me. I could have called newInstance instead, but then I’d be required to pass TrackRenderers, which is unnecessary in my case.

Now let’s define the audio formats my app should be able to support, extract, and render.

private static class AudioExtractorsFactory implements ExtractorsFactory {

@Override
public Extractor[] createExtractors() {
return new Extractor[]{
new OggExtractor(),
new WavExtractor(),
new Mp3Extractor(),
new Mp4Extractor()};
}
}

My AudioExtractorsFactory class will be used when I build my MediaSource class. I include extractors for the Ogg Vorbis, WAV, MP3, and MP4 audio formats since podcasts are generally available in these audio formats.

Let’s handle building the MediaSource. The MediaSource class is tasked with retrieving, decoding, and sending audio samples to the ExoPlayer instance. I want to support two cases in PremoFM, 1) the audio file is already on the local file system and 2) the audio file is on a server somewhere on the Internet. ExoPlayer, again, makes this really easy, I either use the FileDataSourceFactory or the DefaultHttpDataSourceFactory.

private MediaSource buildMediaSource() {
DataSource.Factory dataSourceFactory = null;
Uri uri = null;
// return the uri to play
switch (mEpisode.getDownloadStatus()) {
case DownloadStatus.DOWNLOADED:
uri = Uri.parse(mEpisode.getLocalMediaUrl());
dataSourceFactory = new FileDataSourceFactory();
break;
case DownloadStatus.DOWNLOADING:
case DownloadStatus.NOT_DOWNLOADED:
uri = Uri.parse(mEpisode.getRemoteMediaUrl());
dataSourceFactory = new DefaultHttpDataSourceFactory(
mContext.getString(R.string.user_agent), null,
DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
true);
break;
}
if (uri != null && dataSourceFactory != null) {
return new ExtractorMediaSource(uri, dataSourceFactory, new AudioExtractorsFactory(),
mHandler, null);
}
throw new IllegalStateException("Unable to build media source");
}

The FileDataSourceFactory is easy to configure. The Uri in this case could look like:

file://data/data/com.mainmethod.premofm/…./this-podcast.mp3

The DefaultHttpDataSourceFactory has a few more parameters, if you need them. By default, the single parameter constructor for DefaultHttpDataSourceFactory does not handle cross domain/scheme redirects, which is a very common case when streaming podcast audio. I found out the hard way and now I use the 4 argument constructor. The additional parameters allow me to configure connection and read timeouts. I opt for the default values and pass a ‘true’ to enable cross domain/scheme redirects. The Uri in this case could look like:

http://feedproxy.google.com/~r/TheCombatJackShow/~5/s_9fWPxLDu0/188058705-thecombatjackshow-the-j-cole-episode.mp3

Finally, ExoPlayer 2, like the more recent versions of ExoPlayer 1.x, supports variable speed audio playback on Android 6.0+ devices. You can customize audio playback speed and even pitch using the PlaybackParams API.

PlaybackParams playbackParams = new PlaybackParams();
playbackParams.setSpeed(speed);
mExoPlayer.setPlaybackParams(playbackParams);

That’s the majority of the ExoPlayer 2.X related changes. There are some smaller, app-specific things I needed to change. Since I was no longer leveraging a custom TrackRenderer, I needed a way to broadcast track position updates to my user interface. I spin up a separate thread that accomplishes that.

One final note, the ExoPlayer.STATE_PREPARING is no longer a thing. There is a method callback called EventListener.onLoadingChange that can alert you to the state of loading for a particular audio/video track.

You can check out the full diff of my migration here. PremoFM is an open source podcast player for Android. Feel free to fork it and make your contributions or use portions for your own projects.

ExoPlayer is also open source and well maintained by Google. A ton of excellent documentation is also available on GitHub.

I love talking and writing about Android development so be sure to follow me on Medium, recommend or share this article, follow me on Twitter, and visit my website for more.

Finally, if you are a podcast fan or even remotely curious, checkout RadioPublic, currently in beta, at RadioPublic.com.

--

--

Evan

I really love solving fun and interesting problems with software. https://evanhalley.dev