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 17 package androidx.leanback.media; 18 19 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_FAST_FORWARD; 20 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_PLAY_PAUSE; 21 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_REPEAT; 22 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_REWIND; 23 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SHUFFLE; 24 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SKIP_TO_NEXT; 25 import static androidx.leanback.media.PlaybackBaseControlGlue.ACTION_SKIP_TO_PREVIOUS; 26 27 import android.content.Context; 28 import android.graphics.Bitmap; 29 import android.graphics.drawable.BitmapDrawable; 30 import android.graphics.drawable.Drawable; 31 import android.os.Handler; 32 import android.support.v4.media.MediaMetadataCompat; 33 import android.support.v4.media.session.MediaControllerCompat; 34 import android.support.v4.media.session.PlaybackStateCompat; 35 import android.util.Log; 36 37 import androidx.leanback.widget.PlaybackControlsRow; 38 39 /** 40 * A helper class for implementing a adapter layer for {@link MediaControllerCompat}. 41 */ 42 public class MediaControllerAdapter extends PlayerAdapter { 43 44 private static final String TAG = "MediaControllerAdapter"; 45 private static final boolean DEBUG = false; 46 47 private MediaControllerCompat mController; 48 private Handler mHandler = new Handler(); 49 50 // Runnable object to update current media's playing position. 51 private final Runnable mPositionUpdaterRunnable = new Runnable() { 52 @Override 53 public void run() { 54 getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); 55 mHandler.postDelayed(this, getUpdatePeriod()); 56 } 57 }; 58 59 // Update period to post runnable. 60 private int getUpdatePeriod() { 61 return 16; 62 } 63 64 private boolean mIsBuffering = false; 65 66 MediaControllerCompat.Callback mMediaControllerCallback = 67 new MediaControllerCompat.Callback() { 68 @Override 69 public void onPlaybackStateChanged(PlaybackStateCompat state) { 70 if (mIsBuffering && state.getState() != PlaybackStateCompat.STATE_BUFFERING) { 71 getCallback().onBufferingStateChanged(MediaControllerAdapter.this, false); 72 getCallback().onBufferedPositionChanged(MediaControllerAdapter.this); 73 mIsBuffering = false; 74 } 75 if (state.getState() == PlaybackStateCompat.STATE_NONE) { 76 // The STATE_NONE playback state will only occurs when initialize the player 77 // at first time. 78 if (DEBUG) { 79 Log.d(TAG, "Playback state is none"); 80 } 81 } else if (state.getState() == PlaybackStateCompat.STATE_STOPPED) { 82 // STATE_STOPPED is associated with onPlayCompleted() callback. 83 // STATE_STOPPED playback state will only occurs when the last item in 84 // play list is finished. And repeat mode is not enabled. 85 getCallback().onPlayCompleted(MediaControllerAdapter.this); 86 } else if (state.getState() == PlaybackStateCompat.STATE_PAUSED) { 87 getCallback().onPlayStateChanged(MediaControllerAdapter.this); 88 getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); 89 } else if (state.getState() == PlaybackStateCompat.STATE_PLAYING) { 90 getCallback().onPlayStateChanged(MediaControllerAdapter.this); 91 getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); 92 } else if (state.getState() == PlaybackStateCompat.STATE_BUFFERING) { 93 mIsBuffering = true; 94 getCallback().onBufferingStateChanged(MediaControllerAdapter.this, true); 95 getCallback().onBufferedPositionChanged(MediaControllerAdapter.this); 96 } else if (state.getState() == PlaybackStateCompat.STATE_ERROR) { 97 CharSequence errorMessage = state.getErrorMessage(); 98 if (errorMessage == null) { 99 getCallback().onError(MediaControllerAdapter.this, state.getErrorCode(), 100 ""); 101 } else { 102 getCallback().onError(MediaControllerAdapter.this, state.getErrorCode(), 103 state.getErrorMessage().toString()); 104 } 105 } else if (state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING) { 106 getCallback().onPlayStateChanged(MediaControllerAdapter.this); 107 getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); 108 } else if (state.getState() == PlaybackStateCompat.STATE_REWINDING) { 109 getCallback().onPlayStateChanged(MediaControllerAdapter.this); 110 getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); 111 } 112 } 113 114 @Override 115 public void onMetadataChanged(MediaMetadataCompat metadata) { 116 getCallback().onMetadataChanged(MediaControllerAdapter.this); 117 } 118 }; 119 120 /** 121 * Constructor for the adapter using {@link MediaControllerCompat}. 122 * 123 * @param controller Object of MediaControllerCompat.. 124 */ 125 public MediaControllerAdapter(MediaControllerCompat controller) { 126 if (controller == null) { 127 throw new NullPointerException("Object of MediaControllerCompat is null"); 128 } 129 mController = controller; 130 } 131 132 /** 133 * Return the object of {@link MediaControllerCompat} from this class. 134 * 135 * @return Media Controller Compat object owned by this class. 136 */ 137 public MediaControllerCompat getMediaController() { 138 return mController; 139 } 140 141 @Override 142 public void play() { 143 mController.getTransportControls().play(); 144 } 145 146 @Override 147 public void pause() { 148 mController.getTransportControls().pause(); 149 } 150 151 @Override 152 public void seekTo(long positionInMs) { 153 mController.getTransportControls().seekTo(positionInMs); 154 } 155 156 @Override 157 public void next() { 158 mController.getTransportControls().skipToNext(); 159 } 160 161 @Override 162 public void previous() { 163 mController.getTransportControls().skipToPrevious(); 164 } 165 166 @Override 167 public void fastForward() { 168 mController.getTransportControls().fastForward(); 169 } 170 171 @Override 172 public void rewind() { 173 mController.getTransportControls().rewind(); 174 } 175 176 @Override 177 public void setRepeatAction(int repeatActionIndex) { 178 int repeatMode = mapRepeatActionToRepeatMode(repeatActionIndex); 179 mController.getTransportControls().setRepeatMode(repeatMode); 180 } 181 182 @Override 183 public void setShuffleAction(int shuffleActionIndex) { 184 int shuffleMode = mapShuffleActionToShuffleMode(shuffleActionIndex); 185 mController.getTransportControls().setShuffleMode(shuffleMode); 186 } 187 188 @Override 189 public boolean isPlaying() { 190 if (mController.getPlaybackState() == null) { 191 return false; 192 } 193 return mController.getPlaybackState().getState() 194 == PlaybackStateCompat.STATE_PLAYING 195 || mController.getPlaybackState().getState() 196 == PlaybackStateCompat.STATE_FAST_FORWARDING 197 || mController.getPlaybackState().getState() == PlaybackStateCompat.STATE_REWINDING; 198 } 199 200 @Override 201 public long getCurrentPosition() { 202 if (mController.getPlaybackState() == null) { 203 return 0; 204 } 205 return mController.getPlaybackState().getPosition(); 206 } 207 208 @Override 209 public long getBufferedPosition() { 210 if (mController.getPlaybackState() == null) { 211 return 0; 212 } 213 return mController.getPlaybackState().getBufferedPosition(); 214 } 215 216 /** 217 * Get current media's title. 218 * 219 * @return Title of current media. 220 */ 221 public CharSequence getMediaTitle() { 222 if (mController.getMetadata() == null) { 223 return ""; 224 } 225 return mController.getMetadata().getDescription().getTitle(); 226 } 227 228 /** 229 * Get current media's subtitle. 230 * 231 * @return Subtitle of current media. 232 */ 233 public CharSequence getMediaSubtitle() { 234 if (mController.getMetadata() == null) { 235 return ""; 236 } 237 return mController.getMetadata().getDescription().getSubtitle(); 238 } 239 240 /** 241 * Get current media's drawable art. 242 * 243 * @return Drawable art of current media. 244 */ 245 public Drawable getMediaArt(Context context) { 246 if (mController.getMetadata() == null) { 247 return null; 248 } 249 Bitmap bitmap = mController.getMetadata().getDescription().getIconBitmap(); 250 return bitmap == null ? null : new BitmapDrawable(context.getResources(), bitmap); 251 } 252 253 @Override 254 public long getDuration() { 255 if (mController.getMetadata() == null) { 256 return 0; 257 } 258 return (int) mController.getMetadata().getLong( 259 MediaMetadataCompat.METADATA_KEY_DURATION); 260 } 261 262 @Override 263 public void onAttachedToHost(PlaybackGlueHost host) { 264 mController.registerCallback(mMediaControllerCallback); 265 } 266 267 @Override 268 public void onDetachedFromHost() { 269 mController.unregisterCallback(mMediaControllerCallback); 270 } 271 272 @Override 273 public void setProgressUpdatingEnabled(boolean enabled) { 274 mHandler.removeCallbacks(mPositionUpdaterRunnable); 275 if (!enabled) { 276 return; 277 } 278 mHandler.postDelayed(mPositionUpdaterRunnable, getUpdatePeriod()); 279 } 280 281 @Override 282 public long getSupportedActions() { 283 long supportedActions = 0; 284 if (mController.getPlaybackState() == null) { 285 return supportedActions; 286 } 287 long actionsFromController = mController.getPlaybackState().getActions(); 288 // Translation. 289 if ((actionsFromController & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) { 290 supportedActions |= ACTION_PLAY_PAUSE; 291 } 292 if ((actionsFromController & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) { 293 supportedActions |= ACTION_SKIP_TO_NEXT; 294 } 295 if ((actionsFromController & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) { 296 supportedActions |= ACTION_SKIP_TO_PREVIOUS; 297 } 298 if ((actionsFromController & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) { 299 supportedActions |= ACTION_FAST_FORWARD; 300 } 301 if ((actionsFromController & PlaybackStateCompat.ACTION_REWIND) != 0) { 302 supportedActions |= ACTION_REWIND; 303 } 304 if ((actionsFromController & PlaybackStateCompat.ACTION_SET_REPEAT_MODE) != 0) { 305 supportedActions |= ACTION_REPEAT; 306 } 307 if ((actionsFromController & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE) != 0) { 308 supportedActions |= ACTION_SHUFFLE; 309 } 310 return supportedActions; 311 } 312 313 /** 314 * This function will translate the index of RepeatAction in PlaybackControlsRow to 315 * the repeat mode which is defined by PlaybackStateCompat. 316 * 317 * @param repeatActionIndex Index of RepeatAction in PlaybackControlsRow. 318 * @return Repeat Mode in playback state. 319 */ 320 private int mapRepeatActionToRepeatMode(int repeatActionIndex) { 321 switch (repeatActionIndex) { 322 case PlaybackControlsRow.RepeatAction.INDEX_NONE: 323 return PlaybackStateCompat.REPEAT_MODE_NONE; 324 case PlaybackControlsRow.RepeatAction.INDEX_ALL: 325 return PlaybackStateCompat.REPEAT_MODE_ALL; 326 case PlaybackControlsRow.RepeatAction.INDEX_ONE: 327 return PlaybackStateCompat.REPEAT_MODE_ONE; 328 } 329 return -1; 330 } 331 332 /** 333 * This function will translate the index of RepeatAction in PlaybackControlsRow to 334 * the repeat mode which is defined by PlaybackStateCompat. 335 * 336 * @param shuffleActionIndex Index of RepeatAction in PlaybackControlsRow. 337 * @return Repeat Mode in playback state. 338 */ 339 private int mapShuffleActionToShuffleMode(int shuffleActionIndex) { 340 switch (shuffleActionIndex) { 341 case PlaybackControlsRow.ShuffleAction.INDEX_OFF: 342 return PlaybackStateCompat.SHUFFLE_MODE_NONE; 343 case PlaybackControlsRow.ShuffleAction.INDEX_ON: 344 return PlaybackStateCompat.SHUFFLE_MODE_ALL; 345 } 346 return -1; 347 } 348 } 349 350