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