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