/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v17.leanback.media; import static android.support.v17.leanback.media.PlaybackBaseControlGlue.ACTION_FAST_FORWARD; import static android.support.v17.leanback.media.PlaybackBaseControlGlue.ACTION_PLAY_PAUSE; import static android.support.v17.leanback.media.PlaybackBaseControlGlue.ACTION_REPEAT; import static android.support.v17.leanback.media.PlaybackBaseControlGlue.ACTION_REWIND; import static android.support.v17.leanback.media.PlaybackBaseControlGlue.ACTION_SHUFFLE; import static android.support.v17.leanback.media.PlaybackBaseControlGlue.ACTION_SKIP_TO_NEXT; import static android.support.v17.leanback.media.PlaybackBaseControlGlue.ACTION_SKIP_TO_PREVIOUS; import android.content.Context; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.support.v17.leanback.widget.PlaybackControlsRow; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; /** * A helper class for implementing a adapter layer for {@link MediaControllerCompat}. */ public class MediaControllerAdapter extends PlayerAdapter { private static final String TAG = "MediaControllerAdapter"; private static final boolean DEBUG = false; private MediaControllerCompat mController; private Handler mHandler = new Handler(); // Runnable object to update current media's playing position. private final Runnable mPositionUpdaterRunnable = new Runnable() { @Override public void run() { getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); mHandler.postDelayed(this, getUpdatePeriod()); } }; // Update period to post runnable. private int getUpdatePeriod() { return 16; } private boolean mIsBuffering = false; MediaControllerCompat.Callback mMediaControllerCallback = new MediaControllerCompat.Callback() { @Override public void onPlaybackStateChanged(PlaybackStateCompat state) { if (mIsBuffering && state.getState() != PlaybackStateCompat.STATE_BUFFERING) { getCallback().onBufferingStateChanged(MediaControllerAdapter.this, false); getCallback().onBufferedPositionChanged(MediaControllerAdapter.this); mIsBuffering = false; } if (state.getState() == PlaybackStateCompat.STATE_NONE) { // The STATE_NONE playback state will only occurs when initialize the player // at first time. if (DEBUG) { Log.d(TAG, "Playback state is none"); } } else if (state.getState() == PlaybackStateCompat.STATE_STOPPED) { // STATE_STOPPED is associated with onPlayCompleted() callback. // STATE_STOPPED playback state will only occurs when the last item in // play list is finished. And repeat mode is not enabled. getCallback().onPlayCompleted(MediaControllerAdapter.this); } else if (state.getState() == PlaybackStateCompat.STATE_PAUSED) { getCallback().onPlayStateChanged(MediaControllerAdapter.this); getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); } else if (state.getState() == PlaybackStateCompat.STATE_PLAYING) { getCallback().onPlayStateChanged(MediaControllerAdapter.this); getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); } else if (state.getState() == PlaybackStateCompat.STATE_BUFFERING) { mIsBuffering = true; getCallback().onBufferingStateChanged(MediaControllerAdapter.this, true); getCallback().onBufferedPositionChanged(MediaControllerAdapter.this); } else if (state.getState() == PlaybackStateCompat.STATE_ERROR) { CharSequence errorMessage = state.getErrorMessage(); if (errorMessage == null) { getCallback().onError(MediaControllerAdapter.this, state.getErrorCode(), ""); } else { getCallback().onError(MediaControllerAdapter.this, state.getErrorCode(), state.getErrorMessage().toString()); } } else if (state.getState() == PlaybackStateCompat.STATE_FAST_FORWARDING) { getCallback().onPlayStateChanged(MediaControllerAdapter.this); getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); } else if (state.getState() == PlaybackStateCompat.STATE_REWINDING) { getCallback().onPlayStateChanged(MediaControllerAdapter.this); getCallback().onCurrentPositionChanged(MediaControllerAdapter.this); } } @Override public void onMetadataChanged(MediaMetadataCompat metadata) { getCallback().onMetadataChanged(MediaControllerAdapter.this); } }; /** * Constructor for the adapter using {@link MediaControllerCompat}. * * @param controller Object of MediaControllerCompat.. */ public MediaControllerAdapter(MediaControllerCompat controller) { if (controller == null) { throw new NullPointerException("Object of MediaControllerCompat is null"); } mController = controller; } /** * Return the object of {@link MediaControllerCompat} from this class. * * @return Media Controller Compat object owned by this class. */ public MediaControllerCompat getMediaController() { return mController; } @Override public void play() { mController.getTransportControls().play(); } @Override public void pause() { mController.getTransportControls().pause(); } @Override public void seekTo(long positionInMs) { mController.getTransportControls().seekTo(positionInMs); } @Override public void next() { mController.getTransportControls().skipToNext(); } @Override public void previous() { mController.getTransportControls().skipToPrevious(); } @Override public void fastForward() { mController.getTransportControls().fastForward(); } @Override public void rewind() { mController.getTransportControls().rewind(); } @Override public void setRepeatAction(int repeatActionIndex) { int repeatMode = mapRepeatActionToRepeatMode(repeatActionIndex); mController.getTransportControls().setRepeatMode(repeatMode); } @Override public void setShuffleAction(int shuffleActionIndex) { int shuffleMode = mapShuffleActionToShuffleMode(shuffleActionIndex); mController.getTransportControls().setShuffleMode(shuffleMode); } @Override public boolean isPlaying() { if (mController.getPlaybackState() == null) { return false; } return mController.getPlaybackState().getState() == PlaybackStateCompat.STATE_PLAYING || mController.getPlaybackState().getState() == PlaybackStateCompat.STATE_FAST_FORWARDING || mController.getPlaybackState().getState() == PlaybackStateCompat.STATE_REWINDING; } @Override public long getCurrentPosition() { if (mController.getPlaybackState() == null) { return 0; } return mController.getPlaybackState().getPosition(); } @Override public long getBufferedPosition() { if (mController.getPlaybackState() == null) { return 0; } return mController.getPlaybackState().getBufferedPosition(); } /** * Get current media's title. * * @return Title of current media. */ public CharSequence getMediaTitle() { if (mController.getMetadata() == null) { return ""; } return mController.getMetadata().getDescription().getTitle(); } /** * Get current media's subtitle. * * @return Subtitle of current media. */ public CharSequence getMediaSubtitle() { if (mController.getMetadata() == null) { return ""; } return mController.getMetadata().getDescription().getSubtitle(); } /** * Get current media's drawable art. * * @return Drawable art of current media. */ public Drawable getMediaArt(Context context) { if (mController.getMetadata() == null) { return null; } Bitmap bitmap = mController.getMetadata().getDescription().getIconBitmap(); return bitmap == null ? null : new BitmapDrawable(context.getResources(), bitmap); } @Override public long getDuration() { if (mController.getMetadata() == null) { return 0; } return (int) mController.getMetadata().getLong( MediaMetadataCompat.METADATA_KEY_DURATION); } @Override public void onAttachedToHost(PlaybackGlueHost host) { mController.registerCallback(mMediaControllerCallback); } @Override public void onDetachedFromHost() { mController.unregisterCallback(mMediaControllerCallback); } @Override public void setProgressUpdatingEnabled(boolean enabled) { mHandler.removeCallbacks(mPositionUpdaterRunnable); if (!enabled) { return; } mHandler.postDelayed(mPositionUpdaterRunnable, getUpdatePeriod()); } @Override public long getSupportedActions() { long supportedActions = 0; if (mController.getPlaybackState() == null) { return supportedActions; } long actionsFromController = mController.getPlaybackState().getActions(); // Translation. if ((actionsFromController & PlaybackStateCompat.ACTION_PLAY_PAUSE) != 0) { supportedActions |= ACTION_PLAY_PAUSE; } if ((actionsFromController & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) { supportedActions |= ACTION_SKIP_TO_NEXT; } if ((actionsFromController & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) { supportedActions |= ACTION_SKIP_TO_PREVIOUS; } if ((actionsFromController & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) { supportedActions |= ACTION_FAST_FORWARD; } if ((actionsFromController & PlaybackStateCompat.ACTION_REWIND) != 0) { supportedActions |= ACTION_REWIND; } if ((actionsFromController & PlaybackStateCompat.ACTION_SET_REPEAT_MODE) != 0) { supportedActions |= ACTION_REPEAT; } if ((actionsFromController & PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE) != 0) { supportedActions |= ACTION_SHUFFLE; } return supportedActions; } /** * This function will translate the index of RepeatAction in PlaybackControlsRow to * the repeat mode which is defined by PlaybackStateCompat. * * @param repeatActionIndex Index of RepeatAction in PlaybackControlsRow. * @return Repeat Mode in playback state. */ private int mapRepeatActionToRepeatMode(int repeatActionIndex) { switch (repeatActionIndex) { case PlaybackControlsRow.RepeatAction.INDEX_NONE: return PlaybackStateCompat.REPEAT_MODE_NONE; case PlaybackControlsRow.RepeatAction.INDEX_ALL: return PlaybackStateCompat.REPEAT_MODE_ALL; case PlaybackControlsRow.RepeatAction.INDEX_ONE: return PlaybackStateCompat.REPEAT_MODE_ONE; } return -1; } /** * This function will translate the index of RepeatAction in PlaybackControlsRow to * the repeat mode which is defined by PlaybackStateCompat. * * @param shuffleActionIndex Index of RepeatAction in PlaybackControlsRow. * @return Repeat Mode in playback state. */ private int mapShuffleActionToShuffleMode(int shuffleActionIndex) { switch (shuffleActionIndex) { case PlaybackControlsRow.ShuffleAction.INDEX_OFF: return PlaybackStateCompat.SHUFFLE_MODE_NONE; case PlaybackControlsRow.ShuffleAction.INDEX_ON: return PlaybackStateCompat.SHUFFLE_MODE_ALL; } return -1; } }