at.ac.tuwien.detlef.fragments.PlayerFragment.java Source code

Java tutorial

Introduction

Here is the source code for at.ac.tuwien.detlef.fragments.PlayerFragment.java

Source

/* *************************************************************************
 *  Copyright 2012 The detlef developers                                   *
 *                                                                         *
 *  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 2 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 at.ac.tuwien.detlef.fragments;

import java.util.concurrent.TimeUnit;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.MediaMetadataRetriever;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import at.ac.tuwien.detlef.Detlef;
import at.ac.tuwien.detlef.R;
import at.ac.tuwien.detlef.Singletons;
import at.ac.tuwien.detlef.db.EpisodeDAO;
import at.ac.tuwien.detlef.db.PlaylistDAO;
import at.ac.tuwien.detlef.domain.Episode;
import at.ac.tuwien.detlef.domain.Episode.StorageState;
import at.ac.tuwien.detlef.mediaplayer.IMediaPlayerService;
import at.ac.tuwien.detlef.mediaplayer.MediaPlayerService;

public class PlayerFragment extends Fragment
        implements PlaylistDAO.OnPlaylistChangeListener, EpisodeDAO.OnEpisodeChangeListener {

    private static final int PROGRESS_BAR_UPDATE_INTERVAL = 500;

    private final Handler playProgressUpdateHandler = new Handler();
    private IMediaPlayerService service;
    private Episode activeEpisode = null;

    private boolean fragmentPaused = true;
    private boolean progressUpdaterRunning = false;
    private boolean bound = false;
    private boolean trackingTouch = false;

    private ImageButton buttonPlayStop;
    private SeekBar seekBar;
    private TextView alreadyPlayed;
    private TextView remainingTime;
    private ImageButton buttonFF;
    private ImageButton buttonRew;
    private ImageView podcastIcon;

    /** Logging Tag */
    private static final String TAG = PlayerFragment.class.getCanonicalName();

    /**
     * Handles the connection to the MediaPlayerService that plays music.
     */
    private final ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder iBinder) {
            bound = true;
            MediaPlayerService.MediaPlayerBinder binder = (MediaPlayerService.MediaPlayerBinder) iBinder;
            service = binder.getService();
            activeEpisode = service.getNextEpisode();
            setEpisodeInfoControls(activeEpisode);
            if (!progressUpdaterRunning) {
                progressUpdaterRunning = true;
                startPlayProgressUpdater();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            bound = false;
        }
    };

    /**
     * Initializes the buttons play, ff, rew and the slide etc. to perform their
     * tasks when needed.
     */
    private PlayerFragment initPlayingControls(View v) {
        initButtonPlayStop(v);
        initButtonFF(v);
        initButtonRew(v);
        initSeekBar(v);

        alreadyPlayed = (TextView) v.findViewById(R.id.playerAlreadyPlayed);
        remainingTime = (TextView) v.findViewById(R.id.playerRemainingTime);
        return this;
    }

    private void initSeekBar(View v) {
        seekBar = (SeekBar) v.findViewById(R.id.SeekBar01);
        seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onStopTrackingTouch(SeekBar seekBar1) {
                service.seekTo(seekBar1.getProgress());
                if (activeEpisode != null) {
                    activeEpisode.setPlayPosition(seekBar1.getProgress());
                }
                trackingTouch = false;
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar1) {
                trackingTouch = true;
            }

            @Override
            public void onProgressChanged(SeekBar seekBar1, int progress, boolean fromUser) {
                if (!fromUser) {
                    return;
                }
                if (bound && (service != null)) {
                    alreadyPlayed.setText(getAlreadyPlayed(progress));
                    remainingTime.setText("-" + getRemainingTime(service.getDuration(), progress));
                }
            }
        });
    }

    private void initButtonRew(View v) {
        buttonRew = (ImageButton) v.findViewById(R.id.ButtonRewind);
        buttonRew.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v1) {
                rewind();
            }
        });
    }

    private void initButtonFF(View v) {
        buttonFF = (ImageButton) v.findViewById(R.id.ButtonFF);
        buttonFF.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v1) {
                fastForward();
            }
        });
    }

    private void initButtonPlayStop(View v) {
        buttonPlayStop = (ImageButton) v.findViewById(R.id.ButtonPlayStop);
        buttonPlayStop.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v1) {
                startStop();
            }
        });
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Singletons.i().getPlaylistDAO().addPlaylistChangedListener(this);
        Singletons.i().getEpisodeDAO().addEpisodeChangedListener(this);

        if (!MediaPlayerService.isRunning()) {
            Intent serviceIntent = new Intent(Detlef.getAppContext(), MediaPlayerService.class);
            Detlef.getAppContext().startService(serviceIntent);
        }
        Intent intent = new Intent(getActivity(), MediaPlayerService.class);
        getActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onDestroy() {
        Singletons.i().getPlaylistDAO().removePlaylistChangeListener(this);
        Singletons.i().getEpisodeDAO().removeEpisodeChangedListener(this);
        super.onDestroy();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        return inflater.inflate(R.layout.player_layout, container, false);
    }

    @Override
    public void onStop() {
        super.onStop();
        if (bound) {
            getActivity().unbindService(connection);
            bound = false;
        }
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        initPlayingControls(view);
        setEpisodeInfoControls(activeEpisode);
    }

    @Override
    public void onResume() {
        super.onResume();
        fragmentPaused = false;
        setEpisodeInfoControls(activeEpisode);
        if (!progressUpdaterRunning) {
            progressUpdaterRunning = true;
            startPlayProgressUpdater();
        }
    }

    @Override
    public void onPause() {
        fragmentPaused = true;
        if (service != null) {
            service.stopStreamingIfPaused();
        }
        super.onPause();
    }

    /**
     * Handles the updates of the seek/progressbar as well as the state of the
     * play/pause button.
     */
    private PlayerFragment startPlayProgressUpdater() {
        if (fragmentPaused) {
            progressUpdaterRunning = false;
            return this;
        }
        if (service != null) {
            updateControls();
        } else {
            progressUpdaterRunning = false;
        }
        return this;
    }

    private void updateControls() {
        if ((service.getNextEpisode() != null) && (service.getNextEpisode() != activeEpisode)) {
            setActiveEpisode(service.getNextEpisode());
        }
        if (service.isCurrentlyPlaying()) {
            buttonPlayStop.setImageResource(android.R.drawable.ic_media_pause);
            setPlayingSeekBarAndTime();
        } else {
            buttonPlayStop.setImageResource(android.R.drawable.ic_media_play);
            setNotPlayingSeekBarAndTime(activeEpisode);
        }
        if ((activeEpisode != null) && (activeEpisode.getStorageState() != StorageState.DOWNLOADED)) {
            seekBar.setSecondaryProgress(service.getDownloadProgress());
        } else {
            seekBar.setSecondaryProgress(0);
        }
        Runnable notification = new Runnable() {
            @Override
            public void run() {
                startPlayProgressUpdater();
            }
        };
        playProgressUpdateHandler.postDelayed(notification, PROGRESS_BAR_UPDATE_INTERVAL);
    }

    public PlayerFragment startPlaying() {
        if (service == null) {
            return this;
        }

        try {
            if (!service.isCurrentlyPlaying()) {
                service.setNextEpisode(activeEpisode);
                service.startPlaying();
                buttonPlayStop.setImageResource(android.R.drawable.ic_media_pause);
            }
        } catch (Exception e) {
            showGenericError();
            Log.e(TAG, "startPlaying() Exception", e);
        }
        return this;
    }

    /**
     * Shows a generic error message as Toast.
     */
    private void showGenericError() {
        try {
            Toast.makeText(this.getActivity(), R.string.error_occured, Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
        }
    }

    public PlayerFragment stopPlaying() {
        if (service == null) {
            return this;
        }

        try {
            if (service.isCurrentlyPlaying()) {
                service.pausePlaying();
                buttonPlayStop.setImageResource(android.R.drawable.ic_media_play);
            }
        } catch (Exception e) {
            showGenericError();
            Log.e(TAG, "stopPlaying() Exception", e);
        }
        return this;
    }

    /**
     * Starts or pauses the playback and updates the UI fields accordingly.
     */
    private PlayerFragment startStop() {
        if (service == null) {
            return this;
        }
        try {
            if (service.isCurrentlyPlaying()) {
                stopPlaying();
            } else {
                startPlaying();
            }
        } catch (Exception e) {
            showGenericError();
            Log.e(TAG, "startStop() Exception", e);
        }

        return this;
    }

    /**
     * Handles clicks on the fastForward button.
     */
    private PlayerFragment fastForward() {
        if (service == null) {
            return this;
        }
        try {
            service.fastForward();
            if (service.getNextEpisode() != activeEpisode) {
                setActiveEpisode(service.getNextEpisode());
                if (service.isCurrentlyPlaying()) {
                    stopPlaying();
                    startPlaying();
                }
            }
        } catch (Exception e) {
            showGenericError();
            Log.e(TAG, "fastForward() Exception", e);
        }

        return this;
    }

    /**
     * Handles clicks on the rewind button.
     */
    private PlayerFragment rewind() {
        if (service == null) {
            return this;
        }

        try {
            service.rewind();
            if (service.getNextEpisode() != activeEpisode) {
                setActiveEpisode(service.getNextEpisode());
                if (service.isCurrentlyPlaying()) {
                    stopPlaying();
                    startPlaying();
                }
            }
        } catch (Exception e) {
            showGenericError();
            Log.e(TAG, "fastForward() Exception", e);
        }

        return this;
    }

    private PlayerFragment setEpisodeInfoControls(Episode ep) {
        View view = getView();
        if (view == null) {
            return this;
        }
        WebView episodeDescription = (WebView) getView().findViewById(R.id.playerEpisodeDescription);
        TextView podcast = (TextView) getView().findViewById(R.id.playerPodcast);
        TextView episode = (TextView) getView().findViewById(R.id.playerEpisode);

        podcastIcon = (ImageView) getView().findViewById(R.id.imageView1);

        if ((ep == null) && (service != null)) {
            ep = service.getNextEpisode();
        }
        // reset episodeDescription in any case, otherwise there will
        // be problems if html text is refreshed.
        episodeDescription.loadData("", "text/html; charset=UTF-8", null);

        if (ep == null) {
            episode.setText(getActivity().getText(R.string.no_episode_selected).toString());
            podcast.setText("");
            podcastIcon.setImageDrawable(getResources().getDrawable(R.drawable.ic_feed_icon));
        } else {
            episodeDescription.loadData("", "text/html; charset=UTF-8", null);
            episodeDescription.loadData(ep.getDescription() == null ? "" : ep.getDescription(),
                    "text/html; charset=UTF-8", null);
            podcast.setText(ep.getPodcast().getTitle() == null ? "" : ep.getPodcast().getTitle());
            episode.setText(ep.getTitle() == null ? "" : ep.getTitle());
            remainingTime.setText("-00:00");
            alreadyPlayed.setText("00:00");
            seekBar.setMax(1);
            seekBar.setProgress(0);
            seekBar.setSecondaryProgress(0);
            setNotPlayingSeekBarAndTime(ep);
            podcastIcon.setImageDrawable(ep.getPodcast().getLogoIcon());
        }

        return this;
    }

    private void setNotPlayingSeekBarAndTime(Episode ep) {
        if (service == null) {
            return;
        }
        if (trackingTouch) {
            return;
        }
        try {
            if (service.episodeFileOK(ep)) {
                MediaMetadataRetriever metaData = new MediaMetadataRetriever();
                metaData.setDataSource(ep.getFilePath());
                String durationString = metaData.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                int duration = Integer.parseInt(durationString);
                int playingPosition = ep.getPlayPosition();
                int pos = 0;
                if (((playingPosition) > 0) && (playingPosition < duration)) {
                    pos = playingPosition;
                }
                String minutesSecondsRemaining = getRemainingTime(duration, pos);
                String minutesSecondsAlreadyPlayed = getAlreadyPlayed(pos);
                remainingTime.setText("-" + minutesSecondsRemaining);
                alreadyPlayed.setText(minutesSecondsAlreadyPlayed);
                seekBar.setMax(duration);
                seekBar.setProgress(pos);
                seekBar.setSecondaryProgress(0);
            } else if ((service.getNextEpisode() == ep) && service.hasRunningEpisode()) {
                seekBar.setMax(service.getDuration());
                seekBar.setProgress(service.getCurrentPosition());
                alreadyPlayed.setText(getAlreadyPlayed(service.getCurrentPosition()));
                remainingTime.setText("-" + getRemainingTime(service.getDuration(), service.getCurrentPosition()));
            }
        } catch (Exception ex) {
            Log.d(getClass().getName(), "Error while retrieving duration of episode: " + ex.getMessage());
        }
    }

    private void setPlayingSeekBarAndTime() {
        if (service == null) {
            return;
        }
        if (trackingTouch) {
            return;
        }
        seekBar.setMax(service.getDuration());
        seekBar.setProgress(service.getCurrentPosition());
        alreadyPlayed.setText(getAlreadyPlayed(service.getCurrentPosition()));
        remainingTime.setText("-" + getRemainingTime(service.getDuration(), service.getCurrentPosition()));
    }

    private PlayerFragment setActiveEpisode(Episode ep) {
        if (ep != activeEpisode) {
            activeEpisode = ep;
            setEpisodeInfoControls(ep);
        }
        return this;
    }

    private String getAlreadyPlayed(int progress) {
        return String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(progress),
                TimeUnit.MILLISECONDS.toSeconds(progress)
                        - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(progress)));
    }

    private String getRemainingTime(int duration, int progress) {
        if (duration < progress) {
            return "00:00";
        }
        return String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(duration - progress),
                TimeUnit.MILLISECONDS.toSeconds(duration - progress)
                        - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration - progress)));
    }

    @Override
    public void onPlaylistEpisodeAdded(int position, Episode episode) {
        if ((activeEpisode == null) && (position == 0)) {
            activeEpisode = episode;
            setEpisodeInfoControls(activeEpisode);
        }
    }

    @Override
    public void onPlaylistEpisodePositionChanged(int firstPosition, int secondPosition) {
        // not our problem here
    }

    @Override
    public void onPlaylistEpisodeRemoved(int position) {
        // not of interest here.
    }

    @Override
    public void onEpisodeChanged(Episode episode) {
        // don't care - let's suppose this doesn't happen,
        // and even if the info is miraculously updated
        // during playback, I'll just ignore this
    }

    @Override
    public void onEpisodeAdded(Episode episode) {
        // thankfully I can be totally indifferent about this
    }

    @Override
    public void onEpisodeDeleted(final Episode episode) {
        Activity activity = getActivity();
        if (activity == null) {
            return;
        }

        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (activeEpisode == episode) {
                    stopPlaying();
                    activeEpisode = null;
                    setEpisodeInfoControls(activeEpisode);
                }
            }
        });
    }

    public PlayerFragment setManualEpisode(Episode episode) {
        if (service == null) {
            return this;
        }
        service.setManualEpisode(episode);
        setActiveEpisode(episode);
        stopPlaying();
        startPlaying();
        return this;
    }

}