Home | History | Annotate | Download | only in media
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.leanback.media;
     18 
     19 import android.content.Context;
     20 import android.graphics.drawable.Drawable;
     21 import android.os.Handler;
     22 import android.os.Message;
     23 import android.util.Log;
     24 import android.view.KeyEvent;
     25 import android.view.View;
     26 
     27 import androidx.annotation.RestrictTo;
     28 import androidx.leanback.widget.AbstractDetailsDescriptionPresenter;
     29 import androidx.leanback.widget.Action;
     30 import androidx.leanback.widget.ArrayObjectAdapter;
     31 import androidx.leanback.widget.ControlButtonPresenterSelector;
     32 import androidx.leanback.widget.OnActionClickedListener;
     33 import androidx.leanback.widget.PlaybackControlsRow;
     34 import androidx.leanback.widget.PlaybackControlsRowPresenter;
     35 import androidx.leanback.widget.PlaybackRowPresenter;
     36 import androidx.leanback.widget.PresenterSelector;
     37 import androidx.leanback.widget.RowPresenter;
     38 import androidx.leanback.widget.SparseArrayObjectAdapter;
     39 
     40 import java.lang.ref.WeakReference;
     41 import java.util.List;
     42 
     43 /**
     44  * A helper class for managing a {@link PlaybackControlsRow}
     45  * and {@link PlaybackGlueHost} that implements a
     46  * recommended approach to handling standard playback control actions such as play/pause,
     47  * fast forward/rewind at progressive speed levels, and skip to next/previous. This helper class
     48  * is a glue layer in that manages the configuration of and interaction between the
     49  * leanback UI components by defining a functional interface to the media player.
     50  *
     51  * <p>You can instantiate a concrete subclass such as MediaPlayerGlue or you must
     52  * subclass this abstract helper.  To create a subclass you must implement all of the
     53  * abstract methods and the subclass must invoke {@link #onMetadataChanged()} and
     54  * {@link #onStateChanged()} appropriately.
     55  * </p>
     56  *
     57  * <p>To use an instance of the glue layer, first construct an instance.  Constructor parameters
     58  * inform the glue what speed levels are supported for fast forward/rewind.
     59  * </p>
     60  *
     61  * <p>You may override {@link #onCreateControlsRowAndPresenter()} which will create a
     62  * {@link PlaybackControlsRow} and a {@link PlaybackControlsRowPresenter}. You may call
     63  * {@link #setControlsRow(PlaybackControlsRow)} and
     64  * {@link #setPlaybackRowPresenter(PlaybackRowPresenter)} to customize your own row and presenter.
     65  * </p>
     66  *
     67  * <p>The helper sets a {@link SparseArrayObjectAdapter}
     68  * on the controls row as the primary actions adapter, and adds actions to it. You can provide
     69  * additional actions by overriding {@link #onCreatePrimaryActions}. This helper does not
     70  * deal in secondary actions so those you may add separately.
     71  * </p>
     72  *
     73  * <p>Provide a click listener on your fragment and if an action is clicked, call
     74  * {@link #onActionClicked}.
     75  * </p>
     76  *
     77  * <p>This helper implements a key event handler. If you pass a
     78  * {@link PlaybackGlueHost}, it will configure its
     79  * fragment to intercept all key events.  Otherwise, you should set the glue object as key event
     80  * handler to the ViewHolder when bound by your row presenter; see
     81  * {@link RowPresenter.ViewHolder#setOnKeyListener(android.view.View.OnKeyListener)}.
     82  * </p>
     83  *
     84  * <p>To update the controls row progress during playback, override {@link #enableProgressUpdating}
     85  * to manage the lifecycle of a periodic callback to {@link #updateProgress()}.
     86  * {@link #getUpdatePeriod()} provides a recommended update period.
     87  * </p>
     88  *
     89  */
     90 public abstract class PlaybackControlGlue extends PlaybackGlue
     91         implements OnActionClickedListener, View.OnKeyListener {
     92     /**
     93      * The adapter key for the first custom control on the left side
     94      * of the predefined primary controls.
     95      */
     96     public static final int ACTION_CUSTOM_LEFT_FIRST = 0x1;
     97 
     98     /**
     99      * The adapter key for the skip to previous control.
    100      */
    101     public static final int ACTION_SKIP_TO_PREVIOUS = 0x10;
    102 
    103     /**
    104      * The adapter key for the rewind control.
    105      */
    106     public static final int ACTION_REWIND = 0x20;
    107 
    108     /**
    109      * The adapter key for the play/pause control.
    110      */
    111     public static final int ACTION_PLAY_PAUSE = 0x40;
    112 
    113     /**
    114      * The adapter key for the fast forward control.
    115      */
    116     public static final int ACTION_FAST_FORWARD = 0x80;
    117 
    118     /**
    119      * The adapter key for the skip to next control.
    120      */
    121     public static final int ACTION_SKIP_TO_NEXT = 0x100;
    122 
    123     /**
    124      * The adapter key for the first custom control on the right side
    125      * of the predefined primary controls.
    126      */
    127     public static final int ACTION_CUSTOM_RIGHT_FIRST = 0x1000;
    128 
    129     /**
    130      * Invalid playback speed.
    131      */
    132     public static final int PLAYBACK_SPEED_INVALID = -1;
    133 
    134     /**
    135      * Speed representing playback state that is paused.
    136      */
    137     public static final int PLAYBACK_SPEED_PAUSED = 0;
    138 
    139     /**
    140      * Speed representing playback state that is playing normally.
    141      */
    142     public static final int PLAYBACK_SPEED_NORMAL = 1;
    143 
    144     /**
    145      * The initial (level 0) fast forward playback speed.
    146      * The negative of this value is for rewind at the same speed.
    147      */
    148     public static final int PLAYBACK_SPEED_FAST_L0 = 10;
    149 
    150     /**
    151      * The level 1 fast forward playback speed.
    152      * The negative of this value is for rewind at the same speed.
    153      */
    154     public static final int PLAYBACK_SPEED_FAST_L1 = 11;
    155 
    156     /**
    157      * The level 2 fast forward playback speed.
    158      * The negative of this value is for rewind at the same speed.
    159      */
    160     public static final int PLAYBACK_SPEED_FAST_L2 = 12;
    161 
    162     /**
    163      * The level 3 fast forward playback speed.
    164      * The negative of this value is for rewind at the same speed.
    165      */
    166     public static final int PLAYBACK_SPEED_FAST_L3 = 13;
    167 
    168     /**
    169      * The level 4 fast forward playback speed.
    170      * The negative of this value is for rewind at the same speed.
    171      */
    172     public static final int PLAYBACK_SPEED_FAST_L4 = 14;
    173 
    174     static final String TAG = "PlaybackControlGlue";
    175     static final boolean DEBUG = false;
    176 
    177     static final int MSG_UPDATE_PLAYBACK_STATE = 100;
    178     private static final int UPDATE_PLAYBACK_STATE_DELAY_MS = 2000;
    179     private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4
    180             - PLAYBACK_SPEED_FAST_L0 + 1;
    181 
    182     private final int[] mFastForwardSpeeds;
    183     private final int[] mRewindSpeeds;
    184     private PlaybackControlsRow mControlsRow;
    185     private PlaybackRowPresenter mControlsRowPresenter;
    186     private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
    187     private PlaybackControlsRow.SkipNextAction mSkipNextAction;
    188     private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
    189     private PlaybackControlsRow.FastForwardAction mFastForwardAction;
    190     private PlaybackControlsRow.RewindAction mRewindAction;
    191     private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
    192     private boolean mFadeWhenPlaying = true;
    193 
    194     static class UpdatePlaybackStateHandler extends Handler {
    195         @Override
    196         public void handleMessage(Message msg) {
    197             if (msg.what == MSG_UPDATE_PLAYBACK_STATE) {
    198                 PlaybackControlGlue glue = ((WeakReference<PlaybackControlGlue>) msg.obj).get();
    199                 if (glue != null) {
    200                     glue.updatePlaybackState();
    201                 }
    202             }
    203         }
    204     }
    205 
    206     static final Handler sHandler = new UpdatePlaybackStateHandler();
    207 
    208     final WeakReference<PlaybackControlGlue> mGlueWeakReference =  new WeakReference(this);
    209 
    210     /**
    211      * Constructor for the glue.
    212      *
    213      * @param context
    214      * @param seekSpeeds Array of seek speeds for fast forward and rewind.
    215      */
    216     public PlaybackControlGlue(Context context, int[] seekSpeeds) {
    217         this(context, seekSpeeds, seekSpeeds);
    218     }
    219 
    220     /**
    221      * Constructor for the glue.
    222      *
    223      * @param context
    224      * @param fastForwardSpeeds Array of seek speeds for fast forward.
    225      * @param rewindSpeeds Array of seek speeds for rewind.
    226      */
    227     public PlaybackControlGlue(Context context,
    228                                int[] fastForwardSpeeds,
    229                                int[] rewindSpeeds) {
    230         super(context);
    231         if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
    232             throw new IllegalStateException("invalid fastForwardSpeeds array size");
    233         }
    234         mFastForwardSpeeds = fastForwardSpeeds;
    235         if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
    236             throw new IllegalStateException("invalid rewindSpeeds array size");
    237         }
    238         mRewindSpeeds = rewindSpeeds;
    239     }
    240 
    241     @Override
    242     protected void onAttachedToHost(PlaybackGlueHost host) {
    243         super.onAttachedToHost(host);
    244         host.setOnKeyInterceptListener(this);
    245         host.setOnActionClickedListener(this);
    246         if (getControlsRow() == null || getPlaybackRowPresenter() == null) {
    247             onCreateControlsRowAndPresenter();
    248         }
    249         host.setPlaybackRowPresenter(getPlaybackRowPresenter());
    250         host.setPlaybackRow(getControlsRow());
    251     }
    252 
    253     @Override
    254     protected void onHostStart() {
    255         enableProgressUpdating(true);
    256     }
    257 
    258     @Override
    259     protected void onHostStop() {
    260         enableProgressUpdating(false);
    261     }
    262 
    263     @Override
    264     protected void onDetachedFromHost() {
    265         enableProgressUpdating(false);
    266         super.onDetachedFromHost();
    267     }
    268 
    269     /**
    270      * Instantiating a {@link PlaybackControlsRow} and corresponding
    271      * {@link PlaybackControlsRowPresenter}. Subclass may override.
    272      */
    273     protected void onCreateControlsRowAndPresenter() {
    274         if (getControlsRow() == null) {
    275             PlaybackControlsRow controlsRow = new PlaybackControlsRow(this);
    276             setControlsRow(controlsRow);
    277         }
    278         if (getPlaybackRowPresenter() == null) {
    279             final AbstractDetailsDescriptionPresenter detailsPresenter =
    280                     new AbstractDetailsDescriptionPresenter() {
    281                         @Override
    282                         protected void onBindDescription(ViewHolder
    283                                 viewHolder, Object object) {
    284                             PlaybackControlGlue glue = (PlaybackControlGlue) object;
    285                             if (glue.hasValidMedia()) {
    286                                 viewHolder.getTitle().setText(glue.getMediaTitle());
    287                                 viewHolder.getSubtitle().setText(glue.getMediaSubtitle());
    288                             } else {
    289                                 viewHolder.getTitle().setText("");
    290                                 viewHolder.getSubtitle().setText("");
    291                             }
    292                         }
    293                     };
    294 
    295             setPlaybackRowPresenter(new PlaybackControlsRowPresenter(detailsPresenter) {
    296                 @Override
    297                 protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
    298                     super.onBindRowViewHolder(vh, item);
    299                     vh.setOnKeyListener(PlaybackControlGlue.this);
    300                 }
    301                 @Override
    302                 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
    303                     super.onUnbindRowViewHolder(vh);
    304                     vh.setOnKeyListener(null);
    305                 }
    306             });
    307         }
    308     }
    309 
    310     /**
    311      * Returns the fast forward speeds.
    312      */
    313     public int[] getFastForwardSpeeds() {
    314         return mFastForwardSpeeds;
    315     }
    316 
    317     /**
    318      * Returns the rewind speeds.
    319      */
    320     public int[] getRewindSpeeds() {
    321         return mRewindSpeeds;
    322     }
    323 
    324     /**
    325      * Sets the controls to fade after a timeout when media is playing.
    326      */
    327     public void setFadingEnabled(boolean enable) {
    328         mFadeWhenPlaying = enable;
    329         if (!mFadeWhenPlaying && getHost() != null) {
    330             getHost().setControlsOverlayAutoHideEnabled(false);
    331         }
    332     }
    333 
    334     /**
    335      * Returns true if controls are set to fade when media is playing.
    336      */
    337     public boolean isFadingEnabled() {
    338         return mFadeWhenPlaying;
    339     }
    340 
    341     /**
    342      * Sets the controls row to be managed by the glue layer.
    343      * The primary actions and playback state related aspects of the row
    344      * are updated by the glue.
    345      */
    346     public void setControlsRow(PlaybackControlsRow controlsRow) {
    347         mControlsRow = controlsRow;
    348         mControlsRow.setPrimaryActionsAdapter(
    349                 createPrimaryActionsAdapter(new ControlButtonPresenterSelector()));
    350         // Add secondary actions
    351         ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
    352                 new ControlButtonPresenterSelector());
    353         onCreateSecondaryActions(secondaryActions);
    354         getControlsRow().setSecondaryActionsAdapter(secondaryActions);
    355         updateControlsRow();
    356     }
    357 
    358     /**
    359      * @hide
    360      */
    361     @RestrictTo(RestrictTo.Scope.LIBRARY)
    362     protected SparseArrayObjectAdapter createPrimaryActionsAdapter(
    363             PresenterSelector presenterSelector) {
    364         SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter(presenterSelector);
    365         onCreatePrimaryActions(adapter);
    366         return adapter;
    367     }
    368 
    369     /**
    370      * Sets the controls row Presenter to be managed by the glue layer.
    371      * @deprecated PlaybackControlGlue supports any PlaybackRowPresenter, use
    372      * {@link #setPlaybackRowPresenter(PlaybackRowPresenter)}.
    373      */
    374     @Deprecated
    375     public void setControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
    376         mControlsRowPresenter = presenter;
    377     }
    378 
    379     /**
    380      * Returns the playback controls row managed by the glue layer.
    381      */
    382     public PlaybackControlsRow getControlsRow() {
    383         return mControlsRow;
    384     }
    385 
    386     /**
    387      * Returns the playback controls row Presenter managed by the glue layer.
    388      * @deprecated PlaybackControlGlue supports any PlaybackRowPresenter, use
    389      * {@link #getPlaybackRowPresenter()}.
    390      */
    391     @Deprecated
    392     public PlaybackControlsRowPresenter getControlsRowPresenter() {
    393         return mControlsRowPresenter instanceof PlaybackControlsRowPresenter
    394                 ? (PlaybackControlsRowPresenter) mControlsRowPresenter : null;
    395     }
    396 
    397     /**
    398      * Sets the controls row Presenter to be passed to {@link PlaybackGlueHost} in
    399      * {@link #onAttachedToHost(PlaybackGlueHost)}.
    400      */
    401     public void setPlaybackRowPresenter(PlaybackRowPresenter presenter) {
    402         mControlsRowPresenter = presenter;
    403     }
    404 
    405     /**
    406      * Returns the playback row Presenter to be passed to {@link PlaybackGlueHost} in
    407      * {@link #onAttachedToHost(PlaybackGlueHost)}.
    408      */
    409     public PlaybackRowPresenter getPlaybackRowPresenter() {
    410         return mControlsRowPresenter;
    411     }
    412 
    413     /**
    414      * Override this to start/stop a runnable to call {@link #updateProgress} at
    415      * an interval such as {@link #getUpdatePeriod}.
    416      */
    417     public void enableProgressUpdating(boolean enable) {
    418     }
    419 
    420     /**
    421      * Returns the time period in milliseconds that should be used
    422      * to update the progress.  See {@link #updateProgress()}.
    423      */
    424     public int getUpdatePeriod() {
    425         // TODO: calculate a better update period based on total duration and screen size
    426         return 500;
    427     }
    428 
    429     /**
    430      * Updates the progress bar based on the current media playback position.
    431      */
    432     public void updateProgress() {
    433         int position = getCurrentPosition();
    434         if (DEBUG) Log.v(TAG, "updateProgress " + position);
    435         if (mControlsRow != null) {
    436             mControlsRow.setCurrentTime(position);
    437         }
    438     }
    439 
    440     /**
    441      * Handles action clicks.  A subclass may override this add support for additional actions.
    442      */
    443     @Override
    444     public void onActionClicked(Action action) {
    445         dispatchAction(action, null);
    446     }
    447 
    448     /**
    449      * Handles key events and returns true if handled.  A subclass may override this to provide
    450      * additional support.
    451      */
    452     @Override
    453     public boolean onKey(View v, int keyCode, KeyEvent event) {
    454         switch (keyCode) {
    455             case KeyEvent.KEYCODE_DPAD_UP:
    456             case KeyEvent.KEYCODE_DPAD_DOWN:
    457             case KeyEvent.KEYCODE_DPAD_RIGHT:
    458             case KeyEvent.KEYCODE_DPAD_LEFT:
    459             case KeyEvent.KEYCODE_BACK:
    460             case KeyEvent.KEYCODE_ESCAPE:
    461                 boolean abortSeek = mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0
    462                         || mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0;
    463                 if (abortSeek) {
    464                     mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
    465                     play(mPlaybackSpeed);
    466                     updatePlaybackStatusAfterUserAction();
    467                     return keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE;
    468                 }
    469                 return false;
    470         }
    471         final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
    472                 mControlsRow.getPrimaryActionsAdapter();
    473         Action action = mControlsRow.getActionForKeyCode(primaryActionsAdapter, keyCode);
    474 
    475         if (action != null) {
    476             if (action == primaryActionsAdapter.lookup(ACTION_PLAY_PAUSE)
    477                     || action == primaryActionsAdapter.lookup(ACTION_REWIND)
    478                     || action == primaryActionsAdapter.lookup(ACTION_FAST_FORWARD)
    479                     || action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_PREVIOUS)
    480                     || action == primaryActionsAdapter.lookup(ACTION_SKIP_TO_NEXT)) {
    481                 if (event.getAction() == KeyEvent.ACTION_DOWN) {
    482                     dispatchAction(action, event);
    483                 }
    484                 return true;
    485             }
    486         }
    487         return false;
    488     }
    489 
    490     /**
    491      * Called when the given action is invoked, either by click or keyevent.
    492      */
    493     boolean dispatchAction(Action action, KeyEvent keyEvent) {
    494         boolean handled = false;
    495         if (action == mPlayPauseAction) {
    496             boolean canPlay = keyEvent == null
    497                     || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
    498                     || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;
    499             boolean canPause = keyEvent == null
    500                     || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
    501                     || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
    502             //            PLAY_PAUSE    PLAY      PAUSE
    503             // playing    paused                  paused
    504             // paused     playing       playing
    505             // ff/rw      playing       playing   paused
    506             if (canPause
    507                     && (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
    508                         mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
    509                 mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
    510                 pause();
    511             } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
    512                 mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
    513                 play(mPlaybackSpeed);
    514             }
    515             updatePlaybackStatusAfterUserAction();
    516             handled = true;
    517         } else if (action == mSkipNextAction) {
    518             next();
    519             handled = true;
    520         } else if (action == mSkipPreviousAction) {
    521             previous();
    522             handled = true;
    523         } else if (action == mFastForwardAction) {
    524             if (mPlaybackSpeed < getMaxForwardSpeedId()) {
    525                 switch (mPlaybackSpeed) {
    526                     case PLAYBACK_SPEED_FAST_L0:
    527                     case PLAYBACK_SPEED_FAST_L1:
    528                     case PLAYBACK_SPEED_FAST_L2:
    529                     case PLAYBACK_SPEED_FAST_L3:
    530                         mPlaybackSpeed++;
    531                         break;
    532                     default:
    533                         mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
    534                         break;
    535                 }
    536                 play(mPlaybackSpeed);
    537                 updatePlaybackStatusAfterUserAction();
    538             }
    539             handled = true;
    540         } else if (action == mRewindAction) {
    541             if (mPlaybackSpeed > -getMaxRewindSpeedId()) {
    542                 switch (mPlaybackSpeed) {
    543                     case -PLAYBACK_SPEED_FAST_L0:
    544                     case -PLAYBACK_SPEED_FAST_L1:
    545                     case -PLAYBACK_SPEED_FAST_L2:
    546                     case -PLAYBACK_SPEED_FAST_L3:
    547                         mPlaybackSpeed--;
    548                         break;
    549                     default:
    550                         mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
    551                         break;
    552                 }
    553                 play(mPlaybackSpeed);
    554                 updatePlaybackStatusAfterUserAction();
    555             }
    556             handled = true;
    557         }
    558         return handled;
    559     }
    560 
    561     private int getMaxForwardSpeedId() {
    562         return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
    563     }
    564 
    565     private int getMaxRewindSpeedId() {
    566         return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
    567     }
    568 
    569     private void updateControlsRow() {
    570         updateRowMetadata();
    571         updateControlButtons();
    572         sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
    573         updatePlaybackState();
    574     }
    575 
    576     private void updatePlaybackStatusAfterUserAction() {
    577         updatePlaybackState(mPlaybackSpeed);
    578         // Sync playback state after a delay
    579         sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
    580         sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
    581                 mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
    582     }
    583 
    584     /**
    585      * Start playback at the given speed.
    586      *
    587      * @param speed The desired playback speed.  For normal playback this will be
    588      *              {@link #PLAYBACK_SPEED_NORMAL}; higher positive values for fast forward,
    589      *              and negative values for rewind.
    590      */
    591     public void play(int speed) {
    592     }
    593 
    594     @Override
    595     public final void play() {
    596         play(PLAYBACK_SPEED_NORMAL);
    597     }
    598 
    599     private void updateRowMetadata() {
    600         if (mControlsRow == null) {
    601             return;
    602         }
    603 
    604         if (DEBUG) Log.v(TAG, "updateRowMetadata");
    605 
    606         if (!hasValidMedia()) {
    607             mControlsRow.setImageDrawable(null);
    608             mControlsRow.setTotalTime(0);
    609             mControlsRow.setCurrentTime(0);
    610         } else {
    611             mControlsRow.setImageDrawable(getMediaArt());
    612             mControlsRow.setTotalTime(getMediaDuration());
    613             mControlsRow.setCurrentTime(getCurrentPosition());
    614         }
    615 
    616         if (getHost() != null) {
    617             getHost().notifyPlaybackRowChanged();
    618         }
    619     }
    620 
    621     void updatePlaybackState() {
    622         if (hasValidMedia()) {
    623             mPlaybackSpeed = getCurrentSpeedId();
    624             updatePlaybackState(mPlaybackSpeed);
    625         }
    626     }
    627 
    628     void updateControlButtons() {
    629         final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
    630                 getControlsRow().getPrimaryActionsAdapter();
    631         final long actions = getSupportedActions();
    632         if ((actions & ACTION_SKIP_TO_PREVIOUS) != 0 && mSkipPreviousAction == null) {
    633             mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getContext());
    634             primaryActionsAdapter.set(ACTION_SKIP_TO_PREVIOUS, mSkipPreviousAction);
    635         } else if ((actions & ACTION_SKIP_TO_PREVIOUS) == 0 && mSkipPreviousAction != null) {
    636             primaryActionsAdapter.clear(ACTION_SKIP_TO_PREVIOUS);
    637             mSkipPreviousAction = null;
    638         }
    639         if ((actions & ACTION_REWIND) != 0 && mRewindAction == null) {
    640             mRewindAction = new PlaybackControlsRow.RewindAction(getContext(),
    641                     mRewindSpeeds.length);
    642             primaryActionsAdapter.set(ACTION_REWIND, mRewindAction);
    643         } else if ((actions & ACTION_REWIND) == 0 && mRewindAction != null) {
    644             primaryActionsAdapter.clear(ACTION_REWIND);
    645             mRewindAction = null;
    646         }
    647         if ((actions & ACTION_PLAY_PAUSE) != 0 && mPlayPauseAction == null) {
    648             mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(getContext());
    649             primaryActionsAdapter.set(ACTION_PLAY_PAUSE, mPlayPauseAction);
    650         } else if ((actions & ACTION_PLAY_PAUSE) == 0 && mPlayPauseAction != null) {
    651             primaryActionsAdapter.clear(ACTION_PLAY_PAUSE);
    652             mPlayPauseAction = null;
    653         }
    654         if ((actions & ACTION_FAST_FORWARD) != 0 && mFastForwardAction == null) {
    655             mFastForwardAction = new PlaybackControlsRow.FastForwardAction(getContext(),
    656                     mFastForwardSpeeds.length);
    657             primaryActionsAdapter.set(ACTION_FAST_FORWARD, mFastForwardAction);
    658         } else if ((actions & ACTION_FAST_FORWARD) == 0 && mFastForwardAction != null) {
    659             primaryActionsAdapter.clear(ACTION_FAST_FORWARD);
    660             mFastForwardAction = null;
    661         }
    662         if ((actions & ACTION_SKIP_TO_NEXT) != 0 && mSkipNextAction == null) {
    663             mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getContext());
    664             primaryActionsAdapter.set(ACTION_SKIP_TO_NEXT, mSkipNextAction);
    665         } else if ((actions & ACTION_SKIP_TO_NEXT) == 0 && mSkipNextAction != null) {
    666             primaryActionsAdapter.clear(ACTION_SKIP_TO_NEXT);
    667             mSkipNextAction = null;
    668         }
    669     }
    670 
    671     private void updatePlaybackState(int playbackSpeed) {
    672         if (mControlsRow == null) {
    673             return;
    674         }
    675 
    676         final SparseArrayObjectAdapter primaryActionsAdapter = (SparseArrayObjectAdapter)
    677                 getControlsRow().getPrimaryActionsAdapter();
    678 
    679         if (mFastForwardAction != null) {
    680             int index = 0;
    681             if (playbackSpeed >= PLAYBACK_SPEED_FAST_L0) {
    682                 index = playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
    683             }
    684             if (mFastForwardAction.getIndex() != index) {
    685                 mFastForwardAction.setIndex(index);
    686                 notifyItemChanged(primaryActionsAdapter, mFastForwardAction);
    687             }
    688         }
    689         if (mRewindAction != null) {
    690             int index = 0;
    691             if (playbackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
    692                 index = -playbackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
    693             }
    694             if (mRewindAction.getIndex() != index) {
    695                 mRewindAction.setIndex(index);
    696                 notifyItemChanged(primaryActionsAdapter, mRewindAction);
    697             }
    698         }
    699 
    700         if (playbackSpeed == PLAYBACK_SPEED_PAUSED) {
    701             updateProgress();
    702             enableProgressUpdating(false);
    703         } else {
    704             enableProgressUpdating(true);
    705         }
    706 
    707         if (mFadeWhenPlaying && getHost() != null) {
    708             getHost().setControlsOverlayAutoHideEnabled(playbackSpeed == PLAYBACK_SPEED_NORMAL);
    709         }
    710 
    711         if (mPlayPauseAction != null) {
    712             int index = playbackSpeed == PLAYBACK_SPEED_PAUSED
    713                     ? PlaybackControlsRow.PlayPauseAction.INDEX_PLAY
    714                     : PlaybackControlsRow.PlayPauseAction.INDEX_PAUSE;
    715             if (mPlayPauseAction.getIndex() != index) {
    716                 mPlayPauseAction.setIndex(index);
    717                 notifyItemChanged(primaryActionsAdapter, mPlayPauseAction);
    718             }
    719         }
    720         List<PlayerCallback> callbacks = getPlayerCallbacks();
    721         if (callbacks != null) {
    722             for (int i = 0, size = callbacks.size(); i < size; i++) {
    723                 callbacks.get(i).onPlayStateChanged(this);
    724             }
    725         }
    726     }
    727 
    728     private static void notifyItemChanged(SparseArrayObjectAdapter adapter, Object object) {
    729         int index = adapter.indexOf(object);
    730         if (index >= 0) {
    731             adapter.notifyArrayItemRangeChanged(index, 1);
    732         }
    733     }
    734 
    735     private static String getSpeedString(int speed) {
    736         switch (speed) {
    737             case PLAYBACK_SPEED_INVALID:
    738                 return "PLAYBACK_SPEED_INVALID";
    739             case PLAYBACK_SPEED_PAUSED:
    740                 return "PLAYBACK_SPEED_PAUSED";
    741             case PLAYBACK_SPEED_NORMAL:
    742                 return "PLAYBACK_SPEED_NORMAL";
    743             case PLAYBACK_SPEED_FAST_L0:
    744                 return "PLAYBACK_SPEED_FAST_L0";
    745             case PLAYBACK_SPEED_FAST_L1:
    746                 return "PLAYBACK_SPEED_FAST_L1";
    747             case PLAYBACK_SPEED_FAST_L2:
    748                 return "PLAYBACK_SPEED_FAST_L2";
    749             case PLAYBACK_SPEED_FAST_L3:
    750                 return "PLAYBACK_SPEED_FAST_L3";
    751             case PLAYBACK_SPEED_FAST_L4:
    752                 return "PLAYBACK_SPEED_FAST_L4";
    753             case -PLAYBACK_SPEED_FAST_L0:
    754                 return "-PLAYBACK_SPEED_FAST_L0";
    755             case -PLAYBACK_SPEED_FAST_L1:
    756                 return "-PLAYBACK_SPEED_FAST_L1";
    757             case -PLAYBACK_SPEED_FAST_L2:
    758                 return "-PLAYBACK_SPEED_FAST_L2";
    759             case -PLAYBACK_SPEED_FAST_L3:
    760                 return "-PLAYBACK_SPEED_FAST_L3";
    761             case -PLAYBACK_SPEED_FAST_L4:
    762                 return "-PLAYBACK_SPEED_FAST_L4";
    763         }
    764         return null;
    765     }
    766 
    767     /**
    768      * Returns true if there is a valid media item.
    769      */
    770     public abstract boolean hasValidMedia();
    771 
    772     /**
    773      * Returns true if media is currently playing.
    774      */
    775     public abstract boolean isMediaPlaying();
    776 
    777     @Override
    778     public boolean isPlaying() {
    779         return isMediaPlaying();
    780     }
    781 
    782     /**
    783      * Returns the title of the media item.
    784      */
    785     public abstract CharSequence getMediaTitle();
    786 
    787     /**
    788      * Returns the subtitle of the media item.
    789      */
    790     public abstract CharSequence getMediaSubtitle();
    791 
    792     /**
    793      * Returns the duration of the media item in milliseconds.
    794      */
    795     public abstract int getMediaDuration();
    796 
    797     /**
    798      * Returns a bitmap of the art for the media item.
    799      */
    800     public abstract Drawable getMediaArt();
    801 
    802     /**
    803      * Returns a bitmask of actions supported by the media player.
    804      */
    805     public abstract long getSupportedActions();
    806 
    807     /**
    808      * Returns the current playback speed.  When playing normally,
    809      * {@link #PLAYBACK_SPEED_NORMAL} should be returned.
    810      */
    811     public abstract int getCurrentSpeedId();
    812 
    813     /**
    814      * Returns the current position of the media item in milliseconds.
    815      */
    816     public abstract int getCurrentPosition();
    817 
    818     /**
    819      * May be overridden to add primary actions to the adapter.
    820      *
    821      * @param primaryActionsAdapter The adapter to add primary {@link Action}s.
    822      */
    823     protected void onCreatePrimaryActions(SparseArrayObjectAdapter primaryActionsAdapter) {
    824     }
    825 
    826     /**
    827      * May be overridden to add secondary actions to the adapter.
    828      *
    829      * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
    830      */
    831     protected void onCreateSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
    832     }
    833 
    834     /**
    835      * Must be called appropriately by a subclass when the playback state has changed.
    836      * It updates the playback state displayed on the media player.
    837      */
    838     protected void onStateChanged() {
    839         if (DEBUG) Log.v(TAG, "onStateChanged");
    840         // If a pending control button update is present, delay
    841         // the update until the state settles.
    842         if (!hasValidMedia()) {
    843             return;
    844         }
    845         if (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) {
    846             sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference);
    847             if (getCurrentSpeedId() != mPlaybackSpeed) {
    848                 if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update");
    849                 sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE,
    850                         mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS);
    851             } else {
    852                 if (DEBUG) Log.v(TAG, "Update state matches expectation");
    853                 updatePlaybackState();
    854             }
    855         } else {
    856             updatePlaybackState();
    857         }
    858     }
    859 
    860     /**
    861      * Must be called appropriately by a subclass when the metadata state has changed.
    862      */
    863     protected void onMetadataChanged() {
    864         if (DEBUG) Log.v(TAG, "onMetadataChanged");
    865         updateRowMetadata();
    866     }
    867 }
    868