FileDocCategorySizeDatePackage
PlaybackControlGlue.javaAPI DocAndroid 5.1 API29732Thu Mar 12 22:22:56 GMT 2015android.support.v17.leanback.app

PlaybackControlGlue

public abstract class PlaybackControlGlue extends Object
A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow} and {@link PlaybackOverlayFragment} that implements a recommended approach to handling standard playback control actions such as play/pause, fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class is a glue layer in that it manages the configuration of and interaction between the leanback UI components by defining a functional interface to the media player.

You can instantiate a concrete subclass such as {@link MediaControllerGlue} or you must subclass this abstract helper. To create a subclass you must implement all of the abstract methods and the subclass must invoke {@link #onMetadataChanged()} and {@link #onStateChanged()} appropriately.

To use an instance of the glue layer, first construct an instance. Constructor parameters inform the glue what speed levels are supported for fast forward/rewind. If you have your own controls row you must pass it to {@link #setControlsRow}. The row will be updated by the glue layer based on the media metadata and playback state. Alternatively, you may call {@link #createControlsRowAndPresenter()} which will set a controls row and return a row presenter you can use to present the row.

The helper sets a {@link android.support.v17.leanback.widget.SparseArrayObjectAdapter} on the controls row as the primary actions adapter, and adds actions to it. You can provide additional actions by overriding {@link #createPrimaryActionsAdapter}. This helper does not deal in secondary actions so those you may add separately.

The helper sets an {@link android.support.v17.leanback.widget.OnItemViewClickedListener} on the fragment. To receive callbacks on clicks for elements unknown to the helper, pass a listener to {@link #setOnItemViewClickedListener}.

To update the controls row progress during playback, override {@link #enableProgressUpdating} to manage the lifecycle of a periodic callback to {@link #updateProgress()}. {@link #getUpdatePeriod()} provides a recommended update period.

Fields Summary
public static final int
ACTION_CUSTOM_LEFT_FIRST
The adapter key for the first custom control on the right side of the predefined primary controls.
public static final int
ACTION_SKIP_TO_PREVIOUS
The adapter key for the skip to previous control.
public static final int
ACTION_REWIND
The adapter key for the rewind control.
public static final int
ACTION_PLAY_PAUSE
The adapter key for the play/pause control.
public static final int
ACTION_FAST_FORWARD
The adapter key for the fast forward control.
public static final int
ACTION_SKIP_TO_NEXT
The adapter key for the skip to next control.
public static final int
ACTION_CUSTOM_RIGHT_FIRST
The adapter key for the first custom control on the right side of the predefined primary controls.
public static final int
PLAYBACK_SPEED_INVALID
Invalid playback speed.
public static final int
PLAYBACK_SPEED_PAUSED
Speed representing playback state that is paused.
public static final int
PLAYBACK_SPEED_NORMAL
Speed representing playback state that is playing normally.
public static final int
PLAYBACK_SPEED_FAST_L0
The initial (level 0) fast forward playback speed. The negative of this value is for rewind at the same speed.
public static final int
PLAYBACK_SPEED_FAST_L1
The level 1 fast forward playback speed. The negative of this value is for rewind at the same speed.
public static final int
PLAYBACK_SPEED_FAST_L2
The level 2 fast forward playback speed. The negative of this value is for rewind at the same speed.
public static final int
PLAYBACK_SPEED_FAST_L3
The level 3 fast forward playback speed. The negative of this value is for rewind at the same speed.
public static final int
PLAYBACK_SPEED_FAST_L4
The level 4 fast forward playback speed. The negative of this value is for rewind at the same speed.
private static final String
TAG
private static final boolean
DEBUG
private static final int
MSG_UPDATE_PLAYBACK_STATE
private static final int
UPDATE_PLAYBACK_STATE_DELAY_MS
private static final int
NUMBER_OF_SEEK_SPEEDS
private final PlaybackOverlayFragment
mFragment
private final android.content.Context
mContext
private final int[]
mFastForwardSpeeds
private final int[]
mRewindSpeeds
private android.support.v17.leanback.widget.PlaybackControlsRow
mControlsRow
private android.support.v17.leanback.widget.SparseArrayObjectAdapter
mPrimaryActionsAdapter
private PlaybackControlsRow.PlayPauseAction
mPlayPauseAction
private PlaybackControlsRow.SkipNextAction
mSkipNextAction
private PlaybackControlsRow.SkipPreviousAction
mSkipPreviousAction
private PlaybackControlsRow.FastForwardAction
mFastForwardAction
private PlaybackControlsRow.RewindAction
mRewindAction
private android.support.v17.leanback.widget.OnItemViewClickedListener
mExternalOnItemViewClickedListener
private int
mPlaybackSpeed
private boolean
mFadeWhenPlaying
private final android.os.Handler
mHandler
private final android.support.v17.leanback.widget.OnItemViewClickedListener
mOnItemViewClickedListener
private final PlaybackOverlayFragment.InputEventHandler
mInputEventHandler
Constructors Summary
public PlaybackControlGlue(android.content.Context context, PlaybackOverlayFragment fragment, int[] seekSpeeds)
Constructor for the glue.

The {@link PlaybackOverlayFragment} must be passed in. A {@link OnItemViewClickedListener} and {@link PlaybackOverlayFragment.InputEventHandler} will be set on the fragment.

param
context
param
fragment
param
seekSpeeds Array of seek speeds for fast forward and rewind.


                                                
      
                                
                                 
        this(context, fragment, seekSpeeds, seekSpeeds);
    
public PlaybackControlGlue(android.content.Context context, PlaybackOverlayFragment fragment, int[] fastForwardSpeeds, int[] rewindSpeeds)
Constructor for the glue.

The {@link PlaybackOverlayFragment} must be passed in. A {@link OnItemViewClickedListener} and {@link PlaybackOverlayFragment.InputEventHandler} will be set on the fragment.

param
context
param
fragment
param
fastForwardSpeeds Array of seek speeds for fast forward.
param
rewindSpeeds Array of seek speeds for rewind.

        mContext = context;
        mFragment = fragment;
        if (mFragment.getOnItemViewClickedListener() != null) {
            throw new IllegalStateException("Fragment OnItemViewClickedListener already present");
        }
        mFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
        if (mFragment.getInputEventHandler() != null) {
            throw new IllegalStateException("Fragment InputEventListener already present");
        }
        mFragment.setInputEventHandler(mInputEventHandler);
        if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
            throw new IllegalStateException("invalid fastForwardSpeeds array size");
        }
        mFastForwardSpeeds = fastForwardSpeeds;
        if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
            throw new IllegalStateException("invalid rewindSpeeds array size");
        }
        mRewindSpeeds = rewindSpeeds;
    
Methods Summary
public android.support.v17.leanback.widget.PlaybackControlsRowPresentercreateControlsRowAndPresenter()
Helper method for instantiating a {@link android.support.v17.leanback.widget.PlaybackControlsRow} and corresponding {@link android.support.v17.leanback.widget.PlaybackControlsRowPresenter}.

        PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
        setControlsRow(controlsRow);

        return new PlaybackControlsRowPresenter(new AbstractDetailsDescriptionPresenter() {
            @Override
            protected void onBindDescription(AbstractDetailsDescriptionPresenter.ViewHolder
                                                     viewHolder, Object object) {
                PlaybackControlGlue glue = (PlaybackControlGlue) object;
                if (glue.hasValidMedia()) {
                    viewHolder.getTitle().setText(glue.getMediaTitle());
                    viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
                } else {
                    viewHolder.getTitle().setText("");
                    viewHolder.getSubtitle().setText("");
                }
            }
        });
    
protected android.support.v17.leanback.widget.SparseArrayObjectAdaptercreatePrimaryActionsAdapter(android.support.v17.leanback.widget.PresenterSelector presenterSelector)
Creates the primary action adapter. May be overridden to add additional primary actions to the adapter.

        return new SparseArrayObjectAdapter(presenterSelector);
    
public voidenableProgressUpdating(boolean enable)
Override this to start/stop a runnable to call {@link #updateProgress} at an interval such as {@link #getUpdatePeriod}.

    
public android.content.ContextgetContext()
Returns the context.

        return mContext;
    
public android.support.v17.leanback.widget.PlaybackControlsRowgetControlsRow()
Returns the playback controls row managed by the glue layer.

        return mControlsRow;
    
public abstract intgetCurrentPosition()
Returns the current position of the media item in milliseconds.

public abstract intgetCurrentSpeedId()
Returns the current playback speed. When playing normally, {@link #PLAYBACK_SPEED_NORMAL} should be returned.

public int[]getFastForwardSpeeds()
Returns the fast forward speeds.

        return mFastForwardSpeeds;
    
public PlaybackOverlayFragmentgetFragment()
Returns the fragment.

        return mFragment;
    
private intgetMaxForwardSpeedId()

        return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
    
private intgetMaxRewindSpeedId()

        return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
    
public abstract android.graphics.drawable.DrawablegetMediaArt()
Returns a bitmap of the art for the media item.

public abstract intgetMediaDuration()
Returns the duration of the media item in milliseconds.

public abstract java.lang.CharSequencegetMediaSubtitle()
Returns the subtitle of the media item.

public abstract java.lang.CharSequencegetMediaTitle()
Returns the title of the media item.

public android.support.v17.leanback.widget.OnItemViewClickedListenergetOnItemViewClickedListener()
Returns the {@link OnItemViewClickedListener}.

        return mExternalOnItemViewClickedListener;
    
public int[]getRewindSpeeds()
Returns the rewind speeds.

        return mRewindSpeeds;
    
private static java.lang.StringgetSpeedString(int speed)

        switch (speed) {
            case PLAYBACK_SPEED_INVALID:
                return "PLAYBACK_SPEED_INVALID";
            case PLAYBACK_SPEED_PAUSED:
                return "PLAYBACK_SPEED_PAUSED";
            case PLAYBACK_SPEED_NORMAL:
                return "PLAYBACK_SPEED_NORMAL";
            case PLAYBACK_SPEED_FAST_L0:
                return "PLAYBACK_SPEED_FAST_L0";
            case PLAYBACK_SPEED_FAST_L1:
                return "PLAYBACK_SPEED_FAST_L1";
            case PLAYBACK_SPEED_FAST_L2:
                return "PLAYBACK_SPEED_FAST_L2";
            case PLAYBACK_SPEED_FAST_L3:
                return "PLAYBACK_SPEED_FAST_L3";
            case PLAYBACK_SPEED_FAST_L4:
                return "PLAYBACK_SPEED_FAST_L4";
            case -PLAYBACK_SPEED_FAST_L0:
                return "-PLAYBACK_SPEED_FAST_L0";
            case -PLAYBACK_SPEED_FAST_L1:
                return "-PLAYBACK_SPEED_FAST_L1";
            case -PLAYBACK_SPEED_FAST_L2:
                return "-PLAYBACK_SPEED_FAST_L2";
            case -PLAYBACK_SPEED_FAST_L3:
                return "-PLAYBACK_SPEED_FAST_L3";
            case -PLAYBACK_SPEED_FAST_L4:
                return "-PLAYBACK_SPEED_FAST_L4";
        }
        return null;
    
public abstract longgetSupportedActions()
Returns a bitmask of actions supported by the media player.

public intgetUpdatePeriod()
Returns the time period in milliseconds that should be used to update the progress. See {@link #updateProgress()}.

        // TODO: calculate a better update period based on total duration and screen size
        return 500;
    
private booleanhandleActionClicked(android.support.v17.leanback.widget.Action action)

        boolean handled = false;
        if (action == mPlayPauseAction) {
            if (mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
                mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
                startPlayback(mPlaybackSpeed);
            } else {
                mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
                pausePlayback();
            }
            updatePlaybackStatusAfterUserAction();
            handled = true;
        } else if (action == mSkipNextAction) {
            skipToNext();
            handled = true;
        } else if (action == mSkipPreviousAction) {
            skipToPrevious();
            handled = true;
        } else if (action == mFastForwardAction) {
            if (mPlaybackSpeed < getMaxForwardSpeedId()) {
                switch (mPlaybackSpeed) {
                    case PLAYBACK_SPEED_NORMAL:
                    case PLAYBACK_SPEED_PAUSED:
                        mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
                        break;
                    case PLAYBACK_SPEED_FAST_L0:
                    case PLAYBACK_SPEED_FAST_L1:
                    case PLAYBACK_SPEED_FAST_L2:
                    case PLAYBACK_SPEED_FAST_L3:
                        mPlaybackSpeed++;
                        break;
                }
                startPlayback(mPlaybackSpeed);
                updatePlaybackStatusAfterUserAction();
            }
            handled = true;
        } else if (action == mRewindAction) {
            if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
                switch (mPlaybackSpeed) {
                    case PLAYBACK_SPEED_NORMAL:
                    case PLAYBACK_SPEED_PAUSED:
                        mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
                        break;
                    case -PLAYBACK_SPEED_FAST_L0:
                    case -PLAYBACK_SPEED_FAST_L1:
                    case -PLAYBACK_SPEED_FAST_L2:
                    case -PLAYBACK_SPEED_FAST_L3:
                        mPlaybackSpeed--;
                        break;
                }
                startPlayback(mPlaybackSpeed);
                updatePlaybackStatusAfterUserAction();
            }
            handled = true;
        }
        return handled;
    
public abstract booleanhasValidMedia()
Returns true if there is a valid media item.

public booleanisFadingEnabled()
Returns true if controls are set to fade when media is playing.

        return mFadeWhenPlaying;
    
public abstract booleanisMediaPlaying()
Returns true if media is currently playing.

private static voidnotifyItemChanged(android.support.v17.leanback.widget.SparseArrayObjectAdapter adapter, java.lang.Object object)

        int index = adapter.indexOf(object);
        if (index >= 0) {
            adapter.notifyArrayItemRangeChanged(index, 1);
        }
    
protected voidonMetadataChanged()
Must be called appropriately by a subclass when the metadata state has changed.

        if (DEBUG) Log.v(TAG, "onMetadataChanged");
        updateRowMetadata();
    
protected abstract voidonRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow row)
Invoked when the playback controls row has changed. The adapter containing this row should be notified.

protected voidonStateChanged()
Must be called appropriately by a subclass when the playback state has changed.

        if (DEBUG) Log.v(TAG, "onStateChanged");
        // If a pending control button update is present, delay
        // the update until the state settles.
        if (!hasValidMedia()) {
            return;
        }
        if (mHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE)) {
            mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
            if (getCurrentSpeedId() != mPlaybackSpeed) {
                if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
                mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE,
                        UPDATE_PLAYBACK_STATE_DELAY_MS);
            } else {
                if (DEBUG) Log.v(TAG, "Update state matches expectation");
                updatePlaybackState();
            }
        } else {
            updatePlaybackState();
        }
    
protected abstract voidpausePlayback()
Pause playback.

public voidsetControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow controlsRow)
Sets the controls row to be managed by the glue layer. The primary actions and playback state related aspects of the row are updated by the glue.

        mControlsRow = controlsRow;
        mPrimaryActionsAdapter = createPrimaryActionsAdapter(
                new ControlButtonPresenterSelector());
        mControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
        updateControlsRow();
    
public voidsetFadingEnabled(boolean enable)
Sets the controls to fade after a timeout when media is playing.

        mFadeWhenPlaying = enable;
        if (!mFadeWhenPlaying) {
            mFragment.setFadingEnabled(false);
        }
    
public voidsetOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener listener)
Set the {@link OnItemViewClickedListener} to be called if the click event is not handled internally.

