Back to project page MusicPlayer.
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.
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(); } } } }