public class

ForwardingSimpleBasePlayer

extends SimpleBasePlayer

 java.lang.Object

androidx.media3.common.BasePlayer

androidx.media3.common.SimpleBasePlayer

↳androidx.media3.common.ForwardingSimpleBasePlayer

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-common', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-common
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-common:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

A SimpleBasePlayer that forwards all calls to another Player instance.

The class can be used to selectively override ForwardingSimpleBasePlayer.getState() or handle{Action} methods:

{@code
 new ForwardingSimpleBasePlayer(player) {

Summary

Fields
from BasePlayerwindow
Constructors
publicForwardingSimpleBasePlayer(Player player)

Creates the forwarding player.

Methods
protected final PlayergetPlayer()

Returns the wrapped player.

protected abstract SimpleBasePlayer.StategetState()

Returns the current SimpleBasePlayer.State of the player.

protected <any>handleAddMediaItems(int index, java.util.List<MediaItem> mediaItems)

Handles calls to Player.addMediaItem(MediaItem) and Player.addMediaItems(List).

protected <any>handleClearVideoOutput(java.lang.Object videoOutput)

Handles calls to clear the video output.

protected <any>handleDecreaseDeviceVolume(int flags)

Handles calls to Player.decreaseDeviceVolume() and Player.decreaseDeviceVolume(int).

protected <any>handleIncreaseDeviceVolume(int flags)

Handles calls to Player.increaseDeviceVolume() and Player.increaseDeviceVolume(int).

protected <any>handleMoveMediaItems(int fromIndex, int toIndex, int newIndex)

Handles calls to Player.moveMediaItem(int, int) and Player.moveMediaItems(int, int, int).

protected <any>handlePrepare()

Handles calls to Player.prepare().

protected <any>handleRelease()

Handles calls to Player.release().

protected <any>handleRemoveMediaItems(int fromIndex, int toIndex)

Handles calls to Player.removeMediaItem(int) and Player.removeMediaItems(int, int).

protected <any>handleReplaceMediaItems(int fromIndex, int toIndex, java.util.List<MediaItem> mediaItems)

Handles calls to Player.replaceMediaItem(int, MediaItem) and Player.replaceMediaItems(int, int, List).

protected <any>handleSeek(int mediaItemIndex, long positionMs, int seekCommand)

Handles calls to Player.seekTo(long) and other seek operations (for example, Player.seekToNext()).

protected <any>handleSetAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus)

Handles calls to set the audio attributes.

protected <any>handleSetDeviceMuted(boolean muted, int flags)

Handles calls to Player.setDeviceMuted(boolean) and Player.setDeviceMuted(boolean, int).

protected <any>handleSetDeviceVolume(int deviceVolume, int flags)

Handles calls to Player.setDeviceVolume(int) and Player.setDeviceVolume(int, int).

protected <any>handleSetMediaItems(java.util.List<MediaItem> mediaItems, int startIndex, long startPositionMs)

Handles calls to Player.setMediaItem(MediaItem) and Player.setMediaItems(List).

protected <any>handleSetPlaybackParameters(PlaybackParameters playbackParameters)

Handles calls to Player.setPlaybackParameters(PlaybackParameters) or Player.setPlaybackSpeed(float).

protected <any>handleSetPlaylistMetadata(MediaMetadata playlistMetadata)

Handles calls to Player.setPlaylistMetadata(MediaMetadata).

protected <any>handleSetPlayWhenReady(boolean playWhenReady)

Handles calls to Player.setPlayWhenReady(boolean), Player.play() and Player.pause().

protected <any>handleSetRepeatMode(int repeatMode)

Handles calls to Player.setRepeatMode(int).

protected <any>handleSetShuffleModeEnabled(boolean shuffleModeEnabled)

Handles calls to Player.setShuffleModeEnabled(boolean).

protected <any>handleSetTrackSelectionParameters(TrackSelectionParameters trackSelectionParameters)

Handles calls to Player.setTrackSelectionParameters(TrackSelectionParameters).

protected <any>handleSetVideoOutput(java.lang.Object videoOutput)

Handles calls to set the video output.

protected <any>handleSetVolume(float volume)

Handles calls to Player.setVolume(float).

protected <any>handleStop()

Handles calls to Player.stop().

from SimpleBasePlayeraddListener, addMediaItems, clearVideoSurface, clearVideoSurface, clearVideoSurfaceHolder, clearVideoSurfaceView, clearVideoTextureView, decreaseDeviceVolume, decreaseDeviceVolume, getApplicationLooper, getAudioAttributes, getAvailableCommands, getBufferedPosition, getContentBufferedPosition, getContentPosition, getCurrentAdGroupIndex, getCurrentAdIndexInAdGroup, getCurrentCues, getCurrentMediaItemIndex, getCurrentPeriodIndex, getCurrentPosition, getCurrentTimeline, getCurrentTracks, getDeviceInfo, getDeviceVolume, getDuration, getMaxSeekToPreviousPosition, getMediaMetadata, getPlaceholderMediaItemData, getPlaceholderState, getPlaybackParameters, getPlaybackState, getPlaybackSuppressionReason, getPlayerError, getPlaylistMetadata, getPlayWhenReady, getRepeatMode, getSeekBackIncrement, getSeekForwardIncrement, getShuffleModeEnabled, getSurfaceSize, getTotalBufferedDuration, getTrackSelectionParameters, getVideoSize, getVolume, increaseDeviceVolume, increaseDeviceVolume, invalidateState, isDeviceMuted, isLoading, isPlayingAd, moveMediaItems, prepare, release, removeListener, removeMediaItems, replaceMediaItems, seekTo, setAudioAttributes, setDeviceMuted, setDeviceMuted, setDeviceVolume, setDeviceVolume, setMediaItems, setMediaItems, setPlaybackParameters, setPlaylistMetadata, setPlayWhenReady, setRepeatMode, setShuffleModeEnabled, setTrackSelectionParameters, setVideoSurface, setVideoSurfaceHolder, setVideoSurfaceView, setVideoTextureView, setVolume, stop, verifyApplicationThread
from BasePlayeraddMediaItem, addMediaItem, addMediaItems, canAdvertiseSession, clearMediaItems, getBufferedPercentage, getContentDuration, getCurrentLiveOffset, getCurrentManifest, getCurrentMediaItem, getCurrentWindowIndex, getMediaItemAt, getMediaItemCount, getNextMediaItemIndex, getNextWindowIndex, getPreviousMediaItemIndex, getPreviousWindowIndex, hasNext, hasNextMediaItem, hasNextWindow, hasPreviousMediaItem, isCommandAvailable, isCurrentMediaItemDynamic, isCurrentMediaItemLive, isCurrentMediaItemSeekable, isCurrentWindowDynamic, isCurrentWindowLive, isCurrentWindowSeekable, isPlaying, moveMediaItem, next, pause, play, removeMediaItem, replaceMediaItem, seekBack, seekForward, seekTo, seekTo, seekToDefaultPosition, seekToDefaultPosition, seekToNext, seekToNextMediaItem, seekToNextWindow, seekToPrevious, seekToPreviousMediaItem, seekToPreviousWindow, setMediaItem, setMediaItem, setMediaItem, setMediaItems, setPlaybackSpeed
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public ForwardingSimpleBasePlayer(Player player)

Creates the forwarding player.

Parameters:

player: The Player to forward to.

Methods

protected final Player getPlayer()

Returns the wrapped player.

protected abstract SimpleBasePlayer.State getState()

Returns the current SimpleBasePlayer.State of the player.

The SimpleBasePlayer.State should include all available commands indicating which player methods are allowed to be called.

Note that this method won't be called while asynchronous handling of player methods is in progress. This means that the implementation doesn't need to handle state changes caused by these asynchronous operations until they are done and can return the currently known state directly. The placeholder state used while these asynchronous operations are in progress can be customized by overriding SimpleBasePlayer.getPlaceholderState(SimpleBasePlayer.State) if required.

protected <any> handleSetPlayWhenReady(boolean playWhenReady)

Handles calls to Player.setPlayWhenReady(boolean), Player.play() and Player.pause().

Will only be called if Player.COMMAND_PLAY_PAUSE is available.

Parameters:

playWhenReady: The requested SimpleBasePlayer.State.playWhenReady

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handlePrepare()

Handles calls to Player.prepare().

Will only be called if Player.COMMAND_PREPARE is available.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleStop()

Handles calls to Player.stop().

Will only be called if Player.COMMAND_STOP is available.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleRelease()

Handles calls to Player.release().

Will only be called if Player.COMMAND_RELEASE is available.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetRepeatMode(int repeatMode)

Handles calls to Player.setRepeatMode(int).

Will only be called if Player.COMMAND_SET_REPEAT_MODE is available.

Parameters:

repeatMode: The requested .

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetShuffleModeEnabled(boolean shuffleModeEnabled)

Handles calls to Player.setShuffleModeEnabled(boolean).

Will only be called if Player.COMMAND_SET_SHUFFLE_MODE is available.

Parameters:

shuffleModeEnabled: Whether shuffle mode was requested to be enabled.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetPlaybackParameters(PlaybackParameters playbackParameters)

Handles calls to Player.setPlaybackParameters(PlaybackParameters) or Player.setPlaybackSpeed(float).

Will only be called if Player.COMMAND_SET_SPEED_AND_PITCH is available.

Parameters:

playbackParameters: The requested PlaybackParameters.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetTrackSelectionParameters(TrackSelectionParameters trackSelectionParameters)

Handles calls to Player.setTrackSelectionParameters(TrackSelectionParameters).

Will only be called if Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS is available.

Parameters:

trackSelectionParameters: The requested TrackSelectionParameters.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetPlaylistMetadata(MediaMetadata playlistMetadata)

Handles calls to Player.setPlaylistMetadata(MediaMetadata).

Will only be called if Player.COMMAND_SET_PLAYLIST_METADATA is available.

Parameters:

playlistMetadata: The requested playlist metadata.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetVolume(float volume)

Handles calls to Player.setVolume(float).

Will only be called if Player.COMMAND_SET_VOLUME is available.

Parameters:

volume: The requested audio volume, with 0 being silence and 1 being unity gain (signal unchanged).

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetDeviceVolume(int deviceVolume, int flags)

Handles calls to Player.setDeviceVolume(int) and Player.setDeviceVolume(int, int).

Will only be called if Player.COMMAND_SET_DEVICE_VOLUME or Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS is available.

Parameters:

deviceVolume: The requested device volume.
flags: Either 0 or a bitwise combination of one or more C.VolumeFlags.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleIncreaseDeviceVolume(int flags)

Handles calls to Player.increaseDeviceVolume() and Player.increaseDeviceVolume(int).

Will only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME or Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS is available.

Parameters:

flags: Either 0 or a bitwise combination of one or more C.VolumeFlags.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleDecreaseDeviceVolume(int flags)

Handles calls to Player.decreaseDeviceVolume() and Player.decreaseDeviceVolume(int).

Will only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME or Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS is available.

Parameters:

flags: Either 0 or a bitwise combination of one or more C.VolumeFlags.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetDeviceMuted(boolean muted, int flags)

Handles calls to Player.setDeviceMuted(boolean) and Player.setDeviceMuted(boolean, int).

Will only be called if Player.COMMAND_ADJUST_DEVICE_VOLUME or Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS is available.

Parameters:

muted: Whether the device was requested to be muted.
flags: Either 0 or a bitwise combination of one or more C.VolumeFlags.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus)

