diff options
-rw-r--r-- | subsonic-android/src/github/daneren2005/dsub/service/DownloadServiceImpl.java | 259 |
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 + ")"; + } + } } |