Cleaned up and rewrote big parts of the MusicService. Added gapless playback option in settings.

This commit is contained in:
Karim Abou Zeid 2015-06-19 02:29:44 +02:00
commit 3bdecbebe4
10 changed files with 471 additions and 271 deletions

View file

@ -36,11 +36,10 @@ public class MusicPlayerWidget extends AppWidgetProvider {
}
public static void updateWidgets(final Context context, final Song song, boolean isPlaying) {
if (song.id == -1) return;
widgetLayout = new RemoteViews(context.getPackageName(), R.layout.music_player_widget);
linkButtons(context, widgetLayout);
if (song.id != -1) {
widgetLayout.setTextViewText(R.id.song_title, song.title);
}
widgetLayout.setTextViewText(R.id.song_title, song.title);
updateWidgetsPlayState(context, isPlaying);
loadAlbumArt(context, song);
}

View file

@ -45,7 +45,7 @@ public class MusicPlayerRemote {
MusicService.MusicBinder binder = (MusicService.MusicBinder) service;
musicService = binder.getService();
musicService.restorePreviousState(restoredOriginalQueue, playingQueue, position);
if(startAfterConnected) resumePlaying();
if (startAfterConnected) resumePlaying();
}
@Override
@ -78,7 +78,7 @@ public class MusicPlayerRemote {
public static void pauseSong() {
if (musicService != null) {
musicService.pausePlaying(false);
musicService.pause(false);
}
}
@ -101,23 +101,12 @@ public class MusicPlayerRemote {
}
public static boolean isPlaying() {
return musicService != null && musicService.isPlaying();
return musicService != null && musicService.isPlayingAndNotFadingDown();
}
public static void resumePlaying() {
if (musicService != null) {
musicService.resumePlaying(false);
}
}
public static long getCurrentSongId() {
if (musicService != null) {
return musicService.getCurrentSongId();
}
try {
return playingQueue.get(position).id;
} catch (Exception e) {
return -1;
musicService.play(false);
}
}
@ -131,12 +120,12 @@ public class MusicPlayerRemote {
}
public static Song getCurrentSong() {
final int position = getPosition();
if (position != -1) {
try {
return getPlayingQueue().get(position);
} catch (Exception ignored) {
}
if (musicService != null) {
return musicService.getCurrentSong();
}
try {
return getPlayingQueue().get(getPosition());
} catch (Exception ignored) {
}
return new Song();
}
@ -148,13 +137,6 @@ public class MusicPlayerRemote {
return position;
}
private static void setPosition(int position) {
MusicPlayerRemote.position = position;
if (musicService != null) {
musicService.setPosition(position);
}
}
public static ArrayList<Song> getPlayingQueue() {
if (musicService != null) {
playingQueue = musicService.getPlayingQueue();
@ -163,18 +145,14 @@ public class MusicPlayerRemote {
}
public static int getSongProgressMillis() {
if (isPlayerPrepared()) {
if (musicService != null) {
return musicService.getSongProgressMillis();
}
return -1;
}
public static boolean isPlayerPrepared() {
return musicService != null && musicService.isPlayerPrepared();
}
public static int getSongDurationMillis() {
if (isPlayerPrepared()) {
if (musicService != null) {
return musicService.getSongDurationMillis();
}
return -1;
@ -182,7 +160,7 @@ public class MusicPlayerRemote {
public static void seekTo(int millis) {
if (musicService != null) {
musicService.seekTo(millis);
musicService.seek(millis);
}
}

View file

@ -83,6 +83,7 @@ public class PlayingNotificationHelper {
}
private void buildNotification(final Song song, final boolean isPlaying, final boolean isColored) {
if (song.id == -1) return;
this.isColored = isColored;
currentSong = song;
this.isPlaying = isPlaying;

View file

@ -31,7 +31,7 @@ public class MediaButtonIntentReceiver extends BroadcastReceiver {
command = MusicService.ACTION_TOGGLE_PLAYBACK;
break;
case KeyEvent.KEYCODE_MEDIA_PLAY:
command = MusicService.ACTION_RESUME;
command = MusicService.ACTION_PLAY;
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
command = MusicService.ACTION_SKIP;

View file

@ -0,0 +1,260 @@
package com.kabouzeid.gramophone.service;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.os.Handler;
import android.os.PowerManager;
import android.util.Log;
import android.widget.Toast;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.util.PreferenceUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
public final class MultiPlayer implements MediaPlayer.OnErrorListener,
MediaPlayer.OnCompletionListener {
public static final String TAG = MultiPlayer.class.getSimpleName();
private final WeakReference<MusicService> mService;
private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
private MediaPlayer mNextMediaPlayer;
private Handler mHandler;
private boolean mIsInitialized = false;
/**
* Constructor of <code>MultiPlayer</code>
*/
public MultiPlayer(final MusicService service) {
mService = new WeakReference<>(service);
mCurrentMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
}
/**
* @param path The path of the file, or the http/rtsp URL of the stream
* you want to play
*/
public void setDataSource(final String path) {
mIsInitialized = false;
mIsInitialized = setDataSourceImpl(mCurrentMediaPlayer, path);
if (mIsInitialized) {
setNextDataSource(null);
}
}
/**
* @param player The {@link MediaPlayer} to use
* @param path The path of the file, or the http/rtsp URL of the stream
* you want to play
* @return True if the <code>player</code> has been prepared and is
* ready to play, false otherwise
*/
private boolean setDataSourceImpl(final MediaPlayer player, final String path) {
try {
player.reset();
player.setOnPreparedListener(null);
if (path.startsWith("content://")) {
player.setDataSource(mService.get(), Uri.parse(path));
} else {
player.setDataSource(path);
}
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.prepare();
} catch (final IOException e) {
return false;
} catch (final IllegalArgumentException e) {
return false;
}
player.setOnCompletionListener(this);
player.setOnErrorListener(this);
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, mService.get().getPackageName());
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
mService.get().sendBroadcast(intent);
return true;
}
/**
* Set the MediaPlayer to start when this MediaPlayer finishes playback.
*
* @param path The path of the file, or the http/rtsp URL of the stream
* you want to play
*/
public void setNextDataSource(final String path) {
try {
mCurrentMediaPlayer.setNextMediaPlayer(null);
} catch (IllegalArgumentException e) {
Log.i(TAG, "Next media player is current one, continuing");
} catch (IllegalStateException e) {
Log.e(TAG, "Media player not initialized!");
return;
}
if (mNextMediaPlayer != null) {
mNextMediaPlayer.release();
mNextMediaPlayer = null;
}
if (path == null) {
return;
}
if (PreferenceUtils.getInstance(mService.get()).gaplessPlayback()) {
mNextMediaPlayer = new MediaPlayer();
mNextMediaPlayer.setWakeMode(mService.get(), PowerManager.PARTIAL_WAKE_LOCK);
mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
if (setDataSourceImpl(mNextMediaPlayer, path)) {
mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
} else {
if (mNextMediaPlayer != null) {
mNextMediaPlayer.release();
mNextMediaPlayer = null;
}
}
}
}
/**
* Sets the handler
*
* @param handler The handler to use
*/
public void setHandler(final Handler handler) {
mHandler = handler;
}
/**
* @return True if the player is ready to go, false otherwise
*/
public boolean isInitialized() {
return mIsInitialized;
}
/**
* Starts or resumes playback.
*/
public void start() {
mCurrentMediaPlayer.start();
}
/**
* Resets the MediaPlayer to its uninitialized state.
*/
public void stop() {
mCurrentMediaPlayer.reset();
mIsInitialized = false;
}
/**
* Releases resources associated with this MediaPlayer object.
*/
public void release() {
stop();
mCurrentMediaPlayer.release();
}
/**
* Pauses playback. Call start() to resume.
*/
public void pause() {
mCurrentMediaPlayer.pause();
}
/**
* Checks whether the MultiPlayer is playing.
*/
public boolean isPlaying() {
return mIsInitialized && mCurrentMediaPlayer.isPlaying();
}
/**
* Gets the duration of the file.
*
* @return The duration in milliseconds
*/
public int duration() {
return mCurrentMediaPlayer.getDuration();
}
/**
* Gets the current playback position.
*
* @return The current position in milliseconds
*/
public int position() {
return mCurrentMediaPlayer.getCurrentPosition();
}
/**
* Gets the current playback position.
*
* @param whereto The offset in milliseconds from the start to seek to
* @return The offset in milliseconds from the start to seek to
*/
public long seek(final long whereto) {
mCurrentMediaPlayer.seekTo((int) whereto);
return whereto;
}
/**
* Sets the volume on this player.
*
* @param vol Left and right volume scalar
*/
public void setVolume(final float vol) {
mCurrentMediaPlayer.setVolume(vol, vol);
}
/**
* Sets the audio session ID.
*
* @param sessionId The audio session ID
*/
public void setAudioSessionId(final int sessionId) {
mCurrentMediaPlayer.setAudioSessionId(sessionId);
}
/**
* Returns the audio session ID.
*
* @return The current audio session ID.
*/
public int getAudioSessionId() {
return mCurrentMediaPlayer.getAudioSessionId();
}
/**
* {@inheritDoc}
*/
@Override
public boolean onError(final MediaPlayer mp, final int what, final int extra) {
Toast.makeText(mService.get().getApplicationContext(), mService.get().getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
mService.get().playNextSong(true);
return false;
}
/**
* {@inheritDoc}
*/
@Override
public void onCompletion(final MediaPlayer mp) {
if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
mIsInitialized = false;
mCurrentMediaPlayer.release();
mCurrentMediaPlayer = mNextMediaPlayer;
mIsInitialized = true;
mNextMediaPlayer = null;
mHandler.sendEmptyMessage(MusicService.TRACK_WENT_TO_NEXT);
} else {
mService.get().acquireWakeLock(30000);
mHandler.sendEmptyMessage(MusicService.TRACK_ENDED);
mHandler.sendEmptyMessage(MusicService.RELEASE_WAKELOCK);
}
}
}

View file

@ -11,10 +11,8 @@ import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.media.RemoteControlClient;
import android.media.audiofx.AudioEffect;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@ -50,15 +48,12 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class MusicService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, MediaPlayer.OnCompletionListener {
public class MusicService extends Service {
public static final String PHONOGRAPH_PACKAGE_NAME = "com.kabouzeid.gramophone";
public static final String MUSIC_PACKAGE_NAME = "com.android.music";
public static final int NOTIFICATION_ID = 1337;
public static final String ACTION_TOGGLE_PLAYBACK = "com.kabouzeid.gramophone.action.TOGGLE_PLAYBACK";
public static final String ACTION_PLAY = "com.kabouzeid.gramophone.action.PLAY";
public static final String ACTION_RESUME = "com.kabouzeid.gramophone.action.RESUME";
public static final String ACTION_PAUSE = "com.kabouzeid.gramophone.action.PAUSE";
public static final String ACTION_STOP = "com.kabouzeid.gramophone.action.STOP";
public static final String ACTION_SKIP = "com.kabouzeid.gramophone.action.SKIP";
@ -71,11 +66,15 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
public static final String SHUFFLEMODE_CHANGED = "com.kabouzeid.gramophone.shufflemodechanged";
public static final String POSITION_IN_SONG_CHANGED = "com.kabouzeid.phonograph.positionchanged";
private static final int FOCUSCHANGE = 5;
private static final int FOCUS_CHANGE = 5;
private static final int DUCK = 6;
private static final int UNDUCK = 7;
private static final int FADEDOWNANDPAUSE = 8;
private static final int FADEUPANDRESUME = 9;
private static final int FADE_DOWN_AND_PAUSE = 8;
private static final int FADE_UP_AND_RESUME = 9;
public static final int RELEASE_WAKELOCK = 10;
public static final int TRACK_ENDED = 11;
public static final int TRACK_WENT_TO_NEXT = 12;
public static final int PLAY_SONG = 13;
public static final int SHUFFLE_MODE_NONE = 0;
public static final int SHUFFLE_MODE_SHUFFLE = 1;
@ -86,14 +85,13 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
private static final String TAG = MusicService.class.getSimpleName();
private final IBinder musicBind = new MusicBinder();
private MediaPlayer player;
private MultiPlayer player;
private ArrayList<Song> playingQueue;
private ArrayList<Song> originalPlayingQueue;
private int currentSongId = -1;
private int position = -1;
private int nextPosition = -1;
private int shuffleMode;
private int repeatMode;
private boolean isPlayerPrepared;
private boolean pausedByTransientLossOfFocus;
private boolean thingsRegistered;
private boolean saveQueuesAgain;
@ -104,15 +102,15 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
private PowerManager.WakeLock wakeLock;
private String currentAlbumArtUri;
private MusicPlayerHandler playerHandler;
private boolean fadingDown = false;
private boolean isFadingDown = false;
private HandlerThread handlerThread;
private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().compareTo(AudioManager.ACTION_AUDIO_BECOMING_NOISY) == 0) {
pausePlaying(true);
pausePlaying(false);
pause(true);
pause(false);
}
}
};
@ -120,14 +118,13 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
private final AudioManager.OnAudioFocusChangeListener audioFocusListener = new AudioManager.OnAudioFocusChangeListener() {
@Override
public void onAudioFocusChange(final int focusChange) {
playerHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget();
playerHandler.obtainMessage(FOCUS_CHANGE, focusChange, 0).sendToTarget();
}
};
@Override
public void onCreate() {
super.onCreate();
isPlayerPrepared = false;
playingQueue = new ArrayList<>();
originalPlayingQueue = new ArrayList<>();
playingNotificationHelper = new PlayingNotificationHelper(this);
@ -144,6 +141,9 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
handlerThread.start();
playerHandler = new MusicPlayerHandler(this, handlerThread.getLooper());
player = new MultiPlayer(this);
player.setHandler(playerHandler);
registerEverything();
}
@ -183,26 +183,22 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
setUpMediaPlayerIfNeeded();
if (intent != null) {
if (intent.getAction() != null) {
String action = intent.getAction();
switch (action) {
case ACTION_TOGGLE_PLAYBACK:
if (isPlaying()) {
pausePlaying(false);
if (isPlayingAndNotFadingDown()) {
pause(false);
} else {
resumePlaying(false);
play(false);
}
break;
case ACTION_PAUSE:
pausePlaying(false);
pause(false);
break;
case ACTION_PLAY:
playSong();
break;
case ACTION_RESUME:
resumePlaying(false);
play(false);
break;
case ACTION_REWIND:
back(true);
@ -211,7 +207,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
playNextSong(true);
break;
case ACTION_STOP:
stopPlaying();
stop();
break;
case ACTION_QUIT:
killEverythingAndReleaseResources();
@ -260,33 +256,22 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
}
private void killEverythingAndReleaseResources() {
stopPlaying();
stop();
playingNotificationHelper.killNotification();
savePosition();
saveQueues();
stopSelf();
}
public void stopPlaying() {
public void stop() {
pausedByTransientLossOfFocus = false;
isPlayerPrepared = false;
if (player != null) {
player.stop();
player.release();
player = null;
}
player.stop();
player.release();
notifyChange(PLAYSTATE_CHANGED);
}
public boolean isPlaying() {
return isPlaying(false);
}
private boolean isPlaying(boolean doNotConsiderFadingDown) {
if (doNotConsiderFadingDown)
return player != null && isPlayerPrepared && player.isPlaying();
else
return player != null && isPlayerPrepared && player.isPlaying() && !fadingDown;
public boolean isPlayingAndNotFadingDown() {
return player.isPlaying() && !isFadingDown;
}
public void saveQueues() {
@ -315,59 +300,33 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
return position;
}
public void setPosition(int position) {
private void setPosition(int position) {
this.position = position;
}
@Override
public void onCompletion(MediaPlayer mp) {
if (isLastTrack() && getRepeatMode() == REPEAT_MODE_NONE) {
notifyChange(PLAYSTATE_CHANGED);
} else {
acquireWakeLock(30000);
playNextSong(false);
}
}
public void playNextSong(boolean force) {
if (position != -1) {
if (isPlayerPrepared) {
setPosition(getNextPosition(force));
playSong();
}
playSongAt(getNextPosition(force));
}
private void openTrackAndPrepareNextAt(int position) {
synchronized (this) {
setPosition(position);
openCurrent();
prepareNext();
notifyChange(META_CHANGED);
}
}
public void playSong() {
if (requestFocus()) {
setUpMediaPlayerIfNeeded();
registerEverything();
isPlayerPrepared = false;
player.reset();
if (position != -1) {
try {
Uri trackUri = getCurrentPositionTrackUri();
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setDataSource(getApplicationContext(), trackUri);
player.prepareAsync();
} catch (Exception ignored) {
// handled in onError()
}
currentSongId = playingQueue.get(getPosition()).id;
notifyChange(META_CHANGED);
} else {
notifyChange(PLAYSTATE_CHANGED);
}
private void openCurrent() {
synchronized (this) {
player.setDataSource(getTrackUri(getCurrentSong()));
}
}
private void openAudioEffectSession() {
if (player != null) {
final Intent intent = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
intent.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC);
sendBroadcast(intent);
private void prepareNext() {
synchronized (this) {
nextPosition = getNextPosition(false);
player.setNextDataSource(getTrackUri(getSongAt(nextPosition)));
}
}
@ -426,49 +385,40 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
.apply();
}
private void setUpMediaPlayerIfNeeded() {
if (player == null) {
player = new MediaPlayer();
public Song getCurrentSong() {
return getSongAt(getPosition());
}
player.setOnPreparedListener(this);
player.setOnCompletionListener(this);
player.setOnErrorListener(this);
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
player.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
public Song getSongAt(int position) {
if (position >= 0 && position < getPlayingQueue().size()) {
return getPlayingQueue().get(position);
} else {
return new Song();
}
}
private void updateNotification() {
playingNotificationHelper.buildNotification(playingQueue.get(position), isPlaying());
playingNotificationHelper.buildNotification(getCurrentSong(), isPlayingAndNotFadingDown());
}
private void updateWidgets() {
MusicPlayerWidget.updateWidgets(this, playingQueue.get(position), isPlaying());
MusicPlayerWidget.updateWidgets(this, getCurrentSong(), isPlayingAndNotFadingDown());
}
private Uri getCurrentPositionTrackUri() {
return ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, playingQueue.get(position).id);
private static String getTrackUri(Song song) {
return ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, song.id).toString();
}
public int getNextPosition(boolean force) {
int position = 0;
int position = getPosition() + 1;
switch (repeatMode) {
case REPEAT_MODE_NONE:
position = getPosition() + 1;
if (isLastTrack()) {
position -= 1;
}
break;
case REPEAT_MODE_ALL:
position = getPosition() + 1;
if (isLastTrack()) {
position = 0;
}
break;
case REPEAT_MODE_THIS:
if (force) {
position = getPosition() + 1;
if (isLastTrack()) {
position = 0;
}
@ -476,6 +426,12 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
position = getPosition();
}
break;
default:
case REPEAT_MODE_NONE:
if (isLastTrack()) {
position -= 1;
}
break;
}
return position;
}
@ -501,41 +457,24 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
PreferenceManager.getDefaultSharedPreferences(this).edit()
.putInt(AppKeys.SP_REPEAT_MODE, repeatMode)
.apply();
prepareNext();
notifyChange(REPEATMODE_CHANGED);
break;
}
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
isPlayerPrepared = false;
player.reset();
player = null;
notifyChange(PLAYSTATE_CHANGED);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
return false;
}
@Override
public void onPrepared(MediaPlayer mp) {
isPlayerPrepared = true;
openAudioEffectSession();
resumePlaying(false);
savePosition();
releaseWakeLock();
}
public void openQueue(final ArrayList<Song> playingQueue, final int startPosition, final boolean startPlaying) {
if (playingQueue != null && !playingQueue.isEmpty() && startPosition >= 0 && startPosition < playingQueue.size()) {
originalPlayingQueue = playingQueue;
this.playingQueue = new ArrayList<>(originalPlayingQueue);
setPosition(startPosition);
int position = startPosition;
if (shuffleMode == SHUFFLE_MODE_SHUFFLE) {
ShuffleHelper.makeShuffleList(this.playingQueue, startPosition);
setPosition(0);
position = 0;
}
if (startPlaying) {
playSong();
playSongAt(position);
}
saveState();
}
@ -612,9 +551,6 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
for (int i = 0; i < originalPlayingQueue.size(); i++) {
if (originalPlayingQueue.get(i).id == song.id) originalPlayingQueue.remove(i);
}
if (song.id == currentSongId) {
playSong();
}
saveState();
}
@ -633,59 +569,60 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
} else if (from == currentPosition) {
setPosition(to);
}
if (from != to) prepareNext();
saveState();
}
public long getCurrentSongId() {
return currentSongId;
}
public void playSongAt(final int position) {
if (position < getPlayingQueue().size() && position >= 0) {
setPosition(position);
playSong();
} else {
Log.e(TAG, "No song in queue at given index!");
}
// handle this on the handlers thread to avoid blocking the ui thread
playerHandler.removeMessages(PLAY_SONG);
playerHandler.obtainMessage(PLAY_SONG, position, 0).sendToTarget();
}
public void pausePlaying(boolean forceNoFading) {
private void playSongAtImpl(int position) {
openTrackAndPrepareNextAt(position);
play(false);
}
public void pause(boolean forceNoFading) {
pausedByTransientLossOfFocus = false;
if (!forceNoFading && PreferenceUtils.getInstance(this).fadePlayPauseAndInterruptions()) {
playerHandler.removeMessages(FADEUPANDRESUME);
playerHandler.sendEmptyMessage(FADEDOWNANDPAUSE);
if (!forceNoFading && PreferenceUtils.getInstance(this).fadePlayPause()) {
playerHandler.removeMessages(FADE_UP_AND_RESUME);
playerHandler.sendEmptyMessage(FADE_DOWN_AND_PAUSE);
} else {
pause();
pauseImpl();
}
}
private void pause() {
playerHandler.removeMessages(FADEUPANDRESUME);
if (isPlaying(true)) {
private void pauseImpl() {
playerHandler.removeMessages(FADE_UP_AND_RESUME);
if (player.isPlaying()) {
player.pause();
notifyChange(PLAYSTATE_CHANGED);
}
}
public void resumePlaying(boolean forceNoFading) {
if (!forceNoFading && PreferenceUtils.getInstance(this).fadePlayPauseAndInterruptions()) {
playerHandler.removeMessages(FADEDOWNANDPAUSE);
playerHandler.sendEmptyMessage(FADEUPANDRESUME);
public void play(boolean forceNoFading) {
if (!forceNoFading && PreferenceUtils.getInstance(this).fadePlayPause()) {
playerHandler.removeMessages(FADE_DOWN_AND_PAUSE);
playerHandler.sendEmptyMessage(FADE_UP_AND_RESUME);
} else {
if (player != null) player.setVolume(1f, 1f);
resume();
if (player != null) player.setVolume(1f);
playImpl();
}
}
private void resume() {
playerHandler.removeMessages(FADEDOWNANDPAUSE);
if (!isPlaying(true)) {
private void playImpl() {
synchronized (this) {
playerHandler.removeMessages(FADE_DOWN_AND_PAUSE);
if (requestFocus()) {
if (isPlayerPrepared()) {
player.start();
notifyChange(PLAYSTATE_CHANGED);
} else {
playSong();
if (!player.isPlaying()) {
if (!player.isInitialized()) {
playSongAt(getPosition());
} else {
player.start();
notifyChange(PLAYSTATE_CHANGED);
}
}
} else {
Toast.makeText(this, getResources().getString(R.string.audio_focus_denied), Toast.LENGTH_SHORT).show();
@ -694,65 +631,54 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
}
public void playPreviousSong(boolean force) {
if (position != -1) {
setPosition(getPreviousPosition(force));
playSong();
}
playSongAt(getPreviousPosition(force));
}
public void back(boolean force) {
if (position != -1) {
if (isPlayerPrepared() && getSongProgressMillis() > 2000) {
seekTo(0);
} else {
playPreviousSong(force);
}
if (getSongProgressMillis() > 2000) {
seek(0);
} else {
playPreviousSong(force);
}
}
public int getPreviousPosition(boolean force) {
int position = 0;
int newPosition = getPosition() - 1;
switch (repeatMode) {
case REPEAT_MODE_NONE:
position = getPosition() - 1;
if (position < 0) {
position = 0;
}
break;
case REPEAT_MODE_ALL:
position = getPosition() - 1;
if (position < 0) {
position = getPlayingQueue().size() - 1;
if (newPosition < 0) {
newPosition = getPlayingQueue().size() - 1;
}
break;
case REPEAT_MODE_THIS:
if (force) {
position = getPosition() - 1;
if (position < 0) {
position = getPlayingQueue().size() - 1;
if (newPosition < 0) {
newPosition = getPlayingQueue().size() - 1;
}
} else {
position = getPosition();
newPosition = getPosition();
}
break;
default:
case REPEAT_MODE_NONE:
if (newPosition < 0) {
newPosition = 0;
}
break;
}
return position;
return newPosition;
}
public int getSongProgressMillis() {
return player.getCurrentPosition();
return player.isInitialized() ? player.position() : 0;
}
public int getSongDurationMillis() {
return player.getDuration();
return player.isInitialized() ? player.duration() : 0;
}
public void seekTo(int millis) {
player.seekTo(millis);
}
public boolean isPlayerPrepared() {
return player != null && isPlayerPrepared;
public void seek(int millis) {
player.seek(millis);
}
public void cycleRepeatMode() {
@ -788,35 +714,37 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
switch (shuffleMode) {
case SHUFFLE_MODE_SHUFFLE:
this.shuffleMode = shuffleMode;
ShuffleHelper.makeShuffleList(this.playingQueue, getPosition());
ShuffleHelper.makeShuffleList(this.getPlayingQueue(), getPosition());
setPosition(0);
break;
case SHUFFLE_MODE_NONE:
this.shuffleMode = shuffleMode;
int currentSongId = getCurrentSong().id;
playingQueue = new ArrayList<>(originalPlayingQueue);
int newPosition = 0;
for (Song song : playingQueue) {
for (Song song : getPlayingQueue()) {
if (song.id == currentSongId) {
newPosition = playingQueue.indexOf(song);
newPosition = getPlayingQueue().indexOf(song);
}
}
setPosition(newPosition);
break;
}
prepareNext();
notifyChange(SHUFFLEMODE_CHANGED);
}
private void notifyChange(final String what) {
final Intent internalIntent = new Intent(what);
final int position = getPosition();
if (position >= 0 && !playingQueue.isEmpty()) {
final Song currentSong = playingQueue.get(position);
final Song currentSong = getCurrentSong();
if (currentSong.id != -1) {
internalIntent.putExtra("id", currentSong.id);
internalIntent.putExtra("artist", currentSong.artistName);
internalIntent.putExtra("album", currentSong.albumName);
internalIntent.putExtra("track", currentSong.title);
}
internalIntent.putExtra("playing", isPlaying());
internalIntent.putExtra("playing", isPlayingAndNotFadingDown());
sendStickyBroadcast(internalIntent);
//to let other apps know whats playing. i.E. last.fm (scrobbling) or musixmatch
@ -825,7 +753,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
sendStickyBroadcast(publicMusicIntent);
if (what.equals(PLAYSTATE_CHANGED)) {
final boolean isPlaying = isPlaying();
final boolean isPlaying = isPlayingAndNotFadingDown();
playingNotificationHelper.updatePlayState(isPlaying);
MusicPlayerWidget.updateWidgetsPlayState(this, isPlaying);
remoteControlClient.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED);
@ -842,13 +770,13 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
return player.getAudioSessionId();
}
private void releaseWakeLock() {
public void releaseWakeLock() {
if (wakeLock.isHeld()) {
wakeLock.release();
}
}
private void acquireWakeLock(long milli) {
public void acquireWakeLock(long milli) {
wakeLock.acquire(milli);
}
@ -884,7 +812,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
currentDuckVolume = .2f;
}
if (service.player != null)
service.player.setVolume(currentDuckVolume, currentDuckVolume);
service.player.setVolume(currentDuckVolume);
break;
case UNDUCK:
@ -895,48 +823,66 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
currentDuckVolume = 1.0f;
}
if (service.player != null)
service.player.setVolume(currentDuckVolume, currentDuckVolume);
service.player.setVolume(currentDuckVolume);
break;
case FADEDOWNANDPAUSE:
if (!service.fadingDown) {
service.fadingDown = true;
case FADE_DOWN_AND_PAUSE:
if (!service.isFadingDown) {
service.isFadingDown = true;
service.notifyChange(PLAYSTATE_CHANGED);
}
currentPlayPauseFadeVolume -= .2f;
currentPlayPauseFadeVolume -= .125f;
if (currentPlayPauseFadeVolume > 0f) {
sendEmptyMessageDelayed(FADEDOWNANDPAUSE, 10);
sendEmptyMessageDelayed(FADE_DOWN_AND_PAUSE, 10);
} else {
currentPlayPauseFadeVolume = 0f;
service.fadingDown = false;
service.pausePlaying(true);
service.isFadingDown = false;
service.pause(true);
}
if (service.player != null)
service.player.setVolume(currentPlayPauseFadeVolume, currentPlayPauseFadeVolume);
service.player.setVolume(currentPlayPauseFadeVolume);
break;
case FADEUPANDRESUME:
if (service.fadingDown) {
service.fadingDown = false;
case FADE_UP_AND_RESUME:
if (service.isFadingDown) {
service.isFadingDown = false;
service.notifyChange(PLAYSTATE_CHANGED);
}
service.resume();
currentPlayPauseFadeVolume += .2f;
service.playImpl();
currentPlayPauseFadeVolume += .125f;
if (currentPlayPauseFadeVolume < 1.0f) {
sendEmptyMessageDelayed(FADEUPANDRESUME, 10);
sendEmptyMessageDelayed(FADE_UP_AND_RESUME, 10);
} else {
currentPlayPauseFadeVolume = 1.0f;
}
if (service.player != null)
service.player.setVolume(currentPlayPauseFadeVolume, currentPlayPauseFadeVolume);
service.player.setVolume(currentPlayPauseFadeVolume);
break;
case FOCUSCHANGE:
case TRACK_WENT_TO_NEXT:
service.setPosition(service.nextPosition);
service.prepareNext();
service.notifyChange(META_CHANGED);
break;
case TRACK_ENDED:
service.playNextSong(false);
break;
case RELEASE_WAKELOCK:
service.releaseWakeLock();
break;
case PLAY_SONG:
service.playSongAtImpl(msg.arg1);
break;
case FOCUS_CHANGE:
switch (msg.arg1) {
case AudioManager.AUDIOFOCUS_GAIN:
service.registerEverything();
if (!service.isPlaying() && service.pausedByTransientLossOfFocus) {
service.resumePlaying(false);
if (!service.isPlayingAndNotFadingDown() && service.pausedByTransientLossOfFocus) {
service.play(false);
}
removeMessages(DUCK);
sendEmptyMessage(UNDUCK);
@ -944,7 +890,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
service.pausePlaying(true);
service.pause(true);
service.unregisterEverything();
break;
@ -952,8 +898,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
service.pausePlaying(false);
service.pausedByTransientLossOfFocus = service.isPlaying();
service.pause(false);
service.pausedByTransientLossOfFocus = service.isPlayingAndNotFadingDown();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:

View file

@ -329,7 +329,7 @@ public class MusicControllerActivity extends AbsFabActivity {
setHeadersText();
setUpAlbumArtAndApplyPalette();
totalSongDuration.setText(MusicUtil.getReadableDurationString(song.duration));
currentSongProgress.setText(MusicUtil.getReadableDurationString(-1));
currentSongProgress.setText(MusicUtil.getReadableDurationString(0));
}
private void setHeadersText() {
@ -467,6 +467,7 @@ public class MusicControllerActivity extends AbsFabActivity {
progressSlider.setMax(totalMillis);
progressSlider.setProgress(progressMillis);
currentSongProgress.setText(MusicUtil.getReadableDurationString(progressMillis));
totalSongDuration.setText(MusicUtil.getReadableDurationString(totalMillis));
}
};
});

View file

@ -42,6 +42,7 @@ public final class PreferenceUtils {
public static final String PLAYBACK_CONTROLLER_CARD_NOW_PLAYING = "playback_controller_card_now_playing";
public static final String FADE_PLAY_PAUSE = "fade_play_pause";
public static final String COLORED_NOTIFICATION = "colored_notification";
public static final String GAPLESS_PLAYBACK = "gapless_playback";
private static PreferenceUtils sInstance;
@ -193,10 +194,14 @@ public final class PreferenceUtils {
return mPreferences.getBoolean(ALTERNATIVE_PROGRESS_SLIDER_NOW_PLAYING, false);
}
public final boolean fadePlayPauseAndInterruptions() {
public final boolean fadePlayPause() {
return mPreferences.getBoolean(FADE_PLAY_PAUSE, true);
}
public final boolean gaplessPlayback() {
return mPreferences.getBoolean(GAPLESS_PLAYBACK, true);
}
// public final boolean downloadMissingArtistImages() {
// return mPreferences.getBoolean(DOWNLOAD_MISSING_ARTIST_IMAGES, true);
// }

View file

@ -109,6 +109,7 @@
<string name="pref_title_colored_album_footers">Colored album footers</string>
<string name="pref_title_colored_notification">Colored notification</string>
<string name="pref_title_fade_play_pause">Fade play/pause</string>
<string name="pref_title_gapless_playback">Gapless playback</string>
<string name="pref_title_force_square_album_art">Force square album art</string>
<string name="pref_title_opaque_toolbar_now_playing">Opaque toolbar</string>
<string name="pref_title_opaque_statusbar_now_playing">Opaque statusbar</string>
@ -143,6 +144,7 @@
<string name="pref_summary_colored_album_footers">"Album footers in the grid are colored with the album cover\'s vibrant color."</string>
<string name="pref_summary_colored_notification">"The notification is colored with the album cover\'s vibrant color."</string>
<string name="pref_summary_fade_play_pause">"Fades the song in/out on play/pause."</string>
<string name="pref_summary_gapless_playback">"Eliminates the gap between two songs. Disabling this might fix playback issues."</string>
<string name="pref_summary_force_square_album_art">Album art in the now playing view is forced to be squared.</string>
<string name="pref_summary_opaque_toolbar_now_playing">The toolbar is opaque and do not cover the album art.</string>
<string name="pref_summary_opaque_statusbar_now_playing">The statusbar is opaque and do not cover the album art.</string>

View file

@ -11,6 +11,14 @@
android:summary="@string/pref_summary_fade_play_pause"
android:widgetLayout="@layout/preference_dynamic_checkbox" />
<CheckBoxPreference
android:layout="@layout/preference_custom"
android:defaultValue="true"
android:key="gapless_playback"
android:title="@string/pref_title_gapless_playback"
android:summary="@string/pref_summary_gapless_playback"
android:widgetLayout="@layout/preference_dynamic_checkbox" />
<Preference
android:layout="@layout/preference_custom"
android:key="equalizer"