Almost every android application requires playing music videos or mp3 files online or offline (a file exists in internal or maybe in external storage). Playing an audio-video file on your android must have a task to perform. Media can played in various ways like from the server or from your local app memory. So, In this article, we’ll learn how to play these type of media with ExoPlayer.

We will not gonna dive into the history of ExoPlayer or why it is best? Pros and cons of ExoPlayer with

MediaPlayer. You guys just have to know that this Player is mostly used in popular apps like Youtube, NetFlix, Amazon Prime, etc…

Brief Into of ExpPlayer

ExoPlayer is an open-source library provided by Google for Android. It support features not supported by Android MediaPlayer for plyaing online and offline audio video file. It is also possible to play media with a lot of customization from low-level media APIs. The downside of it is very complicated and in most cases it is unnecessary.

Here’s a list of supproted android version for ExoPlayer are.

exoplayer android 2

Get Started With ExoPlayer

To show you how to implement the ExoPlayer we’re gonna play the audio and video file stored in local (

internal storage) and play the media uploaded on server with simple Uri url.

Adding the ExoPlayer Dependency

The ExoPlayer library is split into modules to allow developer to import only a subset of functionality provided by full library. The available library modules are listed below. Adding a dependency to the full ExoPlayer library is equivalent to adding dependencies on all of the library modules individually.

Available modules of ExoPlayer library.

exoplayer-core Core functionality (required)
exoplayer-dash Support for DASH content.
extension-cronet Module depend on external libraries
exoplayer-ui UI components and resources for use with ExoPlayer

There are other modues available for ExoPlayer you can check them out here.

Now add the following exoplayer dependecies into

build.gradle file.

implementation 'com.google.android.exoplayer:exoplayer-core:2.18.1'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.18.1'
implementation 'com.google.android.exoplayer:extension-cronet:2.18.1'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.18.1'

Change the 2.18.1 with the latest version of library.

Turn on Java8 support into build.gradle into android {} section.

compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

Note: Make sure to add the Google and Jcenter repositories into settings.gralde file like this.

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
    repositories {
        google()
        //noinspection JcenterRepositoryObsolete
        jcenter()
        maven { url 'https://jitpack.io' }
        maven { url "https://maven.google.com" }
        mavenCentral()
    }
}

Sync the project after adding the dependency.

Then add internet permission in your AndroidManifest.xml file to get video url, read/write storage are optional for caching, play media in devices or something like that.

<uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />

Also add the following line into AndroidManifest.xml in application tag.

android:requestLegacyExternalStorage="true"

Play Internal Storage AudioFile With ExoPlayer

Here’s the scenario, lets say you need to downaload some media file from server and save it to private storage of application. So, the user can play it when his/her device is not connected to internet. By default, saving and loading files from the internal storage are private to the application and other applications will not have access to these files.

First we gonna play a mp3 file. Suppose we have this file name some-random-audio.mp3 stored into internal stroage.

Add a property called exoPlayer for the player

private lateinit var exoPlayer: ExoPlayer

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

val renderersFactory = buildRenderersFactory(applicationContext, true)  // 1
       val trackSelector = DefaultTrackSelector(applicationContext)  // 2
       exoPlayer = ExoPlayer.Builder(applicationContext, renderersFactory)  // 3
           .setTrackSelector(trackSelector)  
           .build().apply {
               trackSelectionParameters = DefaultTrackSelector.Parameters.Builder(applicationContext).build()  // 4
               addListener(exoPlayerListener)  // 5
               playWhenReady = false  // 6
            }

private fun buildRenderersFactory(   
        context: Context, preferExtensionRenderer: Boolean
    ): RenderersFactory {
        val extensionRendererMode = if (preferExtensionRenderer)
            DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
        else DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON

        return DefaultRenderersFactory(context.applicationContext)
            .setExtensionRendererMode(extensionRendererMode)
            .setEnableDecoderFallback(true)
    }

Going through the above step-by-step:

  1. A RenderersFactory that creates renderer instances for use by ExoPlayer, they render media from some stream.
  2. A TrackSelector is responsible for selecting tracks to be consumed by each of the player’s renderers.
  3. A builder function takes the parameter of Context and RenderersFactory and set the instance to exoPlayer after creation.
  4. Setting up the DefaultTrackSelectionParams with builder method by passing the application Context.
  5. Setting-up listener so that we can get playback state notification. We’ll see the implementation of exoPlayerListener in a couple of minutes.
  6. Whether playback will proceed or not. If playWhenReady is false then the ExoPlayer will not automatically starts the media after setting the media Uri. It will wait until user set playWhenReady value to true.

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