Handles calls to set the audio attributes.

Will only be called if Player.COMMAND_SET_AUDIO_ATTRIBUTES is available.

Parameters:

audioAttributes: The attributes to use for audio playback.
handleAudioFocus: True if the player should handle audio focus, false otherwise.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetVideoOutput(java.lang.Object videoOutput)

Handles calls to set the video output.

Will only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

Parameters:

videoOutput: The requested video output. This is either a , SurfaceHolder, TextureView or .

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleClearVideoOutput(java.lang.Object videoOutput)

Handles calls to clear the video output.

Will only be called if Player.COMMAND_SET_VIDEO_SURFACE is available.

Parameters:

videoOutput: The video output to clear. If null any current output should be cleared. If non-null, the output should only be cleared if it matches the provided argument. This is either a , SurfaceHolder, TextureView or .

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSetMediaItems(java.util.List<MediaItem> mediaItems, int startIndex, long startPositionMs)

Handles calls to Player.setMediaItem(MediaItem) and Player.setMediaItems(List).

Will only be called if Player.COMMAND_SET_MEDIA_ITEM or Player.COMMAND_CHANGE_MEDIA_ITEMS is available. If only Player.COMMAND_SET_MEDIA_ITEM is available, the list of media items will always contain exactly one item.

Parameters:

