Android Open Source - MusicPlayer Music Player Service






From Project

Back to project page MusicPlayer.

License

The source code is released under:

MIT License

If you think the Android project MusicPlayer listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package com.dsvoronin.musicplayer;
//from   w  w  w  .j  a va  2  s  .  co  m
import android.app.Service;
import android.content.*;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.util.List;

/**
 * Embeddable music player service
 * -------------
 * For checking Repeat/Shuffle strategy - use SharedPreferences with name %PREFS_NAME%, and params PREF_REPEAT/PREF_SHUFFLE
 */
public abstract class MusicPlayerService extends Service implements
        AudioManager.OnAudioFocusChangeListener,
        MediaPlayer.OnPreparedListener,
        MediaPlayer.OnErrorListener,
        MediaPlayer.OnCompletionListener {

    //Preferences
    public static final String PREFS_NAME = "MusicPlayer";
    public static final String PREF_SHUFFLE = "shuffle";
    public static final String PREF_REPEAT = "repeat";

    //intent extras
    public static final String EXTRA_TRACK_ID = "track_id";
    public static final String EXTRA_SEEK = "seek";

    static final String TAG = MusicPlayerService.class.getName();

    static final int NO_TRACK_PROVIDED = -1;
    private static final int NO_PENDING = -2;
    private int mPendingTrackId = NO_PENDING;

    //error codes
    private static final int CANT_GET_AUDIOFOCUS = 10;
    private static final int BAD_URL = 20;
    private static int INVALID_POSITION = -1;
    private int mPausePosition = INVALID_POSITION;
    private MediaPlayer mPlayer;
    private AudioManager audioManager;
    private NoisyAudioStreamReceiver mNoisyAudioStreamReceiver = new NoisyAudioStreamReceiver();
    private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
    private List<Playable> mTracks;
    private Playable mCurrentTrack;
    private MusicServiceCallbacks mCallbacks;

    //player settings
    private SharedPreferences mPreferences;
    private boolean mRepeat;
    private boolean mShuffle;

    //player states
    private PlayerState mState;
    private InitState initState = new InitState(this);
    private ReadyState readyState = new ReadyState(this);
    private PlayingState playingState = new PlayingState(this);
    private PausedState pausedState = new PausedState(this);
    private PrepareState prepareState = new PrepareState(this);

    private DefaultSongPicker defaultSongPicker = new DefaultSongPicker();
    private ShuffleSongPicker shuffleSongPicker = new ShuffleSongPicker();

    //LIFECYCLE

    @Override
    public void onCreate() {
        super.onCreate();
        mPreferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
        setState(initState);

        loadTracks(new LoadTracksCallback() {
            @Override
            public void onLoaded(List<Playable> trackList) {

                Log.d(MusicPlayerService.TAG, "Songs: " + trackList.toString());

                mTracks = trackList;

                defaultSongPicker.refresh(mTracks);
                shuffleSongPicker.refresh(mTracks);

                setState(readyState);

                if (hasPendingTrack()) {
                    mState.play(consumePendingTrack());
                }
            }

            @Override
            public void onError(Exception e) {
                Log.e(TAG, "Can't load tracks", e);
                stopSelf();
            }
        });

        audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
        mPlayer = new MediaPlayer();
        mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
        mPlayer.setOnPreparedListener(this);
        mPlayer.setOnErrorListener(this);
        mPlayer.setOnCompletionListener(this);

        Log.d(TAG, "Created");
    }

    /**
     * Handle any intent actions here
     */
    @Override
    public int onStartCommand(final Intent intent, int flags, int startId) {
        String intentAction = intent.getAction();
        Log.d(TAG, "Got action: " + intentAction);
        if (intentAction == null) {
            throw new IllegalArgumentException("Trying to start player service without any action, check docs");
        }
        Action action = Action.valueOf(intentAction);
        switch (action) {
            case STOP_SERVICE:
                stopSelf();
                break;
            case PAUSE:
                mState.pause();
                break;
            case PLAY:
                int trackId = intent.getIntExtra(EXTRA_TRACK_ID, NO_TRACK_PROVIDED);
                mState.play(trackId);
                break;
            case NEXT:
                mState.play(NO_TRACK_PROVIDED);
                break;
            case SEEK_TO:
                mPausePosition = intent.getIntExtra(EXTRA_SEEK, INVALID_POSITION);
                mCurrentTrack = getTrackById(intent.getIntExtra(EXTRA_TRACK_ID, NO_TRACK_PROVIDED));
                Log.d(TAG, "Seek = " + mPausePosition);
                break;
            case REQUEST_STATUS:
                if (mCallbacks != null) {
                    if (mPlayer.isPlaying()) {
                        mCallbacks.onPlaybackStarted(mCurrentTrack, mPlayer.getCurrentPosition() / 1000);
                    } else {
                        mCallbacks.onPlaybackPaused();
                    }
                }
                break;
            default:
                throw new IllegalArgumentException("Unknown action: " + intentAction + ". Check docs");
        }
        return START_NOT_STICKY;
    }

    /**
     * clean up work
     */
    @Override
    public void onDestroy() {
        Log.d(TAG, "Destroyed");
        if (mCallbacks != null) {
            mCallbacks.onPlayerDestroyed();
        }

        if (mPlayer != null) {
            mPlayer.release();
        }
        try {
            unregisterReceiver(mNoisyAudioStreamReceiver);
        } catch (IllegalArgumentException e) {
            Log.d(TAG, "mNoisyAudioStreamReceiver already unregistered");
        }
        audioManager.abandonAudioFocus(this);
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        Log.d(TAG, "Prepared");
        int result = audioManager.requestAudioFocus(MusicPlayerService.this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
        if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            handleError(CANT_GET_AUDIOFOCUS, "Can't get audiofocus for streaming", null);
            return;
        }

        registerReceiver(mNoisyAudioStreamReceiver, intentFilter);

        mPlayer.start();

        if (mPausePosition != INVALID_POSITION) {
            mPlayer.seekTo(mPausePosition * 1000);
            mPausePosition = INVALID_POSITION;
        }

        if (mCallbacks != null) {
            mCallbacks.onPlaybackStarted(mCurrentTrack, mPlayer.getCurrentPosition() / 1000);
        }

        setState(playingState);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        handleError(what, String.format("MediaPlayer error. what=%d ; extra =%d", what, extra), null);
        stopSelf();
        return true;
    }

    /**
     * play next one
     */
    @Override
    public void onCompletion(MediaPlayer mp) {
        play();
    }

    // Handling

    /**
     * If no track provided - choose it by some algorithm
     */
    void play() {
        if (hasPendingTrack()) {
            mState.play(consumePendingTrack());
            return;
        }

        Playable playable = getNextTrack();
        if (playable == null) {
            stopSelf();
            return;
        }

        play(playable);
    }

    void play(int trackId) {
        if (trackId == MusicPlayerService.NO_TRACK_PROVIDED) {
            play();
            return;
        }

        Playable playable = getTrackById(trackId);
        if (playable == null) {
            Log.w(MusicPlayerService.TAG, "Can't find track with id: " + trackId);
            play();
            return;
        }

        play(playable);
    }

    /**
     * Prepare and play provided track
     *
     * @param playable track info
     */
    void play(Playable playable) {
        try {
            mCurrentTrack = playable;
            mPlayer.reset();
            mPlayer.setDataSource(this, Uri.parse(playable.getUrl()));
            mPlayer.prepareAsync();
            setState(prepareState);
        } catch (IOException e) {
            Log.e(TAG, "Can't prepare track: " + playable, e);
        }
    }

    void pause() {
        if (!mPlayer.isPlaying()) {
            Log.w(TAG, "Trying to pause stopped player");
            return;
        }

        mPlayer.pause();
        setState(pausedState);

        setPendingTrackId(mCurrentTrack.getId());
        mPausePosition = mPlayer.getCurrentPosition();

        if (mCallbacks != null) {
            mCallbacks.onPlaybackPaused();
        }

        Log.d(TAG, "Paused");
        try {
            unregisterReceiver(mNoisyAudioStreamReceiver);
        } catch (Exception e) {
            Log.d(TAG, "mNoisyAudioStreamReceiver already unregistered");
        }
    }

    //MISC

    protected void setCallbacks(MusicServiceCallbacks mCallbacks) {
        this.mCallbacks = mCallbacks;
    }

    /**
     * call onDeactivated on previous state
     * change player state
     * call onActivated on new one
     */
    void setState(PlayerState state) {
        if (mState != null) {
            mState.onDeactivated();
        }
        mState = state;
        mState.onActivated();
        Log.d(TAG, "State Changed: " + mState.getClass().getSimpleName());
    }

    void setPendingTrackId(int trackId) {
        mPendingTrackId = trackId;
    }

    boolean hasPendingTrack() {
        return mPendingTrackId != NO_PENDING;
    }

    int consumePendingTrack() {
        int result = mPendingTrackId;
        mPendingTrackId = NO_PENDING;
        return result;
    }

    /**
     * @param id track id
     * @return found track or null if not found / no tracks available
     */
    @Nullable
    Playable getTrackById(int id) {
        if (mTracks == null || mTracks.size() == 0) {
            return null;
        }
        for (Playable playable : mTracks) {
            if (playable.getId() == id) {
                return playable;
            }
        }
        return null;
    }

    /**
     * track picker, all logic inside
     *
     * @return track to play or null if no tracks found/ready
     */
    @Nullable
    private Playable getNextTrack() {
        refreshPlayerSettings();

        if (mRepeat) {
            return mCurrentTrack;
        }

        if (mShuffle) {
            return shuffleSongPicker.pickSong();
        } else {
            return defaultSongPicker.pickSong();
        }
    }

    /**
     * We use SharedPreferences with hardcoded keys to define mPlayer settings from UI, and check them here
     */
    private void refreshPlayerSettings() {
        mRepeat = mPreferences.getBoolean(PREF_REPEAT, false);
        mShuffle = mPreferences.getBoolean(PREF_SHUFFLE, false);
    }

    /**
     * messages ui about exceptions
     * all error logging happens here too
     */
    private void handleError(int code, String message, Throwable e) {
        if (e != null) {
            Log.e(TAG, message, e);
        } else {
            Log.e(TAG, message);
        }

        if (mCallbacks != null) {
            mCallbacks.onPlayerDestroyed();
        }

        stopForeground(true);

        //todo handle
        switch (code) {
            case CANT_GET_AUDIOFOCUS:
                break;
            case BAD_URL:
                break;
            case MediaPlayer.MEDIA_ERROR_UNKNOWN:
                break;
            case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
                break;
            case MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK:
                break;
        }
    }

    @Override
    public void onAudioFocusChange(int focusChange) {
        switch (focusChange) {

            /**
             * resume playback
             */
            case AudioManager.AUDIOFOCUS_GAIN:
                break;

            /**
             * Lost focus for an unbounded amount of time: stop playback and release media mPlayer
             */
            case AudioManager.AUDIOFOCUS_LOSS:
                if (mPlayer != null) {
                    if (mPlayer.isPlaying()) {
                        pause();
                    }
                    mPlayer.release();
                    mPlayer = null;
                }
                break;

            /**
             * Lost focus for a short time, but we have to stop
             * playback. We don't release the media mPlayer because playback
             * is likely to resume
             */
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                if (mPlayer != null) {
                    if (mPlayer.isPlaying()) {
                        pause();
                    }
                }
                break;

            /**
             *Lost focus for a short time, but it's ok to keep playing
             at an attenuated level
             */
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                //todo
                break;
        }
    }

    /**
     * Method to load tracks async from db for example
     */
    protected abstract void loadTracks(LoadTracksCallback callback);

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * UI calls
     */
    public enum Action {
        /**
         * if notification button "cancel" pressed playback must stop
         */
        STOP_SERVICE,

        /**
         * stop button from notification or music fragment
         */
        PAUSE,

        /**
         * play button from notification or music fragment
         */
        PLAY,

        NEXT,

        SEEK_TO,

        REQUEST_STATUS

    }

    public interface LoadTracksCallback {

        public void onLoaded(List<Playable> trackList);

        public void onError(Exception e);

    }

    public interface MusicServiceCallbacks {

        public void onPlaybackStarted(Playable track, int currentPosition);

        public void onPlaybackPaused();

        public void onPlayerDestroyed();

    }

    private class NoisyAudioStreamReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
                pause();
            }
        }
    }
}




Java Source Code List

com.dsvoronin.musicplayer.AbstractSongPicker.java
com.dsvoronin.musicplayer.DefaultSongPicker.java
com.dsvoronin.musicplayer.InitState.java
com.dsvoronin.musicplayer.MusicPlayerService.java
com.dsvoronin.musicplayer.OnPlaybackStartEvent.java
com.dsvoronin.musicplayer.OnPlaybackStopEvent.java
com.dsvoronin.musicplayer.PausedState.java
com.dsvoronin.musicplayer.Playable.java
com.dsvoronin.musicplayer.PlayerState.java
com.dsvoronin.musicplayer.PlayingState.java
com.dsvoronin.musicplayer.PrepareState.java
com.dsvoronin.musicplayer.ReadyState.java
com.dsvoronin.musicplayer.ShuffleSongPicker.java
com.dsvoronin.musicplayer.SongPicker.java