Awesome, we’ve created an instance of the ExoPlayer!. Now we want our player to have the ability to play media. This is how you play the media with ExoPlayer:

val mediaItem = MediaItem.fromUri("data/user/application-name/some-random-audio.mp3") // 1
exoPlayer.setMediaItem(mediaItem)  // 2
exoPlayer.prepare() // 3
exoPlayer.playWhenReady = true // 4

Here’s the summary of the above code:

  1. The MediaItem class fromUri method takes a Uri of the media that you want to play. In this case you’ll play the media from a local internal storage (private file only accessible to your app).
  2. Setting the mediaItem to exoPlayer.
  3. You need to call the prepare() method on the ExoPlayer instance. This method prepares the player to play the provided media source.
  4. And the last step is to set the playWhenReady value to true.

You have now initialized the player, prepared it, and you have set the mediaItem. Well hope so you’ll listen the media file stream 🥳 .

Just one last thing is to see the implementation of exoPlayerListener.

 private val exoPlayerListener = object : Player.Listener {  // 1
        override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {   // 2
               if(playWhenReady) progressBar.gone()
        }

        override fun onPlaybackStateChanged(playbackState: Int) { 
            when (playbackState) {
                Player.STATE_BUFFERING -> {   // 3
                      progressBar.visible()
                 }  
                Player.STATE_READY ->  {   // 4
                     progressBar.gone()
                }  
                Player.STATE_ENDED ->  { // 5
                    exoPlayer.seekTo(0)
                    exoPlayer.playWhenReady = false
                }
            }
        }

        override fun onPlayerError(error: PlaybackException) {}  // 6

Here’s what we’re doing with the above code:

  1. Add a listener to receive events from the ExoPlayer regarding state changes.
  2. Called when the value changes of playing media. You can use this method to hide the progress when the playWhenReady is true.
  3. Called when the media is preparing. You can use this playbackState to show the progress.
  4. Called when the media is ready. You can use this method to show the player control to user so that he/she can play the media.
  5. Called when the local audio or video file finshes. Now you can simply seek the value to initial and set playWhenReady value to false.
  6. This method will be called when any kind of exception occur while playing media. Maybe show error to user here.

Play Internal Storage Video File With ExoPlayer

Next we need to create a PlayerView where we can show our video preview. If you have used Android’s MediaPlayer API you would display videos in a SurfaceView. The ExoPlayer library provides it’s own high level view (StyledPlayerView) for media playback. It displays video, subtitles and album art, and also displays playback controls.

To add it, open the Fragment or Activity related xml layout file from res/layout and add the following:

  <com.google.android.exoplayer2.ui.StyledPlayerView
      android:id="@+id/playerView"
      android:layout_width="match_parent"
      android:layout_height="200dp"
      android:clickable="true"
      android:focusable="true"
      app:auto_show="false"
      app:resize_mode="fill"
      app:surface_type="texture_view" />

Attach the StyledPlayerView to the view is very straightforward. We just set need to set the exoPlayer instance on the player view that you added to the xml by calling the setPlayer(…) method.

val playerView = findViewById<StyledPlayerView>(R.id.playerView)
playerView.player = exoPlayer
val mediaItem = MediaItem.fromUri("data/user/application-name/some-random-audio.mp4")
exoPlayer.setMediaItem(mediaItem) 
exoPlayer.prepare()
exoPlayer.playWhenReady = true

After executing the above code you’ll see your player view will showing the video.

Play Remote Server Online Audio File With ExoPlayer

Playing an audio file with ExoPlayer is not different than playing mp3 file with local storage. We just need to add some more default classes when initializing the exoPlayer instance 😆.

val renderersFactory = buildRenderersFactory(applicationContext, true)
val mediaSourceFactory =
            DefaultMediaSourceFactory(getDataSourceFactory(applicationContext), DefaultExtractorsFactory()) // 1
val trackSelector = DefaultTrackSelector(applicationContext)

exoPlayer = ExoPlayer.Builder(applicationContext, renderersFactory)
    .setTrackSelector(trackSelector)
    .setMediaSourceFactory(mediaSourceFactory)  // 2
    .build().apply {
        addAnalyticsListener(EventLogger())
        trackSelectionParameters =
            DefaultTrackSelector.Parameters.getDefaults(applicationContext)
        addListener(exoPlayerListener)
        playWhenReady = false
    }

private fun buildRenderersFactory(
        context: Context, preferExtensionRenderer: Boolean
    ): RenderersFactory {
        val extensionRendererMode = if (preferExtensionRenderer)
            DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
        else DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON

        return DefaultRenderersFactory(context.applicationContext)
            .setExtensionRendererMode(extensionRendererMode)
            .setEnableDecoderFallback(true)
    }

 private fun getDataSourceFactory(context: Context): DataSource.Factory = // 3
        DefaultDataSource.Factory(context, getHttpDataSourceFactory(context))

 private fun getHttpDataSourceFactory(context: Context): HttpDataSource.Factory { 
      val cronetEngine: CronetEngine? = CronetUtil.buildCronetEngine(context)
      var httpDataSourceFactory: HttpDataSource.Factory? = null

      if (cronetEngine != null) httpDataSourceFactory =
          CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor())

      if (httpDataSourceFactory == null) {
          // We don't want to use Cronet, or we failed to instantiate a CronetEngine.
          val cookieManager = CookieManager()
          cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER)
          CookieHandler.setDefault(cookieManager)
          httpDataSourceFactory = DefaultHttpDataSource.Factory()
      }
      return httpDataSourceFactory
  }

