/*
* 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 com.example.android.uamp.ui.tv;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.PlaybackOverlayFragment;
import android.support.v17.leanback.app.PlaybackOverlaySupportFragment;
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.HeaderItem;
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.PlaybackControlsRow;
import android.support.v17.leanback.widget.PlaybackControlsRow.PlayPauseAction;
import android.support.v17.leanback.widget.PlaybackControlsRow.SkipNextAction;
import android.support.v17.leanback.widget.PlaybackControlsRow.SkipPreviousAction;
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.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import com.example.android.uamp.AlbumArtCache;
import com.example.android.uamp.utils.LogHelper;
import com.example.android.uamp.utils.QueueHelper;
import java.util.List;
/*
* Show details of the currently playing song, along with playback controls and the playing queue.
*/
public class TvPlaybackFragment extends PlaybackOverlaySupportFragment {
private static final String TAG = LogHelper.makeLogTag(TvPlaybackFragment.class);
private static final int BACKGROUND_TYPE = PlaybackOverlayFragment.BG_DARK;
private static final int DEFAULT_UPDATE_PERIOD = 1000;
private static final int UPDATE_PERIOD = 16;
private ArrayObjectAdapter mRowsAdapter;
private ArrayObjectAdapter mPrimaryActionsAdapter;
protected PlayPauseAction mPlayPauseAction;
private SkipNextAction mSkipNextAction;
private SkipPreviousAction mSkipPreviousAction;
private PlaybackControlsRow mPlaybackControlsRow;
private List <MediaSessionCompat.QueueItem> mPlaylistQueue;
private int mDuration;
private Handler mHandler;
private Runnable mRunnable;
private long mLastPosition;
private long mLastPositionUpdateTime;
private BackgroundManager mBackgroundManager;
private ArrayObjectAdapter mListRowAdapter;
private ListRow mListRow;
private ClassPresenterSelector mPresenterSelector;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogHelper.i(TAG, "onCreate");
mBackgroundManager = BackgroundManager.getInstance(getActivity());
mBackgroundManager.attach(getActivity().getWindow());
mHandler = new Handler();
mListRowAdapter = new ArrayObjectAdapter(new CardPresenter());
mPresenterSelector = new ClassPresenterSelector();
mRowsAdapter = new ArrayObjectAdapter(mPresenterSelector);
setBackgroundType(BACKGROUND_TYPE);
setFadingEnabled(false);
}
private void initializePlaybackControls(MediaMetadataCompat metadata) {
setupRows();
addPlaybackControlsRow(metadata);
setAdapter(mRowsAdapter);
setOnItemViewClickedListener(new ItemViewClickedListener());
}
private void setupRows() {
PlaybackControlsRowPresenter playbackControlsRowPresenter;
playbackControlsRowPresenter = new PlaybackControlsRowPresenter(
new DescriptionPresenter());
playbackControlsRowPresenter.setOnActionClickedListener(new OnActionClickedListener() {
public void onActionClicked(Action action) {
if (getActivity() == null) {
return;
}
MediaControllerCompat controller = getActivity().getSupportMediaController();
if (controller == null) {
return;
}
MediaControllerCompat.TransportControls controls = controller.getTransportControls();
if (action.getId() == mPlayPauseAction.getId()) {
if (mPlayPauseAction.getIndex() == PlayPauseAction.PLAY) {
controls.play();
} else {
controls.pause();
}
} else if (action.getId() == mSkipNextAction.getId()) {
controls.skipToNext();
resetPlaybackRow();
} else if (action.getId() == mSkipPreviousAction.getId()) {
controls.skipToPrevious();
resetPlaybackRow();
}
if (action instanceof PlaybackControlsRow.MultiAction) {
((PlaybackControlsRow.MultiAction) action).nextIndex();
notifyChanged(action);
}
}
});
mPresenterSelector.addClassPresenter(PlaybackControlsRow.class,
playbackControlsRowPresenter);
}
private void addPlaybackControlsRow(MediaMetadataCompat metadata) {
mPlaybackControlsRow = new PlaybackControlsRow(new MutableMediaMetadataHolder(metadata));
mRowsAdapter.add(mPlaybackControlsRow);
resetPlaybackRow();
ControlButtonPresenterSelector presenterSelector = new ControlButtonPresenterSelector();
mPrimaryActionsAdapter = new ArrayObjectAdapter(presenterSelector);
mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
mPlayPauseAction = new PlayPauseAction(getActivity());
mSkipNextAction = new PlaybackControlsRow.SkipNextAction(getActivity());
mSkipPreviousAction = new PlaybackControlsRow.SkipPreviousAction(getActivity());
mPrimaryActionsAdapter.add(mSkipPreviousAction);
mPrimaryActionsAdapter.add(mPlayPauseAction);
mPrimaryActionsAdapter.add(mSkipNextAction);
}
protected void updatePlayListRow(List<MediaSessionCompat.QueueItem> playlistQueue) {
if (QueueHelper.equals(mPlaylistQueue, playlistQueue)) {
// if the playlist queue hasn't changed, we don't need to update it
return;
}
LogHelper.d(TAG, "Updating playlist queue ('now playing')");
mPlaylistQueue = playlistQueue;
if (playlistQueue == null || playlistQueue.isEmpty()) {
// Remove the playlist row if no items are in the playlist
mRowsAdapter.remove(mListRow);
mListRow = null;
return;
}
mListRowAdapter.clear();
for (int i = 0; i < playlistQueue.size(); i++) {
MediaSessionCompat.QueueItem item = playlistQueue.get(i);
mListRowAdapter.add(item);
}
if (mListRow == null) {
int queueSize = 0;
MediaControllerCompat controller = getActivity().getSupportMediaController();
if (controller != null && controller.getQueue() != null) {
queueSize = controller.getQueue().size();
}
HeaderItem header = new HeaderItem(0, queueSize + " song(s) in this playlist");
mPresenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
mListRow = new ListRow(header, mListRowAdapter);
mRowsAdapter.add(mListRow);
} else {
mRowsAdapter.notifyArrayItemRangeChanged(mRowsAdapter.indexOf(mListRow), 1);
}
}
private void notifyChanged(Action action) {
ArrayObjectAdapter adapter = mPrimaryActionsAdapter;
if (adapter.indexOf(action) >= 0) {
adapter.notifyArrayItemRangeChanged(adapter.indexOf(action), 1);
}
}
private void resetPlaybackRow() {
mDuration = 0;
mPlaybackControlsRow.setTotalTime(0);
mPlaybackControlsRow.setCurrentTime(0);
mRowsAdapter.notifyArrayItemRangeChanged(
mRowsAdapter.indexOf(mPlaybackControlsRow), 1);
}
private int getUpdatePeriod() {
if (getView() == null || mPlaybackControlsRow.getTotalTime() <= 0) {
return DEFAULT_UPDATE_PERIOD;
}
return Math.max(UPDATE_PERIOD, mPlaybackControlsRow.getTotalTime() / getView().getWidth());
}
protected void startProgressAutomation() {
if (mHandler != null && mRunnable != null) {
mHandler.removeCallbacks(mRunnable);
}
mRunnable = new Runnable() {
@Override
public void run() {
long elapsedTime = SystemClock.elapsedRealtime() - mLastPositionUpdateTime;
int currentPosition = Math.min(mDuration, (int) (mLastPosition + elapsedTime));
mPlaybackControlsRow.setCurrentTime(currentPosition);
mHandler.postDelayed(this, getUpdatePeriod());
}
};
mHandler.postDelayed(mRunnable, getUpdatePeriod());
setFadingEnabled(true);
}
protected void stopProgressAutomation() {
if (mHandler != null && mRunnable != null) {
mHandler.removeCallbacks(mRunnable);
setFadingEnabled(false);
}
mPlaybackControlsRow.setCurrentTime((int) mLastPosition);
}
private void updateAlbumArt(Uri artUri) {
AlbumArtCache.getInstance().fetch(artUri.toString(), new AlbumArtCache.FetchListener() {
@Override
public void onFetched(String artUrl, Bitmap bitmap, Bitmap icon) {
if (bitmap != null) {
Drawable artDrawable = new BitmapDrawable(
TvPlaybackFragment.this.getResources(), bitmap);
Drawable bgDrawable = new BitmapDrawable(
TvPlaybackFragment.this.getResources(), bitmap);
mPlaybackControlsRow.setImageDrawable(artDrawable);
mBackgroundManager.setDrawable(bgDrawable);
mRowsAdapter.notifyArrayItemRangeChanged(
mRowsAdapter.indexOf(mPlaybackControlsRow), 1);
}
}
}
);
}
protected void updateMetadata(MediaMetadataCompat metadata) {
if (mPlaybackControlsRow == null) {
initializePlaybackControls(metadata);
}
mDuration = (int) metadata.getLong(MediaMetadataCompat.METADATA_KEY_DURATION);
mPlaybackControlsRow.setTotalTime(mDuration);
((MutableMediaMetadataHolder) mPlaybackControlsRow.getItem()).metadata = metadata;
mRowsAdapter.notifyArrayItemRangeChanged(
mRowsAdapter.indexOf(mPlaybackControlsRow), 1);
updateAlbumArt(metadata.getDescription().getIconUri());
}
protected void updatePlaybackState(PlaybackStateCompat state) {
if (mPlaybackControlsRow == null) {
// We only update playback state after we get a valid metadata.
return;
}
mLastPosition = state.getPosition();
mLastPositionUpdateTime = state.getLastPositionUpdateTime();
switch (state.getState()) {
case PlaybackStateCompat.STATE_PLAYING:
startProgressAutomation();
mPlayPauseAction.setIndex(PlayPauseAction.PAUSE);
break;
case PlaybackStateCompat.STATE_PAUSED:
stopProgressAutomation();
mPlayPauseAction.setIndex(PlayPauseAction.PLAY);
break;
}
MediaControllerCompat controller = getActivity().getSupportMediaController();
updatePlayListRow(controller.getQueue());
mRowsAdapter.notifyArrayItemRangeChanged(
mRowsAdapter.indexOf(mPlaybackControlsRow), 1);
}
private static final class DescriptionPresenter extends AbstractDetailsDescriptionPresenter {
@Override
protected void onBindDescription(ViewHolder viewHolder, Object item) {
MutableMediaMetadataHolder data = ((MutableMediaMetadataHolder) item);
viewHolder.getTitle().setText(data.metadata.getDescription().getTitle());
viewHolder.getSubtitle().setText(data.metadata.getDescription().getSubtitle());
}
}
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object clickedItem,
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (clickedItem instanceof MediaSessionCompat.QueueItem) {
LogHelper.d(TAG, "item: ", clickedItem.toString());
MediaControllerCompat controller = getActivity().getSupportMediaController();
MediaSessionCompat.QueueItem item = (MediaSessionCompat.QueueItem) clickedItem;
if (!QueueHelper.isQueueItemPlaying(getActivity(), item)
|| controller.getPlaybackState().getState()
!= PlaybackStateCompat.STATE_PLAYING) {
controller.getTransportControls().skipToQueueItem(item.getQueueId());
}
}
}
}
private static final class MutableMediaMetadataHolder {
MediaMetadataCompat metadata;
public MutableMediaMetadataHolder(MediaMetadataCompat metadata) {
this.metadata = metadata;
}
}
}