Java tutorial
/* * Copyright (c) 2014 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 org.mythtv.android.presentation.view.fragment; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.media.MediaDescription; import android.media.MediaMetadata; import android.media.MediaPlayer; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; import android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter; import android.support.v17.leanback.widget.Action; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.ClassPresenterSelector; import android.support.v17.leanback.widget.ControlButtonPresenterSelector; import android.support.v17.leanback.widget.CursorObjectAdapter; import android.support.v17.leanback.widget.ImageCardView; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ListRowPresenter; import android.support.v17.leanback.widget.OnActionClickedListener; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.OnItemViewSelectedListener; import android.support.v17.leanback.widget.PlaybackControlsRow; import android.support.v17.leanback.widget.PlaybackControlsRow.FastForwardAction; import android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction; import android.support.v17.leanback.widget.PlaybackControlsRow.RewindAction; 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.support.v4.app.ActivityOptionsCompat; import android.util.Log; import android.widget.VideoView; import com.bumptech.glide.Glide; import com.bumptech.glide.load.resource.drawable.GlideDrawable; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import org.mythtv.android.R; import org.mythtv.android.presentation.model.VideoModel; import org.mythtv.android.presentation.utils.Utils; import org.mythtv.android.presentation.view.activity.TvPlaybackOverlayActivity; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; import static android.media.session.MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; import static android.media.session.MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS; /* * The PlaybackOverlayFragment class handles the Fragment associated with displaying the UI for the * media controls such as play / pause / skip forward / skip backward etc. * * The UI is updated through events that it receives from its MediaController */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class TvPlaybackOverlayFragment extends android.support.v17.leanback.app.PlaybackOverlayFragment { //} implements LoaderManager.LoaderCallbacks<Cursor> { private static final String TAG = TvPlaybackOverlayFragment.class.getSimpleName(); public static final String VIDEO = "org.mythtv.player.Video"; public static final String SHARED_ELEMENT_NAME = "hero"; private static final boolean SHOW_DETAIL = true; private static final boolean HIDE_MORE_ACTIONS = false; private static final int BACKGROUND_TYPE = TvPlaybackOverlayFragment.BG_LIGHT; private static final int CARD_WIDTH = 150; private static final int CARD_HEIGHT = 240; private static final int DEFAULT_UPDATE_PERIOD = 1000; private static final int UPDATE_PERIOD = 16; private static final int SIMULATED_BUFFERED_TIME = 10000; private static final int CLICK_TRACKING_DELAY = 1000; private static final int INITIAL_SPEED = 10000; private final Handler mClickTrackingHandler = new Handler(); private VideoView mVideoView; // VideoView is used to play the video (media) in a view. private VideoModel mSelectedVideo; // Video is the currently playing Video and its metadata. private ArrayObjectAdapter mRowsAdapter; private ArrayObjectAdapter mPrimaryActionsAdapter; private ArrayObjectAdapter mSecondaryActionsAdapter; private PlayPauseAction mPlayPauseAction; // private ClosedCaptioningAction mClosedCaptioningAction; private FastForwardAction mFastForwardAction; // private HighQualityAction mHighQualityAction; // private MoreActions mMoreActions; // private RepeatAction mRepeatAction; private RewindAction mRewindAction; // private ShuffleAction mShuffleAction; // private SkipNextAction mSkipNextAction; // private SkipPreviousAction mSkipPreviousAction; // private ThumbsDownAction mThumbsDownAction; // private ThumbsUpAction mThumbsUpAction; private PlaybackControlsRow mPlaybackControlsRow; private Handler mHandler; private Runnable mRunnable; private long mDuration = -1; private int mFfwRwdSpeed = INITIAL_SPEED; private Timer mClickTrackingTimer; private int mClickCount; private int mQueueIndex = -1; private List<MediaSession.QueueItem> mQueue; private CursorObjectAdapter mVideoCursorAdapter; private int mPosition = 0; private long mStartTimeMillis; private MediaSession mSession; // MediaSession is used to hold the state of our media playback. // private final static int RECOMMENDED_VIDEOS_LOADER = 1; // private final static int QUEUE_VIDEOS_LOADER = 2; // private int mSpecificVideoLoaderId = 3; // private final VideoCursorMapper mVideoCursorMapper = new VideoCursorMapper(); // private LoaderManager.LoaderCallbacks<Cursor> mCallbacks; private MediaController mMediaController; private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback(); private static final String AUTO_PLAY = "auto_play"; @Override public void onAttach(Context context) { Log.d(TAG, "onAttach"); super.onAttach(context); // mCallbacks = this; createMediaSession(); mMediaController = getActivity().getMediaController(); Log.d(TAG, "register callback of mediaController"); mMediaController.registerCallback(mMediaControllerCallback); } @Override public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "onCreate"); super.onCreate(savedInstanceState); // Initialize instance variables. mVideoView = (VideoView) getActivity().findViewById(R.id.videoView); mSelectedVideo = getActivity().getIntent().getParcelableExtra(VIDEO); mHandler = new Handler(); mQueue = new ArrayList<>(); // Hack to get playback controls to be in focus right away. mVideoView.post(new Runnable() { @Override public void run() { mVideoView.setFocusable(false); mVideoView.setFocusableInTouchMode(false); } }); // Set up UI. setBackgroundType(BACKGROUND_TYPE); setFadingEnabled(false); setupRows(); // Start playing the selected video. setVideoPath(mSelectedVideo.videoUrl); updateMetadata(mSelectedVideo); playPause(true); // Start loading videos for the queue. // Bundle args = new Bundle(); // args.putString( VideoContract.VideoEntry.COLUMN_CATEGORY, mSelectedVideo.category ); // getLoaderManager().initLoader( QUEUE_VIDEOS_LOADER, args, mCallbacks ); // Set up listeners. setOnItemViewSelectedListener(new OnItemViewSelectedListener() { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { } }); setOnItemViewClickedListener(new ItemViewClickedListener()); } @Override public void onStop() { Log.d(TAG, "onStop"); stopProgressAutomation(); mRowsAdapter = null; super.onStop(); } @Override public void onDetach() { Log.d(TAG, "onDetach"); if (mMediaController != null) { Log.d(TAG, "Unregister callback of MediaController"); mMediaController.unregisterCallback(mMediaControllerCallback); } super.onDetach(); } @Override public void onDestroy() { Log.d(TAG, "onDestroy"); super.onDestroy(); mSession.release(); if (mVideoView != null) { mVideoView.stopPlayback(); mVideoView.suspend(); mVideoView.setVideoURI(null); } } private void setPosition(int position) { if (position > mDuration) { mPosition = (int) mDuration; } else if (position < 0) { mPosition = 0; mStartTimeMillis = System.currentTimeMillis(); } else { mPosition = position; } mStartTimeMillis = System.currentTimeMillis(); Log.d(TAG, "position set to " + mPosition); } private void createMediaSession() { Log.d(TAG, "createMediaSession"); if (mSession == null) { mSession = new MediaSession(getActivity(), getResources().getString(R.string.app_name)); mSession.setCallback(new MediaSessionCallback()); mSession.setFlags(FLAG_HANDLES_MEDIA_BUTTONS | FLAG_HANDLES_TRANSPORT_CONTROLS); mSession.setActive(true); // Set the Activity's MediaController used to invoke transport controls / adjust volume. getActivity().setMediaController(new MediaController(getActivity(), mSession.getSessionToken())); setPlaybackState(PlaybackState.STATE_NONE); } } @TargetApi(Build.VERSION_CODES.M) private MediaSession.QueueItem getQueueItem(VideoModel v) { MediaDescription desc = new MediaDescription.Builder().setDescription(v.description).setMediaId(v.id + "") .setMediaUri(Uri.parse(v.videoUrl)).setIconUri(Uri.parse(v.cardImageUrl)).setSubtitle(v.studio) .setTitle(v.title).build(); return new MediaSession.QueueItem(desc, v.id); } private void setPlaybackState(int state) { PlaybackState.Builder stateBuilder = new PlaybackState.Builder().setActions(getAvailableActions(state)); stateBuilder.setState(state, mPosition, 1.0f); mSession.setPlaybackState(stateBuilder.build()); } private long getAvailableActions(int nextState) { long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | PlaybackState.ACTION_PLAY_FROM_SEARCH | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS | PlaybackState.ACTION_PAUSE; if (nextState == PlaybackState.STATE_PLAYING) { actions |= PlaybackState.ACTION_PAUSE; } return actions; } @Override public void onPause() { super.onPause(); if (mVideoView.isPlaying()) { if (!getActivity().requestVisibleBehind(true)) { // Try to play behind launcher, but if it fails, stop playback. playPause(false); } } else { getActivity().requestVisibleBehind(false); } } private void playPause(boolean doPlay) { if (getPlaybackState() == PlaybackState.STATE_NONE) { setupCallbacks(); } if (doPlay && getPlaybackState() != PlaybackState.STATE_PLAYING) { if (mPosition > 0) { mVideoView.seekTo(mPosition); } mVideoView.start(); mStartTimeMillis = System.currentTimeMillis(); setPlaybackState(PlaybackState.STATE_PLAYING); } else { int timeElapsedSinceStart = (int) (System.currentTimeMillis() - mStartTimeMillis); setPosition(mPosition + timeElapsedSinceStart); mVideoView.pause(); setPlaybackState(PlaybackState.STATE_PAUSED); } } private void setVideoPath(String videoUrl) { Log.i(TAG, "setVideoPath : videoUrl=" + videoUrl); setPosition(0); mVideoView.setVideoPath(videoUrl); mStartTimeMillis = 0; mDuration = Utils.getDuration(videoUrl); } private void setupCallbacks() { mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { mVideoView.stopPlayback(); setPlaybackState(PlaybackState.STATE_STOPPED); return false; } }); mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { if (getPlaybackState() == PlaybackState.STATE_PLAYING) { mVideoView.start(); } } }); mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { setPlaybackState(PlaybackState.STATE_STOPPED); } }); } private void setupRows() { ClassPresenterSelector ps = new ClassPresenterSelector(); PlaybackControlsRowPresenter playbackControlsRowPresenter; Activity activity = getActivity(); if (SHOW_DETAIL) { playbackControlsRowPresenter = new PlaybackControlsRowPresenter(new DescriptionPresenter()); } else { playbackControlsRowPresenter = new PlaybackControlsRowPresenter(); } ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector(); mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); mSecondaryActionsAdapter = new ArrayObjectAdapter(presenterSelector); mPlayPauseAction = new PlayPauseAction(getActivity()); // mRepeatAction = new RepeatAction( activity ); // mThumbsUpAction = new ThumbsUpAction( activity ); // mThumbsDownAction = new ThumbsDownAction( activity ); // mShuffleAction = new ShuffleAction( activity ); // mSkipNextAction = new SkipNextAction( activity ); // mSkipPreviousAction = new SkipPreviousAction( activity ); mFastForwardAction = new FastForwardAction(activity); mRewindAction = new RewindAction(activity); // mHighQualityAction = new HighQualityAction( activity ); // mClosedCaptioningAction = new ClosedCaptioningAction( activity ); // mMoreActions = new MoreActions( activity ); // Add main controls to primary adapter. // mPrimaryActionsAdapter.add( mSkipPreviousAction ); mPrimaryActionsAdapter.add(mRewindAction); mPrimaryActionsAdapter.add(mPlayPauseAction); mPrimaryActionsAdapter.add(mFastForwardAction); // mPrimaryActionsAdapter.add( mSkipNextAction ); // Add rest of controls to secondary adapter. // mSecondaryActionsAdapter.add( mThumbsUpAction ); // mSecondaryActionsAdapter.add( mRepeatAction ); // mSecondaryActionsAdapter.add( mShuffleAction ); // mSecondaryActionsAdapter.add( mThumbsDownAction ); // mSecondaryActionsAdapter.add( mHighQualityAction ); // mSecondaryActionsAdapter.add( mClosedCaptioningAction ); // mSecondaryActionsAdapter.add( mMoreActions ); playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() { public void onActionClicked(Action action) { if (action.getId() == mPlayPauseAction.getId()) { togglePlayback(mPlayPauseAction.getIndex() == PlayPauseAction.PLAY); // } else if( action.getId() == mSkipNextAction.getId() ) { // // next(); // // } else if( action.getId() == mSkipPreviousAction.getId() ) { // // prev(); } else if (action.getId() == mFastForwardAction.getId()) { fastForward(); } else if (action.getId() == mRewindAction.getId()) { fastRewind(); } if (action instanceof PlaybackControlsRow.MultiAction) { notifyChanged(action); } } }); playbackControlsRowPresenter.setSecondaryActionsHidden(HIDE_MORE_ACTIONS); ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter); ps.addClassPresenter(ListRow.class, new ListRowPresenter()); mRowsAdapter = new ArrayObjectAdapter(ps); addPlaybackControlsRow(); addOtherRows(); setAdapter(mRowsAdapter); } private void togglePlayback(boolean playPause) { if (playPause) { mMediaController.getTransportControls().play(); } else { mMediaController.getTransportControls().pause(); } } private void addPlaybackControlsRow() { if (SHOW_DETAIL) { mPlaybackControlsRow = new PlaybackControlsRow(mSelectedVideo); } else { mPlaybackControlsRow = new PlaybackControlsRow(); } mRowsAdapter.add(mPlaybackControlsRow); updatePlaybackRow(); mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter); mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter); } private void notifyChanged(Action action) { int index = mPrimaryActionsAdapter.indexOf(action); if (index >= 0) { mPrimaryActionsAdapter.notifyArrayItemRangeChanged(index, 1); } else { index = mSecondaryActionsAdapter.indexOf(action); if (index >= 0) { mSecondaryActionsAdapter.notifyArrayItemRangeChanged(index, 1); } } } private void updatePlaybackRow() { mPlaybackControlsRow.setCurrentTime(0); mPlaybackControlsRow.setBufferedProgress(0); mRowsAdapter.notifyArrayItemRangeChanged(0, 1); } private void updateMovieView(MediaMetadata metadata) { VideoModel v = new VideoModel.VideoModelBuilder().buildFromMediaDesc(metadata.getDescription()); long dur = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION); // PlaybackControlsRow doesn't allow you to set the item, so we must create a new one // because our Video class is now immutable. // TODO(ryanseys): Implement Playback Glue support so this can be mitigated. mPlaybackControlsRow = new PlaybackControlsRow(v); mPlaybackControlsRow.setTotalTime((int) dur); // Show the video card image if there is enough room in the UI for it. // If you have many primary actions, you may not have enough room. updateVideoImage(v.cardImageUrl); mRowsAdapter.clear(); mRowsAdapter.add(mPlaybackControlsRow); updatePlaybackRow(); mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter); mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter); addOtherRows(); } /** * Creates a ListRow for related videos. */ private void addOtherRows() { // mVideoCursorAdapter = new CursorObjectAdapter( new CardPresenter() ); // mVideoCursorAdapter.setMapper( new VideoCursorMapper() ); // // Bundle args = new Bundle(); // args.putString( VideoContract.VideoEntry.COLUMN_CATEGORY, mSelectedVideo.category ); // getLoaderManager().initLoader( RECOMMENDED_VIDEOS_LOADER, args, this ); // // HeaderItem header = new HeaderItem( getString( R.string.related_movies ) ); // mRowsAdapter.add( new ListRow( header, mVideoCursorAdapter ) ); } private int getUpdatePeriod() { if (getView() == null || mPlaybackControlsRow.getTotalTime() <= 0) { return DEFAULT_UPDATE_PERIOD; } return Math.max(UPDATE_PERIOD, mPlaybackControlsRow.getTotalTime() / getView().getWidth()); } private void startProgressAutomation() { if (mRunnable == null) { mRunnable = new Runnable() { @Override public void run() { int updatePeriod = getUpdatePeriod(); int currentTime = mPlaybackControlsRow.getCurrentTime() + updatePeriod; int totalTime = mPlaybackControlsRow.getTotalTime(); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME); if (totalTime > 0 && totalTime <= currentTime) { stopProgressAutomation(); next(); } else { mHandler.postDelayed(this, updatePeriod); } } }; mHandler.postDelayed(mRunnable, getUpdatePeriod()); } } private void next() { mMediaController.getTransportControls().skipToNext(); } private void prev() { mMediaController.getTransportControls().skipToPrevious(); } private void fastForward() { startClickTrackingTimer(); mMediaController.getTransportControls().fastForward(); } private void fastRewind() { startClickTrackingTimer(); mMediaController.getTransportControls().rewind(); } private void stopProgressAutomation() { if (mHandler != null && mRunnable != null) { mHandler.removeCallbacks(mRunnable); mRunnable = null; } } private void updateVideoImage(String uri) { Glide.with(this).load(uri).centerCrop().into(new SimpleTarget<GlideDrawable>(CARD_WIDTH, CARD_HEIGHT) { @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> glideAnimation) { mPlaybackControlsRow.setImageDrawable(resource); mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size()); } }); } private void startClickTrackingTimer() { if (null != mClickTrackingTimer) { mClickCount++; mClickTrackingTimer.cancel(); } else { mClickCount = 0; mFfwRwdSpeed = INITIAL_SPEED; } mClickTrackingTimer = new Timer(); mClickTrackingTimer.schedule(new UpdateFfwRwdSpeedTask(), CLICK_TRACKING_DELAY); } // @Override // public Loader<Cursor> onCreateLoader( int id, Bundle args ) { // switch( id ) { // // case RECOMMENDED_VIDEOS_LOADER: // Fall through. // case QUEUE_VIDEOS_LOADER : { // // String category = args.getString( VideoContract.VideoEntry.COLUMN_CATEGORY ); // return new CursorLoader( // getActivity(), // VideoContract.VideoEntry.CONTENT_URI, // null, // Projection to return - null means return all fields. // VideoContract.VideoEntry.COLUMN_CATEGORY + " = ?", // Selection clause is category. // new String[]{category}, // Select based on the category. // null // Default sort order // ); // // } // // default : { // // // Loading a specific video. // String videoId = args.getString( VideoContract.VideoEntry._ID ); // return new CursorLoader( // getActivity(), // VideoContract.VideoEntry.CONTENT_URI, // null, // Projection to return - null means return all fields. // VideoContract.VideoEntry._ID + " = ?", // Selection clause is id. // new String[]{videoId}, // Select based on the id. // null // Default sort order // ); // // } // // } // // } // @Override // public void onLoadFinished( Loader<Cursor> loader, Cursor cursor ) { // // if( cursor != null && cursor.moveToFirst() ) { // // switch( loader.getId() ) { // // case QUEUE_VIDEOS_LOADER : { // // mQueue.clear(); // while( !cursor.isAfterLast() ) { // VideoModel v = (VideoModel) mVideoCursorMapper.convert( cursor ); // // // Set the queue index to the selected video. // if( v.id == mSelectedVideo.id ) { // // mQueueIndex = mQueue.size(); // Log.d( TAG, "mQueueIndex set to " + mQueueIndex ); // // } // // // Add the video to the queue. // MediaSession.QueueItem item = getQueueItem( v ); // mQueue.add( item ); // // cursor.moveToNext(); // } // // mSession.setQueue( mQueue ); // mSession.setQueueTitle( getString( R.string.queue_name ) ); // // break; // } // // case RECOMMENDED_VIDEOS_LOADER : { // // mVideoCursorAdapter.changeCursor( cursor ); // // break; // } // // default : { // // // Playing a specific video. // VideoModel video = (VideoModel) mVideoCursorMapper.convert( cursor ); // Bundle extras = new Bundle(); // extras.putBoolean( AUTO_PLAY, true ); // playVideo( video, extras ); // // break; // } // // } // // } // // } // @Override // public void onLoaderReset( Loader<Cursor> loader ) { // mVideoCursorAdapter.changeCursor( null ); // } private static class DescriptionPresenter extends AbstractDetailsDescriptionPresenter { @Override protected void onBindDescription(ViewHolder viewHolder, Object item) { VideoModel video = (VideoModel) item; viewHolder.getTitle().setText(video.title); viewHolder.getSubtitle().setText(video.studio); } } private class UpdateFfwRwdSpeedTask extends TimerTask { @Override public void run() { mClickTrackingHandler.post(new Runnable() { @Override public void run() { if (mClickCount == 0) { mFfwRwdSpeed = INITIAL_SPEED; } else if (mClickCount == 1) { mFfwRwdSpeed *= 2; } else if (mClickCount >= 2) { mFfwRwdSpeed *= 4; } mClickCount = 0; mClickTrackingTimer = null; } }); } } private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof VideoModel) { VideoModel video = (VideoModel) item; Log.d(TAG, "Item: " + item.toString()); Intent intent = new Intent(getActivity(), TvPlaybackOverlayActivity.class); intent.putExtra(VIDEO, video); Bundle bundle = ActivityOptionsCompat .makeSceneTransitionAnimation(getActivity(), ((ImageCardView) itemViewHolder.view).getMainImageView(), SHARED_ELEMENT_NAME) .toBundle(); getActivity().startActivity(intent, bundle); } } } private int getPlaybackState() { Activity activity = getActivity(); if (activity != null) { PlaybackState state = activity.getMediaController().getPlaybackState(); if (state != null) { return state.getState(); } else { return PlaybackState.STATE_NONE; } } return PlaybackState.STATE_NONE; } private void updateMetadata(final VideoModel videoModel) { final MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder(); metadataBuilder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, videoModel.id + ""); metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, videoModel.title); metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, videoModel.studio); metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION, videoModel.description); metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI, videoModel.cardImageUrl); metadataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION, mDuration); // And at minimum the title and artist for legacy support metadataBuilder.putString(MediaMetadata.METADATA_KEY_TITLE, videoModel.title); metadataBuilder.putString(MediaMetadata.METADATA_KEY_ARTIST, videoModel.studio); Glide.with(this).load(Uri.parse(videoModel.cardImageUrl)).asBitmap() .into(new SimpleTarget<Bitmap>(500, 500) { @Override public void onResourceReady(Bitmap bitmap, GlideAnimation anim) { metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART, bitmap); mSession.setMetadata(metadataBuilder.build()); } }); } private void playVideo(VideoModel v, Bundle extras) { Log.d(TAG, "Playing video " + v.title + " with url = " + v.videoUrl); Log.d(TAG, "Video: " + v.toString()); setVideoPath(v.videoUrl); setPlaybackState(PlaybackState.STATE_PAUSED); updateMetadata(v); playPause(extras.getBoolean(AUTO_PLAY)); } // An event was triggered by MediaController.TransportControls and must be handled here. // Here we update the media itself to act on the event that was triggered. private class MediaSessionCallback extends MediaSession.Callback { @Override public void onPlay() { playPause(true); } @Override public void onPause() { playPause(false); } @Override // This method should play any media item regardless of the Queue. public void onPlayFromMediaId(String mediaId, Bundle extras) { // Bundle args = new Bundle(); // args.putString( VideoContract.VideoEntry._ID, mediaId ); // getLoaderManager().initLoader( mSpecificVideoLoaderId++, args, mCallbacks ); } @Override public void onSkipToNext() { // Update the media to skip to the next video. mPosition = 0; Bundle bundle = new Bundle(); bundle.putBoolean(AUTO_PLAY, true); int nextIndex = ++mQueueIndex; if (nextIndex < mQueue.size()) { MediaSession.QueueItem item = mQueue.get(nextIndex); String mediaId = item.getDescription().getMediaId(); getActivity().getMediaController().getTransportControls().playFromMediaId(mediaId, bundle); } else { getActivity().onBackPressed(); // Return to details presenter. } } @Override public void onSkipToPrevious() { // Update the media to skip to the previous video. mPosition = 0; setPlaybackState(PlaybackState.STATE_SKIPPING_TO_PREVIOUS); Bundle bundle = new Bundle(); bundle.putBoolean(AUTO_PLAY, true); int prevIndex = --mQueueIndex; if (prevIndex >= 0) { MediaSession.QueueItem item = mQueue.get(prevIndex); String mediaId = item.getDescription().getMediaId(); getActivity().getMediaController().getTransportControls().playFromMediaId(mediaId, bundle); } else { getActivity().onBackPressed(); // Return to details presenter. } } @Override public void onSeekTo(long pos) { setPosition((int) pos); mVideoView.seekTo(mPosition); } @Override public void onFastForward() { if (mDuration != -1) { // Fast forward 10 seconds. int prevState = getPlaybackState(); setPlaybackState(PlaybackState.STATE_FAST_FORWARDING); setPosition(mVideoView.getCurrentPosition() + (10 * 1000)); mVideoView.seekTo(mPosition); setPlaybackState(prevState); } } @Override public void onRewind() { // Rewind 10 seconds. int prevState = getPlaybackState(); setPlaybackState(PlaybackState.STATE_REWINDING); setPosition(mVideoView.getCurrentPosition() - (10 * 1000)); mVideoView.seekTo(mPosition); setPlaybackState(prevState); } } private class MediaControllerCallback extends MediaController.Callback { @Override public void onPlaybackStateChanged(@NonNull PlaybackState state) { // The playback state has changed, so update your UI accordingly. // This should not update any media player / state! Log.d(TAG, "Playback state changed: " + state.getState()); int nextState = state.getState(); if (nextState == PlaybackState.STATE_PLAYING) { startProgressAutomation(); setFadingEnabled(true); mPlayPauseAction.setIndex(PlayPauseAction.PAUSE); mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlayPauseAction.PAUSE)); notifyChanged(mPlayPauseAction); } else if (nextState == PlaybackState.STATE_PAUSED) { stopProgressAutomation(); setFadingEnabled(false); mPlayPauseAction.setIndex(PlayPauseAction.PLAY); mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PlayPauseAction.PLAY)); notifyChanged(mPlayPauseAction); // } else if( nextState == PlaybackState.STATE_SKIPPING_TO_NEXT ) { // // startProgressAutomation(); // setFadingEnabled( true ); // notifyChanged( mSkipNextAction ); // // } else if( nextState == PlaybackState.STATE_SKIPPING_TO_PREVIOUS ) { // // startProgressAutomation(); // setFadingEnabled( true ); // notifyChanged( mSkipPreviousAction ); } int currentTime = (int) state.getPosition(); mPlaybackControlsRow.setCurrentTime(currentTime); mPlaybackControlsRow.setBufferedProgress(currentTime + SIMULATED_BUFFERED_TIME); } @Override public void onMetadataChanged(MediaMetadata metadata) { Log.d(TAG, "onMetadataChanged"); updateMovieView(metadata); } } }