/* * Copyright (C) 2014 Fastboot Mobile, LLC. * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; * if not, see <http://www.gnu.org/licenses>. */ package com.fastbootmobile.encore.app.fragments; import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityOptions; import android.support.v4.app.Fragment; import android.content.Intent; import android.graphics.Bitmap; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.support.v7.graphics.Palette; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; import android.widget.SeekBar; import android.widget.Toast; import com.echonest.api.v4.EchoNestException; import com.fastbootmobile.encore.api.echonest.AutoMixBucket; import com.fastbootmobile.encore.api.echonest.AutoMixManager; import com.fastbootmobile.encore.app.AlbumActivity; import com.fastbootmobile.encore.app.MainActivity; import com.fastbootmobile.encore.app.R; import com.fastbootmobile.encore.app.adapters.PlaybackQueueAdapter; import com.fastbootmobile.encore.app.ui.MaterialTransitionDrawable; import com.fastbootmobile.encore.app.ui.PlayPauseDrawable; import com.fastbootmobile.encore.framework.ListenLogger; import com.fastbootmobile.encore.framework.PlaybackProxy; import com.fastbootmobile.encore.model.Album; import com.fastbootmobile.encore.model.Artist; import com.fastbootmobile.encore.model.Playlist; import com.fastbootmobile.encore.model.SearchResult; import com.fastbootmobile.encore.model.Song; import com.fastbootmobile.encore.providers.ILocalCallback; import com.fastbootmobile.encore.providers.IMusicProvider; import com.fastbootmobile.encore.providers.ProviderAggregator; import com.fastbootmobile.encore.service.BasePlaybackCallback; import com.fastbootmobile.encore.service.PlaybackService; import com.fastbootmobile.encore.utils.Utils; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * Simple fragment for the activity contents */ public class PlaybackQueueFragment extends Fragment { private static final String TAG = "PlaybackQueueFrag"; private View.OnClickListener mArtClickListener = new View.OnClickListener() { @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void onClick(View view) { Bitmap hero = ((MaterialTransitionDrawable) ((ImageView) view).getDrawable()) .getFinalDrawable().getBitmap(); Palette palette = Palette.from(hero).generate(); Palette.Swatch darkVibrantColor = palette.getDarkVibrantSwatch(); Palette.Swatch darkMutedColor = palette.getDarkMutedSwatch(); int color; if (darkVibrantColor != null) { color = darkVibrantColor.getRgb(); } else if (darkMutedColor != null) { color = darkMutedColor.getRgb(); } else { color = getResources().getColor(R.color.default_album_art_background); } Song song = (Song) view.getTag(); Intent intent = AlbumActivity.craftIntent(getActivity(), hero, song.getAlbum(), song.getProvider(), color); if (Utils.hasLollipop()) { ActivityOptions opt = ActivityOptions.makeSceneTransitionAnimation(getActivity(), view, "itemImage"); getActivity().startActivity(intent, opt.toBundle()); } else { getActivity().startActivity(intent); } } }; private BasePlaybackCallback mPlaybackListener = new BasePlaybackCallback() { @Override public void onSongStarted(final boolean buffering, Song s) throws RemoteException { mHandler.sendEmptyMessage(MSG_UPDATE_SEEKBAR); mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE); mHandler.obtainMessage(MSG_UPDATE_PLAYSTATE, buffering ? PLAYSTATE_ARG1_BUFFERING : PLAYSTATE_ARG1_NOT_BUFFERING, PlayPauseDrawable.SHAPE_PAUSE).sendToTarget(); } @Override public void onPlaybackPause() throws RemoteException { mHandler.obtainMessage(MSG_UPDATE_PLAYSTATE, PLAYSTATE_ARG1_NOT_BUFFERING, PlayPauseDrawable.SHAPE_PLAY).sendToTarget(); } @Override public void onPlaybackResume() throws RemoteException { mHandler.sendEmptyMessage(MSG_UPDATE_SEEKBAR); mHandler.obtainMessage(MSG_UPDATE_PLAYSTATE, PLAYSTATE_ARG1_NOT_BUFFERING, PlayPauseDrawable.SHAPE_PAUSE).sendToTarget(); } @Override public void onPlaybackQueueChanged() throws RemoteException { mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE); } }; private ILocalCallback mProviderCallback = new ILocalCallback() { @Override public void onSongUpdate(List<Song> s) { boolean contains = false; final List<Song> playbackQueue = PlaybackProxy.getCurrentPlaybackQueue(); for (Song song : s) { if (playbackQueue.contains(song)) { contains = true; break; } } if (contains) { mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE); } } @Override public void onAlbumUpdate(List<Album> a) { } @Override public void onPlaylistUpdate(List<Playlist> p) { } @Override public void onPlaylistRemoved(String ref) { } @Override public void onArtistUpdate(List<Artist> a) { boolean contains = false; List<Song> playbackQueue = PlaybackProxy.getCurrentPlaybackQueue(); for (Song song : playbackQueue) { for (Artist artist : a) { if (artist.getRef().equals(song.getArtist())) { contains = true; break; } } if (contains) { break; } } if (contains) { mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE); } } @Override public void onProviderConnected(IMusicProvider provider) { } @Override public void onSearchResult(List<SearchResult> searchResult) { } }; private static final int SEEK_UPDATE_DELAY = 1000/15; private static final int MSG_UPDATE_SEEKBAR = 1; private static final int MSG_UPDATE_QUEUE = 2; private static final int MSG_UPDATE_PLAYSTATE = 3; private static final int PLAYSTATE_ARG1_NOT_BUFFERING = 0; private static final int PLAYSTATE_ARG1_BUFFERING = 1; private PlaybackQueueHandler mHandler; private boolean mLockSeekBarUpdate; private FrameLayout mRootView; private ListView mListView; private PlaybackQueueAdapter mAdapter; private View.OnClickListener mPlayFabClickListener; private View.OnClickListener mNextClickListener; private View.OnClickListener mPreviousClickListener; private SeekBar.OnSeekBarChangeListener mSeekListener; private View.OnClickListener mRepeatClickListener; private View.OnClickListener mLikeClickListener; private View.OnClickListener mDislikeClickListener; private View.OnClickListener mAlbumArtClickListener; private View.OnClickListener mShuffleClickListener; private static class PlaybackQueueHandler extends Handler { private WeakReference<PlaybackQueueFragment> mParent; public PlaybackQueueHandler(WeakReference<PlaybackQueueFragment> parent) { mParent = parent; } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_QUEUE: mParent.get().updateQueueLayout(); break; case MSG_UPDATE_SEEKBAR: mParent.get().updateSeekbar(); break; case MSG_UPDATE_PLAYSTATE: mParent.get().updatePlaystate(msg.arg1, msg.arg2); break; } } } public static PlaybackQueueFragment newInstance() { return new PlaybackQueueFragment(); } public PlaybackQueueFragment() { mHandler = new PlaybackQueueHandler(new WeakReference<>(this)); mPlayFabClickListener = new View.OnClickListener() { @Override public void onClick(View v) { switch (PlaybackProxy.getState()) { case PlaybackService.STATE_PAUSED: case PlaybackService.STATE_STOPPED: case PlaybackService.STATE_PAUSING: PlaybackProxy.play(); break; case PlaybackService.STATE_BUFFERING: case PlaybackService.STATE_PLAYING: PlaybackProxy.pause(); break; } } }; mNextClickListener = new View.OnClickListener() { @Override public void onClick(View v) { PlaybackProxy.next(); } }; mPreviousClickListener = new View.OnClickListener() { @Override public void onClick(View v) { PlaybackProxy.previous(); } }; mSeekListener = new SeekBar.OnSeekBarChangeListener() { private int mStartProgress; @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } @Override public void onStartTrackingTouch(SeekBar seekBar) { mLockSeekBarUpdate = true; mStartProgress = seekBar.getProgress(); } @Override public void onStopTrackingTouch(SeekBar seekBar) { mLockSeekBarUpdate = false; int endProgress = seekBar.getProgress(); if (endProgress != mStartProgress) { PlaybackProxy.seek(endProgress); } } }; mRepeatClickListener = new View.OnClickListener() { @Override public void onClick(View v) { boolean currentMode = PlaybackProxy.isRepeatMode(); boolean newMode = !currentMode; PlaybackProxy.setRepeatMode(newMode); v.animate().rotationBy(-360).setDuration(600).start(); if (newMode) { ((ImageView) v).setImageResource(R.drawable.ic_replay); } else { ((ImageView) v).setImageResource(R.drawable.ic_replay_gray); } } }; mShuffleClickListener = new View.OnClickListener() { @Override public void onClick(View v) { boolean currentMode = PlaybackProxy.isShuffleMode(); boolean newMode = !currentMode; PlaybackProxy.setShuffleMode(newMode); if (newMode) { ((ImageView) v).setImageResource(R.drawable.ic_shuffle); } else { ((ImageView) v).setImageResource(R.drawable.ic_shuffle_gray); } } }; mLikeClickListener = new View.OnClickListener() { @Override public void onClick(View v) { ListenLogger logger = new ListenLogger(getActivity()); PlaybackQueueAdapter.ViewHolder tag = (PlaybackQueueAdapter.ViewHolder) v.getTag(); boolean isLiked = logger.isLiked(tag.song.getRef()); if (isLiked) { logger.removeLike(tag.song); ((ImageView) v).setImageResource(R.drawable.ic_thumbs_up_gray); } else { logger.addLike(tag.song); final AutoMixBucket bucket = AutoMixManager.getDefault().getCurrentPlayingBucket(); if (bucket != null) { new Thread() { public void run() { try { bucket.notifyLike(); } catch (EchoNestException e) { Log.e(TAG, "Unable to notify like event to EchoNest"); } } }.start(); } ((ImageView) v).setImageResource(R.drawable.ic_thumbs_up); } } }; mDislikeClickListener = new View.OnClickListener() { @Override public void onClick(View v) { ListenLogger logger = new ListenLogger(getActivity()); PlaybackQueueAdapter.ViewHolder tag = (PlaybackQueueAdapter.ViewHolder) v.getTag(); boolean isDisliked = logger.isDisliked(tag.song.getRef()); if (isDisliked) { logger.removeDislike(tag.song); ((ImageView) v).setImageResource(R.drawable.ic_thumb_down_gray); } else { logger.addDislike(tag.song); final AutoMixBucket bucket = AutoMixManager.getDefault().getCurrentPlayingBucket(); if (bucket != null) { new Thread() { public void run() { try { bucket.notifyDislike(); } catch (EchoNestException e) { Log.e(TAG, "Unable to notify dislike event to EchoNest"); } } }.start(); } ((ImageView) v).setImageResource(R.drawable.ic_thumb_down); } } }; mAlbumArtClickListener = new View.OnClickListener() { @Override @TargetApi(Build.VERSION_CODES.LOLLIPOP) public void onClick(View v) { PlaybackQueueAdapter.ViewHolder tag = (PlaybackQueueAdapter.ViewHolder) v.getTag(); if (tag.song.getAlbum() != null) { Bitmap hero = ((MaterialTransitionDrawable) tag.ivAlbumArt.getDrawable()).getFinalDrawable().getBitmap(); Intent intent = AlbumActivity.craftIntent(getActivity(), hero, tag.song.getAlbum(), tag.song.getProvider(), 0xFF333333); if (Utils.hasLollipop()) { ActivityOptions opts = ActivityOptions.makeSceneTransitionAnimation(getActivity(), v, "itemImage"); getActivity().startActivity(intent, opts.toBundle()); } else { startActivity(intent); } } else { Toast.makeText(getActivity(), R.string.toast_song_no_album, Toast.LENGTH_SHORT).show(); } } }; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mRootView = (FrameLayout) inflater.inflate(R.layout.fragment_playback_queue, container, false); mListView = (ListView) mRootView.findViewById(R.id.lvPlaybackQueue); mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { PlaybackProxy.playAtIndex(position); } }); if (mAdapter != null) { mListView.setAdapter(mAdapter); } updateQueueLayout(); return mRootView; } @Override public void onAttach(Activity activity) { super.onAttach(activity); mAdapter = new PlaybackQueueAdapter(mPlayFabClickListener, mNextClickListener, mPreviousClickListener, mSeekListener, mRepeatClickListener, mLikeClickListener, mDislikeClickListener, mAlbumArtClickListener, mShuffleClickListener); if (mListView != null) { mListView.setAdapter(mAdapter); } if (activity instanceof MainActivity) { MainActivity mainActivity = (MainActivity) activity; mainActivity.onSectionAttached(MainActivity.SECTION_NOW_PLAYING); } } @Override public void onResume() { super.onResume(); // Attach this fragment as Playback Listener PlaybackProxy.addCallback(mPlaybackListener); mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SEEKBAR, 1000); ProviderAggregator.getDefault().addUpdateCallback(mProviderCallback); if (!mHandler.hasMessages(MSG_UPDATE_QUEUE)) { mHandler.sendEmptyMessage(MSG_UPDATE_QUEUE); mHandler.sendEmptyMessageDelayed(MSG_UPDATE_QUEUE, 500); } } @Override public void onPause() { super.onPause(); // Remove callback on various places PlaybackProxy.removeCallback(mPlaybackListener); ProviderAggregator.getDefault().removeUpdateCallback(mProviderCallback); // Stop updating the seekbar mHandler.removeMessages(MSG_UPDATE_SEEKBAR); } public void updateQueueLayout() { final List<Song> songs = new ArrayList<>(PlaybackProxy.getCurrentPlaybackQueue()); mAdapter.setPlaybackQueue(songs); final int trackIndex = PlaybackProxy.getCurrentTrackIndex(); if (trackIndex >= 0) { mListView.smoothScrollToPosition(trackIndex + 1); } if (songs.size() <= 0) { mRootView.findViewById(R.id.txtEmptyQueue).setVisibility(View.VISIBLE); } else { mRootView.findViewById(R.id.txtEmptyQueue).setVisibility(View.GONE); } } public void updateSeekbar() { PlaybackQueueAdapter.ViewHolder tag = mAdapter.getCurrentTrackTag(); if (tag != null && tag.sbSeek != null) { int state = PlaybackProxy.getState(); if (state == PlaybackService.STATE_PLAYING || state == PlaybackService.STATE_PAUSING || state == PlaybackService.STATE_PAUSED) { if (!mLockSeekBarUpdate) { final int length = PlaybackProxy.getCurrentTrackLength(); final int position = PlaybackProxy.getCurrentTrackPosition(); tag.sbSeek.setMax(length); tag.sbSeek.setProgress(position); tag.tvTotalTime.setText(Utils.formatTrackLength(length)); tag.tvCurrentTime.setText(Utils.formatTrackLength(position)); } mHandler.sendEmptyMessageDelayed(MSG_UPDATE_SEEKBAR, SEEK_UPDATE_DELAY); } } } public void updatePlaystate(int arg1, int arg2) { PlaybackQueueAdapter.ViewHolder tag = mAdapter.getCurrentTrackTag(); if (tag != null && tag.fabPlayDrawable != null) { tag.fabPlayDrawable.setBuffering(arg1 == PLAYSTATE_ARG1_BUFFERING); tag.fabPlayDrawable.setShape(arg2); } } }