aboutsummaryrefslogtreecommitdiff
path: root/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/github/daneren2005/dsub/service/DownloadServiceImpl.java')
-rw-r--r--src/github/daneren2005/dsub/service/DownloadServiceImpl.java1539
1 files changed, 1539 insertions, 0 deletions
diff --git a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
new file mode 100644
index 00000000..04875f34
--- /dev/null
+++ b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java
@@ -0,0 +1,1539 @@
+/*
+ This file is part of Subsonic.
+
+ Subsonic is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Subsonic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
+
+ Copyright 2009 (C) Sindre Mehus
+ */
+package github.daneren2005.dsub.service;
+
+import static github.daneren2005.dsub.domain.PlayerState.COMPLETED;
+import static github.daneren2005.dsub.domain.PlayerState.DOWNLOADING;
+import static github.daneren2005.dsub.domain.PlayerState.IDLE;
+import static github.daneren2005.dsub.domain.PlayerState.PAUSED;
+import static github.daneren2005.dsub.domain.PlayerState.PREPARED;
+import static github.daneren2005.dsub.domain.PlayerState.PREPARING;
+import static github.daneren2005.dsub.domain.PlayerState.STARTED;
+import static github.daneren2005.dsub.domain.PlayerState.STOPPED;
+import github.daneren2005.dsub.audiofx.EqualizerController;
+import github.daneren2005.dsub.audiofx.VisualizerController;
+import github.daneren2005.dsub.domain.MusicDirectory;
+import github.daneren2005.dsub.domain.PlayerState;
+import github.daneren2005.dsub.domain.RepeatMode;
+import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver;
+import github.daneren2005.dsub.util.CancellableTask;
+import github.daneren2005.dsub.util.Constants;
+import github.daneren2005.dsub.util.ShufflePlayBuffer;
+import github.daneren2005.dsub.util.SimpleServiceBinder;
+import github.daneren2005.dsub.util.Util;
+import github.daneren2005.dsub.util.compat.RemoteControlClientHelper;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.audiofx.AudioEffect;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.util.Log;
+import android.support.v4.util.LruCache;
+import java.net.URLEncoder;
+
+/**
+ * @author Sindre Mehus
+ * @version $Id$
+ */
+public class DownloadServiceImpl extends Service implements DownloadService {
+
+ private static final String TAG = DownloadServiceImpl.class.getSimpleName();
+
+ public static final String CMD_PLAY = "github.daneren2005.dsub.CMD_PLAY";
+ public static final String CMD_TOGGLEPAUSE = "github.daneren2005.dsub.CMD_TOGGLEPAUSE";
+ public static final String CMD_PAUSE = "github.daneren2005.dsub.CMD_PAUSE";
+ public static final String CMD_STOP = "github.daneren2005.dsub.CMD_STOP";
+ public static final String CMD_PREVIOUS = "github.daneren2005.dsub.CMD_PREVIOUS";
+ public static final String CMD_NEXT = "github.daneren2005.dsub.CMD_NEXT";
+
+
+ private RemoteControlClientHelper mRemoteControl;
+
+ private final IBinder binder = new SimpleServiceBinder<DownloadService>(this);
+ private Looper mediaPlayerLooper;
+ private MediaPlayer mediaPlayer;
+ private MediaPlayer nextMediaPlayer;
+ private boolean nextSetup = false;
+ private boolean isPartial = true;
+ private final List<DownloadFile> downloadList = new ArrayList<DownloadFile>();
+ private final List<DownloadFile> backgroundDownloadList = new ArrayList<DownloadFile>();
+ private final Handler handler = new Handler();
+ private Handler mediaPlayerHandler;
+ private final DownloadServiceLifecycleSupport lifecycleSupport = new DownloadServiceLifecycleSupport(this);
+ private final ShufflePlayBuffer shufflePlayBuffer = new ShufflePlayBuffer(this);
+
+ private final LruCache<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LruCache<MusicDirectory.Entry, DownloadFile>(100);
+ private final List<DownloadFile> cleanupCandidates = new ArrayList<DownloadFile>();
+ 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;
+ private String suggestedPlaylistName;
+ private String suggestedPlaylistId;
+ private PowerManager.WakeLock wakeLock;
+ private boolean keepScreenOn;
+ private int cachedPosition = 0;
+
+ private static boolean equalizerAvailable;
+ private static boolean visualizerAvailable;
+ private EqualizerController equalizerController;
+ private VisualizerController visualizerController;
+ private boolean showVisualization;
+ private boolean jukeboxEnabled;
+ private PositionCache positionCache;
+ private StreamProxy proxy;
+
+ private Timer sleepTimer;
+ private int timerDuration;
+ private boolean autoPlayStart = false;
+
+ static {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+ equalizerAvailable = true;
+ visualizerAvailable = true;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ new Thread(new Runnable() {
+ public void run() {
+ Looper.prepare();
+
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setWakeMode(DownloadServiceImpl.this, PowerManager.PARTIAL_WAKE_LOCK);
+
+ mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(MediaPlayer mediaPlayer, int what, int more) {
+ handleError(new Exception("MediaPlayer error: " + what + " (" + more + ")"));
+ return false;
+ }
+ });
+
+ try {
+ Intent i = new Intent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION);
+ i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mediaPlayer.getAudioSessionId());
+ i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
+ sendBroadcast(i);
+ } catch(Throwable e) {
+ // Froyo or lower
+ }
+
+ mediaPlayerLooper = Looper.myLooper();
+ mediaPlayerHandler = new Handler(mediaPlayerLooper);
+ Looper.loop();
+ }
+ }).start();
+
+ Util.registerMediaButtonEventReceiver(this);
+
+ if (mRemoteControl == null) {
+ // Use the remote control APIs (if available) to set the playback state
+ mRemoteControl = RemoteControlClientHelper.createInstance();
+ ComponentName mediaButtonReceiverComponent = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
+ mRemoteControl.register(this, mediaButtonReceiverComponent);
+ }
+
+ PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
+ wakeLock.setReferenceCounted(false);
+
+ SharedPreferences prefs = Util.getPreferences(this);
+ try {
+ timerDuration = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_SLEEP_TIMER_DURATION, "5"));
+ } catch(Throwable e) {
+ timerDuration = 5;
+ }
+ sleepTimer = null;
+
+ keepScreenOn = prefs.getBoolean(Constants.PREFERENCES_KEY_KEEP_SCREEN_ON, false);
+
+ instance = this;
+ lifecycleSupport.onCreate();
+
+ if(prefs.getBoolean(Constants.PREFERENCES_EQUALIZER_ON, false)) {
+ getEqualizerController();
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ super.onStartCommand(intent, flags, startId);
+ lifecycleSupport.onStart(intent);
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ instance = null;
+
+ if(currentPlaying != null) currentPlaying.setPlaying(false);
+ if(sleepTimer != null){
+ sleepTimer.cancel();
+ sleepTimer.purge();
+ }
+ lifecycleSupport.onDestroy();
+
+ try {
+ Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION);
+ i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mediaPlayer.getAudioSessionId());
+ i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, getPackageName());
+ sendBroadcast(i);
+ } catch(Throwable e) {
+ // Froyo or lower
+ }
+
+ mediaPlayer.release();
+ if(nextMediaPlayer != null) {
+ nextMediaPlayer.release();
+ }
+ mediaPlayerLooper.quit();
+ shufflePlayBuffer.shutdown();
+ if (equalizerController != null) {
+ equalizerController.release();
+ }
+ if (visualizerController != null) {
+ visualizerController.release();
+ }
+ if (mRemoteControl != null) {
+ mRemoteControl.unregister(this);
+ mRemoteControl = null;
+ }
+
+ if(bufferTask != null) {
+ bufferTask.cancel();
+ }
+ if(nextPlayingTask != null) {
+ nextPlayingTask.cancel();
+ }
+ Util.hidePlayingNotification(this, this, handler);
+ }
+
+ public static DownloadService getInstance() {
+ return instance;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return binder;
+ }
+
+ @Override
+ public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle) {
+ setShufflePlayEnabled(false);
+ int offset = 1;
+
+ if (songs.isEmpty()) {
+ return;
+ }
+ if (playNext) {
+ if (autoplay && getCurrentPlayingIndex() >= 0) {
+ offset = 0;
+ }
+ for (MusicDirectory.Entry song : songs) {
+ DownloadFile downloadFile = new DownloadFile(this, song, save);
+ downloadList.add(getCurrentPlayingIndex() + offset, downloadFile);
+ offset++;
+ }
+ revision++;
+ } else {
+ int size = size();
+ int index = getCurrentPlayingIndex();
+ for (MusicDirectory.Entry song : songs) {
+ DownloadFile downloadFile = new DownloadFile(this, song, save);
+ downloadList.add(downloadFile);
+ }
+ if(!autoplay && (size - 1) == index) {
+ setNextPlaying();
+ }
+ revision++;
+ }
+ updateJukeboxPlaylist();
+
+ if(shuffle) {
+ shuffle();
+ }
+
+ if (autoplay) {
+ play(0);
+ } else {
+ if (currentPlaying == null) {
+ currentPlaying = downloadList.get(0);
+ currentPlaying.setPlaying(true);
+ }
+ checkDownloads();
+ }
+ lifecycleSupport.serializeDownloadQueue();
+ }
+ public synchronized void downloadBackground(List<MusicDirectory.Entry> songs, boolean save) {
+ for (MusicDirectory.Entry song : songs) {
+ DownloadFile downloadFile = new DownloadFile(this, song, save);
+ backgroundDownloadList.add(downloadFile);
+ }
+ revision++;
+
+ checkDownloads();
+ lifecycleSupport.serializeDownloadQueue();
+ }
+
+ private void updateJukeboxPlaylist() {
+ if (jukeboxEnabled) {
+ jukeboxService.updatePlaylist();
+ }
+ }
+
+ public void restore(List<MusicDirectory.Entry> songs, int currentPlayingIndex, int currentPlayingPosition) {
+ SharedPreferences prefs = Util.getPreferences(this);
+ boolean startShufflePlay = prefs.getBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, false);
+ download(songs, false, false, false, false);
+ if(startShufflePlay) {
+ shufflePlay = true;
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, true);
+ editor.commit();
+ }
+ if (currentPlayingIndex != -1) {
+ while(mediaPlayer == null) {
+ Util.sleepQuietly(50L);
+ }
+
+ play(currentPlayingIndex, false);
+ if (currentPlaying != null && currentPlaying.isCompleteFileAvailable()) {
+ doPlay(currentPlaying, currentPlayingPosition, autoPlayStart);
+ }
+ autoPlayStart = false;
+ }
+ }
+
+ @Override
+ public synchronized void setShufflePlayEnabled(boolean enabled) {
+ shufflePlay = enabled;
+ if (shufflePlay) {
+ clear();
+ checkDownloads();
+ }
+ SharedPreferences.Editor editor = Util.getPreferences(this).edit();
+ editor.putBoolean(Constants.PREFERENCES_KEY_SHUFFLE_MODE, enabled);
+ editor.commit();
+ }
+
+ @Override
+ public boolean isShufflePlayEnabled() {
+ return shufflePlay;
+ }
+
+ @Override
+ public synchronized void shuffle() {
+ Collections.shuffle(downloadList);
+ if (currentPlaying != null) {
+ downloadList.remove(getCurrentPlayingIndex());
+ downloadList.add(0, currentPlaying);
+ }
+ revision++;
+ lifecycleSupport.serializeDownloadQueue();
+ updateJukeboxPlaylist();
+ setNextPlaying();
+ }
+
+ @Override
+ public RepeatMode getRepeatMode() {
+ return Util.getRepeatMode(this);
+ }
+
+ @Override
+ public void setRepeatMode(RepeatMode repeatMode) {
+ Util.setRepeatMode(this, repeatMode);
+ setNextPlaying();
+ }
+
+ @Override
+ public boolean getKeepScreenOn() {
+ return keepScreenOn;
+ }
+
+ @Override
+ public void setKeepScreenOn(boolean keepScreenOn) {
+ this.keepScreenOn = keepScreenOn;
+
+ SharedPreferences prefs = Util.getPreferences(this);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(Constants.PREFERENCES_KEY_KEEP_SCREEN_ON, keepScreenOn);
+ editor.commit();
+ }
+
+ @Override
+ public boolean getShowVisualization() {
+ return showVisualization;
+ }
+
+ @Override
+ public void setShowVisualization(boolean showVisualization) {
+ this.showVisualization = showVisualization;
+ }
+
+ @Override
+ public synchronized DownloadFile forSong(MusicDirectory.Entry song) {
+ for (DownloadFile downloadFile : downloadList) {
+ if (downloadFile.getSong().equals(song) &&
+ ((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists())
+ || downloadFile.isWorkDone())) {
+ return downloadFile;
+ }
+ }
+ for (DownloadFile downloadFile : backgroundDownloadList) {
+ if (downloadFile.getSong().equals(song)) {
+ return downloadFile;
+ }
+ }
+
+ DownloadFile downloadFile = downloadFileCache.get(song);
+ if (downloadFile == null) {
+ downloadFile = new DownloadFile(this, song, false);
+ downloadFileCache.put(song, downloadFile);
+ }
+ return downloadFile;
+ }
+
+ @Override
+ public synchronized void clear() {
+ clear(true);
+ }
+
+ @Override
+ public synchronized void clearBackground() {
+ if(currentDownloading != null && backgroundDownloadList.contains(currentDownloading)) {
+ currentDownloading.cancelDownload();
+ currentDownloading = null;
+ }
+ backgroundDownloadList.clear();
+ }
+
+ @Override
+ public synchronized void clearIncomplete() {
+ reset();
+ Iterator<DownloadFile> iterator = downloadList.iterator();
+ while (iterator.hasNext()) {
+ DownloadFile downloadFile = iterator.next();
+ if (!downloadFile.isCompleteFileAvailable()) {
+ iterator.remove();
+ }
+ }
+ lifecycleSupport.serializeDownloadQueue();
+ updateJukeboxPlaylist();
+ }
+
+ @Override
+ public synchronized int size() {
+ return downloadList.size();
+ }
+
+ public synchronized void clear(boolean serialize) {
+ reset();
+ downloadList.clear();
+ revision++;
+ if (currentDownloading != null) {
+ currentDownloading.cancelDownload();
+ currentDownloading = null;
+ }
+ setCurrentPlaying(null, false);
+
+ if (serialize) {
+ lifecycleSupport.serializeDownloadQueue();
+ }
+ updateJukeboxPlaylist();
+ setNextPlaying();
+ }
+
+ @Override
+ public synchronized void remove(int which) {
+ downloadList.remove(which);
+ }
+
+ @Override
+ public synchronized void remove(DownloadFile downloadFile) {
+ if (downloadFile == currentDownloading) {
+ currentDownloading.cancelDownload();
+ currentDownloading = null;
+ }
+ if (downloadFile == currentPlaying) {
+ reset();
+ setCurrentPlaying(null, false);
+ }
+ downloadList.remove(downloadFile);
+ backgroundDownloadList.remove(downloadFile);
+ revision++;
+ lifecycleSupport.serializeDownloadQueue();
+ updateJukeboxPlaylist();
+ if(downloadFile == nextPlaying) {
+ setNextPlaying();
+ }
+ }
+
+ @Override
+ public synchronized void delete(List<MusicDirectory.Entry> songs) {
+ for (MusicDirectory.Entry song : songs) {
+ forSong(song).delete();
+ }
+ }
+
+ @Override
+ public synchronized void unpin(List<MusicDirectory.Entry> songs) {
+ for (MusicDirectory.Entry song : songs) {
+ forSong(song).unpin();
+ }
+ }
+
+ synchronized void setCurrentPlaying(int currentPlayingIndex, boolean showNotification) {
+ try {
+ setCurrentPlaying(downloadList.get(currentPlayingIndex), showNotification);
+ } catch (IndexOutOfBoundsException x) {
+ // Ignored
+ }
+ }
+
+ synchronized void setCurrentPlaying(DownloadFile currentPlaying, boolean showNotification) {
+ if(this.currentPlaying != null) {
+ this.currentPlaying.setPlaying(false);
+ }
+ this.currentPlaying = currentPlaying;
+
+ if (currentPlaying != null) {
+ Util.broadcastNewTrackInfo(this, currentPlaying.getSong());
+ mRemoteControl.updateMetadata(this, currentPlaying.getSong());
+ } else {
+ Util.broadcastNewTrackInfo(this, null);
+ Util.hidePlayingNotification(this, this, handler);
+ }
+ }
+
+ synchronized void setNextPlaying() {
+ SharedPreferences prefs = Util.getPreferences(DownloadServiceImpl.this);
+ boolean gaplessPlayback = prefs.getBoolean(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, true);
+ if(!gaplessPlayback) {
+ nextPlaying = null;
+ nextPlayerState = IDLE;
+ return;
+ }
+
+ int index = getCurrentPlayingIndex();
+ if (index != -1) {
+ switch (getRepeatMode()) {
+ case OFF:
+ index = index + 1;
+ break;
+ case ALL:
+ index = (index + 1) % size();
+ break;
+ case SINGLE:
+ break;
+ default:
+ break;
+ }
+ }
+
+ nextSetup = false;
+ if(nextPlayingTask != null) {
+ nextPlayingTask.cancel();
+ nextPlayingTask = null;
+ }
+ if(index < size() && index != -1) {
+ nextPlaying = downloadList.get(index);
+ nextPlayingTask = new CheckCompletionTask(nextPlaying);
+ nextPlayingTask.start();
+ } else {
+ nextPlaying = null;
+ setNextPlayerState(IDLE);
+ }
+ }
+
+ @Override
+ public synchronized int getCurrentPlayingIndex() {
+ return downloadList.indexOf(currentPlaying);
+ }
+
+ @Override
+ public DownloadFile getCurrentPlaying() {
+ return currentPlaying;
+ }
+
+ @Override
+ public DownloadFile getCurrentDownloading() {
+ return currentDownloading;
+ }
+
+ @Override
+ public List<DownloadFile> getSongs() {
+ return downloadList;
+ }
+
+ @Override
+ public synchronized List<DownloadFile> getDownloads() {
+ List<DownloadFile> temp = new ArrayList<DownloadFile>();
+ temp.addAll(downloadList);
+ temp.addAll(backgroundDownloadList);
+ return temp;
+ }
+
+ @Override
+ public List<DownloadFile> getBackgroundDownloads() {
+ return backgroundDownloadList;
+ }
+
+ /** Plays either the current song (resume) or the first/next one in queue. */
+ public synchronized void play()
+ {
+ int current = getCurrentPlayingIndex();
+ if (current == -1) {
+ play(0);
+ } else {
+ play(current);
+ }
+ }
+
+ @Override
+ public synchronized void play(int index) {
+ play(index, true);
+ }
+
+ private synchronized void play(int index, boolean start) {
+ if (index < 0 || index >= size()) {
+ reset();
+ setCurrentPlaying(null, false);
+ lifecycleSupport.serializeDownloadQueue();
+ } else {
+ if(nextPlayingTask != null) {
+ nextPlayingTask.cancel();
+ nextPlayingTask = null;
+ }
+ setCurrentPlaying(index, start);
+ if (start) {
+ if (jukeboxEnabled) {
+ jukeboxService.skip(getCurrentPlayingIndex(), 0);
+ setPlayerState(STARTED);
+ } else {
+ bufferAndPlay();
+ }
+ }
+ checkDownloads();
+ setNextPlaying();
+ }
+ }
+ private synchronized void playNext() {
+ if(nextPlaying != null && nextPlayerState == PlayerState.PREPARED) {
+ if(!nextSetup) {
+ playNext(true);
+ } else {
+ nextSetup = false;
+ playNext(false);
+ }
+ } else {
+ onSongCompleted();
+ }
+ }
+ private synchronized void playNext(boolean start) {
+ // Swap the media players since nextMediaPlayer is ready to play
+ if(start) {
+ nextMediaPlayer.start();
+ } else {
+ Log.i(TAG, "nextMediaPlayer already playing");
+ }
+ MediaPlayer tmp = mediaPlayer;
+ mediaPlayer = nextMediaPlayer;
+ nextMediaPlayer = tmp;
+ setCurrentPlaying(nextPlaying, true);
+ setPlayerState(PlayerState.STARTED);
+ setupHandlers(currentPlaying, false);
+ setNextPlaying();
+
+ // Proxy should not be being used here since the next player was already setup to play
+ if(proxy != null) {
+ proxy.stop();
+ proxy = null;
+ }
+ }
+
+ /** Plays or resumes the playback, depending on the current player state. */
+ public synchronized void togglePlayPause() {
+ if (playerState == PAUSED || playerState == COMPLETED || playerState == STOPPED) {
+ start();
+ } else if (playerState == STOPPED || playerState == IDLE) {
+ autoPlayStart = true;
+ play();
+ } else if (playerState == STARTED) {
+ pause();
+ }
+ }
+
+ @Override
+ public synchronized void seekTo(int position) {
+ try {
+ if (jukeboxEnabled) {
+ jukeboxService.skip(getCurrentPlayingIndex(), position / 1000);
+ } else {
+ mediaPlayer.seekTo(position);
+ cachedPosition = position;
+ }
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+
+ @Override
+ public synchronized void previous() {
+ int index = getCurrentPlayingIndex();
+ if (index == -1) {
+ return;
+ }
+
+ // Restart song if played more than five seconds.
+ if (getPlayerPosition() > 5000 || index == 0) {
+ play(index);
+ } else {
+ play(index - 1);
+ }
+ }
+
+ @Override
+ public synchronized void next() {
+ int index = getCurrentPlayingIndex();
+ if (index != -1) {
+ play(index + 1);
+ }
+ }
+
+ private void onSongCompleted() {
+ int index = getCurrentPlayingIndex();
+ if (index != -1) {
+ switch (getRepeatMode()) {
+ case OFF:
+ play(index + 1);
+ break;
+ case ALL:
+ play((index + 1) % size());
+ break;
+ case SINGLE:
+ play(index);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ @Override
+ public synchronized void pause() {
+ try {
+ if (playerState == STARTED) {
+ if (jukeboxEnabled) {
+ jukeboxService.stop();
+ } else {
+ mediaPlayer.pause();
+ }
+ setPlayerState(PAUSED);
+ }
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+
+ @Override
+ public synchronized void stop() {
+ try {
+ if (playerState == STARTED) {
+ if (jukeboxEnabled) {
+ jukeboxService.stop();
+ } else {
+ mediaPlayer.pause();
+ }
+ setPlayerState(STOPPED);
+ } else if(playerState == PAUSED) {
+ setPlayerState(STOPPED);
+ }
+ } catch(Exception x) {
+ handleError(x);
+ }
+ }
+
+ @Override
+ public synchronized void start() {
+ try {
+ if (jukeboxEnabled) {
+ jukeboxService.start();
+ } else {
+ mediaPlayer.start();
+ }
+ setPlayerState(STARTED);
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+
+ @Override
+ public synchronized void reset() {
+ if (bufferTask != null) {
+ bufferTask.cancel();
+ }
+ try {
+ setPlayerState(IDLE);
+ mediaPlayer.setOnErrorListener(null);
+ mediaPlayer.setOnCompletionListener(null);
+ mediaPlayer.reset();
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+
+ @Override
+ public synchronized int getPlayerPosition() {
+ try {
+ if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING) {
+ return 0;
+ }
+ if (jukeboxEnabled) {
+ return jukeboxService.getPositionSeconds() * 1000;
+ } else {
+ return cachedPosition;
+ }
+ } catch (Exception x) {
+ handleError(x);
+ return 0;
+ }
+ }
+
+ @Override
+ public synchronized int getPlayerDuration() {
+ if (currentPlaying != null) {
+ Integer duration = currentPlaying.getSong().getDuration();
+ if (duration != null) {
+ return duration * 1000;
+ }
+ }
+ if (playerState != IDLE && playerState != DOWNLOADING && playerState != PlayerState.PREPARING) {
+ try {
+ return mediaPlayer.getDuration();
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public PlayerState getPlayerState() {
+ return playerState;
+ }
+
+ public synchronized void setPlayerState(final PlayerState playerState) {
+ Log.i(TAG, this.playerState.name() + " -> " + playerState.name() + " (" + currentPlaying + ")");
+
+ if (playerState == PAUSED) {
+ lifecycleSupport.serializeDownloadQueue();
+ }
+
+ boolean show = playerState == PlayerState.STARTED;
+ boolean pause = playerState == PlayerState.PAUSED;
+ boolean hide = playerState == PlayerState.STOPPED;
+ Util.broadcastPlaybackStatusChange(this, playerState);
+
+ this.playerState = playerState;
+
+ if(playerState == STARTED) {
+ Util.requestAudioFocus(this);
+ }
+
+ if (show) {
+ Util.showPlayingNotification(this, this, handler, currentPlaying.getSong());
+ } else if (pause) {
+ SharedPreferences prefs = Util.getPreferences(this);
+ if(prefs.getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false)) {
+ Util.showPlayingNotification(this, this, handler, currentPlaying.getSong());
+ } else {
+ Util.hidePlayingNotification(this, this, handler);
+ }
+ } else if(hide) {
+ Util.hidePlayingNotification(this, this, handler);
+ }
+ mRemoteControl.setPlaybackState(playerState.getRemoteControlClientPlayState());
+
+ if (playerState == STARTED) {
+ scrobbler.scrobble(this, currentPlaying, false);
+ } else if (playerState == COMPLETED) {
+ scrobbler.scrobble(this, currentPlaying, true);
+ }
+
+ if(playerState == STARTED && positionCache == null) {
+ positionCache = new PositionCache();
+ Thread thread = new Thread(positionCache);
+ thread.start();
+ } else if(playerState != STARTED && positionCache != null) {
+ positionCache.stop();
+ positionCache = null;
+ }
+ }
+
+ private class PositionCache implements Runnable {
+ boolean isRunning = true;
+
+ public void stop() {
+ isRunning = false;
+ }
+
+ @Override
+ public void run() {
+ // Stop checking position before the song reaches completion
+ while(isRunning) {
+ try {
+ if(mediaPlayer != null && playerState == STARTED) {
+ cachedPosition = mediaPlayer.getCurrentPosition();
+ }
+ Thread.sleep(200L);
+ }
+ catch(Exception e) {
+ Log.w(TAG, "Crashed getting current position", e);
+ isRunning = false;
+ positionCache = null;
+ }
+ }
+ }
+ }
+
+ private void setPlayerStateCompleted() {
+ Log.i(TAG, this.playerState.name() + " -> " + PlayerState.COMPLETED + " (" + currentPlaying + ")");
+ this.playerState = PlayerState.COMPLETED;
+ if(positionCache != null) {
+ positionCache.stop();
+ positionCache = null;
+ }
+ scrobbler.scrobble(this, currentPlaying, true);
+ }
+
+ private synchronized void setNextPlayerState(PlayerState playerState) {
+ Log.i(TAG, "Next: " + this.nextPlayerState.name() + " -> " + playerState.name() + " (" + nextPlaying + ")");
+ this.nextPlayerState = playerState;
+ }
+
+ @Override
+ public void setSuggestedPlaylistName(String name, String id) {
+ this.suggestedPlaylistName = name;
+ this.suggestedPlaylistId = id;
+ }
+
+ @Override
+ public String getSuggestedPlaylistName() {
+ return suggestedPlaylistName;
+ }
+
+ @Override
+ public String getSuggestedPlaylistId() {
+ return suggestedPlaylistId;
+ }
+
+ @Override
+ public boolean getEqualizerAvailable() {
+ return equalizerAvailable;
+ }
+
+ @Override
+ public boolean getVisualizerAvailable() {
+ return visualizerAvailable;
+ }
+
+ @Override
+ public EqualizerController getEqualizerController() {
+ if (equalizerAvailable && equalizerController == null) {
+ equalizerController = new EqualizerController(this, mediaPlayer);
+ if (!equalizerController.isAvailable()) {
+ equalizerController = null;
+ } else {
+ equalizerController.loadSettings();
+ }
+ }
+ return equalizerController;
+ }
+
+ @Override
+ public VisualizerController getVisualizerController() {
+ if (visualizerAvailable && visualizerController == null) {
+ visualizerController = new VisualizerController(this, mediaPlayer);
+ if (!visualizerController.isAvailable()) {
+ visualizerController = null;
+ }
+ }
+ return visualizerController;
+ }
+
+ @Override
+ public boolean isJukeboxEnabled() {
+ return jukeboxEnabled;
+ }
+
+ @Override
+ public void setJukeboxEnabled(boolean jukeboxEnabled) {
+ this.jukeboxEnabled = jukeboxEnabled;
+ jukeboxService.setEnabled(jukeboxEnabled);
+ if (jukeboxEnabled) {
+ reset();
+
+ // Cancel current download, if necessary.
+ if (currentDownloading != null) {
+ currentDownloading.cancelDownload();
+ }
+ }
+ }
+
+ @Override
+ public void adjustJukeboxVolume(boolean up) {
+ jukeboxService.adjustVolume(up);
+ }
+
+ private synchronized void bufferAndPlay() {
+ if(playerState != PREPARED) {
+ reset();
+
+ 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 {
+ downloadFile.setPlaying(true);
+ final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
+ isPartial = file.equals(downloadFile.getPartialFile());
+ downloadFile.updateModificationDate();
+
+ mediaPlayer.setOnCompletionListener(null);
+ mediaPlayer.reset();
+ setPlayerState(IDLE);
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ String dataSource = file.getPath();
+ if(isPartial) {
+ if (proxy == null) {
+ proxy = new StreamProxy(this);
+ proxy.start();
+ }
+ dataSource = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), URLEncoder.encode(dataSource, Constants.UTF_8));
+ Log.i(TAG, "Data Source: " + dataSource);
+ } else if(proxy != null) {
+ proxy.stop();
+ proxy = null;
+ }
+ mediaPlayer.setDataSource(dataSource);
+ setPlayerState(PREPARING);
+
+ mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
+ public void onBufferingUpdate(MediaPlayer mp, int percent) {
+ Log.i(TAG, "Buffered " + percent + "%");
+ if(percent == 100) {
+ mediaPlayer.setOnBufferingUpdateListener(null);
+ }
+ }
+ });
+
+ 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;
+
+ if (start) {
+ mediaPlayer.start();
+ setPlayerState(STARTED);
+ } else {
+ setPlayerState(PAUSED);
+ }
+ }
+
+ lifecycleSupport.serializeDownloadQueue();
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+ });
+
+ setupHandlers(downloadFile, isPartial);
+
+ mediaPlayer.prepareAsync();
+ } catch (Exception x) {
+ handleError(x);
+ }
+ }
+
+ private synchronized void setupNext(final DownloadFile downloadFile) {
+ try {
+ final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
+ if(nextMediaPlayer != null) {
+ nextMediaPlayer.setOnCompletionListener(null);
+ nextMediaPlayer.release();
+ nextMediaPlayer = null;
+ }
+ nextMediaPlayer = new MediaPlayer();
+ nextMediaPlayer.setWakeMode(DownloadServiceImpl.this, PowerManager.PARTIAL_WAKE_LOCK);
+ try {
+ nextMediaPlayer.setAudioSessionId(mediaPlayer.getAudioSessionId());
+ } catch(Throwable e) {
+ nextMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ }
+ nextMediaPlayer.setDataSource(file.getPath());
+ setNextPlayerState(PREPARING);
+
+ nextMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ public void onPrepared(MediaPlayer mp) {
+ try {
+ setNextPlayerState(PREPARED);
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)) {
+ mediaPlayer.setNextMediaPlayer(nextMediaPlayer);
+ nextSetup = true;
+ }
+ } 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 next " + "(" + what + ", " + extra + "): " + downloadFile);
+ return true;
+ }
+ });
+
+ nextMediaPlayer.prepareAsync();
+ } catch (Exception x) {
+ handleErrorNext(x);
+ }
+ }
+
+ private void setupHandlers(final DownloadFile downloadFile, final boolean isPartial) {
+ final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
+ 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();
+ if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000))) {
+ playNext();
+ } else {
+ downloadFile.setPlaying(false);
+ doPlay(downloadFile, pos, true);
+ downloadFile.setPlaying(true);
+ }
+ return true;
+ }
+ });
+
+ mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ // 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);
+
+ setPlayerStateCompleted();
+
+ int pos = cachedPosition;
+ Log.i(TAG, "Ending position " + pos + " of " + duration);
+ if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 10000))) {
+ playNext();
+ return;
+ }
+
+ // If file is not completely downloaded, restart the playback from the current position.
+ synchronized (DownloadServiceImpl.this) {
+ if(downloadFile.isWorkDone()) {
+ // Complete was called early even though file is fully buffered
+ Log.i(TAG, "Requesting restart from " + pos + " of " + duration);
+ reset();
+ downloadFile.setPlaying(false);
+ doPlay(downloadFile, pos, true);
+ downloadFile.setPlaying(true);
+ } else {
+ Log.i(TAG, "Requesting restart from " + pos + " of " + duration);
+ reset();
+ bufferTask = new BufferTask(downloadFile, pos);
+ bufferTask.start();
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setSleepTimerDuration(int duration){
+ timerDuration = duration;
+ }
+
+ @Override
+ public void startSleepTimer(){
+ if(sleepTimer != null){
+ sleepTimer.cancel();
+ sleepTimer.purge();
+ }
+
+ sleepTimer = new Timer();
+
+ sleepTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ pause();
+ sleepTimer.cancel();
+ sleepTimer.purge();
+ sleepTimer = null;
+ }
+
+ }, timerDuration * 60 * 1000);
+ }
+
+ @Override
+ public void stopSleepTimer() {
+ if(sleepTimer != null){
+ sleepTimer.cancel();
+ sleepTimer.purge();
+ }
+ sleepTimer = null;
+ }
+
+ @Override
+ public boolean getSleepTimer() {
+ return sleepTimer != null;
+ }
+
+ @Override
+ public void setVolume(float volume) {
+ if(mediaPlayer != null) {
+ mediaPlayer.setVolume(volume, volume);
+ }
+ }
+
+ @Override
+ public synchronized void swap(boolean mainList, int from, int to) {
+ List<DownloadFile> list = mainList ? downloadList : backgroundDownloadList;
+ int max = list.size();
+ if(to >= max) {
+ to = max - 1;
+ }
+ else if(to < 0) {
+ to = 0;
+ }
+
+ int currentPlayingIndex = getCurrentPlayingIndex();
+ DownloadFile movedSong = list.remove(from);
+ list.add(to, movedSong);
+ if(jukeboxEnabled && mainList) {
+ updateJukeboxPlaylist();
+ } else if(mainList && (movedSong == nextPlaying || (currentPlayingIndex + 1) == to)) {
+ // Moving next playing or moving a song to be next playing
+ setNextPlaying();
+ }
+ }
+
+ private void handleError(Exception x) {
+ Log.w(TAG, "Media player error: " + x, x);
+ 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()) {
+ return;
+ }
+
+ if (shufflePlay) {
+ checkShufflePlay();
+ }
+
+ if (jukeboxEnabled || !Util.isNetworkConnected(this)) {
+ return;
+ }
+
+ if (downloadList.isEmpty() && backgroundDownloadList.isEmpty()) {
+ return;
+ }
+
+ // Need to download current playing?
+ if (currentPlaying != null && currentPlaying != currentDownloading && !currentPlaying.isWorkDone()) {
+ // Cancel current download, if necessary.
+ if (currentDownloading != null) {
+ currentDownloading.cancelDownload();
+ }
+
+ currentDownloading = currentPlaying;
+ currentDownloading.download();
+ cleanupCandidates.add(currentDownloading);
+ }
+
+ // Find a suitable target for download.
+ else if (currentDownloading == null || currentDownloading.isWorkDone() || currentDownloading.isFailed() && (!downloadList.isEmpty() || !backgroundDownloadList.isEmpty())) {
+ currentDownloading = null;
+ int n = size();
+
+ int preloaded = 0;
+
+ if(n != 0) {
+ int start = currentPlaying == null ? 0 : getCurrentPlayingIndex();
+ if(start == -1) {
+ start = 0;
+ }
+ int i = start;
+ do {
+ DownloadFile downloadFile = downloadList.get(i);
+ if (!downloadFile.isWorkDone()) {
+ if (downloadFile.shouldSave() || preloaded < Util.getPreloadCount(this)) {
+ currentDownloading = downloadFile;
+ currentDownloading.download();
+ cleanupCandidates.add(currentDownloading);
+ if(i == (start + 1)) {
+ setNextPlayerState(DOWNLOADING);
+ }
+ break;
+ }
+ } else if (currentPlaying != downloadFile) {
+ preloaded++;
+ }
+
+ i = (i + 1) % n;
+ } while (i != start);
+ }
+
+ if((preloaded + 1 == n || preloaded >= Util.getPreloadCount(this) || downloadList.isEmpty()) && !backgroundDownloadList.isEmpty()) {
+ for(int i = 0; i < backgroundDownloadList.size(); i++) {
+ DownloadFile downloadFile = backgroundDownloadList.get(i);
+ if(downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved())) {
+ // Don't need to keep list like active song list
+ backgroundDownloadList.remove(i);
+ revision++;
+ i--;
+ } else {
+ currentDownloading = downloadFile;
+ currentDownloading.download();
+ cleanupCandidates.add(currentDownloading);
+ break;
+ }
+ }
+ }
+ }
+
+ // Delete obsolete .partial and .complete files.
+ cleanup();
+ }
+
+ private synchronized void checkShufflePlay() {
+
+ // Get users desired random playlist size
+ SharedPreferences prefs = Util.getPreferences(this);
+ int listSize = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_RANDOM_SIZE, "20"));
+ boolean wasEmpty = downloadList.isEmpty();
+
+ long revisionBefore = revision;
+
+ // First, ensure that list is at least 20 songs long.
+ int size = size();
+ if (size < listSize) {
+ for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size)) {
+ DownloadFile downloadFile = new DownloadFile(this, song, false);
+ downloadList.add(downloadFile);
+ revision++;
+ }
+ }
+
+ int currIndex = currentPlaying == null ? 0 : getCurrentPlayingIndex();
+
+ // Only shift playlist if playing song #5 or later.
+ if (currIndex > 4) {
+ int songsToShift = currIndex - 2;
+ for (MusicDirectory.Entry song : shufflePlayBuffer.get(songsToShift)) {
+ downloadList.add(new DownloadFile(this, song, false));
+ downloadList.get(0).cancelDownload();
+ downloadList.remove(0);
+ revision++;
+ }
+ }
+
+ if (revisionBefore != revision) {
+ updateJukeboxPlaylist();
+ }
+
+ if (wasEmpty && !downloadList.isEmpty()) {
+ play(0);
+ }
+ }
+
+ public long getDownloadListUpdateRevision() {
+ return revision;
+ }
+
+ private synchronized void cleanup() {
+ Iterator<DownloadFile> iterator = cleanupCandidates.iterator();
+ while (iterator.hasNext()) {
+ DownloadFile downloadFile = iterator.next();
+ if (downloadFile != currentPlaying && downloadFile != currentDownloading) {
+ if (downloadFile.cleanup()) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ private class BufferTask extends CancellableTask {
+ private final DownloadFile downloadFile;
+ private final int position;
+ private final long expectedFileSize;
+ private final File partialFile;
+
+ public BufferTask(DownloadFile downloadFile, int position) {
+ this.downloadFile = downloadFile;
+ this.position = position;
+ partialFile = downloadFile.getPartialFile();
+
+ SharedPreferences prefs = Util.getPreferences(DownloadServiceImpl.this);
+ long bufferLength = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_BUFFER_LENGTH, "5"));
+ if(bufferLength == 0) {
+ // Set to seconds in a day, basically infinity
+ bufferLength = 86400L;
+ }
+
+ // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to.
+ int bitRate = downloadFile.getBitRate();
+ long byteCount = Math.max(100000, bitRate * 1024L / 8L * bufferLength);
+
+ // Find out how large the file should grow before resuming playback.
+ Log.i(TAG, "Buffering from position " + position + " and bitrate " + bitRate);
+ expectedFileSize = (position * bitRate / 8) + byteCount;
+ }
+
+ @Override
+ public void execute() {
+ setPlayerState(DOWNLOADING);
+
+ while (!bufferComplete()) {
+ Util.sleepQuietly(1000L);
+ if (isCancelled()) {
+ return;
+ }
+ }
+ doPlay(downloadFile, position, true);
+ }
+
+ private boolean bufferComplete() {
+ boolean completeFileAvailable = downloadFile.isWorkDone();
+ long size = partialFile.length();
+
+ Log.i(TAG, "Buffering " + partialFile + " (" + size + "/" + expectedFileSize + ", " + completeFileAvailable + ")");
+ return completeFileAvailable || size >= expectedFileSize;
+ }
+
+ @Override
+ public String toString() {
+ 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;
+ }
+
+ // Do an initial sleep so this prepare can't compete with main prepare
+ Util.sleepQuietly(5000L);
+ while (!bufferComplete()) {
+ Util.sleepQuietly(5000L);
+ if (isCancelled()) {
+ return;
+ }
+ }
+
+ // Start the setup of the next media player
+ mediaPlayerHandler.post(new Runnable() {
+ public void run() {
+ 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 + ")";
+ }
+ }
+}