mediaItems: The media items to add.
startIndex: The index at which to start playback from, or C.INDEX_UNSET to start at the default item.
startPositionMs: The position in milliseconds to start playback from, or C.TIME_UNSET to start at the default position in the media item.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleAddMediaItems(int index, java.util.List<MediaItem> mediaItems)

Handles calls to Player.addMediaItem(MediaItem) and Player.addMediaItems(List).

Will only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

Parameters:

index: The index at which to add the items. The index is in the range 0 <= index <= BasePlayer.getMediaItemCount().
mediaItems: The media items to add.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleMoveMediaItems(int fromIndex, int toIndex, int newIndex)

Handles calls to Player.moveMediaItem(int, int) and Player.moveMediaItems(int, int, int).

Will only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

Parameters:

fromIndex: The start index of the items to move. The index is in the range 0 <= fromIndex < BasePlayer.getMediaItemCount().
toIndex: The index of the first item not to be included in the move (exclusive). The index is in the range fromIndex < toIndex <= BasePlayer.getMediaItemCount().
newIndex: The new index of the first moved item. The index is in the range 0 <= newIndex < - (toIndex - fromIndex).

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleReplaceMediaItems(int fromIndex, int toIndex, java.util.List<MediaItem> mediaItems)

Handles calls to Player.replaceMediaItem(int, MediaItem) and Player.replaceMediaItems(int, int, List).