Most of code for initialzing the ExoPlayer is same as before. Other than that here’s the explanation of the remaing code:

  1. Every piece of media is represented by a MediaSourceFactory. To play a piece of media, you must first create a corresponding MediaSourceFactory. The getDataSourceFactory method is a component from which streams of data can be read. You have to set the ExtractorsFactory, which just returns the array of default extractors.
  2. Set the mediaSourceFactory to instance to exoPlayer.
  3. The DataSource.Factory to be used to create DefaultDataSource instances. The dataSource is normally an HttpDataSource, and is responsible for fetching data over HTTP and HTTPS, as well as any other URI schemes.

Next create the MediaItem with the online remote server audio file and prepare the exoPlayer.

val mediaItem = MediaItem.fromUri("https://www.some-random-website-url/random-audio.mp3")
exoPlayer.setMediaItem(mediaItem)
exoPlayer.prepare()
exoPlayer.playWhenReady = true

Play Remote Server Online Video File With ExoPlayer

Last step is easy as sweet; we just need to change the the mediaItem url and set the assign the exoPlayer to the StyledPlayerView.

val playerView = findViewById<StyledPlayerView>(R.id.playerView)
playerView.player = exoPlayer
val mediaItem = MediaItem.fromUri("data/user/application-name/some-random-audio.mp4")
exoPlayer.setMediaItem(mediaItem) 
exoPlayer.prepare()
exoPlayer.playWhenReady = true

Bonus

I was working on some application where I have this requirement to apply some curves on the StyledPlayerView. So, just thought why not share this bit of info with you guys too.

Before adding the following code our player view will look this: No curves:

exoplayer android 3

Now for the curve just add the following code:

val playerView = findViewById<StyledPlayerView>(R.id.playerView)
outlineProvider = object : ViewOutlineProvider() {
                override fun getOutline(view: View, outline: Outline) {
                    outline.setRoundRect(0, 0, view.width, view.height, 20f)
                }
            }
clipToOutline = true

Here’s the final output will look like.

exoplayer android 4

Conclusion

So, we have covered a simple and minimlistic information about ExoPlayer of how to play online and offline(local internal storage) audio and video file in this blog. But this is not all about ExoPlayer, there are a lot of other features of ExoPlayer. Actually, if you can’t do something with the MediaPlayer, then there is a high chance that you will find that feature in the ExoPlayer library.

However, be careful of over-engineering! You must be thinking now: “ExoPlayer is awesome, I’ll use it all the time!” Before you do that, ask yourself this: “Do I really need an it?” If the requirements are simple just to a simple sound go for MediaPlayer.

I hope you enjoyed this tutorial and learned something from it. If you have any question or comments, or you want to share your experience with ExoPlayer, please join in the discussion section below.

Thanks for being here and keep reading…