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) { 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); widgetLayout = new RemoteViews(context.getPackageName(), R.layout.music_player_widget);
linkButtons(context, widgetLayout); 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); updateWidgetsPlayState(context, isPlaying);
loadAlbumArt(context, song); loadAlbumArt(context, song);
} }

View file

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

View file

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

View file

@ -329,7 +329,7 @@ public class MusicControllerActivity extends AbsFabActivity {
setHeadersText(); setHeadersText();
setUpAlbumArtAndApplyPalette(); setUpAlbumArtAndApplyPalette();
totalSongDuration.setText(MusicUtil.getReadableDurationString(song.duration)); totalSongDuration.setText(MusicUtil.getReadableDurationString(song.duration));
currentSongProgress.setText(MusicUtil.getReadableDurationString(-1)); currentSongProgress.setText(MusicUtil.getReadableDurationString(0));
} }
private void setHeadersText() { private void setHeadersText() {
@ -467,6 +467,7 @@ public class MusicControllerActivity extends AbsFabActivity {
progressSlider.setMax(totalMillis); progressSlider.setMax(totalMillis);
progressSlider.setProgress(progressMillis); progressSlider.setProgress(progressMillis);
currentSongProgress.setText(MusicUtil.getReadableDurationString(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 PLAYBACK_CONTROLLER_CARD_NOW_PLAYING = "playback_controller_card_now_playing";
public static final String FADE_PLAY_PAUSE = "fade_play_pause"; public static final String FADE_PLAY_PAUSE = "fade_play_pause";
public static final String COLORED_NOTIFICATION = "colored_notification"; public static final String COLORED_NOTIFICATION = "colored_notification";
public static final String GAPLESS_PLAYBACK = "gapless_playback";
private static PreferenceUtils sInstance; private static PreferenceUtils sInstance;
@ -193,10 +194,14 @@ public final class PreferenceUtils {
return mPreferences.getBoolean(ALTERNATIVE_PROGRESS_SLIDER_NOW_PLAYING, false); return mPreferences.getBoolean(ALTERNATIVE_PROGRESS_SLIDER_NOW_PLAYING, false);
} }
public final boolean fadePlayPauseAndInterruptions() { public final boolean fadePlayPause() {
return mPreferences.getBoolean(FADE_PLAY_PAUSE, true); return mPreferences.getBoolean(FADE_PLAY_PAUSE, true);
} }
public final boolean gaplessPlayback() {
return mPreferences.getBoolean(GAPLESS_PLAYBACK, true);
}
// public final boolean downloadMissingArtistImages() { // public final boolean downloadMissingArtistImages() {
// return mPreferences.getBoolean(DOWNLOAD_MISSING_ARTIST_IMAGES, true); // 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_album_footers">Colored album footers</string>
<string name="pref_title_colored_notification">Colored notification</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_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_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_toolbar_now_playing">Opaque toolbar</string>
<string name="pref_title_opaque_statusbar_now_playing">Opaque statusbar</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_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_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_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_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_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> <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:summary="@string/pref_summary_fade_play_pause"
android:widgetLayout="@layout/preference_dynamic_checkbox" /> 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 <Preference
android:layout="@layout/preference_custom" android:layout="@layout/preference_custom"
android:key="equalizer" android:key="equalizer"