package se.chalmers.pd.playlistmanager; import android.animation.ValueAnimator; import android.app.Activity; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import java.util.ArrayList; /** * This fragment shows the player controls and track information. */ public class PlayerFragment extends Fragment implements View.OnClickListener, OnSeekBarChangeListener { private static final int DELAY_MILLIS = 1000; private View pause; private View play; private TextView trackInfo; private SeekBar seekbar; private FragmentCallback callback; private ArrayList<Track> tracks; private ValueAnimator seekbarAnimator; private boolean playing = false; public PlayerFragment() { } /** * Saves the activity as a callback * * @param activity * @throws ClassCastException if the activity doesn't implement the FragmentCallback */ @Override public void onAttach(Activity activity) throws ClassCastException { super.onAttach(activity); try { callback = (FragmentCallback) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement FragmentCallback"); } } /** * Fetches the playlist from the arguments. The playlist is shared with the other * fragment so we can't create a new instance. Have to reference the same. * * @param savedInstanceState */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); Bundle arguments = getArguments(); tracks = arguments.getParcelableArrayList("playlist"); } /** * Sets up the root view and its buttons. * * @param inflater * @param container * @param savedInstanceState * @return the root view */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_player, null); setupButtons(rootView); seekbar.setOnSeekBarChangeListener(this); trackInfo = (TextView) rootView.findViewById(R.id.track_info); updateTrackInformation(); return rootView; } /** * Sets up all the buttons for the view and adds onclick listeners. */ private void setupButtons(View rootView) { View previous = rootView.findViewById(R.id.prev); View next = rootView.findViewById(R.id.next); play = rootView.findViewById(R.id.play); pause = rootView.findViewById(R.id.pause); seekbar = (SeekBar) rootView.findViewById(R.id.seekbar); previous.setOnClickListener(this); next.setOnClickListener(this); play.setOnClickListener(this); pause.setOnClickListener(this); } /** * Handles the clicks on the buttons and calls back to the parent activity * the clicked action. * * @param v the view that was clicked */ @Override public void onClick(View v) { if (!tracks.isEmpty()) { Action action = Action.NONE; switch (v.getId()) { case R.id.play: action = Action.play; break; case R.id.next: action = Action.next; break; case R.id.pause: action = Action.pause; break; case R.id.prev: action = Action.prev; break; } callback.onPlayerAction(action); } } /** * Called when there has been an incoming message and the UI needs to * update. Switches play/pause buttons, handles previous and next and * also updates the seek. * * @param action * @param t * @param <T> */ public <T extends Object> void updateAction(Action action, T t) { switch (action) { case pause: stopProgress(); pause.setVisibility(View.GONE); play.setVisibility(View.VISIBLE); playing = false; break; case play: pause.setVisibility(View.VISIBLE); play.setVisibility(View.GONE); updateTrackInformation(); setSeekbarMax(); startProgress(); playing = true; break; case next: case prev: updateTrackInformation(); stopProgress(); resetProgress(); setSeekbarMax(); if (playing) { startProgress(); } break; case seek: float position = (Float) t; stopProgress(); setProgress(((int) (position * seekbar.getMax()))); if (playing) { startProgress(); } break; } } /** * Sets a new max on the seekbar from the first track in the * current list of tracks. */ private void setSeekbarMax() { if(tracks.size() > 0) { Track track = tracks.get(0); if (track != null && seekbar != null) { seekbar.setMax(track.getLength()); } } } /** * Updates the track information in the text view with * the first track in the playlist. */ private void updateTrackInformation() { if(tracks.size() > 0) { Track track = tracks.get(0); if (track != null && trackInfo != null) { trackInfo.setText(track.getArtist() + " - " + track.getName()); } } } /** * Stops the seek bar animation */ private void stopProgress() { if(seekbarAnimator != null) { seekbarAnimator.cancel(); seekbarAnimator.removeAllUpdateListeners(); } } /** * Starts the seek bar animation */ private void startProgress() { seekbarAnimator = ValueAnimator.ofInt(seekbar.getProgress(), seekbar.getMax()); seekbarAnimator.setDuration(DELAY_MILLIS * seekbar.getMax()); seekbarAnimator.setInterpolator(null); seekbarAnimator.addUpdateListener(new SeekbarAnimatorUpdateListener()); seekbarAnimator.start(); } /** * Sets the progress when a seek action has been triggered. * * @param progress the progress to set */ private void setProgress(int progress) { if(seekbar != null) { seekbar.setProgress(progress); } } /** * Resets the progress to 0 */ private void resetProgress() { setProgress(0); } /** * When the user touches the seekbar we stop progress to prevent jerkiness. * * @param seekBar */ @Override public void onStartTrackingTouch(SeekBar seekBar) { if (playing) { stopProgress(); } } /** * When the user stops touching the seekbar we track the position and tell the * parent activity which publishes a message about new the progress. * * @param seekBar */ @Override public void onStopTrackingTouch(SeekBar seekBar) { float position = (float) seekBar.getProgress() / seekBar.getMax(); callback.onPlayerAction(position); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // Not used } private class SeekbarAnimatorUpdateListener implements ValueAnimator.AnimatorUpdateListener { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { int animProgress = (Integer) valueAnimator.getAnimatedValue(); setProgress(animProgress); } } }