/* This file is part of the Android Clementine Remote.
* Copyright (C) 2013, Andreas Muttscheller <asfa194@gmail.com>
*
* 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 de.qspool.clementineremote.ui.fragments.playerpages;
import com.afollestad.materialdialogs.MaterialDialog;
import android.annotation.SuppressLint;
import android.app.Fragment;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import de.qspool.clementineremote.App;
import de.qspool.clementineremote.R;
import de.qspool.clementineremote.backend.Clementine;
import de.qspool.clementineremote.backend.downloader.DownloadManager;
import de.qspool.clementineremote.backend.pb.ClementineMessage;
import de.qspool.clementineremote.backend.pb.ClementineMessageFactory;
import de.qspool.clementineremote.backend.pb.ClementineRemoteProtocolBuffer.DownloadItem;
import de.qspool.clementineremote.backend.pb.ClementineRemoteProtocolBuffer.MsgType;
import de.qspool.clementineremote.backend.player.LyricsProvider;
import de.qspool.clementineremote.backend.player.MySong;
import de.qspool.clementineremote.ui.dialogs.DownloadChooserDialog;
import de.qspool.clementineremote.ui.interfaces.BackPressHandleable;
import de.qspool.clementineremote.ui.interfaces.NameableTitle;
import de.qspool.clementineremote.ui.interfaces.RemoteDataReceiver;
import de.qspool.clementineremote.utils.Utilities;
public class PlayerPageFragment extends Fragment
implements BackPressHandleable, RemoteDataReceiver, NameableTitle {
private final String TAG = "PlayerPageFragment";
private final static int ANIMATION_DURATION = 750;
private TextView mTvArtist;
private TextView mTvTitle;
private TextView mTvAlbum;
private TextView mTvGenre;
private TextView mTvYear;
private TextView mTvLength;
private SeekBar mSbPosition;
private ImageView mImgArt;
private AlphaAnimation mAlphaDown;
private AlphaAnimation mAlphaUp;
private boolean mCoverUpdated = false;
MenuItem mMenuRepeat;
MenuItem mMenuShuffle;
private Toast mToast;
private boolean mFirstCall = true;
private MaterialDialog mPdDownloadLyrics;
private MySong mCurrentSong = new MySong();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_player_control,
container, false);
// Get the Views
mTvArtist = (TextView) view.findViewById(R.id.tvArtist);
mTvTitle = (TextView) view.findViewById(R.id.tvTitle);
mTvAlbum = (TextView) view.findViewById(R.id.tvAlbum);
mTvGenre = (TextView) view.findViewById(R.id.tvGenre);
mTvYear = (TextView) view.findViewById(R.id.tvYear);
mTvLength = (TextView) view.findViewById(R.id.tvLength);
mSbPosition = (SeekBar) view.findViewById(R.id.sbPosition);
mImgArt = (ImageView) view.findViewById(R.id.imgArt);
mImgArt.setOnClickListener(oclControl);
mSbPosition.setOnSeekBarChangeListener(onSeekBarChanged);
// Animation for track change
mAlphaDown = new AlphaAnimation(1.0f, 0.0f);
mAlphaUp = new AlphaAnimation(0.0f, 1.0f);
mAlphaDown.setDuration(ANIMATION_DURATION);
mAlphaUp.setDuration(ANIMATION_DURATION);
mAlphaDown.setFillAfter(true);
mAlphaUp.setFillAfter(true);
mAlphaUp.setAnimationListener(mAnimationListener);
mAlphaDown.setAnimationListener(mAnimationListener);
mAlphaDown.setInterpolator(new AccelerateInterpolator());
mAlphaUp.setInterpolator(new AccelerateInterpolator());
// Initialize interface
updateTrackMetadata();
updateTrackPosition();
return view;
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.player_menu, menu);
mMenuRepeat = menu.findItem(R.id.repeat);
mMenuShuffle = menu.findItem(R.id.shuffle);
updateShuffleIcon();
updateRepeatIcon();
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onResume() {
super.onResume();
mCurrentSong = new MySong();
mFirstCall = true;
updateTrackMetadata();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.shuffle:
doShuffle();
break;
case R.id.repeat:
doRepeat();
break;
case R.id.download:
if (App.Clementine.getCurrentSong() == null) {
Toast.makeText(getActivity(), R.string.player_nosong, Toast.LENGTH_LONG).show();
break;
}
if (App.Clementine.getCurrentSong().isLocal()) {
DownloadChooserDialog downloadChooserDialog = new DownloadChooserDialog(getActivity());
downloadChooserDialog.setCallback(new DownloadChooserDialog.Callback() {
@Override
public void onItemClick(DownloadChooserDialog.Type type) {
switch (type) {
case SONG:
DownloadManager.getInstance().addJob(ClementineMessageFactory
.buildDownloadSongsMessage(DownloadItem.CurrentItem));
break;
case ALBUM:
DownloadManager.getInstance().addJob(ClementineMessageFactory
.buildDownloadSongsMessage(DownloadItem.ItemAlbum));
break;
case PLAYLIST:
DownloadManager.getInstance().addJob(ClementineMessageFactory
.buildDownloadSongsMessage(DownloadItem.APlaylist,
App.Clementine.getPlaylistManager().getActivePlaylistId()));
break;
}
}
});
downloadChooserDialog.showDialog();
} else {
Toast.makeText(getActivity(), R.string.player_song_is_stream, Toast.LENGTH_LONG)
.show();
}
break;
case R.id.stop:
Message msg = Message.obtain();
msg.obj = ClementineMessage.getMessage(MsgType.STOP);
App.ClementineConnection.mHandler.sendMessage(msg);
break;
default:
return false;
}
return true;
}
@Override
public void MessageFromClementine(ClementineMessage clementineMessage) {
switch (clementineMessage.getMessageType()) {
case UPDATE_TRACK_POSITION:
case STOP:
updateTrackPosition();
break;
case CURRENT_METAINFO:
updateTrackMetadata();
break;
case SHUFFLE:
updateShuffleIcon();
break;
case REPEAT:
updateRepeatIcon();
break;
case LYRICS:
showLyricsDialog();
break;
default:
break;
}
}
/**
* Update the track position. This method updates the seekbar and the time printed on the right
* hand side.
*/
private void updateTrackPosition() {
mSbPosition.setEnabled(true);
mSbPosition.setMax(mCurrentSong.getLength());
if (App.Clementine.getState() == Clementine.State.STOP) {
mSbPosition.setProgress(0);
} else {
mSbPosition.setProgress(App.Clementine.getSongPosition());
}
mTvLength.setText(buildTrackPosition());
}
/**
* The track changed. Update the metadata shown on the user interface
*/
@SuppressLint("NewApi")
public void updateTrackMetadata() {
// Get the currently played song
MySong currentSong = App.Clementine.getCurrentSong();
if (currentSong == null) {
// If none is played right now, show a text and the clementine icon
mTvArtist.setText(getString(R.string.player_nosong));
mTvTitle.setText("");
mTvAlbum.setText("");
mTvGenre.setText("");
mTvYear.setText("");
mTvLength.setText("");
mSbPosition.setEnabled(false);
mImgArt.setImageResource(R.drawable.icon_large);
} else {
mTvArtist.setText(currentSong.getArtist());
mTvTitle.setText(currentSong.getTitle());
mTvAlbum.setText(currentSong.getAlbum());
mTvGenre.setText(currentSong.getGenre());
mTvYear.setText(currentSong.getYear());
// Check if a coverart is valid
Bitmap newArt = currentSong.getArt();
Bitmap oldArt = mCurrentSong.getArt();
if (newArt == null) {
mImgArt.setImageResource(R.drawable.icon_large);
} else if (oldArt == null
|| !oldArt.sameAs(newArt)) {
// Transit only if the cover changed
if (mFirstCall) {
mImgArt.setImageBitmap(newArt);
} else {
mImgArt.startAnimation(mAlphaDown);
}
}
mCurrentSong = currentSong;
}
mFirstCall = false;
}
/**
* Change shuffle mode and update view
*/
private void doShuffle() {
Message msg = Message.obtain();
App.Clementine.nextShuffleMode();
msg.obj = ClementineMessageFactory.buildShuffle();
App.ClementineConnection.mHandler.sendMessage(msg);
switch (App.Clementine.getShuffleMode()) {
case OFF:
makeToast(R.string.shuffle_off, Toast.LENGTH_SHORT);
break;
case ALL:
makeToast(R.string.shuffle_all, Toast.LENGTH_SHORT);
break;
case INSIDE_ALBUM:
makeToast(R.string.shuffle_inside_album, Toast.LENGTH_SHORT);
break;
case ALBUMS:
makeToast(R.string.shuffle_albums, Toast.LENGTH_SHORT);
break;
}
updateShuffleIcon();
}
/**
* Update the shuffle icon in the actionbar
*/
private void updateShuffleIcon() {
// Make sure the menu item is already there. Since this is called from the backend
// mMenuShuffle might be null. It will be set correctly once onCreateOptionsMenu is called
if (mMenuShuffle == null)
return;
switch (App.Clementine.getShuffleMode()) {
case OFF:
mMenuShuffle.setIcon(R.drawable.ab_shuffle_off);
break;
case ALL:
mMenuShuffle.setIcon(R.drawable.ab_shuffle);
break;
case INSIDE_ALBUM:
mMenuShuffle.setIcon(R.drawable.ab_shuffle_album);
break;
case ALBUMS:
mMenuShuffle.setIcon(R.drawable.ab_shuffle_albums);
break;
}
}
/**
* Change repeat mode and update view
*/
public void doRepeat() {
Message msg = Message.obtain();
App.Clementine.nextRepeatMode();
msg.obj = ClementineMessageFactory.buildRepeat();
App.ClementineConnection.mHandler.sendMessage(msg);
switch (App.Clementine.getRepeatMode()) {
case OFF:
makeToast(R.string.repeat_off, Toast.LENGTH_SHORT);
break;
case TRACK:
makeToast(R.string.repeat_track, Toast.LENGTH_SHORT);
break;
case ALBUM:
makeToast(R.string.repeat_album, Toast.LENGTH_SHORT);
break;
case PLAYLIST:
makeToast(R.string.repeat_playlist, Toast.LENGTH_SHORT);
break;
}
updateRepeatIcon();
}
/**
* Update the repeat icon in the actionbar
*/
private void updateRepeatIcon() {
// Make sure the menu item is already there. Since this is called from the backend
// mMenuShuffle might be null. It will be set correctly once onCreateOptionsMenu is called
if (mMenuRepeat == null)
return;
switch (App.Clementine.getRepeatMode()) {
case OFF:
mMenuRepeat.setIcon(R.drawable.ab_repeat_off);
break;
case TRACK:
mMenuRepeat.setIcon(R.drawable.ab_repeat_track);
break;
case ALBUM:
mMenuRepeat.setIcon(R.drawable.ab_repeat_album);
break;
case PLAYLIST:
mMenuRepeat.setIcon(R.drawable.ab_repeat_playlist);
break;
}
}
/**
* Build the current track position. Format: "01:30/3:33"
*
* @return The current and total track position as a string
*/
private String buildTrackPosition() {
StringBuilder sb = new StringBuilder();
if (!mCurrentSong.isLocal()) {
sb.append(getString(R.string.player_stream));
sb.append(" ");
}
if (mCurrentSong.getLength() == 0) {
sb.append(Utilities.PrettyTime(App.Clementine.getSongPosition()));
} else {
if (App.Clementine.getState() == Clementine.State.STOP) {
sb.append(Utilities.PrettyTime(0));
} else {
sb.append(Utilities.PrettyTime(App.Clementine.getSongPosition()));
}
sb.append("/");
sb.append(Utilities.PrettyTime(mCurrentSong.getLength()));
}
return sb.toString();
}
/**
* Opens a dialog to show the lyrics
*/
public void showLyricsDialog() {
// Only show lyrics dialog, if the user is still waiting for it
if (mPdDownloadLyrics == null || !mPdDownloadLyrics.isShowing()) {
return;
}
// Dismiss the dialog
mPdDownloadLyrics.dismiss();
// Check for a valid lyric
if (mCurrentSong.getLyricsProvider().isEmpty()) {
Toast.makeText(getActivity(), R.string.player_no_lyrics, Toast.LENGTH_SHORT).show();
return;
}
// Receive the provider and show the dialog
LyricsProvider provider = getBestLyricsProvider(mCurrentSong.getLyricsProvider());
// Show the dialog
Utilities.ShowMessageDialog(getActivity(), provider.getTitle(), provider.getContent(),
false);
}
public ImageView getImageArt() {
return mImgArt;
}
/**
* Get the best lyrics provider for this song (currently the one with the most characters
*
* @param providers A list of lyrics providers
* @return The best possible provider
*/
private LyricsProvider getBestLyricsProvider(List<LyricsProvider> providers) {
LyricsProvider bestProvider = providers.get(0);
for (LyricsProvider lyric : providers) {
// For now the provider with the longest lyrics wins
if (lyric.getContent().length() > bestProvider.getContent().length()) {
bestProvider = lyric;
}
}
return bestProvider;
}
/**
* Show text in a toast. Cancels previous toast
*
* @param resId The resource id
* @param length length
*/
private void makeToast(int resId, int length) {
makeToast(getString(resId), length);
}
/**
* Show text in a toast. Cancels previous toast
*
* @param text The text to show
* @param length length
*/
private void makeToast(String text, int length) {
if (mToast != null) {
mToast.cancel();
}
mToast = Toast.makeText(getActivity(), text, length);
mToast.show();
}
private OnClickListener oclControl = new OnClickListener() {
@Override
public void onClick(View v) {
Message msg = Message.obtain();
switch (v.getId()) {
case R.id.imgArt:
// Shall we download the lyrics or do we have them already downloaded?
mPdDownloadLyrics = new MaterialDialog.Builder(getActivity())
.cancelable(true)
.content(R.string.player_download_lyrics)
.progress(true, -1)
.show();
if (mCurrentSong.getLyricsProvider().isEmpty()) {
msg.obj = ClementineMessage.getMessage(MsgType.GET_LYRICS);
} else {
showLyricsDialog();
}
break;
default:
break;
}
// Send the request to the thread
if (msg.obj != null) {
App.ClementineConnection.mHandler.sendMessage(msg);
}
}
};
private OnSeekBarChangeListener onSeekBarChanged = new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// If the user changed the position, send a request to Clementine
if (fromUser) {
Message msg = Message.obtain();
msg.obj = ClementineMessageFactory.buildTrackPosition(progress);
App.ClementineConnection.mHandler.sendMessage(msg);
App.Clementine.setSongPosition(progress);
}
}
};
private AnimationListener mAnimationListener = new AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
if (!mCoverUpdated) {
mImgArt.setImageBitmap(App.Clementine.getCurrentSong().getArt());
mImgArt.startAnimation(mAlphaUp);
}
mCoverUpdated = !mCoverUpdated;
}
@Override
public void onAnimationRepeat(Animation animation) {
}
@Override
public void onAnimationStart(Animation animation) {
}
};
@Override
public boolean onBackPressed() {
return false;
}
@Override
public int getTitleId() {
return R.string.fragment_title_player;
}
}