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 * @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