Create a basic media player app using Media3 ExoPlayer

Jetpack Media3 defines a Player interface that outlines basic functionality for playback of video and audio files. ExoPlayer is the default implementation of this interface in Media3. We recommend using ExoPlayer, as it provides a comprehensive set of features that cover most playback use-cases and is customizable to handle any additional use-cases you might have. ExoPlayer also abstracts away device and OS fragmentation so your code works consistently across the entire Android ecosystem. ExoPlayer includes:

This page walks you through some of the key steps in building a playback app, and for more details you can head to our full guides on Media3 ExoPlayer.

Getting started

To get started, add a dependency on the ExoPlayer, UI, and Common modules of Jetpack Media3:

implementation "androidx.media3:media3-exoplayer:1.4.1"
implementation "androidx.media3:media3-ui:1.4.1"
implementation "androidx.media3:media3-common:1.4.1"

Depending on your use-case, you may also need additional modules from Media3, such as exoplayer-dash to play streams in the DASH format.

Make sure to replace 1.4.1 with your preferred version of the library. You can refer to the release notes to see the latest version.

Creating a media player

With Media3, you can either use the included implementation of the Player interface, ExoPlayer, or you can build your own custom implementation.

Creating an ExoPlayer

The simplest way to create an ExoPlayer instance is as follows:

Kotlin

val player = ExoPlayer.Builder(context).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();

You can create your media player in the onCreate() lifecycle method of the Activity, Fragment, or Service where it lives.

The Builder includes a range of customization options you may be interested in, such as:

Media3 provides a PlayerView UI component that you can include in your app's layout file. This component encapsulates a PlayerControlView for playback controls, SubtitleView for displaying subtitles, and Surface for rendering video.

Preparing the player

Add media items to a playlist for playback with methods like setMediaItem() and addMediaItem(). Then, call prepare() to start loading media and acquire the necessary resources.

You shouldn't perform these steps before the app is in the foreground. If your player is in an Activity or Fragment, this means preparing the player in the onStart() lifecycle method on API level 24 and higher or the onResume() lifecycle method on API level 23 and below. For a player that's in a Service, you can prepare it in onCreate().

Control the player

After the player has been prepared, you can control playback by calling methods on the player such as:

UI components such as the PlayerView or PlayerControlView will update accordingly when bound to a player.

Release the player

Playback can require resources that are in limited supply, such as video decoders, so it's important to call release() on your player to free up resources when the player is no longer needed.

If your player is in an Activity or Fragment, release the player in the onStop() lifecycle method on API level 24 and higher or the onPause() method on API level 23 and below. For a player that's in a Service, you can release it in onDestroy().

Managing playback with a media session

On Android, media sessions provide a standardized way to interact with a media player across process boundaries. Connecting a media session to your player allows you to advertise your media playback externally and to receive playback commands from external sources, for example to integrate with system media controls on mobile and large screen devices.

To use media sessions, add a dependency on the Media3 Session module:

implementation "androidx.media3:media3-session:1.4.1"

Create a media session

You can create a MediaSession after initializing a player as follows:

Kotlin

val player = ExoPlayer.Builder(context).build()
val mediaSession = MediaSession.Builder(context, player).build()

Java

ExoPlayer player = new ExoPlayer.Builder(context).build();
MediaSession mediaSession = new MediaSession.Builder(context, player).build();

Media3 automatically syncs the state of the Player with the state of the MediaSession. This works with any Player implementation, including ExoPlayer, CastPlayer, or a custom implementation.

Grant control to other clients

Client apps can implement a media controller to control playback of your media session. To receive these requests, set a callback object when building your MediaSession.

When a controller is about to connect to your media session, the onConnect() method is called. You can use the provided ControllerInfo to decide whether to accept or reject the request. See an example of this in the Media3 Session demo app.

Once connected, a controller can send playback commands to the session. The session then delegates those commands down to the player. Playback and playlist commands defined in the Player interface are automatically handled by the session.

Other callback methods allow you to handle, for example, requests for custom playback commands and modifying the playlist. These callbacks similarly include a ControllerInfo object so you can determine access control on a request-by-request basis.

Playing media in the background

To continue playing media when your app is not in the foreground, for example to play music, audiobooks, or podcasts even when the user doesn't have your app open, your Player and MediaSession should be encapsulated in a foreground service. Media3 provides the MediaSessionService interface for this purpose.

Implementing a MediaSessionService

Create a class that extends MediaSessionService and instantiate your MediaSession in the onCreate() lifecycle method.

Kotlin

class PlaybackService : MediaSessionService() {
    private var mediaSession: MediaSession? = null

    // Create your Player and MediaSession in the onCreate lifecycle event
    override fun onCreate() {
        super.onCreate()
        val player = ExoPlayer.Builder(this).build()
        mediaSession = MediaSession.Builder(this, player).build()
    }