param
listener

        mExternalOnItemViewClickedListener = listener;
    
protected abstract voidskipToNext()
Skip to the next track.

protected abstract voidskipToPrevious()
Skip to the previous track.

protected abstract voidstartPlayback(int speed)
Start playback at the given speed.

param
speed The desired playback speed. For normal playback this will be {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward, and negative values for rewind.

private voidupdateControlsRow()

        updateRowMetadata();
        mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
        updatePlaybackState();
    
private voidupdatePlaybackState()

        if (hasValidMedia()) {
            mPlaybackSpeed = getCurrentSpeedId();
            updatePlaybackState(mPlaybackSpeed);
        }
    
private voidupdatePlaybackState(int playbackSpeed)

        if (mControlsRow == null) {
            return;
        }

        final long actions = getSupportedActions();
        if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0) {
            if (mSkipPreviousAction == null) {
                mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(mContext);
            }
            mPrimaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
        } else {
            mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
            mSkipPreviousAction = null;
        }
        if ((actions & ACTION_REWIND) != 0) {
            if (mRewindAction == null) {
                mRewindAction = new PlaybackControlsRow.RewindAction(mContext,
                        mRewindSpeeds.length);
            }
            mPrimaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
        } else {
            mPrimaryActionsAdapter.clear(ACTION_REWIND);
            mRewindAction = null;
        }
        if ((actions & ACTION_PLAY_PAUSE) != 0) {
            if (mPlayPauseAction == null) {
                mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(mContext);
            }
            mPrimaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
        } else {
            mPrimaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
            mPlayPauseAction = null;
        }
        if ((actions & ACTION_FAST_FORWARD) != 0) {
            if (mFastForwardAction == null) {
                mFastForwardAction = new PlaybackControlsRow.FastForwardAction(mContext,
                        mFastForwardSpeeds.length);
            }
            mPrimaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
        } else {
            mPrimaryActionsAdapter.clear(ACTION_FAST_FORWARD);
            mFastForwardAction = null;
        }
        if ((actions & ACTION_SKIP_TO_NEXT) != 0) {
            if (mSkipNextAction == null) {
                mSkipNextAction = new PlaybackControlsRow.SkipNextAction(mContext);
            }
            mPrimaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
        } else {
            mPrimaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
            mSkipNextAction = null;
        }

        if (mFastForwardAction != null) {
            int index = 0;
            if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
                index = playbackSpeed - PLAYBACK_SPEED_FAST_L0;
                if (playbackSpeed < getMaxForwardSpeedId()) {
                    index++;
                }
            }
            if (mFastForwardAction.getIndex() != index) {
                mFastForwardAction.setIndex(index);
                notifyItemChanged(mPrimaryActionsAdapter, mFastForwardAction);
            }
        }
        if (mRewindAction != null) {
            int index = 0;
            if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
                index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0;
                if (-playbackSpeed < getMaxRewindSpeedId()) {
                    index++;
                }
            }
            if (mRewindAction.getIndex() != index) {
                mRewindAction.setIndex(index);
                notifyItemChanged(mPrimaryActionsAdapter, mRewindAction);
            }
        }

        if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
            updateProgress();
            enableProgressUpdating(false);
        } else {
            enableProgressUpdating(true);
        }

        if (mFadeWhenPlaying) {
            mFragment.setFadingEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
        }

        if (mPlayPauseAction != null) {
            int index = playbackSpeed == PLAYBACK_SPEED_PAUSED ?
                    PlaybackControlsRow.PlayPauseAction.PLAY :
                    PlaybackControlsRow.PlayPauseAction.PAUSE;
            if (mPlayPauseAction.getIndex() != index) {
                mPlayPauseAction.setIndex(index);
                notifyItemChanged(mPrimaryActionsAdapter, mPlayPauseAction);
            }
        }
    
private voidupdatePlaybackStatusAfterUserAction()

        updatePlaybackState(mPlaybackSpeed);
        // Sync playback state after a delay
        mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE);
        mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE,
                UPDATE_PLAYBACK_STATE_DELAY_MS);
    
public voidupdateProgress()
Updates the progress bar based on the current media playback position.

        int position = getCurrentPosition();
        if (DEBUG) Log.v(TAG, "updateProgress " + position);
        mControlsRow.setCurrentTime(position);
    
private voidupdateRowMetadata()

        if (mControlsRow == null) {
            return;
        }

        if (DEBUG) Log.v(TAG, "updateRowMetadata hasValidMedia " + hasValidMedia());

        if (!hasValidMedia()) {
            mControlsRow.setImageDrawable(null);
            mControlsRow.setTotalTime(0);
            mControlsRow.setCurrentTime(0);
        } else {
            mControlsRow.setImageDrawable(getMediaArt());
            mControlsRow.setTotalTime(getMediaDuration());
            mControlsRow.setCurrentTime(getCurrentPosition());
        }

        onRowChanged(mControlsRow);