Will only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

Parameters:

fromIndex: The start index of the items to replace. The index is in the range 0 <= fromIndex < BasePlayer.getMediaItemCount().
toIndex: The index of the first item not to be replaced (exclusive). The index is in the range fromIndex < toIndex <= BasePlayer.getMediaItemCount().
mediaItems: The media items to replace the specified range with.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleRemoveMediaItems(int fromIndex, int toIndex)

Handles calls to Player.removeMediaItem(int) and Player.removeMediaItems(int, int).

Will only be called if Player.COMMAND_CHANGE_MEDIA_ITEMS is available.

Parameters:

fromIndex: The index at which to start removing media items. The index is in the range 0 <= fromIndex < BasePlayer.getMediaItemCount().
toIndex: The index of the first item to be kept (exclusive). The index is in the range fromIndex < toIndex <= BasePlayer.getMediaItemCount().

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

protected <any> handleSeek(int mediaItemIndex, long positionMs, int seekCommand)

Handles calls to Player.seekTo(long) and other seek operations (for example, Player.seekToNext()).

Will only be called if the appropriate Player.Command, for example Player.COMMAND_SEEK_TO_MEDIA_ITEM or Player.COMMAND_SEEK_TO_NEXT, is available.

Parameters:

mediaItemIndex: The media item index to seek to. If the original seek operation did not directly specify an index, this is the most likely implied index based on the available player state. If the implied action is to do nothing, this will be C.INDEX_UNSET.
positionMs: The position in milliseconds to start playback from, or C.TIME_UNSET to start at the default position in the media item. If the original seek operation did not directly specify a position, this is the most likely implied position based on the available player state.
seekCommand: The Player.Command used to trigger the seek.

Returns:

A indicating the completion of all immediate SimpleBasePlayer.State changes caused by this call.

