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