Support gapless playback
Use the new setNextMediaPlayer API.
Change-Id: I2af509caad75bd96b516cf24bc11a5669c8df0b2
diff --git a/src/com/android/music/MediaPlaybackService.java b/src/com/android/music/MediaPlaybackService.java
index 873d7ca..aa606eb 100644
--- a/src/com/android/music/MediaPlaybackService.java
+++ b/src/com/android/music/MediaPlaybackService.java
@@ -105,6 +105,7 @@
private static final int FOCUSCHANGE = 4;
private static final int FADEDOWN = 5;
private static final int FADEUP = 6;
+ private static final int TRACK_WENT_TO_NEXT = 7;
private static final int MAX_HISTORY_SIZE = 100;
private MultiPlayer mPlayer;
@@ -118,6 +119,7 @@
private Vector<Integer> mHistory = new Vector<Integer>(MAX_HISTORY_SIZE);
private Cursor mCursor;
private int mPlayPos = -1;
+ private int mNextPlayPos = -1;
private static final String LOGTAG = "MediaPlaybackService";
private final Shuffler mRand = new Shuffler();
private int mOpenFailedCounter = 0;
@@ -191,9 +193,20 @@
// reopen the same song (it will start again
// from the beginning though when the user
// restarts)
- openCurrent();
+ openCurrentAndNext();
}
break;
+ case TRACK_WENT_TO_NEXT:
+ mPlayPos = mNextPlayPos;
+ if (mCursor != null) {
+ mCursor.close();
+ mCursor = null;
+ }
+ mCursor = getCursorForId(mPlayList[mPlayPos]);
+ notifyChange(META_CHANGED);
+ updateNotification();
+ setNextTrack();
+ break;
case TRACK_ENDED:
if (mRepeatMode == REPEAT_CURRENT) {
seek(0);
@@ -536,7 +549,7 @@
// own, potentially at some random inconvenient time.
mOpenFailedCounter = 20;
mQuietMode = true;
- openCurrent();
+ openCurrentAndNext();
mQuietMode = false;
if (!mPlayer.isInitialized()) {
// couldn't restore the saved state
@@ -872,7 +885,7 @@
notifyChange(QUEUE_CHANGED);
if (action == NOW) {
mPlayPos = mPlayListLen - list.length;
- openCurrent();
+ openCurrentAndNext();
play();
notifyChange(META_CHANGED);
return;
@@ -880,7 +893,7 @@
}
if (mPlayPos < 0) {
mPlayPos = 0;
- openCurrent();
+ openCurrentAndNext();
play();
notifyChange(META_CHANGED);
}
@@ -925,7 +938,7 @@
mHistory.clear();
saveBookmarkIfNeeded();
- openCurrent();
+ openCurrentAndNext();
if (oldId != getAudioId()) {
notifyChange(META_CHANGED);
}
@@ -987,7 +1000,17 @@
}
}
- private void openCurrent() {
+ private Cursor getCursorForId(long lid) {
+ String id = String.valueOf(lid);
+
+ Cursor c = getContentResolver().query(
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+ mCursorCols, "_id=" + id , null, null);
+ c.moveToFirst();
+ return c;
+ }
+
+ private void openCurrentAndNext() {
synchronized (this) {
if (mCursor != null) {
mCursor.close();
@@ -999,14 +1022,11 @@
}
stop(false);
- String id = String.valueOf(mPlayList[mPlayPos]);
-
- mCursor = getContentResolver().query(
- MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
- mCursorCols, "_id=" + id , null, null);
- if (mCursor != null) {
- mCursor.moveToFirst();
- open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
+ Cursor c = getCursorForId(mPlayList[mPlayPos]);
+
+ if (c != null) {
+ mCursor = c;
+ open(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + mCursor.getLong(IDCOLIDX));
// go to bookmark if needed
if (isPodcast()) {
long bookmark = getBookmark();
@@ -1014,10 +1034,23 @@
// so it's easier to get back in to the narrative.
seek(bookmark - 5000);
}
+ mNextPlayPos = getNextPosition(false);
+ if (mNextPlayPos >= 0) {
+ long id = mPlayList[mNextPlayPos];
+ mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
+ }
}
}
}
+ private void setNextTrack() {
+ mNextPlayPos = getNextPosition(false);
+ if (mNextPlayPos >= 0) {
+ long id = mPlayList[mNextPlayPos];
+ mPlayer.setNextDataSource(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/" + id);
+ }
+ }
+
/**
* Opens the specified file and readies it for playback.
*
@@ -1108,36 +1141,7 @@
mMediaplayerHandler.removeMessages(FADEDOWN);
mMediaplayerHandler.sendEmptyMessage(FADEUP);
- RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
- views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
- if (getAudioId() < 0) {
- // streaming
- views.setTextViewText(R.id.trackname, getPath());
- views.setTextViewText(R.id.artistalbum, null);
- } else {
- String artist = getArtistName();
- views.setTextViewText(R.id.trackname, getTrackName());
- if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
- artist = getString(R.string.unknown_artist_name);
- }
- String album = getAlbumName();
- if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
- album = getString(R.string.unknown_album_name);
- }
-
- views.setTextViewText(R.id.artistalbum,
- getString(R.string.notification_artist_album, artist, album)
- );
- }
-
- Notification status = new Notification();
- status.contentView = views;
- status.flags |= Notification.FLAG_ONGOING_EVENT;
- status.icon = R.drawable.stat_notify_musicplayer;
- status.contentIntent = PendingIntent.getActivity(this, 0,
- new Intent("com.android.music.PLAYBACK_VIEWER")
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
- startForeground(PLAYBACKSERVICE_STATUS, status);
+ updateNotification();
if (!mIsSupposedToBePlaying) {
mIsSupposedToBePlaying = true;
notifyChange(PLAYSTATE_CHANGED);
@@ -1150,7 +1154,39 @@
setShuffleMode(SHUFFLE_AUTO);
}
}
-
+
+ private void updateNotification() {
+ RemoteViews views = new RemoteViews(getPackageName(), R.layout.statusbar);
+ views.setImageViewResource(R.id.icon, R.drawable.stat_notify_musicplayer);
+ if (getAudioId() < 0) {
+ // streaming
+ views.setTextViewText(R.id.trackname, getPath());
+ views.setTextViewText(R.id.artistalbum, null);
+ } else {
+ String artist = getArtistName();
+ views.setTextViewText(R.id.trackname, getTrackName());
+ if (artist == null || artist.equals(MediaStore.UNKNOWN_STRING)) {
+ artist = getString(R.string.unknown_artist_name);
+ }
+ String album = getAlbumName();
+ if (album == null || album.equals(MediaStore.UNKNOWN_STRING)) {
+ album = getString(R.string.unknown_album_name);
+ }
+
+ views.setTextViewText(R.id.artistalbum,
+ getString(R.string.notification_artist_album, artist, album)
+ );
+ }
+ Notification status = new Notification();
+ status.contentView = views;
+ status.flags |= Notification.FLAG_ONGOING_EVENT;
+ status.icon = R.drawable.stat_notify_musicplayer;
+ status.contentIntent = PendingIntent.getActivity(this, 0,
+ new Intent("com.android.music.PLAYBACK_VIEWER")
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0);
+ startForeground(PLAYBACKSERVICE_STATUS, status);
+ }
+
private void stop(boolean remove_status_icon) {
if (mPlayer.isInitialized()) {
mPlayer.stop();
@@ -1246,7 +1282,7 @@
}
saveBookmarkIfNeeded();
stop(false);
- openCurrent();
+ openCurrentAndNext();
play();
notifyChange(META_CHANGED);
}
@@ -1259,7 +1295,10 @@
* assigned to mPlayPos;
*/
private int getNextPosition(boolean force) {
- if (mShuffleMode == SHUFFLE_NORMAL) {
+ if (mRepeatMode == REPEAT_CURRENT) {
+ if (mPlayPos < 0) return 0;
+ return mPlayPos;
+ } else if (mShuffleMode == SHUFFLE_NORMAL) {
// Pick random next track from the not-yet-played ones
// TODO: make it work right after adding/removing items in the queue.
@@ -1353,7 +1392,8 @@
mPlayPos = pos;
saveBookmarkIfNeeded();
stop(false);
- openCurrent();
+ mPlayPos = pos;
+ openCurrentAndNext();
play();
notifyChange(META_CHANGED);
}
@@ -1547,7 +1587,7 @@
}
boolean wasPlaying = isPlaying();
stop(false);
- openCurrent();
+ openCurrentAndNext();
if (wasPlaying) {
play();
}
@@ -1591,7 +1631,7 @@
mPlayListLen = 0;
doAutoShuffleUpdate();
mPlayPos = 0;
- openCurrent();
+ openCurrentAndNext();
play();
notifyChange(META_CHANGED);
return;
@@ -1610,6 +1650,7 @@
public void setRepeatMode(int repeatmode) {
synchronized(this) {
mRepeatMode = repeatmode;
+ setNextTrack();
saveQueue(false);
}
}
@@ -1660,7 +1701,7 @@
synchronized(this) {
stop(false);
mPlayPos = pos;
- openCurrent();
+ openCurrentAndNext();
play();
notifyChange(META_CHANGED);
if (mShuffleMode == SHUFFLE_AUTO) {
@@ -1792,25 +1833,31 @@
* other media files.
*/
private class MultiPlayer {
- private MediaPlayer mMediaPlayer = new MediaPlayer();
+ private MediaPlayer mCurrentMediaPlayer = new MediaPlayer();
+ private MediaPlayer mNextMediaPlayer;
private Handler mHandler;
private boolean mIsInitialized = false;
public MultiPlayer() {
- mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
+ mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
}
public void setDataSource(String path) {
+ setDataSource(mCurrentMediaPlayer, path);
+ setNextDataSource(null);
+ }
+
+ public void setDataSource(MediaPlayer player, String path) {
try {
- mMediaPlayer.reset();
- mMediaPlayer.setOnPreparedListener(null);
+ player.reset();
+ player.setOnPreparedListener(null);
if (path.startsWith("content://")) {
- mMediaPlayer.setDataSource(MediaPlaybackService.this, Uri.parse(path));
+ player.setDataSource(MediaPlaybackService.this, Uri.parse(path));
} else {
- mMediaPlayer.setDataSource(path);
+ player.setDataSource(path);
}
- mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mMediaPlayer.prepare();
+ player.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ player.prepare();
} catch (IOException ex) {
// TODO: notify the user why the file couldn't be opened
mIsInitialized = false;
@@ -1820,14 +1867,30 @@
mIsInitialized = false;
return;
}
- mMediaPlayer.setOnCompletionListener(listener);
- mMediaPlayer.setOnErrorListener(errorListener);
+ player.setOnCompletionListener(listener);
+ player.setOnErrorListener(errorListener);
Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, getAudioSessionId());
i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
sendBroadcast(i);
mIsInitialized = true;
}
+
+ public void setNextDataSource(String path) {
+ mCurrentMediaPlayer.setNextMediaPlayer(null);
+ if (mNextMediaPlayer != null) {
+ mNextMediaPlayer.release();
+ mNextMediaPlayer = null;
+ }
+ if (path == null) {
+ return;
+ }
+ mNextMediaPlayer = new MediaPlayer();
+ mNextMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
+ mNextMediaPlayer.setAudioSessionId(getAudioSessionId());
+ setDataSource(mNextMediaPlayer, path);
+ mCurrentMediaPlayer.setNextMediaPlayer(mNextMediaPlayer);
+ }
public boolean isInitialized() {
return mIsInitialized;
@@ -1835,11 +1898,11 @@
public void start() {
MusicUtils.debugLog(new Exception("MultiPlayer.start called"));
- mMediaPlayer.start();
+ mCurrentMediaPlayer.start();
}
public void stop() {
- mMediaPlayer.reset();
+ mCurrentMediaPlayer.reset();
mIsInitialized = false;
}
@@ -1848,11 +1911,11 @@
*/
public void release() {
stop();
- mMediaPlayer.release();
+ mCurrentMediaPlayer.release();
}
public void pause() {
- mMediaPlayer.pause();
+ mCurrentMediaPlayer.pause();
}
public void setHandler(Handler handler) {
@@ -1861,14 +1924,21 @@
MediaPlayer.OnCompletionListener listener = new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
- // Acquire a temporary wakelock, since when we return from
- // this callback the MediaPlayer will release its wakelock
- // and allow the device to go to sleep.
- // This temporary wakelock is released when the RELEASE_WAKELOCK
- // message is processed, but just in case, put a timeout on it.
- mWakeLock.acquire(30000);
- mHandler.sendEmptyMessage(TRACK_ENDED);
- mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
+ if (mp == mCurrentMediaPlayer && mNextMediaPlayer != null) {
+ mCurrentMediaPlayer.release();
+ mCurrentMediaPlayer = mNextMediaPlayer;
+ mNextMediaPlayer = null;
+ mHandler.sendEmptyMessage(TRACK_WENT_TO_NEXT);
+ } else {
+ // Acquire a temporary wakelock, since when we return from
+ // this callback the MediaPlayer will release its wakelock
+ // and allow the device to go to sleep.
+ // This temporary wakelock is released when the RELEASE_WAKELOCK
+ // message is processed, but just in case, put a timeout on it.
+ mWakeLock.acquire(30000);
+ mHandler.sendEmptyMessage(TRACK_ENDED);
+ mHandler.sendEmptyMessage(RELEASE_WAKELOCK);
+ }
}
};
@@ -1877,12 +1947,12 @@
switch (what) {
case MediaPlayer.MEDIA_ERROR_SERVER_DIED:
mIsInitialized = false;
- mMediaPlayer.release();
+ mCurrentMediaPlayer.release();
// Creating a new MediaPlayer and settings its wakemode does not
// require the media service, so it's OK to do this now, while the
// service is still being restarted
- mMediaPlayer = new MediaPlayer();
- mMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
+ mCurrentMediaPlayer = new MediaPlayer();
+ mCurrentMediaPlayer.setWakeMode(MediaPlaybackService.this, PowerManager.PARTIAL_WAKE_LOCK);
mHandler.sendMessageDelayed(mHandler.obtainMessage(SERVER_DIED), 2000);
return true;
default:
@@ -1894,28 +1964,28 @@
};
public long duration() {
- return mMediaPlayer.getDuration();
+ return mCurrentMediaPlayer.getDuration();
}
public long position() {
- return mMediaPlayer.getCurrentPosition();
+ return mCurrentMediaPlayer.getCurrentPosition();
}
public long seek(long whereto) {
- mMediaPlayer.seekTo((int) whereto);
+ mCurrentMediaPlayer.seekTo((int) whereto);
return whereto;
}
public void setVolume(float vol) {
- mMediaPlayer.setVolume(vol, vol);
+ mCurrentMediaPlayer.setVolume(vol, vol);
}
public void setAudioSessionId(int sessionId) {
- mMediaPlayer.setAudioSessionId(sessionId);
+ mCurrentMediaPlayer.setAudioSessionId(sessionId);
}
public int getAudioSessionId() {
- return mMediaPlayer.getAudioSessionId();
+ return mCurrentMediaPlayer.getAudioSessionId();
}
}
@@ -2036,7 +2106,7 @@
writer.println(getTrackName());
writer.println(getPath());
writer.println("playing: " + mIsSupposedToBePlaying);
- writer.println("actual: " + mPlayer.mMediaPlayer.isPlaying());
+ writer.println("actual: " + mPlayer.mCurrentMediaPlayer.isPlaying());
writer.println("shuffle mode: " + mShuffleMode);
MusicUtils.debugDump(writer);
}