Source

/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.common;

import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;

// LINT.IfChange(javadoc)
/**
 * A {@link SimpleBasePlayer} that forwards all calls to another {@link Player} instance.
 *
 * <p>The class can be used to selectively override {@link #getState()} or {@code handle{Action}}
 * methods:
 *
 * <pre>{@code
 * new ForwardingSimpleBasePlayer(player) {
 *   @Override
 *   protected State getState() {
 *     State state = super.getState();
 *     // Modify current state as required:
 *     return state.buildUpon().setAvailableCommands(filteredCommands).build();
 *   }
 *
 *   @Override
 *   protected ListenableFuture<?> handleSetRepeatMode(int repeatMode) {
 *     // Modify actions by directly calling the underlying player as needed:
 *     getPlayer().setShuffleModeEnabled(true);
 *     // ..or forward to the default handling with modified parameters:
 *     return super.handleSetRepeatMode(Player.REPEAT_MODE_ALL);
 *   }
 * }
 * }</pre>
 *
 * This base class handles many aspect of the player implementation to simplify the subclass, for
 * example listener handling. See the documentation of {@link SimpleBasePlayer} for a more detailed
 * description.
 */
@UnstableApi
public class ForwardingSimpleBasePlayer extends SimpleBasePlayer {

  private final Player player;

  private ForwardingPositionSupplier currentPositionSupplier;
  private Metadata lastTimedMetadata;
  private @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason;
  private @Player.DiscontinuityReason int pendingDiscontinuityReason;
  private long pendingPositionDiscontinuityNewPositionMs;
  private boolean pendingFirstFrameRendered;

