aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java259
1 files changed, 201 insertions, 58 deletions
diff --git a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
index 9e6662cf..ba4f4bfe 100644
--- a/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
+++ b/subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
@@ -84,6 +84,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
private final IBinder binder = new SimpleServiceBinder<DownloadService>(this);
private MediaPlayer mediaPlayer;
+ private MediaPlayer nextMediaPlayer;
private final List<DownloadFile> downloadList = new ArrayList<DownloadFile>();
private final List<DownloadFile> backgroundDownloadList = new ArrayList<DownloadFile>();
private final Handler handler = new Handler();
@@ -95,9 +96,12 @@ public class DownloadServiceImpl extends Service implements DownloadService {
private final Scrobbler scrobbler = new Scrobbler();
private final JukeboxService jukeboxService = new JukeboxService(this);
private DownloadFile currentPlaying;
+ private DownloadFile nextPlaying;
private DownloadFile currentDownloading;
private CancellableTask bufferTask;
+ private CancellableTask nextPlayingTask;
private PlayerState playerState = IDLE;
+ private PlayerState nextPlayerState = IDLE;
private boolean shufflePlay;
private long revision;
private static DownloadService instance;
@@ -148,6 +152,17 @@ public class DownloadServiceImpl extends Service implements DownloadService {
return false;
}
});
+
+ nextMediaPlayer = new MediaPlayer();
+ nextMediaPlayer.setWakeMode(this, PowerManager.PARTIAL_WAKE_LOCK);
+
+ nextMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mediaPlayer, int what, int more) {
+ handleErrorNext(new Exception("MediaPlayer error: " + what + " (" + more + ")"));
+ return false;
+ }
+ });
Util.registerMediaButtonEventReceiver(this);
@@ -197,6 +212,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
lifecycleSupport.onDestroy();
mediaPlayer.release();
+ nextMediaPlayer.release();
shufflePlayBuffer.shutdown();
if (equalizerController != null) {
equalizerController.release();
@@ -480,6 +496,18 @@ public class DownloadServiceImpl extends Service implements DownloadService {
Util.hidePlayingNotification(this, this, handler);
}
}
+
+ synchronized void setNextPlaying(int index) {
+ if((index + 1) < size()) {
+ nextPlaying = downloadList.get(index + 1);
+ nextPlayingTask = new CheckCompletionTask(nextPlaying);
+ nextPlayingTask.start();
+ } else {
+ nextPlaying = null;
+ nextPlayingTask = null;
+ setNextPlayerState(IDLE);
+ }
+ }
@Override
public synchronized int getCurrentPlayingIndex() {
@@ -539,8 +567,17 @@ public class DownloadServiceImpl extends Service implements DownloadService {
reset();
setCurrentPlaying(null, false);
} else {
+ if(nextPlayingTask != null) {
+ nextPlayingTask.cancel();
+ }
setCurrentPlaying(index, start);
- checkDownloads();
+ if(currentPlaying == nextPlaying) {
+ // Swap the media players since nextMediaPlayer is ready to play
+ MediaPlayer tmp = mediaPlayer;
+ mediaPlayer = nextMediaPlayer;
+ nextMediaPlayer = tmp;
+ setPlayerState(nextPlayerState);
+ }
if (start) {
if (jukeboxEnabled) {
jukeboxService.skip(getCurrentPlayingIndex(), 0);
@@ -549,12 +586,13 @@ public class DownloadServiceImpl extends Service implements DownloadService {
bufferAndPlay();
}
}
+ checkDownloads();
+ setNextPlaying(index);
}
}
/** Plays or resumes the playback, depending on the current player state. */
- public synchronized void togglePlayPause()
- {
+ public synchronized void togglePlayPause() {
if (playerState == PAUSED || playerState == COMPLETED) {
start();
} else if (playerState == STOPPED || playerState == IDLE) {
@@ -760,6 +798,11 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
}
}
+
+ synchronized void setNextPlayerState(PlayerState playerState) {
+ Log.i(TAG, "Next: " + this.nextPlayerState.name() + " -> " + playerState.name() + " (" + nextPlaying + ")");
+ this.nextPlayerState = playerState;
+ }
@Override
public void setSuggestedPlaylistName(String name) {
@@ -830,69 +873,95 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
private synchronized void bufferAndPlay() {
- reset();
+ if(playerState != PREPARED) {
+ reset();
- bufferTask = new BufferTask(currentPlaying, 0);
- bufferTask.start();
+ bufferTask = new BufferTask(currentPlaying, 0);
+ bufferTask.start();
+ } else {
+ doPlay(currentPlaying, 0, true);
+ }
}
private synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start) {
try {
final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
+ final boolean isPartial = file.equals(downloadFile.getPartialFile());
downloadFile.updateModificationDate();
- mediaPlayer.setOnCompletionListener(null);
- mediaPlayer.reset();
- setPlayerState(IDLE);
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mediaPlayer.setDataSource(file.getPath());
- setPlayerState(PREPARING);
- mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
- public void onPrepared(MediaPlayer mediaPlayer) {
- try {
- setPlayerState(PREPARED);
+ if(playerState == PlayerState.PREPARED) {
+ if (start) {
+ mediaPlayer.start();
+ setPlayerState(STARTED);
+ } else {
+ setPlayerState(PAUSED);
+ }
+ } else {
+ mediaPlayer.setOnCompletionListener(null);
+ mediaPlayer.reset();
+ setPlayerState(IDLE);
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mediaPlayer.setDataSource(file.getPath());
+ setPlayerState(PREPARING);
+
+ mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer mediaPlayer) {
+ try {
+ setPlayerState(PREPARED);
+
+ synchronized (DownloadServiceImpl.this) {
+ if (position != 0) {
+ Log.i(TAG, "Restarting player from position " + position);
+ mediaPlayer.seekTo(position);
+ }
+ cachedPosition = position;
- synchronized (DownloadServiceImpl.this) {
- if (position != 0) {
- Log.i(TAG, "Restarting player from position " + position);
- mediaPlayer.seekTo(position);
+ if (start) {
+ mediaPlayer.start();
+ setPlayerState(STARTED);
+ } else {
+ setPlayerState(PAUSED);
+ }
}
- cachedPosition = position;
- if (start) {
- mediaPlayer.start();
- setPlayerState(STARTED);
- } else {
- setPlayerState(PAUSED);
- }
+ lifecycleSupport.serializeDownloadQueue();
+ } catch (Exception x) {
+ handleError(x);
}
-
- lifecycleSupport.serializeDownloadQueue();
- } catch (Exception x) {
- handleError(x);
}
+ });
+ }
+
+ mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
+ Log.w(TAG, "Error on playing file " + "(" + what + ", " + extra + "): " + downloadFile);
+ int pos = cachedPosition;
+ reset();
+ downloadFile.setPlaying(false);
+ doPlay(downloadFile, pos, true);
+ downloadFile.setPlaying(true);
+ return true;
}
});
- mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mediaPlayer) {
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ setPlayerState(COMPLETED);
- // 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.
- wakeLock.acquire(60000);
+ // 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.
+ wakeLock.acquire(60000);
- setPlayerState(COMPLETED);
-
- if (!file.equals(downloadFile.getPartialFile())) {
+ if (!isPartial) {
onSongCompleted();
return;
}
- // If file is not completely downloaded, restart the playback from the current position.
- int pos = cachedPosition;
- synchronized (DownloadServiceImpl.this) {
+ // If file is not completely downloaded, restart the playback from the current position.
+ int pos = cachedPosition;
+ synchronized (DownloadServiceImpl.this) {
int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
Log.i(TAG, "Ending position " + pos + " of " + duration);
if(downloadFile.isWorkDone()) {
@@ -913,27 +982,50 @@ public class DownloadServiceImpl extends Service implements DownloadService {
bufferTask = new BufferTask(downloadFile, pos);
bufferTask.start();
}
- }
- }
- });
+ }
+ }
+ });
- mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ if(playerState == PREPARING) {
+ mediaPlayer.prepareAsync();
+ }
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+
+ private synchronized void setupNext(final DownloadFile downloadFile) {
+ try {
+ final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
+ nextMediaPlayer.setOnCompletionListener(null);
+ nextMediaPlayer.reset();
+ setNextPlayerState(IDLE);
+ nextMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ nextMediaPlayer.setDataSource(file.getPath());
+ setNextPlayerState(PREPARING);
+
+ nextMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer mediaPlayer) {
+ try {
+ setNextPlayerState(PREPARED);
+ } catch (Exception x) {
+ handleErrorNext(x);
+ }
+ }
+ });
+
+ nextMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
- Log.w(TAG, "Error on playing file " + "(" + what + ", " + extra + "): " + downloadFile);
- int pos = cachedPosition;
- reset();
- downloadFile.setPlaying(false);
- doPlay(downloadFile, pos, true);
- downloadFile.setPlaying(true);
+ Log.w(TAG, "Error on playing next " + "(" + what + ", " + extra + "): " + downloadFile);
return true;
}
});
- mediaPlayer.prepareAsync();
+ nextMediaPlayer.prepareAsync();
} catch (Exception x) {
- handleError(x);
+ handleErrorNext(x);
}
- }
+ }
@Override
public void setSleepTimerDuration(int duration){
@@ -987,6 +1079,11 @@ public class DownloadServiceImpl extends Service implements DownloadService {
mediaPlayer.reset();
setPlayerState(IDLE);
}
+ private void handleErrorNext(Exception x) {
+ Log.w(TAG, "Next Media player error: " + x, x);
+ nextMediaPlayer.reset();
+ setNextPlayerState(IDLE);
+ }
protected synchronized void checkDownloads() {
if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable()) {
@@ -1033,6 +1130,9 @@ public class DownloadServiceImpl extends Service implements DownloadService {
currentDownloading = downloadFile;
currentDownloading.download();
cleanupCandidates.add(currentDownloading);
+ if(i == (start + 1)) {
+ setNextPlayerState(DOWNLOADING);
+ }
break;
}
} else if (currentPlaying != downloadFile) {
@@ -1149,7 +1249,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
}
@Override
- public void execute() {
+ public void execute() {
setPlayerState(DOWNLOADING);
while (!bufferComplete()) {
@@ -1174,4 +1274,47 @@ public class DownloadServiceImpl extends Service implements DownloadService {
return "BufferTask (" + downloadFile + ")";
}
}
+
+ private class CheckCompletionTask extends CancellableTask {
+ private final DownloadFile downloadFile;
+ private final File partialFile;
+
+ public CheckCompletionTask(DownloadFile downloadFile) {
+ setNextPlayerState(PlayerState.IDLE);
+ this.downloadFile = downloadFile;
+ if(downloadFile != null) {
+ partialFile = downloadFile.getPartialFile();
+ } else {
+ partialFile = null;
+ }
+ }
+
+ @Override
+ public void execute() {
+ if(downloadFile == null) {
+ return;
+ }
+
+ while (!bufferComplete()) {
+ Util.sleepQuietly(2000L);
+ if (isCancelled()) {
+ return;
+ }
+ }
+
+ // Do something
+ setupNext(downloadFile);
+ }
+
+ private boolean bufferComplete() {
+ boolean completeFileAvailable = downloadFile.isWorkDone();
+ Log.i(TAG, "Buffering next " + partialFile + " (" + partialFile.length() + ")");
+ return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED);
+ }
+
+ @Override
+ public String toString() {
+ return "CheckCompletionTask (" + downloadFile + ")";
+ }
+ }
}