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 com.android.tv.dvr.ui; 18 19 import android.app.Activity; 20 import android.graphics.drawable.Drawable; 21 import android.media.MediaMetadata; 22 import android.media.session.MediaController; 23 import android.media.session.MediaController.TransportControls; 24 import android.media.session.PlaybackState; 25 import android.support.v17.leanback.app.PlaybackControlGlue; 26 import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter; 27 import android.support.v17.leanback.widget.Action; 28 import android.support.v17.leanback.widget.OnActionClickedListener; 29 import android.support.v17.leanback.widget.PlaybackControlsRow; 30 import android.support.v17.leanback.widget.PlaybackControlsRowPresenter; 31 import android.support.v17.leanback.widget.RowPresenter; 32 import android.text.TextUtils; 33 import android.util.Log; 34 import android.view.KeyEvent; 35 import android.view.View; 36 37 import com.android.tv.R; 38 import com.android.tv.util.TimeShiftUtils; 39 40 /** 41 * A helper class to assist {@link DvrPlaybackOverlayFragment} to manage its controls row and 42 * send command to the media controller. It also helps to update playback states displayed in the 43 * fragment according to information the media session provides. 44 */ 45 public class DvrPlaybackControlHelper extends PlaybackControlGlue { 46 private static final String TAG = "DvrPlaybackControlHelper"; 47 private static final boolean DEBUG = false; 48 49 /** 50 * Indicates the ID of the media under playback is unknown. 51 */ 52 public static int UNKNOWN_MEDIA_ID = -1; 53 54 private int mPlaybackState = PlaybackState.STATE_NONE; 55 private int mPlaybackSpeedLevel; 56 private int mPlaybackSpeedId; 57 private boolean mReadyToControl; 58 59 private final MediaController mMediaController; 60 private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback(); 61 private final TransportControls mTransportControls; 62 private final int mExtraPaddingTopForNoDescription; 63 DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment)64 public DvrPlaybackControlHelper(Activity activity, DvrPlaybackOverlayFragment overlayFragment) { 65 super(activity, overlayFragment, new int[TimeShiftUtils.MAX_SPEED_LEVEL + 1]); 66 mMediaController = activity.getMediaController(); 67 mMediaController.registerCallback(mMediaControllerCallback); 68 mTransportControls = mMediaController.getTransportControls(); 69 mExtraPaddingTopForNoDescription = activity.getResources() 70 .getDimensionPixelOffset(R.dimen.dvr_playback_controls_extra_padding_top); 71 } 72 73 @Override createControlsRowAndPresenter()74 public PlaybackControlsRowPresenter createControlsRowAndPresenter() { 75 PlaybackControlsRow controlsRow = new PlaybackControlsRow(this); 76 setControlsRow(controlsRow); 77 AbstractDetailsDescriptionPresenter detailsPresenter = 78 new AbstractDetailsDescriptionPresenter() { 79 @Override 80 protected void onBindDescription( 81 AbstractDetailsDescriptionPresenter.ViewHolder viewHolder, Object object) { 82 PlaybackControlGlue glue = (PlaybackControlGlue) object; 83 if (glue.hasValidMedia()) { 84 viewHolder.getTitle().setText(glue.getMediaTitle()); 85 viewHolder.getSubtitle().setText(glue.getMediaSubtitle()); 86 } else { 87 viewHolder.getTitle().setText(""); 88 viewHolder.getSubtitle().setText(""); 89 } 90 if (TextUtils.isEmpty(viewHolder.getSubtitle().getText())) { 91 viewHolder.view.setPadding(viewHolder.view.getPaddingLeft(), 92 mExtraPaddingTopForNoDescription, 93 viewHolder.view.getPaddingRight(), viewHolder.view.getPaddingBottom()); 94 } 95 } 96 }; 97 PlaybackControlsRowPresenter presenter = 98 new PlaybackControlsRowPresenter(detailsPresenter) { 99 @Override 100 protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) { 101 super.onBindRowViewHolder(vh, item); 102 vh.setOnKeyListener(DvrPlaybackControlHelper.this); 103 } 104 105 @Override 106 protected void onUnbindRowViewHolder(RowPresenter.ViewHolder vh) { 107 super.onUnbindRowViewHolder(vh); 108 vh.setOnKeyListener(null); 109 } 110 }; 111 presenter.setProgressColor(getContext().getResources() 112 .getColor(R.color.play_controls_progress_bar_watched)); 113 presenter.setBackgroundColor(getContext().getResources() 114 .getColor(R.color.play_controls_body_background_enabled)); 115 presenter.setOnActionClickedListener(new OnActionClickedListener() { 116 @Override 117 public void onActionClicked(Action action) { 118 if (mReadyToControl) { 119 DvrPlaybackControlHelper.super.onActionClicked(action); 120 } 121 } 122 }); 123 return presenter; 124 } 125 126 @Override onKey(View v, int keyCode, KeyEvent event)127 public boolean onKey(View v, int keyCode, KeyEvent event) { 128 if (mReadyToControl) { 129 if (keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE && event.getAction() == KeyEvent.ACTION_DOWN 130 && (mPlaybackState == PlaybackState.STATE_FAST_FORWARDING 131 || mPlaybackState == PlaybackState.STATE_REWINDING)) { 132 // Workaround of b/31489271. Clicks play/pause button first to reset play controls 133 // to "play" state. Then we can pass MEDIA_PAUSE to let playback be paused. 134 onActionClicked(getControlsRow().getActionForKeyCode(keyCode)); 135 } 136 return super.onKey(v, keyCode, event); 137 } 138 return false; 139 } 140 141 @Override hasValidMedia()142 public boolean hasValidMedia() { 143 PlaybackState playbackState = mMediaController.getPlaybackState(); 144 return playbackState != null; 145 } 146 147 @Override isMediaPlaying()148 public boolean isMediaPlaying() { 149 PlaybackState playbackState = mMediaController.getPlaybackState(); 150 if (playbackState == null) { 151 return false; 152 } 153 int state = playbackState.getState(); 154 return state != PlaybackState.STATE_NONE && state != PlaybackState.STATE_CONNECTING 155 && state != PlaybackState.STATE_PAUSED; 156 } 157 158 /** 159 * Returns the ID of the media under playback. 160 */ getMediaId()161 public long getMediaId() { 162 MediaMetadata mediaMetadata = mMediaController.getMetadata(); 163 return mediaMetadata == null ? UNKNOWN_MEDIA_ID 164 : mediaMetadata.getLong(MediaMetadata.METADATA_KEY_MEDIA_ID); 165 } 166 167 @Override getMediaTitle()168 public CharSequence getMediaTitle() { 169 MediaMetadata mediaMetadata = mMediaController.getMetadata(); 170 return mediaMetadata == null ? "" 171 : mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); 172 } 173 174 @Override getMediaSubtitle()175 public CharSequence getMediaSubtitle() { 176 MediaMetadata mediaMetadata = mMediaController.getMetadata(); 177 return mediaMetadata == null ? "" 178 : mediaMetadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE); 179 } 180 181 @Override getMediaDuration()182 public int getMediaDuration() { 183 MediaMetadata mediaMetadata = mMediaController.getMetadata(); 184 return mediaMetadata == null ? 0 185 : (int) mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); 186 } 187 188 @Override getMediaArt()189 public Drawable getMediaArt() { 190 // Do not show the poster art on control row. 191 return null; 192 } 193 194 @Override getSupportedActions()195 public long getSupportedActions() { 196 return ACTION_PLAY_PAUSE | ACTION_FAST_FORWARD | ACTION_REWIND; 197 } 198 199 @Override getCurrentSpeedId()200 public int getCurrentSpeedId() { 201 return mPlaybackSpeedId; 202 } 203 204 @Override getCurrentPosition()205 public int getCurrentPosition() { 206 PlaybackState playbackState = mMediaController.getPlaybackState(); 207 if (playbackState == null) { 208 return 0; 209 } 210 return (int) playbackState.getPosition(); 211 } 212 213 /** 214 * Unregister media controller's callback. 215 */ unregisterCallback()216 public void unregisterCallback() { 217 mMediaController.unregisterCallback(mMediaControllerCallback); 218 } 219 220 @Override startPlayback(int speedId)221 protected void startPlayback(int speedId) { 222 if (getCurrentSpeedId() == speedId) { 223 return; 224 } 225 if (speedId == PLAYBACK_SPEED_NORMAL) { 226 mTransportControls.play(); 227 } else if (speedId <= -PLAYBACK_SPEED_FAST_L0) { 228 mTransportControls.rewind(); 229 } else if (speedId >= PLAYBACK_SPEED_FAST_L0){ 230 mTransportControls.fastForward(); 231 } 232 } 233 234 @Override pausePlayback()235 protected void pausePlayback() { 236 mTransportControls.pause(); 237 } 238 239 @Override skipToNext()240 protected void skipToNext() { 241 // Do nothing. 242 } 243 244 @Override skipToPrevious()245 protected void skipToPrevious() { 246 // Do nothing. 247 } 248 249 @Override onRowChanged(PlaybackControlsRow row)250 protected void onRowChanged(PlaybackControlsRow row) { 251 // Do nothing. 252 } 253 onStateChanged(int state, long positionMs, int speedLevel)254 private void onStateChanged(int state, long positionMs, int speedLevel) { 255 if (DEBUG) Log.d(TAG, "onStateChanged"); 256 getControlsRow().setCurrentTime((int) positionMs); 257 if (state == mPlaybackState && mPlaybackSpeedLevel == speedLevel) { 258 // Only position is changed, no need to update controls row 259 return; 260 } 261 // NOTICE: The below two variables should only be used in this method. 262 // The only usage of them is to confirm if the state is changed or not. 263 mPlaybackState = state; 264 mPlaybackSpeedLevel = speedLevel; 265 switch (state) { 266 case PlaybackState.STATE_PLAYING: 267 mPlaybackSpeedId = PLAYBACK_SPEED_NORMAL; 268 setFadingEnabled(true); 269 mReadyToControl = true; 270 break; 271 case PlaybackState.STATE_PAUSED: 272 mPlaybackSpeedId = PLAYBACK_SPEED_PAUSED; 273 setFadingEnabled(true); 274 mReadyToControl = true; 275 break; 276 case PlaybackState.STATE_FAST_FORWARDING: 277 mPlaybackSpeedId = PLAYBACK_SPEED_FAST_L0 + speedLevel; 278 setFadingEnabled(false); 279 mReadyToControl = true; 280 break; 281 case PlaybackState.STATE_REWINDING: 282 mPlaybackSpeedId = -PLAYBACK_SPEED_FAST_L0 - speedLevel; 283 setFadingEnabled(false); 284 mReadyToControl = true; 285 break; 286 case PlaybackState.STATE_CONNECTING: 287 setFadingEnabled(false); 288 mReadyToControl = false; 289 break; 290 case PlaybackState.STATE_NONE: 291 mReadyToControl = false; 292 break; 293 default: 294 setFadingEnabled(true); 295 break; 296 } 297 onStateChanged(); 298 } 299 300 private class MediaControllerCallback extends MediaController.Callback { 301 @Override onPlaybackStateChanged(PlaybackState state)302 public void onPlaybackStateChanged(PlaybackState state) { 303 if (DEBUG) Log.d(TAG, "Playback state changed: " + state.getState()); 304 onStateChanged(state.getState(), state.getPosition(), (int) state.getPlaybackSpeed()); 305 } 306 307 @Override onMetadataChanged(MediaMetadata metadata)308 public void onMetadataChanged(MediaMetadata metadata) { 309 DvrPlaybackControlHelper.this.onMetadataChanged(); 310 ((DvrPlaybackOverlayFragment) getFragment()).onMediaControllerUpdated(); 311 } 312 } 313 }