    // Remember to release the player and media session in onDestroy
    override fun onDestroy() {
        mediaSession?.run {
            player.release()
            release()
            mediaSession = null
        }
        super.onDestroy()
    }
}

Java

public class PlaybackService extends MediaSessionService {
    private MediaSession mediaSession = null;

    @Override
    public void onCreate() {
        super.onCreate();
        ExoPlayer player = new ExoPlayer.Builder(this).build();
        mediaSession = new MediaSession.Builder(this, player).build();
    }

    @Override
    public void onDestroy() {
        mediaSession.getPlayer().release();
        mediaSession.release();
        mediaSession = null;
        super.onDestroy();
    }
}

In your manifest, your Service class with a MediaSessionService intent filter and request the FOREGROUND_SERVICE permission to run a foreground service:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

Lastly, in the class you created, override the onGetSession() method to control client access to your media session. Return a MediaSession to accept the connection request, or return null to reject the request.

Kotlin

// This example always accepts the connection request
override fun onGetSession(
    controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession

Java

@Override
public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
  // This example always accepts the connection request
  return mediaSession;
}

Connecting to your UI

Now that your media session is in a Service separate from the Activity or Fragment where your player UI lives, you can use a MediaController to link them together. In the onStart() method of the Activity or Fragment with your UI, create a SessionToken for your MediaSession, then use the SessionToken to build a MediaController. Building a MediaController happens asynchronously.

Kotlin

override fun onStart() {
  val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
  val controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
  controllerFuture.addListener(
    {
        // Call controllerFuture.get() to retrieve the MediaController.
        // MediaController implements the Player interface, so it can be
        // attached to the PlayerView UI component.
        playerView.setPlayer(controllerFuture.get())
      },
    MoreExecutors.directExecutor()
  )
}

Java

@Override
public void onStart() {
  SessionToken sessionToken =
    new SessionToken(this, new ComponentName(this, PlaybackService.class));
  ListenableFuture<MediaController> controllerFuture =
    new MediaController.Builder(this, sessionToken).buildAsync();
  controllerFuture.addListener(() -> {
    // Call controllerFuture.get() to retrieve the MediaController.
    // MediaController implements the Player interface, so it can be
    // attached to the PlayerView UI component.
    playerView.setPlayer(controllerFuture.get());
  }, MoreExecutors.directExecutor())
}

MediaController implements the Player interface, so you can use the same methods such as play() and pause() to control playback. Similar to other components, remember to release the MediaController when it is no longer needed, such as the onStop() lifecycle method of an Activity, by calling MediaController.releaseFuture().

Publishing a notification

Foreground services are required to publish a notification while active. A MediaSessionService will automatically create a MediaStyle notification for you in the form of a MediaNotification. To provide a custom notification, create a MediaNotification.Provider with DefaultMediaNotificationProvider.Builder or by creating a custom implementation of the provider interface. Add your provider to your MediaSession with setMediaNotificationProvider.

Advertising your content library

A MediaLibraryService builds on a MediaSessionService by allowing client apps to browse the media content provided by your app. Client apps implement a MediaBrowser to interact with your MediaLibraryService.

Implementing a MediaLibraryService is similar to implementing a MediaSessionService, except that in onGetSession() you should return a MediaLibrarySession instead of a MediaSession. Compared to a MediaSession.Callback, the MediaLibrarySession.Callback includes additional methods that allow a browser client to navigate the content offered by your library service.

Similar to the MediaSessionService, declare the MediaLibraryService in your manifest and request the FOREGROUND_SERVICE permission to run a foreground service:

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaLibraryService"/>
        <action android:name="android.media.browse.MediaBrowserService"/>
    </intent-filter>
</service>

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

The example above includes an intent filter for both the MediaLibraryService and, for backward compatibility, the legacy MediaBrowserService. The additional intent filter enables client apps using the MediaBrowserCompat API to recognize your Service.

A MediaLibrarySession lets you serve your content library in a tree structure, with a single root MediaItem. Each MediaItem in the tree can have any number of children MediaItem nodes. You can serve a different root, or a different tree, based on the client app's request. For example, the tree you return to a client looking for a list of recommended media items might only contain the root MediaItem and a single level of children MediaItem nodes, whereas the tree you return to a different client app may represent a more complete library of content.

Creating a MediaLibrarySession

A MediaLibrarySession extends the MediaSession API to add content browsing APIs. Compared to the MediaSession callback, the MediaLibrarySession callback adds methods such as:

  • onGetLibraryRoot() for when a client requests the root MediaItem of a content tree
  • onGetChildren() for when a client requests the children of a MediaItem in the content tree
  • onGetSearchResult() for when a client requests search results from the content tree for a given query

Relevant callback methods will include a LibraryParams object with additional signals about the type of content tree that a client app is interested in.