1/*
2 * Copyright (C) 2017 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
17package android.support.v17.leanback.media;
18
19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.content.Context;
22import android.support.annotation.IntDef;
23import android.support.annotation.NonNull;
24import android.support.annotation.RestrictTo;
25import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter;
26import android.support.v17.leanback.widget.Action;
27import android.support.v17.leanback.widget.ArrayObjectAdapter;
28import android.support.v17.leanback.widget.ObjectAdapter;
29import android.support.v17.leanback.widget.PlaybackControlsRow;
30import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
31import android.support.v17.leanback.widget.PlaybackRowPresenter;
32import android.support.v17.leanback.widget.RowPresenter;
33import android.util.Log;
34import android.view.KeyEvent;
35import android.view.View;
36
37import java.lang.annotation.Retention;
38import java.lang.annotation.RetentionPolicy;
39
40/**
41 * A helper class for managing a {@link PlaybackControlsRow} being displayed in
42 * {@link PlaybackGlueHost}. It supports standard playback control actions play/pause and
43 * skip next/previous. This helper class is a glue layer that manages interaction between the
44 * leanback UI components {@link PlaybackControlsRow} {@link PlaybackControlsRowPresenter}
45 * and a functional {@link PlayerAdapter} which represents the underlying
46 * media player.
47 *
48 * <p>Apps must pass a {@link PlayerAdapter} in the constructor for a specific
49 * implementation e.g. a {@link MediaPlayerAdapter}.
50 * </p>
51 *
52 * <p>The glue has two action bars: primary action bars and secondary action bars. Apps
53 * can provide additional actions by overriding {@link #onCreatePrimaryActions} and / or
54 * {@link #onCreateSecondaryActions} and respond to actions by overriding
55 * {@link #onActionClicked(Action)}.
56 * </p>
57 *
58 * <p>The subclass is responsible for implementing the "repeat mode" in
59 * {@link #onPlayCompleted()}.
60 * </p>
61 *
62 * Sample Code:
63 * <pre><code>
64 * public class MyVideoFragment extends VideoFragment {
65 *     &#64;Override
66 *     public void onCreate(Bundle savedInstanceState) {
67 *         super.onCreate(savedInstanceState);
68 *         PlaybackBannerControlGlue<MediaPlayerAdapter> playerGlue =
69 *                 new PlaybackBannerControlGlue(getActivity(),
70 *                         new MediaPlayerAdapter(getActivity()));
71 *         playerGlue.setHost(new VideoFragmentGlueHost(this));
72 *         playerGlue.setSubtitle("Leanback artist");
73 *         playerGlue.setTitle("Leanback team at work");
74 *         String uriPath = "android.resource://com.example.android.leanback/raw/video";
75 *         playerGlue.getPlayerAdapter().setDataSource(Uri.parse(uriPath));
76 *         playerGlue.playWhenPrepared();
77 *     }
78 * }
79 * </code></pre>
80 * @param <T> Type of {@link PlayerAdapter} passed in constructor.
81 */
82public class PlaybackBannerControlGlue<T extends PlayerAdapter>
83        extends PlaybackBaseControlGlue<T> {
84
85    /** @hide */
86    @IntDef(
87            flag = true,
88            value = {
89            ACTION_CUSTOM_LEFT_FIRST,
90            ACTION_SKIP_TO_PREVIOUS,
91            ACTION_REWIND,
92            ACTION_PLAY_PAUSE,
93            ACTION_FAST_FORWARD,
94            ACTION_SKIP_TO_NEXT,
95            ACTION_CUSTOM_RIGHT_FIRST
96    })
97    @RestrictTo(LIBRARY_GROUP)
98    @Retention(RetentionPolicy.SOURCE)
99    public @interface ACTION_ {}
100
101    /**
102     * The adapter key for the first custom control on the left side
103     * of the predefined primary controls.
104     */
105    public static final int ACTION_CUSTOM_LEFT_FIRST =
106            PlaybackBaseControlGlue.ACTION_CUSTOM_LEFT_FIRST;
107
108    /**
109     * The adapter key for the skip to previous control.
110     */
111    public static final int ACTION_SKIP_TO_PREVIOUS =
112            PlaybackBaseControlGlue.ACTION_SKIP_TO_PREVIOUS;
113
114    /**
115     * The adapter key for the rewind control.
116     */
117    public static final int ACTION_REWIND = PlaybackBaseControlGlue.ACTION_REWIND;
118
119    /**
120     * The adapter key for the play/pause control.
121     */
122    public static final int ACTION_PLAY_PAUSE = PlaybackBaseControlGlue.ACTION_PLAY_PAUSE;
123
124    /**
125     * The adapter key for the fast forward control.
126     */
127    public static final int ACTION_FAST_FORWARD = PlaybackBaseControlGlue.ACTION_FAST_FORWARD;
128
129    /**
130     * The adapter key for the skip to next control.
131     */
132    public static final int ACTION_SKIP_TO_NEXT = PlaybackBaseControlGlue.ACTION_SKIP_TO_NEXT;
133
134    /**
135     * The adapter key for the first custom control on the right side
136     * of the predefined primary controls.
137     */
138    public static final int ACTION_CUSTOM_RIGHT_FIRST =
139            PlaybackBaseControlGlue.ACTION_CUSTOM_RIGHT_FIRST;
140
141
142    /** @hide */
143    @IntDef({
144                    PLAYBACK_SPEED_INVALID,
145                    PLAYBACK_SPEED_PAUSED,
146                    PLAYBACK_SPEED_NORMAL,
147                    PLAYBACK_SPEED_FAST_L0,
148                    PLAYBACK_SPEED_FAST_L1,
149                    PLAYBACK_SPEED_FAST_L2,
150                    PLAYBACK_SPEED_FAST_L3,
151                    PLAYBACK_SPEED_FAST_L4
152    })
153    @RestrictTo(LIBRARY_GROUP)
154    @Retention(RetentionPolicy.SOURCE)
155    private @interface SPEED {}
156
157    /**
158     * Invalid playback speed.
159     */
160    public static final int PLAYBACK_SPEED_INVALID = -1;
161
162    /**
163     * Speed representing playback state that is paused.
164     */
165    public static final int PLAYBACK_SPEED_PAUSED = 0;
166
167    /**
168     * Speed representing playback state that is playing normally.
169     */
170    public static final int PLAYBACK_SPEED_NORMAL = 1;
171
172    /**
173     * The initial (level 0) fast forward playback speed.
174     * The negative of this value is for rewind at the same speed.
175     */
176    public static final int PLAYBACK_SPEED_FAST_L0 = 10;
177
178    /**
179     * The level 1 fast forward playback speed.
180     * The negative of this value is for rewind at the same speed.
181     */
182    public static final int PLAYBACK_SPEED_FAST_L1 = 11;
183
184    /**
185     * The level 2 fast forward playback speed.
186     * The negative of this value is for rewind at the same speed.
187     */
188    public static final int PLAYBACK_SPEED_FAST_L2 = 12;
189
190    /**
191     * The level 3 fast forward playback speed.
192     * The negative of this value is for rewind at the same speed.
193     */
194    public static final int PLAYBACK_SPEED_FAST_L3 = 13;
195
196    /**
197     * The level 4 fast forward playback speed.
198     * The negative of this value is for rewind at the same speed.
199     */
200    public static final int PLAYBACK_SPEED_FAST_L4 = 14;
201
202    private static final String TAG = PlaybackBannerControlGlue.class.getSimpleName();
203    private static final int NUMBER_OF_SEEK_SPEEDS = PLAYBACK_SPEED_FAST_L4
204            - PLAYBACK_SPEED_FAST_L0 + 1;
205
206    private final int[] mFastForwardSpeeds;
207    private final int[] mRewindSpeeds;
208    private PlaybackControlsRow.PlayPauseAction mPlayPauseAction;
209    private PlaybackControlsRow.SkipNextAction mSkipNextAction;
210    private PlaybackControlsRow.SkipPreviousAction mSkipPreviousAction;
211    private PlaybackControlsRow.FastForwardAction mFastForwardAction;
212    private PlaybackControlsRow.RewindAction mRewindAction;
213
214    @SPEED
215    private int mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
216    private long mStartTime;
217    private long mStartPosition = 0;
218
219    // Flag for is customized FastForward/ Rewind Action supported.
220    // If customized actions are not supported, the adapter can still use default behavior through
221    // setting ACTION_REWIND and ACTION_FAST_FORWARD as supported actions.
222    private boolean mIsCustomizedFastForwardSupported;
223    private boolean mIsCustomizedRewindSupported;
224
225    /**
226     * Constructor for the glue.
227     *
228     * @param context
229     * @param seekSpeeds The array of seek speeds for fast forward and rewind. The maximum length of
230     *                   the array is defined as NUMBER_OF_SEEK_SPEEDS.
231     * @param impl Implementation to underlying media player.
232     */
233    public PlaybackBannerControlGlue(Context context,
234            int[] seekSpeeds,
235            T impl) {
236        this(context, seekSpeeds, seekSpeeds, impl);
237    }
238
239    /**
240     * Constructor for the glue.
241     *
242     * @param context
243     * @param fastForwardSpeeds The array of seek speeds for fast forward. The maximum length of
244     *                   the array is defined as NUMBER_OF_SEEK_SPEEDS.
245     * @param rewindSpeeds The array of seek speeds for rewind. The maximum length of
246     *                   the array is defined as NUMBER_OF_SEEK_SPEEDS.
247     * @param impl Implementation to underlying media player.
248     */
249    public PlaybackBannerControlGlue(Context context,
250                                    int[] fastForwardSpeeds,
251                                    int[] rewindSpeeds,
252                                    T impl) {
253        super(context, impl);
254
255        if (fastForwardSpeeds.length == 0 || fastForwardSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
256            throw new IllegalArgumentException("invalid fastForwardSpeeds array size");
257        }
258        mFastForwardSpeeds = fastForwardSpeeds;
259
260        if (rewindSpeeds.length == 0 || rewindSpeeds.length > NUMBER_OF_SEEK_SPEEDS) {
261            throw new IllegalArgumentException("invalid rewindSpeeds array size");
262        }
263        mRewindSpeeds = rewindSpeeds;
264        if ((mPlayerAdapter.getSupportedActions() & ACTION_FAST_FORWARD) != 0) {
265            mIsCustomizedFastForwardSupported = true;
266        }
267        if ((mPlayerAdapter.getSupportedActions() & ACTION_REWIND) != 0) {
268            mIsCustomizedRewindSupported = true;
269        }
270    }
271
272    @Override
273    public void setControlsRow(PlaybackControlsRow controlsRow) {
274        super.setControlsRow(controlsRow);
275        onUpdatePlaybackState();
276    }
277
278    @Override
279    protected void onCreatePrimaryActions(ArrayObjectAdapter primaryActionsAdapter) {
280        final long supportedActions = getSupportedActions();
281        if ((supportedActions & ACTION_SKIP_TO_PREVIOUS) != 0 && mSkipPreviousAction == null) {
282            primaryActionsAdapter.add(mSkipPreviousAction =
283                    new PlaybackControlsRow.SkipPreviousAction(getContext()));
284        } else if ((supportedActions & ACTION_SKIP_TO_PREVIOUS) == 0
285                && mSkipPreviousAction != null) {
286            primaryActionsAdapter.remove(mSkipPreviousAction);
287            mSkipPreviousAction = null;
288        }
289        if ((supportedActions & ACTION_REWIND) != 0 && mRewindAction == null) {
290            primaryActionsAdapter.add(mRewindAction =
291                    new PlaybackControlsRow.RewindAction(getContext(), mRewindSpeeds.length));
292        } else if ((supportedActions & ACTION_REWIND) == 0 && mRewindAction != null) {
293            primaryActionsAdapter.remove(mRewindAction);
294            mRewindAction = null;
295        }
296        if ((supportedActions & ACTION_PLAY_PAUSE) != 0 && mPlayPauseAction == null) {
297            mPlayPauseAction = new PlaybackControlsRow.PlayPauseAction(getContext());
298            primaryActionsAdapter.add(mPlayPauseAction =
299                    new PlaybackControlsRow.PlayPauseAction(getContext()));
300        } else if ((supportedActions & ACTION_PLAY_PAUSE) == 0 && mPlayPauseAction != null) {
301            primaryActionsAdapter.remove(mPlayPauseAction);
302            mPlayPauseAction = null;
303        }
304        if ((supportedActions & ACTION_FAST_FORWARD) != 0 && mFastForwardAction == null) {
305            mFastForwardAction = new PlaybackControlsRow.FastForwardAction(getContext(),
306                    mFastForwardSpeeds.length);
307            primaryActionsAdapter.add(mFastForwardAction =
308                    new PlaybackControlsRow.FastForwardAction(getContext(),
309                            mFastForwardSpeeds.length));
310        } else if ((supportedActions & ACTION_FAST_FORWARD) == 0 && mFastForwardAction != null) {
311            primaryActionsAdapter.remove(mFastForwardAction);
312            mFastForwardAction = null;
313        }
314        if ((supportedActions & ACTION_SKIP_TO_NEXT) != 0 && mSkipNextAction == null) {
315            primaryActionsAdapter.add(mSkipNextAction =
316                    new PlaybackControlsRow.SkipNextAction(getContext()));
317        } else if ((supportedActions & ACTION_SKIP_TO_NEXT) == 0 && mSkipNextAction != null) {
318            primaryActionsAdapter.remove(mSkipNextAction);
319            mSkipNextAction = null;
320        }
321    }
322
323    @Override
324    protected PlaybackRowPresenter onCreateRowPresenter() {
325        final AbstractDetailsDescriptionPresenter detailsPresenter =
326                new AbstractDetailsDescriptionPresenter() {
327                    @Override
328                    protected void onBindDescription(ViewHolder
329                            viewHolder, Object object) {
330                        PlaybackBannerControlGlue glue = (PlaybackBannerControlGlue) object;
331                        viewHolder.getTitle().setText(glue.getTitle());
332                        viewHolder.getSubtitle().setText(glue.getSubtitle());
333                    }
334                };
335
336        PlaybackControlsRowPresenter rowPresenter =
337                new PlaybackControlsRowPresenter(detailsPresenter) {
338            @Override
339            protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
340                super.onBindRowViewHolder(vh, item);
341                vh.setOnKeyListener(PlaybackBannerControlGlue.this);
342            }
343            @Override
344            protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) {
345                super.onUnbindRowViewHolder(vh);
346                vh.setOnKeyListener(null);
347            }
348        };
349
350        return rowPresenter;
351    }
352
353    /**
354     * Handles action clicks.  A subclass may override this add support for additional actions.
355     */
356    @Override
357    public void onActionClicked(Action action) {
358        dispatchAction(action, null);
359    }
360
361    /**
362     * Handles key events and returns true if handled.  A subclass may override this to provide
363     * additional support.
364     */
365    @Override
366    public boolean onKey(View v, int keyCode, KeyEvent event) {
367        switch (keyCode) {
368            case KeyEvent.KEYCODE_DPAD_UP:
369            case KeyEvent.KEYCODE_DPAD_DOWN:
370            case KeyEvent.KEYCODE_DPAD_RIGHT:
371            case KeyEvent.KEYCODE_DPAD_LEFT:
372            case KeyEvent.KEYCODE_BACK:
373            case KeyEvent.KEYCODE_ESCAPE:
374                boolean abortSeek = mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0
375                        || mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0;
376                if (abortSeek) {
377                    play();
378                    onUpdatePlaybackStatusAfterUserAction();
379                    return keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE;
380                }
381                return false;
382        }
383
384        final ObjectAdapter primaryActionsAdapter = mControlsRow.getPrimaryActionsAdapter();
385        Action action = mControlsRow.getActionForKeyCode(primaryActionsAdapter, keyCode);
386        if (action == null) {
387            action = mControlsRow.getActionForKeyCode(mControlsRow.getSecondaryActionsAdapter(),
388                    keyCode);
389        }
390
391        if (action != null) {
392            if (event.getAction() == KeyEvent.ACTION_DOWN) {
393                dispatchAction(action, event);
394            }
395            return true;
396        }
397        return false;
398    }
399
400    void onUpdatePlaybackStatusAfterUserAction() {
401        updatePlaybackState(mIsPlaying);
402    }
403
404    // Helper function to increment mPlaybackSpeed when necessary. The mPlaybackSpeed will control
405    // the UI of fast forward button in control row.
406    private void incrementFastForwardPlaybackSpeed() {
407        switch (mPlaybackSpeed) {
408            case PLAYBACK_SPEED_FAST_L0:
409            case PLAYBACK_SPEED_FAST_L1:
410            case PLAYBACK_SPEED_FAST_L2:
411            case PLAYBACK_SPEED_FAST_L3:
412                mPlaybackSpeed++;
413                break;
414            default:
415                mPlaybackSpeed = PLAYBACK_SPEED_FAST_L0;
416                break;
417        }
418    }
419
420    // Helper function to decrement mPlaybackSpeed when necessary. The mPlaybackSpeed will control
421    // the UI of rewind button in control row.
422    private void decrementRewindPlaybackSpeed() {
423        switch (mPlaybackSpeed) {
424            case -PLAYBACK_SPEED_FAST_L0:
425            case -PLAYBACK_SPEED_FAST_L1:
426            case -PLAYBACK_SPEED_FAST_L2:
427            case -PLAYBACK_SPEED_FAST_L3:
428                mPlaybackSpeed--;
429                break;
430            default:
431                mPlaybackSpeed = -PLAYBACK_SPEED_FAST_L0;
432                break;
433        }
434    }
435
436    /**
437     * Called when the given action is invoked, either by click or key event.
438     */
439    boolean dispatchAction(Action action, KeyEvent keyEvent) {
440        boolean handled = false;
441        if (action == mPlayPauseAction) {
442            boolean canPlay = keyEvent == null
443                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
444                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY;
445            boolean canPause = keyEvent == null
446                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
447                    || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE;
448            //            PLAY_PAUSE    PLAY      PAUSE
449            // playing    paused                  paused
450            // paused     playing       playing
451            // ff/rw      playing       playing   paused
452            if (canPause
453                    && (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL :
454                    mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) {
455                pause();
456            } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) {
457                play();
458            }
459            onUpdatePlaybackStatusAfterUserAction();
460            handled = true;
461        } else if (action == mSkipNextAction) {
462            next();
463            handled = true;
464        } else if (action == mSkipPreviousAction) {
465            previous();
466            handled = true;
467        } else if (action == mFastForwardAction) {
468            if (mPlayerAdapter.isPrepared() && mPlaybackSpeed < getMaxForwardSpeedId()) {
469                // When the customized fast forward action is available, it will be executed
470                // when fast forward button is pressed. If current media item is not playing, the UI
471                // will be updated to PLAYING status.
472                if (mIsCustomizedFastForwardSupported) {
473                    // Change UI to Playing status.
474                    mIsPlaying = true;
475                    // Execute customized fast forward action.
476                    mPlayerAdapter.fastForward();
477                } else {
478                    // When the customized fast forward action is not supported, the fakePause
479                    // operation is needed to stop the media item but still indicating the media
480                    // item is playing from the UI perspective
481                    // Also the fakePause() method must be called before
482                    // incrementFastForwardPlaybackSpeed() method to make sure fake fast forward
483                    // computation is accurate.
484                    fakePause();
485                }
486                // Change mPlaybackSpeed to control the UI.
487                incrementFastForwardPlaybackSpeed();
488                onUpdatePlaybackStatusAfterUserAction();
489            }
490            handled = true;
491        } else if (action == mRewindAction) {
492            if (mPlayerAdapter.isPrepared() && mPlaybackSpeed > -getMaxRewindSpeedId()) {
493                if (mIsCustomizedFastForwardSupported) {
494                    mIsPlaying = true;
495                    mPlayerAdapter.rewind();
496                } else {
497                    fakePause();
498                }
499                decrementRewindPlaybackSpeed();
500                onUpdatePlaybackStatusAfterUserAction();
501            }
502            handled = true;
503        }
504        return handled;
505    }
506
507    @Override
508    protected void onPlayStateChanged() {
509        if (DEBUG) Log.v(TAG, "onStateChanged");
510
511        onUpdatePlaybackState();
512        super.onPlayStateChanged();
513    }
514
515    @Override
516    protected void onPlayCompleted() {
517        super.onPlayCompleted();
518        mIsPlaying = false;
519        mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
520        mStartPosition = getCurrentPosition();
521        mStartTime = System.currentTimeMillis();
522        onUpdatePlaybackState();
523    }
524
525    void onUpdatePlaybackState() {
526        updatePlaybackState(mIsPlaying);
527    }
528
529    private void updatePlaybackState(boolean isPlaying) {
530        if (mControlsRow == null) {
531            return;
532        }
533
534        if (!isPlaying) {
535            onUpdateProgress();
536            mPlayerAdapter.setProgressUpdatingEnabled(false);
537        } else {
538            mPlayerAdapter.setProgressUpdatingEnabled(true);
539        }
540
541        if (mFadeWhenPlaying && getHost() != null) {
542            getHost().setControlsOverlayAutoHideEnabled(isPlaying);
543        }
544
545
546        final ArrayObjectAdapter primaryActionsAdapter =
547                (ArrayObjectAdapter) getControlsRow().getPrimaryActionsAdapter();
548        if (mPlayPauseAction != null) {
549            int index = !isPlaying
550                    ? PlaybackControlsRow.PlayPauseAction.PLAY
551                    : PlaybackControlsRow.PlayPauseAction.PAUSE;
552            if (mPlayPauseAction.getIndex() != index) {
553                mPlayPauseAction.setIndex(index);
554                notifyItemChanged(primaryActionsAdapter, mPlayPauseAction);
555            }
556        }
557
558        if (mFastForwardAction != null) {
559            int index = 0;
560            if (mPlaybackSpeed >= PLAYBACK_SPEED_FAST_L0) {
561                index = mPlaybackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
562            }
563            if (mFastForwardAction.getIndex() != index) {
564                mFastForwardAction.setIndex(index);
565                notifyItemChanged(primaryActionsAdapter, mFastForwardAction);
566            }
567        }
568        if (mRewindAction != null) {
569            int index = 0;
570            if (mPlaybackSpeed <= -PLAYBACK_SPEED_FAST_L0) {
571                index = -mPlaybackSpeed - PLAYBACK_SPEED_FAST_L0 + 1;
572            }
573            if (mRewindAction.getIndex() != index) {
574                mRewindAction.setIndex(index);
575                notifyItemChanged(primaryActionsAdapter, mRewindAction);
576            }
577        }
578    }
579
580    /**
581     * Returns the fast forward speeds.
582     */
583    @NonNull
584    public int[] getFastForwardSpeeds() {
585        return mFastForwardSpeeds;
586    }
587
588    /**
589     * Returns the rewind speeds.
590     */
591    @NonNull
592    public int[] getRewindSpeeds() {
593        return mRewindSpeeds;
594    }
595
596    private int getMaxForwardSpeedId() {
597        return PLAYBACK_SPEED_FAST_L0 + (mFastForwardSpeeds.length - 1);
598    }
599
600    private int getMaxRewindSpeedId() {
601        return PLAYBACK_SPEED_FAST_L0 + (mRewindSpeeds.length - 1);
602    }
603
604    /**
605     * Gets current position of the player. If the player is playing/paused, this
606     * method returns current position from {@link PlayerAdapter}. Otherwise, if the player is
607     * fastforwarding/rewinding, the method fake-pauses the {@link PlayerAdapter} and returns its
608     * own calculated position.
609     * @return Current position of the player.
610     */
611    @Override
612    public long getCurrentPosition() {
613        int speed;
614        if (mPlaybackSpeed == PlaybackControlGlue.PLAYBACK_SPEED_PAUSED
615                || mPlaybackSpeed == PlaybackControlGlue.PLAYBACK_SPEED_NORMAL) {
616            // If the adapter is playing/paused, using the position from adapter instead.
617            return mPlayerAdapter.getCurrentPosition();
618        } else if (mPlaybackSpeed >= PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
619            // If fast forward operation is supported in this scenario, current player position
620            // can be get from mPlayerAdapter.getCurrentPosition() directly
621            if (mIsCustomizedFastForwardSupported) {
622                return mPlayerAdapter.getCurrentPosition();
623            }
624            int index = mPlaybackSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
625            speed = getFastForwardSpeeds()[index];
626        } else if (mPlaybackSpeed <= -PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0) {
627            // If fast rewind is supported in this scenario, current player position
628            // can be get from mPlayerAdapter.getCurrentPosition() directly
629            if (mIsCustomizedRewindSupported) {
630                return mPlayerAdapter.getCurrentPosition();
631            }
632            int index = -mPlaybackSpeed - PlaybackControlGlue.PLAYBACK_SPEED_FAST_L0;
633            speed = -getRewindSpeeds()[index];
634        } else {
635            return -1;
636        }
637
638        long position = mStartPosition + (System.currentTimeMillis() - mStartTime) * speed;
639        if (position > getDuration()) {
640            mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
641            position = getDuration();
642            mPlayerAdapter.seekTo(position);
643            mStartPosition = 0;
644            pause();
645        } else if (position < 0) {
646            mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
647            position = 0;
648            mPlayerAdapter.seekTo(position);
649            mStartPosition = 0;
650            pause();
651        }
652        return position;
653    }
654
655
656    @Override
657    public void play() {
658        if (!mPlayerAdapter.isPrepared()) {
659            return;
660        }
661
662        // Solves the situation that a player pause at the end and click play button. At this case
663        // the player will restart from the beginning.
664        if (mPlaybackSpeed == PLAYBACK_SPEED_PAUSED
665                && mPlayerAdapter.getCurrentPosition() >= mPlayerAdapter.getDuration()) {
666            mStartPosition = 0;
667        } else {
668            mStartPosition = getCurrentPosition();
669        }
670
671        mStartTime = System.currentTimeMillis();
672        mIsPlaying = true;
673        mPlaybackSpeed = PLAYBACK_SPEED_NORMAL;
674        mPlayerAdapter.seekTo(mStartPosition);
675        super.play();
676
677        onUpdatePlaybackState();
678    }
679
680    @Override
681    public void pause() {
682        mIsPlaying = false;
683        mPlaybackSpeed = PLAYBACK_SPEED_PAUSED;
684        mStartPosition = getCurrentPosition();
685        mStartTime = System.currentTimeMillis();
686        super.pause();
687
688        onUpdatePlaybackState();
689    }
690
691    /**
692     * Control row shows PLAY, but the media is actually paused when the player is
693     * fastforwarding/rewinding.
694     */
695    private void fakePause() {
696        mIsPlaying = true;
697        mStartPosition = getCurrentPosition();
698        mStartTime = System.currentTimeMillis();
699        super.pause();
700
701        onUpdatePlaybackState();
702    }
703}
704