summaryrefslogtreecommitdiffstats
path: root/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java
diff options
context:
space:
mode:
Diffstat (limited to 'samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java')
-rw-r--r--samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java446
1 files changed, 446 insertions, 0 deletions
diff --git a/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java
new file mode 100644
index 000000000..bc757cd19
--- /dev/null
+++ b/samples/SupportLeanbackShowcase/app/src/main/java/android/support/v17/leanback/supportleanbackshowcase/app/media/MediaPlayerGlue.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2015 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.supportleanbackshowcase.app.media;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.v17.leanback.app.PlaybackControlGlue;
+import android.support.v17.leanback.app.PlaybackOverlayFragment;
+import android.support.v17.leanback.widget.Action;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.ControlButtonPresenterSelector;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PlaybackControlsRow;
+import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+
+import java.io.IOException;
+
+/**
+ * This glue extends the {@link PlaybackControlGlue} with a {@link MediaPlayer} synchronization. It
+ * supports 7 actions: <ul> <li>{@link android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction}</li>
+ * <li>{@link android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction}</li> <li>{@link
+ * android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction}</li> <li>{@link
+ * android.support.v17.leanback.widget.PlaybackControlsRow.ShuffleAction}</li> <li>{@link
+ * android.support.v17.leanback.widget.PlaybackControlsRow.RepeatAction}</li> <li>{@link
+ * android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsDownAction}</li> <li>{@link
+ * android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsUpAction}</li> </ul>
+ * <p/>
+ */
+public abstract class MediaPlayerGlue extends PlaybackControlGlue implements
+ OnItemViewSelectedListener {
+
+ public static final int FAST_FORWARD_REWIND_STEP = 10 * 1000; // in milliseconds
+ public static final int FAST_FORWARD_REWIND_REPEAT_DELAY = 200; // in milliseconds
+ private static final String TAG = "MediaPlayerGlue";
+ protected final PlaybackControlsRow.ThumbsDownAction mThumbsDownAction;
+ protected final PlaybackControlsRow.ThumbsUpAction mThumbsUpAction;
+ private final Context mContext;
+ private final MediaPlayer mPlayer = new MediaPlayer();
+ private final PlaybackControlsRow.RepeatAction mRepeatAction;
+ private final PlaybackControlsRow.ShuffleAction mShuffleAction;
+ private PlaybackControlsRow mControlsRow;
+ private Runnable mRunnable;
+ private Handler mHandler = new Handler();
+ private boolean mPaused = false;
+ private boolean mInitialized = false; // true when the MediaPlayer is prepared/initialized
+ private OnMediaFileFinishedPlayingListener mMediaFileFinishedPlayingListener;
+ private Action mSelectedAction; // the action which is currently selected by the user
+ private long mLastKeyDownEvent = 0L; // timestamp when the last DPAD_CENTER KEY_DOWN occurred
+ private MetaData mMetaData;
+ private Uri mMediaSourceUri = null;
+ private String mMediaSourcePath = null;
+
+ public MediaPlayerGlue(Context context, PlaybackOverlayFragment fragment) {
+ super(context, fragment, new int[]{1});
+ mContext = context;
+
+ // Instantiate secondary actions
+ mShuffleAction = new PlaybackControlsRow.ShuffleAction(mContext);
+ mRepeatAction = new PlaybackControlsRow.RepeatAction(mContext);
+ mThumbsDownAction = new PlaybackControlsRow.ThumbsDownAction(mContext);
+ mThumbsUpAction = new PlaybackControlsRow.ThumbsUpAction(mContext);
+ mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
+ mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
+
+ // Setup controls and notify UI about change.
+ setFadingEnabled(false);
+ onStateChanged();
+
+ // Register selected listener such that we know what action the user currently has focused.
+ fragment.setOnItemViewSelectedListener(this);
+ }
+
+ /**
+ * Will reset the {@link MediaPlayer} and the glue such that a new file can be played. You are
+ * not required to call this method before playing the first file. However you have to call it
+ * before playing a second one.
+ */
+ public void reset() {
+ mPaused = mInitialized = false;
+ mPlayer.reset();
+ }
+
+ public void setOnMediaFileFinishedPlayingListener(OnMediaFileFinishedPlayingListener listener) {
+ mMediaFileFinishedPlayingListener = listener;
+ }
+
+ /**
+ * Override this method in case you need to add different secondary actions.
+ *
+ * @param secondaryActionsAdapter The adapter you need to add the {@link Action}s to.
+ */
+ protected void addSecondaryActions(ArrayObjectAdapter secondaryActionsAdapter) {
+ secondaryActionsAdapter.add(mShuffleAction);
+ secondaryActionsAdapter.add(mRepeatAction);
+ secondaryActionsAdapter.add(mThumbsDownAction);
+ secondaryActionsAdapter.add(mThumbsUpAction);
+ }
+
+ /**
+ * @see MediaPlayer#setDisplay(SurfaceHolder)
+ */
+ public void setDisplay(SurfaceHolder surfaceHolder) {
+ mPlayer.setDisplay(surfaceHolder);
+ }
+
+ /**
+ * Use this method to setup the {@link PlaybackControlsRowPresenter}. It'll be called
+ * <u>after</u> the {@link PlaybackControlsRowPresenter} has been created and the primary and
+ * secondary actions have been added.
+ *
+ * @param presenter The PlaybackControlsRowPresenter used to display the controls.
+ */
+ public void setupControlsRowPresenter(PlaybackControlsRowPresenter presenter) {
+ // TODO: hahnr@ move into resources
+ presenter.setProgressColor(Color.parseColor("#feab91"));
+ presenter.setBackgroundColor(Color.parseColor("#db2a0f"));
+ }
+
+ @Override public PlaybackControlsRowPresenter createControlsRowAndPresenter() {
+ PlaybackControlsRowPresenter presenter = super.createControlsRowAndPresenter();
+ mControlsRow = getControlsRow();
+
+ // Add secondary actions and change the control row color.
+ ArrayObjectAdapter secondaryActions = new ArrayObjectAdapter(
+ new ControlButtonPresenterSelector());
+ mControlsRow.setSecondaryActionsAdapter(secondaryActions);
+ addSecondaryActions(secondaryActions);
+ setupControlsRowPresenter(presenter);
+ return presenter;
+ }
+
+ @Override public void enableProgressUpdating(final boolean enabled) {
+ if (!enabled) {
+ if (mRunnable != null) mHandler.removeCallbacks(mRunnable);
+ return;
+ }
+ mRunnable = new Runnable() {
+ @Override public void run() {
+ updateProgress();
+ Log.d(TAG, "enableProgressUpdating(boolean)");
+ mHandler.postDelayed(this, getUpdatePeriod());
+ }
+ };
+ mHandler.postDelayed(mRunnable, getUpdatePeriod());
+ }
+
+ @Override public void onActionClicked(Action action) {
+ // If either 'Shuffle' or 'Repeat' has been clicked we need to make sure the acitons index
+ // is incremented and the UI updated such that we can display the new state.
+ super.onActionClicked(action);
+ if (action instanceof PlaybackControlsRow.ShuffleAction) {
+ mShuffleAction.nextIndex();
+ } else if (action instanceof PlaybackControlsRow.RepeatAction) {
+ mRepeatAction.nextIndex();
+ } else if (action instanceof PlaybackControlsRow.ThumbsUpAction) {
+ if (mThumbsUpAction.getIndex() == PlaybackControlsRow.ThumbsAction.SOLID) {
+ mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
+ } else {
+ mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.SOLID);
+ mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
+ }
+ } else if (action instanceof PlaybackControlsRow.ThumbsDownAction) {
+ if (mThumbsDownAction.getIndex() == PlaybackControlsRow.ThumbsAction.SOLID) {
+ mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
+ } else {
+ mThumbsDownAction.setIndex(PlaybackControlsRow.ThumbsAction.SOLID);
+ mThumbsUpAction.setIndex(PlaybackControlsRow.ThumbsAction.OUTLINE);
+ }
+ }
+ onMetadataChanged();
+ }
+
+ @Override public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // This method is overridden in order to make implement fast forwarding and rewinding when
+ // the user keeps the corresponding action pressed.
+ // We only consume DPAD_CENTER Action_DOWN events on the Fast-Forward and Rewind action and
+ // only if it has not been pressed in the last X milliseconds.
+ boolean consume = mSelectedAction instanceof PlaybackControlsRow.RewindAction;
+ consume = consume || mSelectedAction instanceof PlaybackControlsRow.FastForwardAction;
+ consume = consume && mInitialized;
+ consume = consume && event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER;
+ consume = consume && event.getAction() == KeyEvent.ACTION_DOWN;
+ consume = consume && System
+ .currentTimeMillis() - mLastKeyDownEvent > FAST_FORWARD_REWIND_REPEAT_DELAY;
+ if (consume) {
+ mLastKeyDownEvent = System.currentTimeMillis();
+ int newPosition = getCurrentPosition() + FAST_FORWARD_REWIND_STEP;
+ if (mSelectedAction instanceof PlaybackControlsRow.RewindAction) {
+ newPosition = getCurrentPosition() - FAST_FORWARD_REWIND_STEP;
+ }
+ // Make sure the new calculated duration is in the range 0 >= X >= MediaDuration
+ if (newPosition < 0) newPosition = 0;
+ if (newPosition > getMediaDuration()) newPosition = getMediaDuration();
+ seekTo(newPosition);
+ return true;
+ }
+ return super.onKey(v, keyCode, event);
+ }
+
+ @Override public boolean hasValidMedia() {
+ return mMetaData != null;
+ }
+
+ @Override public boolean isMediaPlaying() {
+ return mPlayer.isPlaying();
+ }
+
+ @Override public CharSequence getMediaTitle() {
+ return hasValidMedia() ? mMetaData.getTitle() : "N/a";
+ }
+
+ @Override public CharSequence getMediaSubtitle() {
+ return hasValidMedia() ? mMetaData.getArtist() : "N/a";
+ }
+
+ @Override public int getMediaDuration() {
+ return mInitialized ? mPlayer.getDuration() : 0;
+ }
+
+ @Override public Drawable getMediaArt() {
+ return hasValidMedia() ? mMetaData.getCover() : null;
+ }
+
+ @Override public long getSupportedActions() {
+ return PlaybackControlGlue.ACTION_PLAY_PAUSE | PlaybackControlGlue.ACTION_FAST_FORWARD | PlaybackControlGlue.ACTION_REWIND;
+ }
+
+ @Override public int getCurrentSpeedId() {
+ // 0 = Pause, 1 = Normal Playback Speed
+ return mPlayer.isPlaying() ? 1 : 0;
+ }
+
+ @Override public int getCurrentPosition() {
+ return mInitialized ? mPlayer.getCurrentPosition() : 0;
+ }
+
+ @Override protected void startPlayback(int speed) throws IllegalStateException {
+ if (mPaused) {
+ mPlayer.start();
+ } else if (!isMediaPlaying()) {
+ reset();
+ try {
+ if (mMediaSourceUri != null) mPlayer.setDataSource(getContext(), mMediaSourceUri);
+ else mPlayer.setDataSource(mMediaSourcePath);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override public void onPrepared(MediaPlayer mp) {
+ mInitialized = true;
+ mPaused = false;
+ mPlayer.start();
+ onMetadataChanged();
+ onStateChanged();
+ updateProgress();
+ }
+ });
+ mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override public void onCompletion(MediaPlayer mp) {
+ if (mInitialized && mMediaFileFinishedPlayingListener != null)
+ mMediaFileFinishedPlayingListener.onMediaFileFinishedPlaying(mMetaData);
+ }
+ });
+ mPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
+ @Override public void onBufferingUpdate(MediaPlayer mp, int percent) {
+ mControlsRow.setBufferedProgress((int) (mp.getDuration() * (percent / 100f)));
+ }
+ });
+ mPlayer.prepareAsync();
+ onStateChanged();
+ }
+ }
+
+ @Override protected void pausePlayback() {
+ if (mPlayer.isPlaying()) {
+ mPlayer.pause();
+ mPaused = true;
+ }
+ }
+
+ @Override protected void skipToNext() {
+ // Not supported.
+ }
+
+ @Override protected void skipToPrevious() {
+ // Not supported.
+ }
+
+ /**
+ * Called whenever the user presses fast-forward/rewind or when the user keeps the corresponding
+ * action pressed.
+ *
+ * @param newPosition The new position of the media track in milliseconds.
+ */
+ protected void seekTo(int newPosition) {
+ mPlayer.seekTo(newPosition);
+ }
+
+ /**
+ * @see MediaPlayer#setDataSource(Context, Uri)
+ */
+ public void setMediaSource(Uri uri) {
+ mMediaSourceUri = uri;
+ }
+
+ /**
+ * @see MediaPlayer#setDataSource(String)
+ */
+ public void setMediaSource(String path) {
+ mMediaSourcePath = path;
+ }
+
+ /**
+ * Call to <code>startPlayback(1)</code>.
+ *
+ * @throws IllegalStateException See {@link MediaPlayer} for further information about it's
+ * different states when setting a data source and preparing it to be played.
+ */
+ public void startPlayback() throws IllegalStateException {
+ startPlayback(1);
+ }
+
+ /**
+ * @return Returns <code>true</code> iff 'Shuffle' is <code>ON</code>.
+ */
+ public boolean useShuffle() {
+ return mShuffleAction.getIndex() == PlaybackControlsRow.ShuffleAction.ON;
+ }
+
+ /**
+ * @return Returns <code>true</code> iff 'Repeat-One' is <code>ON</code>.
+ */
+ public boolean repeatOne() {
+ return mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.ONE;
+ }
+
+ /**
+ * @return Returns <code>true</code> iff 'Repeat-All' is <code>ON</code>.
+ */
+ public boolean repeatAll() {
+ return mRepeatAction.getIndex() == PlaybackControlsRow.RepeatAction.ALL;
+ }
+
+ public void setMetaData(MetaData metaData) {
+ mMetaData = metaData;
+ onMetadataChanged();
+ }
+
+ /**
+ * This is a listener implementation for the {@link OnItemViewSelectedListener} of the {@link
+ * PlaybackOverlayFragment}. This implementation is required in order to detect KEY_DOWN events
+ * on the {@link android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction} and
+ * {@link android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction}. Thus you should
+ * <u>NOT</u> set another {@link OnItemViewSelectedListener} on your {@link
+ * PlaybackOverlayFragment}. Instead, override this method and call its super (this)
+ * implementation.
+ *
+ * @see OnItemViewSelectedListener#onItemSelected(Presenter.ViewHolder, Object,
+ * RowPresenter.ViewHolder, Row)
+ */
+ @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, Row row) {
+ if (item instanceof Action) {
+ mSelectedAction = (Action) item;
+ } else {
+ mSelectedAction = null;
+ }
+ }
+
+ /**
+ * A listener which will be called whenever a track is finished playing.
+ */
+ public interface OnMediaFileFinishedPlayingListener {
+
+ /**
+ * Called when a track is finished playing.
+ *
+ * @param metaData The track's {@link MetaData} which just finished playing.
+ */
+ void onMediaFileFinishedPlaying(MetaData metaData);
+
+ }
+
+ /**
+ * Holds the meta data such as track title, artist and cover art. It'll be used by the {@link
+ * MediaPlayerGlue}.
+ */
+ public static class MetaData {
+
+ private String mTitle;
+ private String mArtist;
+ private Drawable mCover;
+
+
+ public String getTitle() {
+ return mTitle;
+ }
+
+ public void setTitle(String title) {
+ this.mTitle = title;
+ }
+
+ public String getArtist() {
+ return mArtist;
+ }
+
+ public void setArtist(String artist) {
+ this.mArtist = artist;
+ }
+
+ public Drawable getCover() {
+ return mCover;
+ }
+
+ public void setCover(Drawable cover) {
+ this.mCover = cover;
+ }
+ }
+
+}