Updated 12 days ago | GitHub

Playing Media with ExoPlayer or TubiPlayer

Overview

ExoPlayer is Google’s 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.

ExoPlayer is distributed as part of AndroidX Media3. The older com.google.android.exoplayer:exoplayer:2.x artifact is deprecated — the google/ExoPlayer repository has been stale since 2024 and points users at androidx/media. New work should depend on androidx.media3:media3-exoplayer instead.

This guide covers the most important pieces of setting up Media3 ExoPlayer in an app so you understand how the player is wired together. For full samples, see the Media3 demos.

ExoPlayer vs. MediaPlayer

ExoPlayer has several key advantages over MediaPlayer, including:

  • Support for multiple media formats, including ones not supported by MediaPlayer such as DASH and SmoothStreaming.
  • The ability to merge, concatenate, or loop media using playlists.
  • Fewer device- and Android-version-specific issues.
  • Advanced HLS features.
  • Modular architecture you can extend and customize.
  • Official extensions for Cast, IMA ads, OkHttp, MediaSession, and more.

Note: For audio-only playback on some devices, ExoPlayer may use more battery than MediaPlayer. It is also worth considering whether any of the features above are needed for your app — MediaPlayer can still be a valid option for simple use cases.

Adding Media3 ExoPlayer to Your Project

Adding Gradle Dependencies

Media3 is published to Google Maven. JCenter stopped accepting new packages in 2021 and is not used by Media3, so the project-level build.gradle only needs google() and mavenCentral():

repositories {
    google()
    mavenCentral()
}

In your app-level build.gradle, make sure Java 8 source/target compatibility is enabled. Use assignment (=) so the snippet works with both Groovy and Kotlin DSL:

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}

Add the Media3 modules you need. At minimum you want the ExoPlayer engine and the UI module that provides PlayerView:

def media3_version = "1.10.1"

implementation "androidx.media3:media3-exoplayer:$media3_version"
implementation "androidx.media3:media3-ui:$media3_version"

If you need DASH, HLS, or SmoothStreaming, also add the matching module — for example media3-exoplayer-hls or media3-exoplayer-dash. A full list of modules is in the AndroidX Media README.

Creating the Player

Media3 collapses the old SimpleExoPlayer and ExoPlayer types into a single ExoPlayer interface, created through a builder:

import androidx.media3.exoplayer.ExoPlayer;

private void initializePlayer() {
    ExoPlayer player = new ExoPlayer.Builder(context).build();
}

ExoPlayer.Builder exposes setters for the parts you most commonly customize (track selector, load control, renderers factory, looper, etc.) — see the ExoPlayer.Builder reference.

Once the player is built, attach it to a view. Media3 ships androidx.media3.ui.PlayerView, which is the easiest option; you can also render directly into a SurfaceView or TextureView:

import androidx.media3.ui.PlayerView;

private void setPlayerView(View playerView, ExoPlayer player) {
    if (playerView instanceof PlayerView) {
        ((PlayerView) playerView).setPlayer(player);
    } else if (playerView instanceof SurfaceView) {
        player.setVideoSurfaceView((SurfaceView) playerView);
    } else if (playerView instanceof TextureView) {
        player.setVideoTextureView((TextureView) playerView);
    }
}

private void initializePlayer() {
    ExoPlayer player = new ExoPlayer.Builder(context).build();
    setPlayerView(playerView, player);
}

If you already have a raw SurfaceHolder or Surface (for example from a custom rendering pipeline), ExoPlayer also exposes setVideoSurfaceHolder(SurfaceHolder) and setVideoSurface(Surface) to hand the output target in directly.

Loading Media and Preparing the Player

In Media3, the simplest way to play a URI is to build a MediaItem and hand it to the player. The player’s internal DefaultMediaSourceFactory infers the right source type (progressive, DASH, HLS, SmoothStreaming) from the URI extension or the MediaItem.LocalConfiguration.mimeType you set, so you no longer need to switch on Util.inferContentType yourself:

import androidx.media3.common.MediaItem;

private void initializePlayer(Uri uri) {
    ExoPlayer player = new ExoPlayer.Builder(context).build();
    setPlayerView(playerView, player);

    MediaItem mediaItem = MediaItem.fromUri(uri);
    player.setMediaItem(mediaItem);
    player.prepare();
}

To play multiple items as a playlist, call player.addMediaItem(mediaItem) for each one before prepare() (or use setMediaItems(list)).

If you need to customize the data source (for example to set a user-agent or attach an OkHttp client) or override the manifest parser for adaptive streams, you can still build a MediaSource explicitly and pass it via setMediaSource(...). The factories live under androidx.media3.exoplayer.{dash,hls,smoothstreaming} and androidx.media3.exoplayer.source for progressive streams:

import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.exoplayer.dash.DashMediaSource;
import androidx.media3.exoplayer.hls.HlsMediaSource;
import androidx.media3.exoplayer.smoothstreaming.SsMediaSource;
import androidx.media3.exoplayer.source.MediaSource;
import androidx.media3.exoplayer.source.ProgressiveMediaSource;

private MediaSource buildMediaSource(Uri uri) {
    DataSource.Factory dataSourceFactory =
            new DefaultHttpDataSource.Factory().setUserAgent("yourApplicationName");
    MediaItem mediaItem = MediaItem.fromUri(uri);
    @C.ContentType int type = Util.inferContentType(uri);
    switch (type) {
        case C.CONTENT_TYPE_DASH:
            return new DashMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem);
        case C.CONTENT_TYPE_SS:
            return new SsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem);
        case C.CONTENT_TYPE_HLS:
            return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem);
        case C.CONTENT_TYPE_OTHER:
            return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem);
        default:
            throw new IllegalStateException("Unsupported type: " + type);
    }
}

Note three changes from the legacy ExoPlayer 2.x API:

  • The progressive (non-adaptive) source is ProgressiveMediaSource, not the removed ExtractorMediaSource.
  • Util.inferContentType constants are C.CONTENT_TYPE_* (the older C.TYPE_* names are deprecated aliases in Media3).
  • createMediaSource(...) now takes a MediaItem, not a Uri.

Controlling the Player

ExoPlayer exposes the playback controls you would expect — play(), pause(), and seekTo(positionMs):

private void playPlayer() {
    if (player != null) {
        player.play();
    }
}

private void pausePlayer() {
    if (player != null) {
        player.pause();
    }
}

private void seekTo(long positionMs) {
    if (player != null) {
        player.seekTo(positionMs);
    }
}

private void releasePlayer() {
    if (player != null) {
        player.release();
        player = null;
    }
}

When the activity or fragment finishes, call player.release() to free the underlying video decoders and any audio focus the player has acquired. Skipping this leaves the player holding native resources that other apps may need.

Additional and Advanced Topics

The official ExoPlayer documentation covers listeners, DRM, background playback with MediaSessionService, track selection, downloads, and more — see the Media3 developer guide and the Media3 demos. If you are porting an older app, follow the AndroidX Media3 migration guide — it lists the package-by-package mapping from com.google.android.exoplayer2.* to androidx.media3.*.

A Note on TubiPlayer

Older versions of this guide pointed readers at the TubiPlayer wrapper around ExoPlayer. The latest commit on its master branch is from December 2018 and its last release tag (release-v3.0) was cut in July 2018; it targets ExoPlayer 2.x and is not compatible with Media3. For new development we recommend depending on androidx.media3:media3-exoplayer directly and using PlayerView from media3-ui for the default playback UI — the surface area is small enough that a third-party wrapper is no longer warranted.

References