package tv.emby.embyatv.playback; import android.animation.ObjectAnimator; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.support.v17.leanback.app.BackgroundManager; import android.support.v17.leanback.app.RowsFragment; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.OnItemViewSelectedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; import android.util.DisplayMetrics; import android.view.KeyEvent; import android.view.View; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; import mediabrowser.model.dto.BaseItemDto; import tv.emby.embyatv.R; import tv.emby.embyatv.TvApp; import tv.emby.embyatv.base.BaseActivity; import tv.emby.embyatv.details.FullDetailsActivity; import tv.emby.embyatv.details.ItemListActivity; import tv.emby.embyatv.imagehandling.PicassoBackgroundManagerTarget; import tv.emby.embyatv.itemhandling.BaseRowItem; import tv.emby.embyatv.model.GotFocusEvent; import tv.emby.embyatv.presentation.PositionableListRowPresenter; import tv.emby.embyatv.ui.ClockUserView; import tv.emby.embyatv.ui.GenreButton; import tv.emby.embyatv.ui.ImageButton; import tv.emby.embyatv.util.InfoLayoutHelper; import tv.emby.embyatv.util.KeyProcessor; import tv.emby.embyatv.util.Utils; /** * Created by Eric on 2/19/2015. */ public class AudioNowPlayingActivity extends BaseActivity { private int BUTTON_SIZE; private LinearLayout mGenreRow; private ImageButton mPlayPauseButton; private ImageButton mNextButton; private ImageButton mPrevButton; private ImageButton mRepeatButton; private ImageButton mShuffleButton; private ImageButton mAlbumButton; private ImageButton mArtistButton; private ImageButton mSaveButton; private ClockUserView mClock; private TextView mCounter; private ScrollView mScrollView; private ImageView mLogoImage; private RelativeLayout mSSArea; private TextView mSSTime; private TextView mSSAlbumSong; private TextView mSSQueueStatus; private TextView mSSUpNext; private String mDisplayDuration; private Target mBackgroundTarget; private Drawable mDefaultBackground; private DisplayMetrics mMetrics; private TextView mArtistName; private TextView mSongTitle; private TextView mAlbumTitle; private TextView mCurrentNdx; private TextView mTotal; private ImageView mPoster; private ProgressBar mCurrentProgress; private TextView mCurrentPos; private TextView mRemainingTime; private int mCurrentDuration; private RowsFragment mRowsFragment; private ArrayObjectAdapter mRowsAdapter; private static PositionableListRowPresenter mAudioQueuePresenter; private TvApp mApplication; private AudioNowPlayingActivity mActivity; private Handler mLoopHandler = new Handler(); private Runnable mBackdropLoop; public static int BACKDROP_ROTATION_INTERVAL = 10000; private Typeface roboto; private BaseItemDto mBaseItem; private ListRow mQueueRow; private long lastUserInteraction; private boolean ssActive; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_audio_now_playing); lastUserInteraction = System.currentTimeMillis(); BUTTON_SIZE = Utils.convertDpToPixel(this, 35); mApplication = TvApp.getApplication(); mActivity = this; roboto = mApplication.getDefaultFont(); mClock = (ClockUserView) findViewById(R.id.clock); mPoster = (ImageView) findViewById(R.id.poster); mArtistName = (TextView) findViewById(R.id.artistTitle); mArtistName.setTypeface(roboto); mGenreRow = (LinearLayout) findViewById(R.id.genreRow); mSongTitle = (TextView) findViewById(R.id.songTitle); mSongTitle.setTypeface(roboto); mAlbumTitle = (TextView) findViewById(R.id.albumTitle); mAlbumTitle.setTypeface(roboto); mCurrentNdx = (TextView) findViewById(R.id.currentNdx); mScrollView = (ScrollView) findViewById(R.id.mainScroller); mCounter = (TextView) findViewById(R.id.counter); mCounter.setTypeface(roboto); mLogoImage = (ImageView) findViewById(R.id.artistLogo); mSSArea = (RelativeLayout) findViewById(R.id.ssInfoArea); mSSTime = (TextView) findViewById(R.id.ssTime); mSSTime.setTypeface(roboto); mSSAlbumSong = (TextView) findViewById(R.id.ssAlbumSong); mSSAlbumSong.setTypeface(roboto); mSSQueueStatus = (TextView) findViewById(R.id.ssQueueStatus); mSSQueueStatus.setTypeface(roboto); mSSUpNext = (TextView) findViewById(R.id.ssUpNext); mSSUpNext.setTypeface(roboto); mPlayPauseButton = (ImageButton) findViewById(R.id.playPauseBtn); mPlayPauseButton.setSecondaryImage(R.drawable.lb_ic_pause); mPlayPauseButton.setPrimaryImage(R.drawable.play); TextView helpView = (TextView) findViewById(R.id.buttonTip); mPrevButton = (ImageButton) findViewById(R.id.prevBtn); mPrevButton.setHelpView(helpView); mPrevButton.setHelpText(getString(R.string.lbl_prev_item)); mPrevButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MediaManager.prevAudioItem(); } }); mPrevButton.setGotFocusListener(mainAreaFocusListener); mNextButton = (ImageButton) findViewById(R.id.nextBtn); mNextButton.setHelpView(helpView); mNextButton.setHelpText(getString(R.string.lbl_next_item)); mNextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MediaManager.nextAudioItem(); } }); mNextButton.setGotFocusListener(mainAreaFocusListener); mRepeatButton = (ImageButton) findViewById(R.id.repeatBtn); mRepeatButton.setHelpView(helpView); mRepeatButton.setHelpText(getString(R.string.lbl_toggle_repeat)); mRepeatButton.setPrimaryImage(R.drawable.loop); mRepeatButton.setSecondaryImage(R.drawable.loopred); mRepeatButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MediaManager.toggleRepeat(); updateButtons(MediaManager.isPlayingAudio()); } }); mSaveButton = (ImageButton) findViewById(R.id.saveBtn); mSaveButton.setHelpView(helpView); mSaveButton.setHelpText(getString(R.string.lbl_save_as_playlist)); mSaveButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { MediaManager.saveAudioQueue(mActivity); } }); mRepeatButton.setGotFocusListener(mainAreaFocusListener); mShuffleButton = (ImageButton) findViewById(R.id.shuffleBtn); mShuffleButton.setHelpView(helpView); mShuffleButton.setHelpText(getString(R.string.lbl_reshuffle_queue)); mShuffleButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(mActivity) .setTitle(R.string.lbl_shuffle) .setMessage(R.string.msg_reshuffle_audio_queue) .setPositiveButton(mActivity.getString(R.string.lbl_yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { MediaManager.shuffleAudioQueue(); mRowsAdapter.remove(mQueueRow); addQueue(); } }) .setNegativeButton(mActivity.getString(R.string.lbl_no), null) .show(); } }); mShuffleButton.setGotFocusListener(mainAreaFocusListener); mAlbumButton = (ImageButton) findViewById(R.id.albumBtn); mAlbumButton.setHelpView(helpView); mAlbumButton.setHelpText(getString(R.string.lbl_open_album)); mAlbumButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent album = new Intent(mActivity, ItemListActivity.class); album.putExtra("ItemId", mBaseItem.getAlbumId()); mActivity.startActivity(album); } }); mAlbumButton.setGotFocusListener(mainAreaFocusListener); mArtistButton = (ImageButton) findViewById(R.id.artistBtn); mArtistButton.setHelpView(helpView); mArtistButton.setHelpText(getString(R.string.lbl_open_artist)); mArtistButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mBaseItem.getAlbumArtists() != null && mBaseItem.getAlbumArtists().size() > 0) { Intent artist = new Intent(mActivity, FullDetailsActivity.class); artist.putExtra("ItemId", mBaseItem.getAlbumArtists().get(0).getId()); mActivity.startActivity(artist); } } }); mArtistButton.setGotFocusListener(mainAreaFocusListener); mCurrentProgress = (ProgressBar) findViewById(R.id.playerProgress); mCurrentPos = (TextView) findViewById(R.id.currentPos); mRemainingTime = (TextView) findViewById(R.id.remainingTime); mTotal = (TextView) findViewById(R.id.total); mPlayPauseButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (MediaManager.isPlayingAudio()) MediaManager.pauseAudio(); else MediaManager.resumeAudio(); } }); BackgroundManager backgroundManager = BackgroundManager.getInstance(this); backgroundManager.attach(getWindow()); mBackgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager); mMetrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(mMetrics); mRowsFragment = new RowsFragment(); getFragmentManager().beginTransaction().add(R.id.rowsFragment, mRowsFragment).commit(); mRowsFragment.setOnItemViewClickedListener(new ItemViewClickedListener()); mRowsFragment.setOnItemViewSelectedListener(new ItemViewSelectedListener()); mAudioQueuePresenter = new PositionableListRowPresenter(); mRowsAdapter = new ArrayObjectAdapter(mAudioQueuePresenter); mRowsFragment.setAdapter(mRowsAdapter); addQueue(); mDefaultBackground = getResources().getDrawable(R.drawable.moviebg); mPlayPauseButton.requestFocus(); } protected void addQueue() { mQueueRow = new ListRow(new HeaderItem("Current Queue"), MediaManager.getCurrentAudioQueue()); MediaManager.getCurrentAudioQueue().setRow(mQueueRow); mRowsAdapter.add(mQueueRow); } @Override protected void onResume() { super.onResume(); loadItem(); rotateBackdrops(); //link events MediaManager.addAudioEventListener(audioEventListener); //Make sure our initial button state reflects playback properly accounting for late loading of the audio stream mLoopHandler.postDelayed(new Runnable() { @Override public void run() { updateButtons(MediaManager.isPlayingAudio()); } }, 750); } @Override protected void onPause() { super.onPause(); mPoster.setKeepScreenOn(false); MediaManager.removeAudioEventListener(audioEventListener); stopRotate(); } @Override protected void onStop() { super.onStop(); stopRotate(); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { lastUserInteraction = System.currentTimeMillis(); switch (keyCode) { case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_MEDIA_PLAY: if (MediaManager.isPlayingAudio()) MediaManager.pauseAudio(); else MediaManager.resumeAudio(); if (ssActive) { stopScreenSaver(); } return true; case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: MediaManager.nextAudioItem(); return true; case KeyEvent.KEYCODE_MEDIA_PREVIOUS: case KeyEvent.KEYCODE_MEDIA_REWIND: MediaManager.prevAudioItem(); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (ssActive) { MediaManager.nextAudioItem(); return true; } break; case KeyEvent.KEYCODE_DPAD_LEFT: if (ssActive) { MediaManager.prevAudioItem(); return true; } break; } if (ssActive) { stopScreenSaver(); return true; } return super.onKeyUp(keyCode, event); } private AudioEventListener audioEventListener = new AudioEventListener() { @Override public void onPlaybackStateChange(PlaybackController.PlaybackState newState, BaseItemDto currentItem) { mApplication.getLogger().Debug("**** Got playstate change: " + newState); if (newState == PlaybackController.PlaybackState.PLAYING && currentItem != mBaseItem) { // new item started loadItem(); updateButtons(true); mAudioQueuePresenter.setPosition(MediaManager.getCurrentAudioQueuePosition()); } else { updateButtons(newState == PlaybackController.PlaybackState.PLAYING); if (newState == PlaybackController.PlaybackState.IDLE && !MediaManager.hasNextAudioItem()) stopScreenSaver(); } } @Override public void onProgress(long pos) { setCurrentTime(pos); } @Override public void onQueueStatusChanged(boolean hasQueue) { if (hasQueue) { loadItem(); updateButtons(MediaManager.isPlayingAudio()); } else { finish(); // entire queue removed nothing to do here } } }; private GotFocusEvent mainAreaFocusListener = new GotFocusEvent() { @Override public void gotFocus(View v) { //scroll so entire main area is in view mScrollView.smoothScrollTo(0, 0); //also re-position queue to current in case they scrolled around mAudioQueuePresenter.setPosition(MediaManager.getCurrentAudioQueuePosition()); } }; private void updatePoster() { if (isFinishing()) return; // Figure image size Double aspect = Utils.getImageAspectRatio(mBaseItem, false); int posterHeight = aspect > 1 ? Utils.convertDpToPixel(mActivity, 150) : Utils.convertDpToPixel(mActivity, 250); int posterWidth = (int) ((aspect) * posterHeight); if (posterHeight < 10) posterWidth = Utils.convertDpToPixel(mActivity, 150); //Guard against zero size images causing picasso to barf String primaryImageUrl = Utils.getPrimaryImageUrl(mBaseItem, mApplication.getApiClient(),false, false, posterHeight); mApplication.getLogger().Debug("Audio Poster url: " + primaryImageUrl); Picasso.with(mActivity) .load(primaryImageUrl) .skipMemoryCache() .error(R.drawable.audioicon) .resize(posterWidth, posterHeight) .centerInside() .into(mPoster); } private void loadItem() { mBaseItem = MediaManager.getCurrentAudioItem(); if (mBaseItem != null) { updatePoster(); updateInfo(mBaseItem); mDisplayDuration = Utils.formatMillis((mBaseItem.getRunTimeTicks() != null ? mBaseItem.getRunTimeTicks() : 0) / 10000); // give audio a chance to start playing before updating next info mLoopHandler.postDelayed(new Runnable() { @Override public void run() { updateSSInfo(); } }, 750); } } private void updateButtons(final boolean playing) { runOnUiThread(new Runnable() { @Override public void run() { mPoster.setKeepScreenOn(playing); mPlayPauseButton.setState(!playing ? ImageButton.STATE_PRIMARY : ImageButton.STATE_SECONDARY); mRepeatButton.setState(MediaManager.isRepeatMode() ? ImageButton.STATE_SECONDARY : ImageButton.STATE_PRIMARY); mSaveButton.setEnabled(MediaManager.getCurrentAudioQueueSize() > 1); mPrevButton.setEnabled(MediaManager.hasPrevAudioItem()); mNextButton.setEnabled(MediaManager.hasNextAudioItem()); mShuffleButton.setEnabled(MediaManager.getCurrentAudioQueueSize() > 1); if (mBaseItem != null) { mAlbumButton.setEnabled(mBaseItem.getAlbumId() != null); mArtistButton.setEnabled(mBaseItem.getAlbumArtists() != null && mBaseItem.getAlbumArtists().size() > 0); } } }); } private String getArtistName(BaseItemDto item) { return item.getArtists() != null && item.getArtists().size() > 0 ? item.getArtists().get(0) : item.getAlbumArtist(); } private void updateInfo(BaseItemDto item) { if (item != null) { mArtistName.setText(getArtistName(item)); mSongTitle.setText(item.getName()); mAlbumTitle.setText(item.getAlbum()); mCurrentNdx.setText(MediaManager.getCurrentAudioQueueDisplayPosition()); mTotal.setText(MediaManager.getCurrentAudioQueueDisplaySize()); mCurrentDuration = ((Long)((item.getRunTimeTicks() != null ? item.getRunTimeTicks() : 0) / 10000)).intValue(); //set progress to match duration mCurrentProgress.setMax(mCurrentDuration); addGenres(mGenreRow); updateBackground(Utils.getBackdropImageUrl(item, TvApp.getApplication().getApiClient(), true)); } } public void setCurrentTime(long time) { if (ssActive) { mSSTime.setText(Utils.formatMillis(time) + " / " + mDisplayDuration); } else { mCurrentProgress.setProgress(((Long) time).intValue()); mCurrentPos.setText(Utils.formatMillis(time)); mRemainingTime.setText(mCurrentDuration > 0 ? "-" + Utils.formatMillis(mCurrentDuration - time) : ""); } } private void addGenres(LinearLayout layout) { layout.removeAllViews(); if (mBaseItem.getGenres() != null && mBaseItem.getGenres().size() > 0) { boolean first = true; for (String genre : mBaseItem.getGenres()) { if (!first) InfoLayoutHelper.addSpacer(this, layout, " / ", 14); first = false; layout.addView(new GenreButton(this, roboto, 16, genre, mBaseItem.getType())); } } } private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(final Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (!(item instanceof BaseRowItem)) return; KeyProcessor.HandleKey(KeyEvent.KEYCODE_MENU, (BaseRowItem) item, mActivity); } } private final class ItemViewSelectedListener implements OnItemViewSelectedListener { @Override public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof BaseRowItem) { //Keep counter mCounter.setText(((BaseRowItem) item).getIndex()+1 + " | "+mQueueRow.getAdapter().size()); } } } private void rotateBackdrops() { mBackdropLoop = new Runnable() { @Override public void run() { updateBackground(Utils.getBackdropImageUrl(mBaseItem, TvApp.getApplication().getApiClient(), true)); //manage our "screen saver" too if (MediaManager.isPlayingAudio() && !ssActive && System.currentTimeMillis() - lastUserInteraction > 60000) { startScreenSaver(); } mLoopHandler.postDelayed(this, BACKDROP_ROTATION_INTERVAL); } }; mLoopHandler.postDelayed(mBackdropLoop, BACKDROP_ROTATION_INTERVAL); } private void stopRotate() { if (mLoopHandler != null && mBackdropLoop != null) { mLoopHandler.removeCallbacks(mBackdropLoop); } } protected void startScreenSaver() { mArtistName.setAlpha(.3f); mGenreRow.setVisibility(View.INVISIBLE); mClock.setAlpha(.3f); ObjectAnimator fadeOut = ObjectAnimator.ofFloat(mScrollView, "alpha", 1f, 0f); fadeOut.setDuration(1000); fadeOut.start(); ObjectAnimator fadeIn = ObjectAnimator.ofFloat(mSSArea, "alpha", 0f, 1f); fadeIn.setDuration(1000); fadeIn.start(); ssActive = true; setCurrentTime(MediaManager.getCurrentAudioPosition()); } protected void stopScreenSaver() { mLogoImage.setVisibility(View.GONE); mSSArea.setAlpha(0f); mArtistName.setAlpha(1f); mGenreRow.setVisibility(View.VISIBLE); mClock.setAlpha(1f); mScrollView.setAlpha(1f); ssActive = false; setCurrentTime(MediaManager.getCurrentAudioPosition()); } protected void updateSSInfo() { mSSAlbumSong.setText((mBaseItem.getAlbum() != null ? mBaseItem.getAlbum() + " / " : "") + mBaseItem.getName()); mSSQueueStatus.setText(MediaManager.getCurrentAudioQueueDisplayPosition() + " | " + MediaManager.getCurrentAudioQueueDisplaySize()); BaseItemDto next = MediaManager.getNextAudioItem(); mSSUpNext.setText(next != null ? getString(R.string.lbl_up_next_colon) + " " + (getArtistName(next) != null ? getArtistName(next) + " / " : "") + next.getName() : ""); } protected void updateLogo() { if (mBaseItem.getHasLogo() || mBaseItem.getParentLogoImageTag() != null) { if (ssActive) { mLogoImage.setVisibility(View.VISIBLE); Picasso.with(this) .load(Utils.getLogoImageUrl(mBaseItem, TvApp.getApplication().getApiClient())) .resize(700, 200) .centerInside() .into(mLogoImage); mArtistName.setVisibility(View.INVISIBLE); } } else { mLogoImage.setVisibility(View.GONE); mArtistName.setVisibility(View.VISIBLE); } } protected void updateBackground(String url) { if (url == null) { BackgroundManager.getInstance(this).setDrawable(mDefaultBackground); } else { Picasso.with(this) .load(url) .skipMemoryCache() .resize(mMetrics.widthPixels, mMetrics.heightPixels) .centerCrop() .error(mDefaultBackground) .into(mBackgroundTarget); } } }