The Player Interface

A player is the component of your app that facilitates playback of media items. The Media3 Player interface sets up an outline for functionality generally handled by a player. This includes:

  • Affecting playback controls, such as playing, pausing, and seeking
  • Querying properties of the currently playing media, such as the playback position
  • Managing a playlist/queue of media items
  • Configuring playback properties, such as shuffling, repeating, speed, and volume
  • Rendering video to the screen

Media3 also provides an implementation of the Player interface, called ExoPlayer.

A common interface between components

Several components in Media3 implement the Player interface, for example:

Component Description & behavior notes
ExoPlayer A media player API, and the default implementation of the Player interface.
MediaController Interacts with a MediaSession to send playback commands. If your Player and MediaSession are in a Service separate from the Activity or Fragment where your player's UI lives, you can assign your MediaController as the player for your PlayerView UI. Playback and playlist method calls are sent to your Player through your MediaSession.
MediaBrowser In addition to the functionality offered by a MediaController, interacts with a MediaLibrarySession to browse available media content.
SimpleBasePlayer A Player implementation that reduces the number of methods to implement to a minimum. Helpful when using a custom player that you want to connect to a MediaSession.
ForwardingSimpleBasePlayer A SimpleBasePlayer subclass designed to forward playback operations to another Player while allowing the same consistent behavior customizations as SimpleBasePlayer. Use this class to suppress or modify specific playback operations.
CastPlayer A Player implementation that communicates with a Cast receiver app. Behavior depends on the underlying Cast session.

Although a MediaSession doesn't implement the Player interface, it requires a Player when creating one. Its purpose is to provide access to the Player from other processes or threads.

Media3 playback architecture

If you have access to a Player, you should call its methods directly to issue playback commands. You can advertise your playback and grant external sources playback control by implementing a MediaSession. These external sources implement a MediaController, which facilitates connecting to a media session and issuing playback command requests.

When playing media in the background, you need to house your media session and player within a MediaSessionService or MediaLibraryService that runs as a foreground service. If you do so, you can separate your player from the Activity in your app that contains the UI for playback control. This may necessitate that you use a media controller.

A diagram showing how Media3 playback components fit into a media app architecture.
Figure 1: The Player interface plays a key role in the architecture of Media3.

Player state

The state of a media player implementing the Player interface consists primarily of 4 categories of information:

  1. Playback state
  2. Playlist of media items
  3. Play/pause properties, such as:
    • playWhenReady: An indication of whether the user wants media to play when possible or remain paused
    • Playback suppression reason: An indication of why playback is suppressed, if applicable, even if playWhenReady is true
    • isPlaying: An indication of whether the player is currently playing, which will only be true if the playback state is STATE_READY, playWhenReady is true, and playback is not suppressed
  4. Playback position, including:

In addition, the Player interface allows access to the available tracks, media metadata, playback speed, volume and other auxiliary properties of the playback.

Listen for changes

Use a Player.Listener to listen for changes in a Player. See the ExoPlayer documentation on Player events for details on how to create and use a listener.

Note that the listener interface doesn't include any callbacks to track normal playback progression. To continuously monitor playback progress, such as to set up a progress bar UI, you should query the current position at proper intervals.

Kotlin

val handler = Handler(Looper.getMainLooper())
fun checkPlaybackPosition(delayMs: Long): Boolean =
  handler.postDelayed(
    {
      val currentPosition = player.currentPosition
      // Update UI based on currentPosition
      checkPlaybackPosition(delayMs)
    },
    delayMs)

Java

Handler handler = new Handler(Looper.getMainLooper());
boolean checkPlaybackPosition(long delayMs) {
    return handler.postDelayed(() -> {
        long currentPosition = player.getCurrentPosition();
        // Update UI based on currentPosition
        checkPlaybackPosition(delayMs);
    }, delayMs);
}

Control playback

The Player interface offers many ways to manipulate the state and control playback:

Custom Player implementations

To create a custom player, you can extend the SimpleBasePlayer included in Media3. This class provides a base implementation of the Player interface to reduce the number of methods you need to implement to a minimum.

Start by overriding the getState() method. This method should populate the current player state when called, including:

  • The set of available commands
  • Playback properties, such as whether the player should start playing when the playback state is STATE_READY, the index of the currently playing media item, and the playback position within the current item

Kotlin

class CustomPlayer : SimpleBasePlayer(looper) {
  override fun getState(): State {
    return State.Builder()
      .setAvailableCommands(...) // Set which playback commands the player can handle
      // Configure additional playback properties
      .setPlayWhenReady(true, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
      .setCurrentMediaItemIndex(0)
      .setContentPositionMs(0)
      .build()
  }
}

Java

public class CustomPlayer extends SimpleBasePlayer {
  public CustomPlayer(Looper looper) {
    super(looper);
  }

  @Override
  protected State getState() {
    return new State.Builder()
      .setAvailableCommands(...) // Set which playback commands the player can handle
      // Configure additional playback properties
      .setPlayWhenReady(true, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
      .setCurrentMediaItemIndex(0)
      .setContentPositionMs(0)
      .build();
  }
}

SimpleBasePlayer will enforce that the State is created with a valid combination of state values. It will also handle listeners and informing listeners of state changes. If you need to manually trigger a state update, call invalidateState().

Beyond the getState() method, you only need to implement methods that are used for commands your player declares to be available. Find the overridable handler method that corresponds to the functionality you want to implement. For example, override the handleSeek() method to support operations like COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM and COMMAND_SEEK_TO_NEXT_MEDIA_ITEM.