  /**
   * Creates the forwarding player.
   *
   * @param player The {@link Player} to forward to.
   */
  public ForwardingSimpleBasePlayer(Player player) {
    super(player.getApplicationLooper());
    this.player = player;
    this.lastTimedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET);
    this.playWhenReadyChangeReason = Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
    this.pendingDiscontinuityReason = Player.DISCONTINUITY_REASON_INTERNAL;
    this.currentPositionSupplier = new ForwardingPositionSupplier(player);
    player.addListener(
        new Listener() {
          @Override
          public void onMetadata(Metadata metadata) {
            lastTimedMetadata = metadata;
          }

          @Override
          public void onPlayWhenReadyChanged(
              boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
            playWhenReadyChangeReason = reason;
          }

          @Override
          public void onPositionDiscontinuity(
              PositionInfo oldPosition,
              PositionInfo newPosition,
              @Player.DiscontinuityReason int reason) {
            pendingDiscontinuityReason = reason;
            pendingPositionDiscontinuityNewPositionMs = newPosition.positionMs;
            // Any previously created State will directly call through to player.getCurrentPosition
            // via the existing position supplier. From this point onwards, this is wrong as the
            // player had a discontinuity and will now return a new position unrelated to the old
            // State. We can disconnect these old State objects from the underlying Player by fixing
            // the position to the one before the discontinuity and using a new (live) position
            // supplier for future State objects.
            currentPositionSupplier.setConstant(
                oldPosition.positionMs, oldPosition.contentPositionMs);
            currentPositionSupplier = new ForwardingPositionSupplier(player);
          }

          @Override
          public void onRenderedFirstFrame() {
            pendingFirstFrameRendered = true;
          }

          @SuppressWarnings("method.invocation.invalid") // Calling method from constructor.
          @Override
          public void onEvents(Player player, Events events) {
            invalidateState();
          }
        });
  }

  /** Returns the wrapped player. */
  protected final Player getPlayer() {
    return player;
  }

  @Override
  protected State getState() {
    // Ordered alphabetically by State.Builder setters.
    State.Builder state = new State.Builder();
    ForwardingPositionSupplier positionSupplier = currentPositionSupplier;
    if (player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)) {
      state.setAdBufferedPositionMs(positionSupplier::getBufferedPositionMs);
      state.setAdPositionMs(positionSupplier::getCurrentPositionMs);
    }
    if (player.isCommandAvailable(Player.COMMAND_GET_AUDIO_ATTRIBUTES)) {
      state.setAudioAttributes(player.getAudioAttributes());
    }
    state.setAvailableCommands(player.getAvailableCommands());
    if (player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)) {
      state.setContentBufferedPositionMs(positionSupplier::getContentBufferedPositionMs);
      state.setContentPositionMs(positionSupplier::getContentPositionMs);
      if (player.isCommandAvailable(Player.COMMAND_GET_TIMELINE)) {
        state.setCurrentAd(player.getCurrentAdGroupIndex(), player.getCurrentAdIndexInAdGroup());
      }
    }
    if (player.isCommandAvailable(Player.COMMAND_GET_TEXT)) {
      state.setCurrentCues(player.getCurrentCues());
    }
    if (player.isCommandAvailable(Player.COMMAND_GET_TIMELINE)) {
      state.setCurrentMediaItemIndex(player.getCurrentMediaItemIndex());
    }
    state.setDeviceInfo(player.getDeviceInfo());
    if (player.isCommandAvailable(Player.COMMAND_GET_DEVICE_VOLUME)) {
      state.setDeviceVolume(player.getDeviceVolume());
      state.setIsDeviceMuted(player.isDeviceMuted());
    }
    state.setIsLoading(player.isLoading());
    state.setMaxSeekToPreviousPositionMs(player.getMaxSeekToPreviousPosition());
    if (pendingFirstFrameRendered) {
      state.setNewlyRenderedFirstFrame(true);
      pendingFirstFrameRendered = false;
    }
    state.setPlaybackParameters(player.getPlaybackParameters());
    state.setPlaybackState(player.getPlaybackState());
    state.setPlaybackSuppressionReason(player.getPlaybackSuppressionReason());
    state.setPlayerError(player.getPlayerError());
    if (player.isCommandAvailable(Player.COMMAND_GET_TIMELINE)) {
      Tracks tracks =
          player.isCommandAvailable(Player.COMMAND_GET_TRACKS)
              ? player.getCurrentTracks()
              : Tracks.EMPTY;
      MediaMetadata mediaMetadata =
          player.isCommandAvailable(Player.COMMAND_GET_METADATA) ? player.getMediaMetadata() : null;
      state.setPlaylist(player.getCurrentTimeline(), tracks, mediaMetadata);
    }
    if (player.isCommandAvailable(Player.COMMAND_GET_METADATA)) {
      state.setPlaylistMetadata(player.getPlaylistMetadata());
    }
    state.setPlayWhenReady(player.getPlayWhenReady(), playWhenReadyChangeReason);
    if (pendingPositionDiscontinuityNewPositionMs != C.TIME_UNSET) {
      state.setPositionDiscontinuity(
          pendingDiscontinuityReason, pendingPositionDiscontinuityNewPositionMs);
      pendingPositionDiscontinuityNewPositionMs = C.TIME_UNSET;
    }
    state.setRepeatMode(player.getRepeatMode());
    state.setSeekBackIncrementMs(player.getSeekBackIncrement());
    state.setSeekForwardIncrementMs(player.getSeekForwardIncrement());
    state.setShuffleModeEnabled(player.getShuffleModeEnabled());
    state.setSurfaceSize(player.getSurfaceSize());
    state.setTimedMetadata(lastTimedMetadata);
    if (player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)) {
      state.setTotalBufferedDurationMs(positionSupplier::getTotalBufferedDurationMs);
    }
    state.setTrackSelectionParameters(player.getTrackSelectionParameters());
    state.setVideoSize(player.getVideoSize());
    if (player.isCommandAvailable(Player.COMMAND_GET_VOLUME)) {
      state.setVolume(player.getVolume());
    }
    return state.build();
  }

  @Override
  protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
    player.setPlayWhenReady(playWhenReady);
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handlePrepare() {
    player.prepare();
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleStop() {
    player.stop();
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleRelease() {
    player.release();
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetRepeatMode(@Player.RepeatMode int repeatMode) {
    player.setRepeatMode(repeatMode);
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetShuffleModeEnabled(boolean shuffleModeEnabled) {
    player.setShuffleModeEnabled(shuffleModeEnabled);
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetPlaybackParameters(PlaybackParameters playbackParameters) {
    player.setPlaybackParameters(playbackParameters);
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetTrackSelectionParameters(
      TrackSelectionParameters trackSelectionParameters) {
    player.setTrackSelectionParameters(trackSelectionParameters);
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetPlaylistMetadata(MediaMetadata playlistMetadata) {
    player.setPlaylistMetadata(playlistMetadata);
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetVolume(float volume) {
    player.setVolume(volume);
    return Futures.immediateVoidFuture();
  }

  @SuppressWarnings("deprecation") // Calling deprecated method if updated command not available.
  @Override
  protected ListenableFuture<?> handleSetDeviceVolume(int deviceVolume, int flags) {
    if (player.isCommandAvailable(Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS)) {
      player.setDeviceVolume(deviceVolume, flags);
    } else {
      player.setDeviceVolume(deviceVolume);
    }
    return Futures.immediateVoidFuture();
  }

  @SuppressWarnings("deprecation") // Calling deprecated method if updated command not available.
  @Override
  protected ListenableFuture<?> handleIncreaseDeviceVolume(@C.VolumeFlags int flags) {
    if (player.isCommandAvailable(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
      player.increaseDeviceVolume(flags);
    } else {
      player.increaseDeviceVolume();
    }
    return Futures.immediateVoidFuture();
  }

  @SuppressWarnings("deprecation") // Calling deprecated method if updated command not available.
  @Override
  protected ListenableFuture<?> handleDecreaseDeviceVolume(@C.VolumeFlags int flags) {
    if (player.isCommandAvailable(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
      player.decreaseDeviceVolume(flags);
    } else {
      player.decreaseDeviceVolume();
    }
    return Futures.immediateVoidFuture();
  }

  @SuppressWarnings("deprecation") // Calling deprecated method if updated command not available.
  @Override
  protected ListenableFuture<?> handleSetDeviceMuted(boolean muted, @C.VolumeFlags int flags) {
    if (player.isCommandAvailable(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
      player.setDeviceMuted(muted, flags);
    } else {
      player.setDeviceMuted(muted);
    }

    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetAudioAttributes(
      AudioAttributes audioAttributes, boolean handleAudioFocus) {
    player.setAudioAttributes(audioAttributes, handleAudioFocus);
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetVideoOutput(Object videoOutput) {
    if (videoOutput instanceof SurfaceView) {
      player.setVideoSurfaceView((SurfaceView) videoOutput);
    } else if (videoOutput instanceof TextureView) {
      player.setVideoTextureView((TextureView) videoOutput);
    } else if (videoOutput instanceof SurfaceHolder) {
      player.setVideoSurfaceHolder((SurfaceHolder) videoOutput);
    } else if (videoOutput instanceof Surface) {
      player.setVideoSurface((Surface) videoOutput);
    } else {
      throw new IllegalStateException();
    }
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleClearVideoOutput(@Nullable Object videoOutput) {
    if (videoOutput instanceof SurfaceView) {
      player.clearVideoSurfaceView((SurfaceView) videoOutput);
    } else if (videoOutput instanceof TextureView) {
      player.clearVideoTextureView((TextureView) videoOutput);
    } else if (videoOutput instanceof SurfaceHolder) {
      player.clearVideoSurfaceHolder((SurfaceHolder) videoOutput);
    } else if (videoOutput instanceof Surface) {
      player.clearVideoSurface((Surface) videoOutput);
    } else if (videoOutput == null) {
      player.clearVideoSurface();
    } else {
      throw new IllegalStateException();
    }
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSetMediaItems(
      List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
    boolean useSingleItemCall =
        mediaItems.size() == 1 && player.isCommandAvailable(Player.COMMAND_SET_MEDIA_ITEM);
    if (startIndex == C.INDEX_UNSET) {
      if (useSingleItemCall) {
        player.setMediaItem(mediaItems.get(0));
      } else {
        player.setMediaItems(mediaItems);
      }
    } else {
      if (useSingleItemCall) {
        player.setMediaItem(mediaItems.get(0), startPositionMs);
      } else {
        player.setMediaItems(mediaItems, startIndex, startPositionMs);
      }
    }
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleAddMediaItems(int index, List<MediaItem> mediaItems) {
    if (mediaItems.size() == 1) {
      player.addMediaItem(index, mediaItems.get(0));
    } else {
      player.addMediaItems(index, mediaItems);
    }
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleMoveMediaItems(int fromIndex, int toIndex, int newIndex) {
    if (toIndex == fromIndex + 1) {
      player.moveMediaItem(fromIndex, newIndex);
    } else {
      player.moveMediaItems(fromIndex, toIndex, newIndex);
    }
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleReplaceMediaItems(
      int fromIndex, int toIndex, List<MediaItem> mediaItems) {
    if (toIndex == fromIndex + 1 && mediaItems.size() == 1) {
      player.replaceMediaItem(fromIndex, mediaItems.get(0));
    } else {
      player.replaceMediaItems(fromIndex, toIndex, mediaItems);
    }
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleRemoveMediaItems(int fromIndex, int toIndex) {
    if (toIndex == fromIndex + 1) {
      player.removeMediaItem(fromIndex);
    } else {
      player.removeMediaItems(fromIndex, toIndex);
    }
    return Futures.immediateVoidFuture();
  }

  @Override
  protected ListenableFuture<?> handleSeek(
      int mediaItemIndex, long positionMs, @Command int seekCommand) {
    switch (seekCommand) {
      case Player.COMMAND_SEEK_BACK:
        player.seekBack();
        break;
      case Player.COMMAND_SEEK_FORWARD:
        player.seekForward();
        break;
      case Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM:
        player.seekTo(positionMs);
        break;
      case Player.COMMAND_SEEK_TO_DEFAULT_POSITION:
        player.seekToDefaultPosition();
        break;
      case Player.COMMAND_SEEK_TO_MEDIA_ITEM:
        if (mediaItemIndex != C.INDEX_UNSET) {
          player.seekTo(mediaItemIndex, positionMs);
        }
        break;
      case Player.COMMAND_SEEK_TO_NEXT:
        player.seekToNext();
        break;
      case Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM:
        player.seekToNextMediaItem();
        break;
      case Player.COMMAND_SEEK_TO_PREVIOUS:
        player.seekToPrevious();
        break;
      case Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM:
        player.seekToPreviousMediaItem();
        break;
      default:
        throw new IllegalStateException();
    }
    return Futures.immediateVoidFuture();
  }

  /**
   * Forwards to the changing position values of the wrapped player until the forwarding is
   * deactivated with constant values.
   */
  private static final class ForwardingPositionSupplier {

    private final Player player;

    private long positionsMs;
    private long contentPositionMs;

    public ForwardingPositionSupplier(Player player) {
      this.player = player;
      this.positionsMs = C.TIME_UNSET;
      this.contentPositionMs = C.TIME_UNSET;
    }

    public void setConstant(long positionMs, long contentPositionMs) {
      this.positionsMs = positionMs;
      this.contentPositionMs = contentPositionMs;
    }

    public long getCurrentPositionMs() {
      return positionsMs == C.TIME_UNSET ? player.getCurrentPosition() : positionsMs;
    }

    public long getBufferedPositionMs() {
      return positionsMs == C.TIME_UNSET ? player.getBufferedPosition() : positionsMs;
    }

    public long getContentPositionMs() {
      return contentPositionMs == C.TIME_UNSET ? player.getContentPosition() : contentPositionMs;
    }

    public long getContentBufferedPositionMs() {
      return contentPositionMs == C.TIME_UNSET
          ? player.getContentBufferedPosition()
          : contentPositionMs;
    }

    public long getTotalBufferedDurationMs() {
      return positionsMs == C.TIME_UNSET ? player.getTotalBufferedDuration() : 0;
    }
  }
}