Subclasses:
MediaPlayerGlue, MediaControllerGlue
Gradle dependencies
compile group: 'androidx.leanback', name: 'leanback', version: '1.2.0-alpha04'
- groupId: androidx.leanback
- artifactId: leanback
- version: 1.2.0-alpha04
Artifact androidx.leanback:leanback:1.2.0-alpha04 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.leanback:leanback com.android.support:leanback-v17
Androidx class mapping:
androidx.leanback.media.PlaybackControlGlue android.support.v17.leanback.media.PlaybackControlGlue
Overview
A helper class for managing a PlaybackControlsRow
and PlaybackGlueHost 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 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 MediaPlayerGlue or you must
subclass this abstract helper. To create a subclass you must implement all of the
abstract methods and the subclass must invoke PlaybackControlGlue.onMetadataChanged() and
PlaybackControlGlue.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.
You may override PlaybackControlGlue.onCreateControlsRowAndPresenter() which will create a
PlaybackControlsRow and a PlaybackControlsRowPresenter. You may call
PlaybackControlGlue.setControlsRow(PlaybackControlsRow) and
PlaybackControlGlue.setPlaybackRowPresenter(PlaybackRowPresenter) to customize your own row and presenter.
The helper sets a SparseArrayObjectAdapter
on the controls row as the primary actions adapter, and adds actions to it. You can provide
additional actions by overriding PlaybackControlGlue.onCreatePrimaryActions(SparseArrayObjectAdapter). This helper does not
deal in secondary actions so those you may add separately.
Provide a click listener on your fragment and if an action is clicked, call
PlaybackControlGlue.onActionClicked(Action).
This helper implements a key event handler. If you pass a
PlaybackGlueHost, it will configure its
fragment to intercept all key events. Otherwise, you should set the glue object as key event
handler to the ViewHolder when bound by your row presenter; see
.
To update the controls row progress during playback, override PlaybackControlGlue.enableProgressUpdating(boolean)
to manage the lifecycle of a periodic callback to PlaybackControlGlue.updateProgress().
PlaybackControlGlue.getUpdatePeriod() provides a recommended update period.
Summary
Constructors |
---|
public | PlaybackControlGlue(Context context, int[] seekSpeeds[])
Constructor for the glue. |
public | PlaybackControlGlue(Context context, int[] fastForwardSpeeds[], int[] rewindSpeeds[])
Constructor for the glue. |
Methods |
---|
protected SparseArrayObjectAdapter | createPrimaryActionsAdapter(PresenterSelector presenterSelector)
|
public void | enableProgressUpdating(boolean enable)
Override this to start/stop a runnable to call PlaybackControlGlue.updateProgress() at
an interval such as PlaybackControlGlue.getUpdatePeriod(). |
public PlaybackControlsRow | getControlsRow()
Returns the playback controls row managed by the glue layer. |
public PlaybackControlsRowPresenter | getControlsRowPresenter()
Returns the playback controls row Presenter managed by the glue layer. |
public abstract int | getCurrentPosition()
Returns the current position of the media item in milliseconds. |
public abstract int | getCurrentSpeedId()
Returns the current playback speed. |
public int[] | getFastForwardSpeeds()
Returns the fast forward speeds. |
public abstract Drawable | getMediaArt()
Returns a bitmap of the art for the media item. |
public abstract int | getMediaDuration()
Returns the duration of the media item in milliseconds. |
public abstract java.lang.CharSequence | getMediaSubtitle()
Returns the subtitle of the media item. |
public abstract java.lang.CharSequence | getMediaTitle()
Returns the title of the media item. |
public PlaybackRowPresenter | getPlaybackRowPresenter()
Returns the playback row Presenter to be passed to PlaybackGlueHost in
PlaybackGlue.onAttachedToHost(PlaybackGlueHost). |
public int[] | getRewindSpeeds()
Returns the rewind speeds. |
public abstract long | getSupportedActions()
Returns a bitmask of actions supported by the media player. |
public int | getUpdatePeriod()
Returns the time period in milliseconds that should be used
to update the progress. |
public abstract boolean | hasValidMedia()
Returns true if there is a valid media item. |
public boolean | isFadingEnabled()
Returns true if controls are set to fade when media is playing. |
public abstract boolean | isMediaPlaying()
Returns true if media is currently playing. |
public boolean | isPlaying()
Returns true if media is currently playing. |
public void | onActionClicked(Action action)
Handles action clicks. |
protected void | onAttachedToHost(PlaybackGlueHost host)
This method is called attached to associated PlaybackGlueHost. |
protected void | onCreateControlsRowAndPresenter()
Instantiating a PlaybackControlsRow and corresponding
PlaybackControlsRowPresenter. |
protected void | onCreatePrimaryActions(SparseArrayObjectAdapter primaryActionsAdapter)
May be overridden to add primary actions to the adapter. |
protected void | onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter)
May be overridden to add secondary actions to the adapter. |
protected void | onDetachedFromHost()
This method is called when current associated PlaybackGlueHost is attached to a
different PlaybackGlue or PlaybackGlueHost is destroyed . |
protected void | onHostStart()
This method is called when PlaybackGlueHost is started. |
protected void | onHostStop()
This method is called when PlaybackGlueHost is stopped. |
public boolean | onKey(View v, int keyCode, KeyEvent event)
Handles key events and returns true if handled. |
protected void | onMetadataChanged()
Must be called appropriately by a subclass when the metadata state has changed. |
protected void | onStateChanged()
Must be called appropriately by a subclass when the playback state has changed. |
public void | play()
Starts the media player. |
public void | play(int speed)
Start playback at the given speed. |
public void | setControlsRow(PlaybackControlsRow controlsRow)
Sets the controls row to be managed by the glue layer. |
public void | setControlsRowPresenter(PlaybackControlsRowPresenter presenter)
Sets the controls row Presenter to be managed by the glue layer. |
public void | setFadingEnabled(boolean enable)
Sets the controls to fade after a timeout when media is playing. |
public void | setPlaybackRowPresenter(PlaybackRowPresenter presenter)
Sets the controls row Presenter to be passed to PlaybackGlueHost in
PlaybackGlue.onAttachedToHost(PlaybackGlueHost). |
public void | updateProgress()
Updates the progress bar based on the current media playback position. |
from PlaybackGlue | addPlayerCallback, getContext, getHost, getPlayerCallbacks, isPrepared, next, onHostPause, onHostResume, pause, playWhenPrepared, previous, removePlayerCallback, setHost |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Fields
public static final int
ACTION_CUSTOM_LEFT_FIRSTThe adapter key for the first custom control on the left side
of the predefined primary controls.
public static final int
ACTION_SKIP_TO_PREVIOUSThe adapter key for the skip to previous control.
public static final int
ACTION_REWINDThe adapter key for the rewind control.
public static final int
ACTION_PLAY_PAUSEThe adapter key for the play/pause control.
public static final int
ACTION_FAST_FORWARDThe adapter key for the fast forward control.
public static final int
ACTION_SKIP_TO_NEXTThe adapter key for the skip to next control.
public static final int
ACTION_CUSTOM_RIGHT_FIRSTThe adapter key for the first custom control on the right side
of the predefined primary controls.
public static final int
PLAYBACK_SPEED_INVALIDInvalid playback speed.
public static final int
PLAYBACK_SPEED_PAUSEDSpeed representing playback state that is paused.
public static final int
PLAYBACK_SPEED_NORMALSpeed representing playback state that is playing normally.
public static final int
PLAYBACK_SPEED_FAST_L0The 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_L1The 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_L2The 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_L3The 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_L4The level 4 fast forward playback speed.
The negative of this value is for rewind at the same speed.
Constructors
public
PlaybackControlGlue(Context context, int[] seekSpeeds[])
Constructor for the glue.
Parameters:
context:
seekSpeeds: Array of seek speeds for fast forward and rewind.
public
PlaybackControlGlue(Context context, int[] fastForwardSpeeds[], int[] rewindSpeeds[])
Constructor for the glue.
Parameters:
context:
fastForwardSpeeds: Array of seek speeds for fast forward.
rewindSpeeds: Array of seek speeds for rewind.
Methods
This method is called attached to associated PlaybackGlueHost. Subclass may override
and call super.onAttachedToHost().
protected void
onHostStart()
This method is called when PlaybackGlueHost is started. Subclass may override.
protected void
onHostStop()
This method is called when PlaybackGlueHost is stopped. Subclass may override.
protected void
onDetachedFromHost()
This method is called when current associated PlaybackGlueHost is attached to a
different PlaybackGlue or PlaybackGlueHost is destroyed . Subclass may
override and call super.onDetachedFromHost() at last. A typical PlaybackGlue will release
resources (e.g. MediaPlayer or connection to playback service) in this method.
protected void
onCreateControlsRowAndPresenter()
Instantiating a PlaybackControlsRow and corresponding
PlaybackControlsRowPresenter. Subclass may override.
public int[]
getFastForwardSpeeds()
Returns the fast forward speeds.
public int[]
getRewindSpeeds()
Returns the rewind speeds.
public void
setFadingEnabled(boolean enable)
Sets the controls to fade after a timeout when media is playing.
public boolean
isFadingEnabled()
Returns true if controls are set to fade when media is playing.
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.
Deprecated: PlaybackControlGlue supports any PlaybackRowPresenter, use
PlaybackControlGlue.setPlaybackRowPresenter(PlaybackRowPresenter).
Sets the controls row Presenter to be managed by the glue layer.
Returns the playback controls row managed by the glue layer.
Deprecated: PlaybackControlGlue supports any PlaybackRowPresenter, use
PlaybackControlGlue.getPlaybackRowPresenter().
Returns the playback controls row Presenter managed by the glue layer.
Sets the controls row Presenter to be passed to PlaybackGlueHost in
PlaybackGlue.onAttachedToHost(PlaybackGlueHost).
Returns the playback row Presenter to be passed to PlaybackGlueHost in
PlaybackGlue.onAttachedToHost(PlaybackGlueHost).
public void
enableProgressUpdating(boolean enable)
Override this to start/stop a runnable to call PlaybackControlGlue.updateProgress() at
an interval such as PlaybackControlGlue.getUpdatePeriod().
public int
getUpdatePeriod()
Returns the time period in milliseconds that should be used
to update the progress. See PlaybackControlGlue.updateProgress().
public void
updateProgress()
Updates the progress bar based on the current media playback position.
public void
onActionClicked(
Action action)
Handles action clicks. A subclass may override this add support for additional actions.
public boolean
onKey(View v, int keyCode, KeyEvent event)
Handles key events and returns true if handled. A subclass may override this to provide
additional support.
public void
play(int speed)
Start playback at the given speed.
Parameters:
speed: The desired playback speed. For normal playback this will be
PlaybackControlGlue.PLAYBACK_SPEED_NORMAL; higher positive values for fast forward,
and negative values for rewind.
Starts the media player. Does nothing if PlaybackGlue.isPrepared() is false. To wait
PlaybackGlue.isPrepared() to be true before playing, use PlaybackGlue.playWhenPrepared().
public abstract boolean
hasValidMedia()
Returns true if there is a valid media item.
public abstract boolean
isMediaPlaying()
Returns true if media is currently playing.
public boolean
isPlaying()
Returns true if media is currently playing.
public abstract java.lang.CharSequence
getMediaTitle()
Returns the title of the media item.
public abstract java.lang.CharSequence
getMediaSubtitle()
Returns the subtitle of the media item.
public abstract int
getMediaDuration()
Returns the duration of the media item in milliseconds.
public abstract Drawable
getMediaArt()
Returns a bitmap of the art for the media item.
public abstract long
getSupportedActions()
Returns a bitmask of actions supported by the media player.
public abstract int
getCurrentSpeedId()
Returns the current playback speed. When playing normally,
PlaybackControlGlue.PLAYBACK_SPEED_NORMAL should be returned.
public abstract int
getCurrentPosition()
Returns the current position of the media item in milliseconds.
May be overridden to add primary actions to the adapter.
Parameters:
primaryActionsAdapter: The adapter to add primary Actions.
May be overridden to add secondary actions to the adapter.
Parameters:
secondaryActionsAdapter: The adapter you need to add the Actions to.
protected void
onStateChanged()
Must be called appropriately by a subclass when the playback state has changed.
It updates the playback state displayed on the media player.
protected void
onMetadataChanged()
Must be called appropriately by a subclass when the metadata state has changed.
Source
/*
* Copyright (C) 2016 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.leanback.media;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.leanback.widget.AbstractDetailsDescriptionPresenter;
import androidx.leanback.widget.Action;
import androidx.leanback.widget.ArrayObjectAdapter;
import androidx.leanback.widget.ControlButtonPresenterSelector;
import androidx.leanback.widget.OnActionClickedListener;
import androidx.leanback.widget.PlaybackControlsRow;
import androidx.leanback.widget.PlaybackControlsRowPresenter;
import androidx.leanback.widget.PlaybackRowPresenter;
import androidx.leanback.widget.PresenterSelector;
import androidx.leanback.widget.RowPresenter;
import androidx.leanback.widget.SparseArrayObjectAdapter;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* A helper class for managing a {@link PlaybackControlsRow}
* and {@link PlaybackGlueHost} 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 manages the configuration of and interaction between the
* leanback UI components by defining a functional interface to the media player.
*
* <p>You can instantiate a concrete subclass such as MediaPlayerGlue 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.
* </p>
*
* <p>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.
* </p>
*
* <p>You may override {@link #onCreateControlsRowAndPresenter()} which will create a
* {@link PlaybackControlsRow} and a {@link PlaybackControlsRowPresenter}. You may call
* {@link #setControlsRow(PlaybackControlsRow)} and
* {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} to customize your own row and presenter.
* </p>
*
* <p>The helper sets a {@link SparseArrayObjectAdapter}
* on the controls row as the primary actions adapter, and adds actions to it. You can provide
* additional actions by overriding {@link #onCreatePrimaryActions}. This helper does not
* deal in secondary actions so those you may add separately.
* </p>
*
* <p>Provide a click listener on your fragment and if an action is clicked, call
* {@link #onActionClicked}.
* </p>
*
* <p>This helper implements a key event handler. If you pass a
* {@link PlaybackGlueHost}, it will configure its
* fragment to intercept all key events. Otherwise, you should set the glue object as key event
* handler to the ViewHolder when bound by your row presenter; see
* {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
* </p>
*
* <p>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.
* </p>
*
*/
public abstract class PlaybackControlGlue extends PlaybackGlue
implements OnActionClickedListener, View.OnKeyListener {
/**
* The adapter key for the first custom control on the left side
* of the predefined primary controls.
*/
public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
/**
* The adapter key for the skip to previous control.
*/
public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
/**
* The adapter key for the rewind control.
*/
public static final int ACTION_REWIND = 0x20;
/**
* The adapter key for the play/pause control.
*/
public static final int ACTION_PLAY_PAUSE = 0x40;
/**
* The adapter key for the fast forward control.
*/
public static final int ACTION_FAST_FORWARD = 0x80;
/**
* The adapter key for the skip to next control.
*/
public static final int ACTION_SKIP_TO_NEXT = 0x100;
/**
* The adapter key for the first custom control on the right side
* of the predefined primary controls.
*/
public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
/**
* Invalid playback speed.
*/
public static final int PLAYBACK_SPEED_INVALID = -1;
/**
* Speed representing playback state that is paused.
*/
public static final int PLAYBACK_SPEED_PAUSED = 0;
/**
* Speed representing playback state that is playing normally.
*/
public static final int PLAYBACK_SPEED_NORMAL = 1;
/**
* 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_L0 = 10;
/**
* 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_L1 = 11;
/**
* 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_L2 = 12;
/**
* 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_L3 = 13;
/**
* The level 4 fast forward playback speed.
* The negative of this value is for rewind at the same speed.
*/
public static final int PLAYBACK_SPEED_FAST_L4 = 14;
static final String TAG = "PlaybackControlGlue";
static final boolean DEBUG = false;
static final int MSG_UPDATE_PLAYBACK_STATE = 100;
private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4
- PLAYBACK_SPEED_FAST_L0 + 1;
private final int[] mFastForwardSpeeds;
private final int[] mRewindSpeeds;
private PlaybackControlsRow mControlsRow;
private PlaybackRowPresenter mControlsRowPresenter;
private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
private PlaybackControlsRow.SkipNextAction mSkipNextAction;
private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
private PlaybackControlsRow.FastForwardAction mFastForwardAction;
private PlaybackControlsRow.RewindAction mRewindAction;
private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
private boolean mFadeWhenPlaying = true;
static class UpdatePlaybackStateHandler extends Handler {
@Override
@SuppressWarnings("unchecked")
public void handleMessage(Message msg) {
if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
PlaybackControlGlue glue = ((WeakReference<PlaybackControlGlue>) msg.obj).get();
if (glue != null) {
glue.updatePlaybackState();
}
}
}
}
static final Handler sHandler = new UpdatePlaybackStateHandler();
final WeakReference<PlaybackControlGlue> mGlueWeakReference = new WeakReference<>(this);
/**
* Constructor for the glue.
*
* @param context
* @param seekSpeeds Array of seek speeds for fast forward and rewind.
*/
public PlaybackControlGlue(Context context, int[] seekSpeeds) {
this(context, seekSpeeds, seekSpeeds);
}
/**
* Constructor for the glue.
*
* @param context
* @param fastForwardSpeeds Array of seek speeds for fast forward.
* @param rewindSpeeds Array of seek speeds for rewind.
*/
public PlaybackControlGlue(Context context,
int[] fastForwardSpeeds,
int[] rewindSpeeds) {
super(context);
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;
}
@Override
protected void onAttachedToHost(@NonNull PlaybackGlueHost host) {
super.onAttachedToHost(host);
host.setOnKeyInterceptListener(this);
host.setOnActionClickedListener(this);
if (getControlsRow() == null || getPlaybackRowPresenter() == null) {
onCreateControlsRowAndPresenter();
}
host.setPlaybackRowPresenter(getPlaybackRowPresenter());
host.setPlaybackRow(getControlsRow());
}
@Override
protected void onHostStart() {
enableProgressUpdating(true);
}
@Override
protected void onHostStop() {
enableProgressUpdating(false);
}
@Override
protected void onDetachedFromHost() {
enableProgressUpdating(false);
super.onDetachedFromHost();
}
/**
* Instantiating a {@link PlaybackControlsRow} and corresponding
* {@link PlaybackControlsRowPresenter}. Subclass may override.
*/
protected void onCreateControlsRowAndPresenter() {
if (getControlsRow() == null) {
PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
setControlsRow(controlsRow);
}
if (getPlaybackRowPresenter() == null) {
final AbstractDetailsDescriptionPresenter detailsPresenter =
new AbstractDetailsDescriptionPresenter() {
@Override
protected void onBindDescription(@NonNull ViewHolder
viewHolder, @NonNull 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("");
}
}
};
setPlaybackRowPresenter(new PlaybackControlsRowPresenter(detailsPresenter) {
@Override
protected void onBindRowViewHolder(
@NonNull RowPresenter.ViewHolder vh,
@NonNull Object item
) {
super.onBindRowViewHolder(vh, item);
vh.setOnKeyListener(PlaybackControlGlue.this);
}
@Override
protected void onUnbindRowViewHolder(@NonNull RowPresenter.ViewHolder vh) {
super.onUnbindRowViewHolder(vh);
vh.setOnKeyListener(null);
}
});
}
}
/**
* Returns the fast forward speeds.
*/
public int[] getFastForwardSpeeds() {
return mFastForwardSpeeds;
}
/**
* Returns the rewind speeds.
*/
public int[] getRewindSpeeds() {
return mRewindSpeeds;
}
/**
* Sets the controls to fade after a timeout when media is playing.
*/
public void setFadingEnabled(boolean enable) {
mFadeWhenPlaying = enable;
if (!mFadeWhenPlaying && getHost() != null) {
getHost().setControlsOverlayAutoHideEnabled(false);
}
}
/**
* Returns true if controls are set to fade when media is playing.
*/
public boolean isFadingEnabled() {
return mFadeWhenPlaying;
}
/**
* 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.
*/
public void setControlsRow(PlaybackControlsRow controlsRow) {
mControlsRow = controlsRow;
mControlsRow.setPrimaryActionsAdapter(
createPrimaryActionsAdapter(new ControlButtonPresenterSelector()));
// Add secondary actions
ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
new ControlButtonPresenterSelector());
onCreateSecondaryActions(secondaryActions);
getControlsRow().setSecondaryActionsAdapter(secondaryActions);
updateControlsRow();
}
/**
*/
@RestrictTo(RestrictTo.Scope.LIBRARY)
protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
PresenterSelector presenterSelector) {
SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
onCreatePrimaryActions(adapter);
return adapter;
}
/**
* Sets the controls row Presenter to be managed by the glue layer.
* @deprecated PlaybackControlGlue supports any PlaybackRowPresenter, use
* {@link #setPlaybackRowPresenter(PlaybackRowPresenter)}.
*/
@Deprecated
public void setControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
mControlsRowPresenter = presenter;
}
/**
* Returns the playback controls row managed by the glue layer.
*/
public PlaybackControlsRow getControlsRow() {
return mControlsRow;
}
/**
* Returns the playback controls row Presenter managed by the glue layer.
* @deprecated PlaybackControlGlue supports any PlaybackRowPresenter, use
* {@link #getPlaybackRowPresenter()}.
*/
@Deprecated
public PlaybackControlsRowPresenter getControlsRowPresenter() {
return mControlsRowPresenter instanceof PlaybackControlsRowPresenter
? (PlaybackControlsRowPresenter) mControlsRowPresenter : null;
}
/**
* Sets the controls row Presenter to be passed to {@link PlaybackGlueHost} in
* {@link PlaybackGlue#onAttachedToHost(PlaybackGlueHost)}.
*/
public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
mControlsRowPresenter = presenter;
}
/**
* Returns the playback row Presenter to be passed to {@link PlaybackGlueHost} in
* {@link PlaybackGlue#onAttachedToHost(PlaybackGlueHost)}.
*/
public PlaybackRowPresenter getPlaybackRowPresenter() {
return mControlsRowPresenter;
}
/**
* Override this to start/stop a runnable to call {@link #updateProgress} at
* an interval such as {@link #getUpdatePeriod}.
*/
public void enableProgressUpdating(boolean enable) {
}
/**
* Returns the time period in milliseconds that should be used
* to update the progress. See {@link #updateProgress()}.
*/
public int getUpdatePeriod() {
// TODO: calculate a better update period based on total duration and screen size
return 500;
}
/**
* Updates the progress bar based on the current media playback position.
*/
public void updateProgress() {
int position = getCurrentPosition();
if (DEBUG) Log.v(TAG, "updateProgress " + position);
if (mControlsRow != null) {
mControlsRow.setCurrentTime(position);
}
}
/**
* Handles action clicks. A subclass may override this add support for additional actions.
*/
@Override
public void onActionClicked(Action action) {
dispatchAction(action, null);
}
/**
* Handles key events and returns true if handled. A subclass may override this to provide
* additional support.
*/
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_ESCAPE:
boolean abortSeek = mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0
|| mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0;
if (abortSeek) {
mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
play(mPlaybackSpeed);
updatePlaybackStatusAfterUserAction();
return keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE;
}
return false;
}
final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
mControlsRow.getPrimaryActionsAdapter();
Action action = mControlsRow.getActionForKeyCode(primaryActionsAdapter, keyCode);
if (action != null) {
if (action == primaryActionsAdapter.lookup(ACTION_PLAY_PAUSE)
|| action == primaryActionsAdapter.lookup(ACTION_REWIND)
|| action == primaryActionsAdapter.lookup(ACTION_FAST_FORWARD)
|| action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_PREVIOUS)
|| action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_NEXT)) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
dispatchAction(action, event);
}
return true;
}
}
return false;
}
/**
* Called when the given action is invoked, either by click or keyevent.
*/
boolean dispatchAction(Action action, KeyEvent keyEvent) {
boolean handled = false;
if (action == mPlayPauseAction) {
boolean canPlay = keyEvent == null
|| keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|| keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;
boolean canPause = keyEvent == null
|| keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|| keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
// PLAY_PAUSE PLAY PAUSE
// playing paused paused
// paused playing playing
// ff/rw playing playing paused
if (canPause
&& (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
pause();
} else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
play(mPlaybackSpeed);
}
updatePlaybackStatusAfterUserAction();
handled = true;
} else if (action == mSkipNextAction) {
next();
handled = true;
} else if (action == mSkipPreviousAction) {
previous();
handled = true;
} else if (action == mFastForwardAction) {
if (mPlaybackSpeed < getMaxForwardSpeedId()) {
switch (mPlaybackSpeed) {
case PLAYBACK_SPEED_FAST_L0:
case PLAYBACK_SPEED_FAST_L1:
case PLAYBACK_SPEED_FAST_L2:
case PLAYBACK_SPEED_FAST_L3:
mPlaybackSpeed++;
break;
default:
mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
break;
}
play(mPlaybackSpeed);
updatePlaybackStatusAfterUserAction();
}
handled = true;
} else if (action == mRewindAction) {
if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
switch (mPlaybackSpeed) {
case -PLAYBACK_SPEED_FAST_L0:
case -PLAYBACK_SPEED_FAST_L1:
case -PLAYBACK_SPEED_FAST_L2:
case -PLAYBACK_SPEED_FAST_L3:
mPlaybackSpeed--;
break;
default:
mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
break;
}
play(mPlaybackSpeed);
updatePlaybackStatusAfterUserAction();
}
handled = true;
}
return handled;
}
private int getMaxForwardSpeedId() {
return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
}
private int getMaxRewindSpeedId() {
return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
}
private void updateControlsRow() {
updateRowMetadata();
updateControlButtons();
sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
updatePlaybackState();
}
private void updatePlaybackStatusAfterUserAction() {
updatePlaybackState(mPlaybackSpeed);
// Sync playback state after a delay
sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
}
/**
* 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.
*/
public void play(int speed) {
}
@Override
public final void play() {
play(PLAYBACK_SPEED_NORMAL);
}
private void updateRowMetadata() {
if (mControlsRow == null) {
return;
}
if (DEBUG) Log.v(TAG, "updateRowMetadata");
if (!hasValidMedia()) {
mControlsRow.setImageDrawable(null);
mControlsRow.setTotalTime(0);
mControlsRow.setCurrentTime(0);
} else {
mControlsRow.setImageDrawable(getMediaArt());
mControlsRow.setTotalTime(getMediaDuration());
mControlsRow.setCurrentTime(getCurrentPosition());
}
if (getHost() != null) {
getHost().notifyPlaybackRowChanged();
}
}
void updatePlaybackState() {
if (hasValidMedia()) {
mPlaybackSpeed = getCurrentSpeedId();
updatePlaybackState(mPlaybackSpeed);
}
}
void updateControlButtons() {
final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
getControlsRow().getPrimaryActionsAdapter();
final long actions = getSupportedActions();
if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0 && mSkipPreviousAction == null) {
mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getContext());
primaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
} else if ((actions & ACTION_SKIP_TO_PREVIOUS) == 0 && mSkipPreviousAction != null) {
primaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
mSkipPreviousAction = null;
}
if ((actions & ACTION_REWIND) != 0 && mRewindAction == null) {
mRewindAction = new PlaybackControlsRow.RewindAction(getContext(),
mRewindSpeeds.length);
primaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
} else if ((actions & ACTION_REWIND) == 0 && mRewindAction != null) {
primaryActionsAdapter.clear(ACTION_REWIND);
mRewindAction = null;
}
if ((actions & ACTION_PLAY_PAUSE) != 0 && mPlayPauseAction == null) {
mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(getContext());
primaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
} else if ((actions & ACTION_PLAY_PAUSE) == 0 && mPlayPauseAction != null) {
primaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
mPlayPauseAction = null;
}
if ((actions & ACTION_FAST_FORWARD) != 0 && mFastForwardAction == null) {
mFastForwardAction = new PlaybackControlsRow.FastForwardAction(getContext(),
mFastForwardSpeeds.length);
primaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
} else if ((actions & ACTION_FAST_FORWARD) == 0 && mFastForwardAction != null) {
primaryActionsAdapter.clear(ACTION_FAST_FORWARD);
mFastForwardAction = null;
}
if ((actions & ACTION_SKIP_TO_NEXT) != 0 && mSkipNextAction == null) {
mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getContext());
primaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
} else if ((actions & ACTION_SKIP_TO_NEXT) == 0 && mSkipNextAction != null) {
primaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
mSkipNextAction = null;
}
}
private void updatePlaybackState(int playbackSpeed) {
if (mControlsRow == null) {
return;
}
final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
getControlsRow().getPrimaryActionsAdapter();
if (mFastForwardAction != null) {
int index = 0;
if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
index = playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
}
if (mFastForwardAction.getIndex() != index) {
mFastForwardAction.setIndex(index);
notifyItemChanged(primaryActionsAdapter, mFastForwardAction);
}
}
if (mRewindAction != null) {
int index = 0;
if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
}
if (mRewindAction.getIndex() != index) {
mRewindAction.setIndex(index);
notifyItemChanged(primaryActionsAdapter, mRewindAction);
}
}
if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
updateProgress();
enableProgressUpdating(false);
} else {
enableProgressUpdating(true);
}
if (mFadeWhenPlaying && getHost() != null) {
getHost().setControlsOverlayAutoHideEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
}
if (mPlayPauseAction != null) {
int index = playbackSpeed == PLAYBACK_SPEED_PAUSED
? PlaybackControlsRow.PlayPauseAction.INDEX_PLAY
: PlaybackControlsRow.PlayPauseAction.INDEX_PAUSE;
if (mPlayPauseAction.getIndex() != index) {
mPlayPauseAction.setIndex(index);
notifyItemChanged(primaryActionsAdapter, mPlayPauseAction);
}
}
List<PlayerCallback> callbacks = getPlayerCallbacks();
if (callbacks != null) {
for (int i = 0, size = callbacks.size(); i < size; i++) {
callbacks.get(i).onPlayStateChanged(this);
}
}
}
private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
int index = adapter.indexOf(object);
if (index >= 0) {
adapter.notifyArrayItemRangeChanged(index, 1);
}
}
/**
* Returns true if there is a valid media item.
*/
public abstract boolean hasValidMedia();
/**
* Returns true if media is currently playing.
*/
public abstract boolean isMediaPlaying();
@Override
public boolean isPlaying() {
return isMediaPlaying();
}
/**
* Returns the title of the media item.
*/
public abstract CharSequence getMediaTitle();
/**
* Returns the subtitle of the media item.
*/
public abstract CharSequence getMediaSubtitle();
/**
* Returns the duration of the media item in milliseconds.
*/
public abstract int getMediaDuration();
/**
* Returns a bitmap of the art for the media item.
*/
public abstract Drawable getMediaArt();
/**
* Returns a bitmask of actions supported by the media player.
*/
public abstract long getSupportedActions();
/**
* Returns the current playback speed. When playing normally,
* {@link #PLAYBACK_SPEED_NORMAL} should be returned.
*/
public abstract int getCurrentSpeedId();
/**
* Returns the current position of the media item in milliseconds.
*/
public abstract int getCurrentPosition();
/**
* May be overridden to add primary actions to the adapter.
*
* @param primaryActionsAdapter The adapter to add primary {@link Action}s.
*/
protected void onCreatePrimaryActions(SparseArrayObjectAdapter primaryActionsAdapter) {
}
/**
* May be overridden to add secondary actions to the adapter.
*
* @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
*/
protected void onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
}
/**
* Must be called appropriately by a subclass when the playback state has changed.
* It updates the playback state displayed on the media player.
*/
protected void onStateChanged() {
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 (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) {
sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
if (getCurrentSpeedId() != mPlaybackSpeed) {
if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
} else {
if (DEBUG) Log.v(TAG, "Update state matches expectation");
updatePlaybackState();
}
} else {
updatePlaybackState();
}
}
/**
* Must be called appropriately by a subclass when the metadata state has changed.
*/
protected void onMetadataChanged() {
if (DEBUG) Log.v(TAG, "onMetadataChanged");
updateRowMetadata();
}
}