diff options
Diffstat (limited to 'src/github/daneren2005/dsub')
23 files changed, 1714 insertions, 1949 deletions
diff --git a/src/github/daneren2005/dsub/activity/SettingsActivity.java b/src/github/daneren2005/dsub/activity/SettingsActivity.java index d496a201..87719e5b 100644 --- a/src/github/daneren2005/dsub/activity/SettingsActivity.java +++ b/src/github/daneren2005/dsub/activity/SettingsActivity.java @@ -35,15 +35,12 @@ import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; import android.preference.PreferenceScreen; -import android.provider.SearchRecentSuggestions; import android.text.InputType; import android.util.Log; import android.view.MenuItem; import github.daneren2005.dsub.R; -import github.daneren2005.dsub.provider.DSubSearchProvider; import github.daneren2005.dsub.service.DownloadService; -import github.daneren2005.dsub.service.DownloadServiceImpl; import github.daneren2005.dsub.service.MusicService; import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.util.Constants; @@ -257,7 +254,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer setCacheLocation(sharedPreferences.getString(key, "")); } else if (Constants.PREFERENCES_KEY_SLEEP_TIMER_DURATION.equals(key)){ - DownloadService downloadService = DownloadServiceImpl.getInstance(); + DownloadService downloadService = DownloadService.getInstance(); downloadService.setSleepTimerDuration(Integer.parseInt(sharedPreferences.getString(key, "60"))); } else if(Constants.PREFERENCES_KEY_SYNC_MOST_RECENT.equals(key)) { @@ -521,7 +518,7 @@ public class SettingsActivity extends PreferenceActivity implements SharedPrefer } // Clear download queue. - DownloadService downloadService = DownloadServiceImpl.getInstance(); + DownloadService downloadService = DownloadService.getInstance(); downloadService.clear(); } } diff --git a/src/github/daneren2005/dsub/activity/SubsonicActivity.java b/src/github/daneren2005/dsub/activity/SubsonicActivity.java index d15f3d7b..95bed957 100644 --- a/src/github/daneren2005/dsub/activity/SubsonicActivity.java +++ b/src/github/daneren2005/dsub/activity/SubsonicActivity.java @@ -53,7 +53,6 @@ import android.widget.Spinner; import github.daneren2005.dsub.R;
import github.daneren2005.dsub.fragments.SubsonicFragment;
import github.daneren2005.dsub.service.DownloadService;
-import github.daneren2005.dsub.service.DownloadServiceImpl;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.ImageLoader;
import github.daneren2005.dsub.util.Util;
@@ -97,7 +96,7 @@ public class SubsonicActivity extends ActionBarActivity implements OnItemSelecte applyTheme();
super.onCreate(bundle);
applyFullscreen();
- startService(new Intent(this, DownloadServiceImpl.class));
+ startService(new Intent(this, DownloadService.class));
setVolumeControlStream(AudioManager.STREAM_MUSIC);
View actionbar = getLayoutInflater().inflate(R.layout.actionbar_spinner, null);
@@ -461,7 +460,7 @@ public class SubsonicActivity extends ActionBarActivity implements OnItemSelecte Util.startActivityWithoutTransition(this, intent);
} else {
finished = true;
- this.stopService(new Intent(this, DownloadServiceImpl.class));
+ this.stopService(new Intent(this, DownloadService.class));
this.finish();
}
}
@@ -675,15 +674,15 @@ public class SubsonicActivity extends ActionBarActivity implements OnItemSelecte // If service is not available, request it to start and wait for it.
for (int i = 0; i < 5; i++) {
- DownloadService downloadService = DownloadServiceImpl.getInstance();
+ DownloadService downloadService = DownloadService.getInstance();
if (downloadService != null) {
return downloadService;
}
Log.w(TAG, "DownloadService not running. Attempting to start it.");
- startService(new Intent(this, DownloadServiceImpl.class));
+ startService(new Intent(this, DownloadService.class));
Util.sleepQuietly(50L);
}
- return DownloadServiceImpl.getInstance();
+ return DownloadService.getInstance();
}
public static String getThemeName() {
diff --git a/src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java b/src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java index d22bc3ab..9c8bcaa3 100644 --- a/src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java +++ b/src/github/daneren2005/dsub/activity/SubsonicFragmentActivity.java @@ -54,7 +54,6 @@ import github.daneren2005.dsub.fragments.SelectShareFragment; import github.daneren2005.dsub.fragments.SubsonicFragment;
import github.daneren2005.dsub.service.DownloadFile;
import github.daneren2005.dsub.service.DownloadService;
-import github.daneren2005.dsub.service.DownloadServiceImpl;
import github.daneren2005.dsub.updates.Updater;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.FileUtil;
@@ -80,7 +79,7 @@ public class SubsonicFragmentActivity extends SubsonicActivity { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_EXIT)) {
- stopService(new Intent(this, DownloadServiceImpl.class));
+ stopService(new Intent(this, DownloadService.class));
finish();
} else if(getIntent().hasExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD)) {
DownloadService service = getDownloadService();
diff --git a/src/github/daneren2005/dsub/fragments/EqualizerFragment.java b/src/github/daneren2005/dsub/fragments/EqualizerFragment.java index bdc41579..71067d7c 100644 --- a/src/github/daneren2005/dsub/fragments/EqualizerFragment.java +++ b/src/github/daneren2005/dsub/fragments/EqualizerFragment.java @@ -21,7 +21,6 @@ package github.daneren2005.dsub.fragments; import android.content.SharedPreferences;
import android.media.audiofx.Equalizer;
import android.os.Bundle;
-import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
@@ -38,7 +37,7 @@ import java.util.Map; import github.daneren2005.dsub.R;
import github.daneren2005.dsub.audiofx.EqualizerController;
-import github.daneren2005.dsub.service.DownloadServiceImpl;
+import github.daneren2005.dsub.service.DownloadService;
import github.daneren2005.dsub.util.Constants;
import github.daneren2005.dsub.util.Util;
@@ -59,7 +58,7 @@ public class EqualizerFragment extends SubsonicFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
rootView = inflater.inflate(R.layout.equalizer, container, false);
- equalizerController = DownloadServiceImpl.getInstance().getEqualizerController();
+ equalizerController = DownloadService.getInstance().getEqualizerController();
equalizer = equalizerController.getEqualizer();
try {
@@ -105,7 +104,7 @@ public class EqualizerFragment extends SubsonicFragment { @Override
public void onResume() {
super.onResume();
- equalizerController = DownloadServiceImpl.getInstance().getEqualizerController();
+ equalizerController = DownloadService.getInstance().getEqualizerController();
equalizer = equalizerController.getEqualizer();
}
diff --git a/src/github/daneren2005/dsub/fragments/SubsonicFragment.java b/src/github/daneren2005/dsub/fragments/SubsonicFragment.java index 1669ad1f..6b63e48d 100644 --- a/src/github/daneren2005/dsub/fragments/SubsonicFragment.java +++ b/src/github/daneren2005/dsub/fragments/SubsonicFragment.java @@ -53,7 +53,6 @@ import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.domain.Share;
import github.daneren2005.dsub.service.DownloadFile;
import github.daneren2005.dsub.service.DownloadService;
-import github.daneren2005.dsub.service.DownloadServiceImpl;
import github.daneren2005.dsub.service.MusicService;
import github.daneren2005.dsub.service.MusicServiceFactory;
import github.daneren2005.dsub.service.OfflineException;
@@ -413,7 +412,7 @@ public class SubsonicFragment extends Fragment { intent.putExtra(Constants.INTENT_EXTRA_NAME_EXIT, true);
Util.startActivityWithoutTransition(context, intent);
} else {
- context.stopService(new Intent(context, DownloadServiceImpl.class));
+ context.stopService(new Intent(context, DownloadService.class));
context.finish();
}
}
diff --git a/src/github/daneren2005/dsub/provider/DSubWidgetProvider.java b/src/github/daneren2005/dsub/provider/DSubWidgetProvider.java index cfa993ec..04c43dbd 100644 --- a/src/github/daneren2005/dsub/provider/DSubWidgetProvider.java +++ b/src/github/daneren2005/dsub/provider/DSubWidgetProvider.java @@ -44,7 +44,6 @@ import github.daneren2005.dsub.activity.DownloadActivity; import github.daneren2005.dsub.activity.SubsonicFragmentActivity; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.service.DownloadService; -import github.daneren2005.dsub.service.DownloadServiceImpl; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.FileUtil; import github.daneren2005.dsub.util.Util; @@ -267,19 +266,19 @@ public class DSubWidgetProvider extends AppWidgetProvider { // Emulate media button clicks. intent = new Intent("DSub.PLAY_PAUSE"); - intent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + intent.setComponent(new ComponentName(context, DownloadService.class)); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); pendingIntent = PendingIntent.getService(context, 0, intent, 0); views.setOnClickPendingIntent(R.id.control_play, pendingIntent); intent = new Intent("DSub.NEXT"); // Use a unique action name to ensure a different PendingIntent to be created. - intent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + intent.setComponent(new ComponentName(context, DownloadService.class)); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT)); pendingIntent = PendingIntent.getService(context, 0, intent, 0); views.setOnClickPendingIntent(R.id.control_next, pendingIntent); intent = new Intent("DSub.PREVIOUS"); // Use a unique action name to ensure a different PendingIntent to be created. - intent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + intent.setComponent(new ComponentName(context, DownloadService.class)); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); pendingIntent = PendingIntent.getService(context, 0, intent, 0); views.setOnClickPendingIntent(R.id.control_previous, pendingIntent); diff --git a/src/github/daneren2005/dsub/receiver/A2dpIntentReceiver.java b/src/github/daneren2005/dsub/receiver/A2dpIntentReceiver.java index c8c3a1f9..f1837fd7 100644 --- a/src/github/daneren2005/dsub/receiver/A2dpIntentReceiver.java +++ b/src/github/daneren2005/dsub/receiver/A2dpIntentReceiver.java @@ -5,7 +5,6 @@ import android.content.Context; import android.content.Intent;
import android.util.Log;
import github.daneren2005.dsub.service.DownloadService;
-import github.daneren2005.dsub.service.DownloadServiceImpl;
public class A2dpIntentReceiver extends BroadcastReceiver {
private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse";
@@ -15,7 +14,7 @@ public class A2dpIntentReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) {
Log.i(TAG, "GOT INTENT " + intent);
- DownloadService downloadService = DownloadServiceImpl.getInstance();
+ DownloadService downloadService = DownloadService.getInstance();
if (downloadService != null){
diff --git a/src/github/daneren2005/dsub/receiver/BluetoothIntentReceiver.java b/src/github/daneren2005/dsub/receiver/BluetoothIntentReceiver.java index ab46c784..13de4d86 100644 --- a/src/github/daneren2005/dsub/receiver/BluetoothIntentReceiver.java +++ b/src/github/daneren2005/dsub/receiver/BluetoothIntentReceiver.java @@ -24,7 +24,8 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.util.Log; -import github.daneren2005.dsub.service.DownloadServiceImpl; + +import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.Util; @@ -49,7 +50,7 @@ public class BluetoothIntentReceiver extends BroadcastReceiver { SharedPreferences prefs = Util.getPreferences(context); int pausePref = Integer.parseInt(prefs.getString(Constants.PREFERENCES_KEY_PAUSE_DISCONNECT, "0")); if(pausePref == 0 || pausePref == 2) { - context.sendBroadcast(new Intent(DownloadServiceImpl.CMD_PAUSE)); + context.sendBroadcast(new Intent(DownloadService.CMD_PAUSE)); } } } diff --git a/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java b/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java index 9ea04474..89a4a87b 100644 --- a/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java +++ b/src/github/daneren2005/dsub/receiver/MediaButtonIntentReceiver.java @@ -23,7 +23,8 @@ import android.content.Context; import android.content.Intent; import android.util.Log; import android.view.KeyEvent; -import github.daneren2005.dsub.service.DownloadServiceImpl; + +import github.daneren2005.dsub.service.DownloadService; /** * @author Sindre Mehus @@ -37,7 +38,7 @@ public class MediaButtonIntentReceiver extends BroadcastReceiver { KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT); Log.i(TAG, "Got MEDIA_BUTTON key event: " + event); - Intent serviceIntent = new Intent(context, DownloadServiceImpl.class); + Intent serviceIntent = new Intent(context, DownloadService.class); serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); context.startService(serviceIntent); if (isOrderedBroadcast()) diff --git a/src/github/daneren2005/dsub/service/ChromeCastController.java b/src/github/daneren2005/dsub/service/ChromeCastController.java index c25805c2..591d23b9 100644 --- a/src/github/daneren2005/dsub/service/ChromeCastController.java +++ b/src/github/daneren2005/dsub/service/ChromeCastController.java @@ -27,7 +27,6 @@ import com.google.android.gms.cast.MediaMetadata; import com.google.android.gms.cast.MediaStatus; import com.google.android.gms.cast.RemoteMediaPlayer; import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; @@ -61,7 +60,7 @@ public class ChromeCastController extends RemoteController { private RemoteMediaPlayer mediaPlayer; private double gain = 0.5; - public ChromeCastController(DownloadServiceImpl downloadService, CastDevice castDevice) { + public ChromeCastController(DownloadService downloadService, CastDevice castDevice) { downloadService.setPlayerState(PlayerState.PREPARING); this.downloadService = downloadService; this.castDevice = castDevice; diff --git a/src/github/daneren2005/dsub/service/DownloadFile.java b/src/github/daneren2005/dsub/service/DownloadFile.java index d25d2dc9..9977c69a 100644 --- a/src/github/daneren2005/dsub/service/DownloadFile.java +++ b/src/github/daneren2005/dsub/service/DownloadFile.java @@ -414,9 +414,9 @@ public class DownloadFile { if (wifiLock != null) { wifiLock.release(); } - new CacheCleaner(context, DownloadServiceImpl.getInstance()).cleanSpace(); - if(DownloadServiceImpl.getInstance() != null) { - ((DownloadServiceImpl)DownloadServiceImpl.getInstance()).checkDownloads(); + new CacheCleaner(context, DownloadService.getInstance()).cleanSpace(); + if(DownloadService.getInstance() != null) { + ((DownloadService) DownloadService.getInstance()).checkDownloads(); } } } diff --git a/src/github/daneren2005/dsub/service/DownloadService.java b/src/github/daneren2005/dsub/service/DownloadService.java index 471b6d5d..999e825d 100644 --- a/src/github/daneren2005/dsub/service/DownloadService.java +++ b/src/github/daneren2005/dsub/service/DownloadService.java @@ -18,136 +18,1706 @@ */ package github.daneren2005.dsub.service; -import android.support.v7.media.MediaRouteSelector; - -import java.util.List; - +import static android.support.v7.media.MediaRouter.RouteInfo; +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.Bookmark; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PlayerState; +import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.domain.RemoteControlState; 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.MediaRouteManager; +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.support.v7.media.MediaRouteSelector; +import android.util.Log; +import android.support.v4.util.LruCache; +import java.net.URLEncoder; /** * @author Sindre Mehus * @version $Id$ */ -public interface DownloadService { +public class DownloadService extends Service { + + private static final String TAG = DownloadService.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"; + public static final String CANCEL_DOWNLOADS = "github.daneren2005.dsub.CANCEL_DOWNLOADS"; + + 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 List<DownloadFile> toDelete = 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 RemoteController remoteController; + private DownloadFile currentPlaying; + private int currentPlayingIndex = -1; + 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 long downloadRevision; + private boolean downloadOngoing = false; + private DownloadFile lastDownloaded = null; + + private static boolean equalizerAvailable; + private static boolean visualizerAvailable; + private EqualizerController equalizerController; + private VisualizerController visualizerController; + private boolean showVisualization; + private RemoteControlState remoteState = RemoteControlState.LOCAL; + private PositionCache positionCache; + private StreamProxy proxy; + + private Timer sleepTimer; + private int timerDuration; + private boolean autoPlayStart = false; + + private MediaRouteManager mediaRouter; + + 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(DownloadService.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); + + mediaRouter = new MediaRouteManager(this); + + instance = this; + lifecycleSupport.onCreate(); + + if(prefs.getBoolean(Constants.PREFERENCES_EQUALIZER_ON, false)) { + getEqualizerController(); + } + if(prefs.getBoolean(Constants.PREFERENCES_VISUALIZER_ON, false)) { + getVisualizerController(); + showVisualization = true; + } + } + + @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(); + } + if(remoteController != null) { + remoteController.stop(); + remoteController.shutdown(); + } + mediaRouter.destroy(); + Util.hidePlayingNotification(this, this, handler); + Util.hideDownloadingNotification(this); + } + + public static DownloadService getInstance() { + return instance; + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + public synchronized void download(Bookmark bookmark) { + clear(); + DownloadFile downloadFile = new DownloadFile(this, bookmark.getEntry(), false); + downloadList.add(downloadFile); + revision++; + updateJukeboxPlaylist(); + play(0, true, bookmark.getPosition()); + lifecycleSupport.serializeDownloadQueue(); + } + + 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) { + if(song != null) { + DownloadFile downloadFile = new DownloadFile(this, song, save); + downloadList.add(getCurrentPlayingIndex() + offset, downloadFile); + offset++; + } + } + setNextPlaying(); + 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); + currentPlayingIndex = 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); + if(!downloadFile.isWorkDone() || (downloadFile.shouldSave() && !downloadFile.isSaved())) { + // Only add to list if there is work to be done + backgroundDownloadList.add(downloadFile); + } + } + revision++; + + checkDownloads(); + lifecycleSupport.serializeDownloadQueue(); + } + + private void updateJukeboxPlaylist() { + if (remoteState != RemoteControlState.LOCAL) { + remoteController.updatePlaylist(); + } + } + + public void restore(List<MusicDirectory.Entry> songs, List<MusicDirectory.Entry> toDelete, int currentPlayingIndex, int currentPlayingPosition) { + SharedPreferences prefs = Util.getPreferences(this); + remoteState = RemoteControlState.values()[prefs.getInt(Constants.PREFERENCES_KEY_CONTROL_MODE, 0)]; + if(remoteState != RemoteControlState.LOCAL) { + String id = prefs.getString(Constants.PREFERENCES_KEY_CONTROL_ID, null); + setRemoteState(remoteState, null, id); + } + 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() && remoteState == RemoteControlState.LOCAL) { + doPlay(currentPlaying, currentPlayingPosition, autoPlayStart); + } + autoPlayStart = false; + } + + if(toDelete != null) { + for(MusicDirectory.Entry entry: toDelete) { + this.toDelete.add(forSong(entry)); + } + } + } + + 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(); + } + + public boolean isShufflePlayEnabled() { + return shufflePlay; + } + + public synchronized void shuffle() { + Collections.shuffle(downloadList); + currentPlayingIndex = downloadList.indexOf(currentPlaying); + if (currentPlaying != null) { + downloadList.remove(getCurrentPlayingIndex()); + downloadList.add(0, currentPlaying); + currentPlayingIndex = 0; + } + revision++; + lifecycleSupport.serializeDownloadQueue(); + updateJukeboxPlaylist(); + setNextPlaying(); + } + + public RepeatMode getRepeatMode() { + return Util.getRepeatMode(this); + } + + public void setRepeatMode(RepeatMode repeatMode) { + Util.setRepeatMode(this, repeatMode); + setNextPlaying(); + } + + public boolean getKeepScreenOn() { + return keepScreenOn; + } + + 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(); + } + + public boolean getShowVisualization() { + return showVisualization; + } + + public void setShowVisualization(boolean showVisualization) { + this.showVisualization = showVisualization; + SharedPreferences.Editor editor = Util.getPreferences(this).edit(); + editor.putBoolean(Constants.PREFERENCES_VISUALIZER_ON, showVisualization); + editor.commit(); + } + + public synchronized DownloadFile forSong(MusicDirectory.Entry song) { + DownloadFile returnFile = null; + for (DownloadFile downloadFile : downloadList) { + if (downloadFile.getSong().equals(song)) { + if(((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone())) { + // If downloading, return immediately + return downloadFile; + } else { + // Otherwise, check to make sure there isn't a background download going on first + returnFile = downloadFile; + } + } + } + for (DownloadFile downloadFile : backgroundDownloadList) { + if (downloadFile.getSong().equals(song)) { + return downloadFile; + } + } + + if(returnFile != null) { + return returnFile; + } + + DownloadFile downloadFile = downloadFileCache.get(song); + if (downloadFile == null) { + downloadFile = new DownloadFile(this, song, false); + downloadFileCache.put(song, downloadFile); + } + return downloadFile; + } + + public synchronized void clear() { + clear(true); + } + + public synchronized void clearBackground() { + if(currentDownloading != null && backgroundDownloadList.contains(currentDownloading)) { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + backgroundDownloadList.clear(); + Util.hideDownloadingNotification(this); + } + + 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(); + } + + public synchronized int size() { + return downloadList.size(); + } + + public synchronized void clear(boolean serialize) { + // Delete podcast if fully listened to + if(currentPlaying != null && currentPlaying.getSong() instanceof PodcastEpisode) { + int duration = getPlayerDuration(); + + // Make sure > 90% of the way through + int cutoffPoint = (int)(duration * 0.90); + if(duration > 0 && cachedPosition > cutoffPoint) { + currentPlaying.delete(); + } + } + for(DownloadFile podcast: toDelete) { + podcast.delete(); + } + toDelete.clear(); + + reset(); + downloadList.clear(); + revision++; + if (currentDownloading != null) { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + setCurrentPlaying(null, false); + + if (serialize) { + lifecycleSupport.serializeDownloadQueue(); + } + updateJukeboxPlaylist(); + setNextPlaying(); + } + + public synchronized void remove(int which) { + downloadList.remove(which); + currentPlayingIndex = downloadList.indexOf(currentPlaying); + } + + public synchronized void remove(DownloadFile downloadFile) { + if (downloadFile == currentDownloading) { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + if (downloadFile == currentPlaying) { + reset(); + setCurrentPlaying(null, false); + } + downloadList.remove(downloadFile); + currentPlayingIndex = downloadList.indexOf(currentPlaying); + backgroundDownloadList.remove(downloadFile); + revision++; + lifecycleSupport.serializeDownloadQueue(); + updateJukeboxPlaylist(); + if(downloadFile == nextPlaying) { + setNextPlaying(); + } + } + + public synchronized void delete(List<MusicDirectory.Entry> songs) { + for (MusicDirectory.Entry song : songs) { + forSong(song).delete(); + } + } + + 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) { + currentPlayingIndex = -1; + } else { + currentPlayingIndex = downloadList.indexOf(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(DownloadService.this); + boolean gaplessPlayback = prefs.getBoolean(Constants.PREFERENCES_KEY_GAPLESS_PLAYBACK, true); + if(!gaplessPlayback) { + nextPlaying = null; + nextPlayerState = IDLE; + return; + } + + int index = getNextPlayingIndex(); + + 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); + } + } + + public int getCurrentPlayingIndex() { + return currentPlayingIndex; + } + private int getNextPlayingIndex() { + 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; + } + } + return index; + } + + public DownloadFile getCurrentPlaying() { + return currentPlaying; + } + + public DownloadFile getCurrentDownloading() { + return currentDownloading; + } + + public List<DownloadFile> getSongs() { + return downloadList; + } + + public List<DownloadFile> getToDelete() { return toDelete; } + + public synchronized List<DownloadFile> getDownloads() { + List<DownloadFile> temp = new ArrayList<DownloadFile>(); + temp.addAll(downloadList); + temp.addAll(backgroundDownloadList); + return temp; + } + + 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); + } + } + + public synchronized void play(int index) { + play(index, true); + } + private synchronized void play(int index, boolean start) { + play(index, start, 0); + } + private synchronized void play(int index, boolean start, int position) { + 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 (remoteState != RemoteControlState.LOCAL) { + remoteController.changeTrack(index, downloadList.get(index)); + setPlayerState(STARTED); + } else { + bufferAndPlay(position); + } + } + if (remoteState == RemoteControlState.LOCAL) { + 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 if(!nextMediaPlayer.isPlaying()) { + Log.w(TAG, "nextSetup lied about it's state!"); + 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(); + } + } + + public synchronized void seekTo(int position) { + try { + if (remoteState != RemoteControlState.LOCAL) { + remoteController.changePosition(position / 1000); + } else { + mediaPlayer.seekTo(position); + cachedPosition = position; + } + } catch (Exception x) { + handleError(x); + } + } + + public synchronized void previous() { + int index = getCurrentPlayingIndex(); + if (index == -1) { + return; + } + + // Restart song if played more than five seconds. + if (getPlayerPosition() > 5000 || (index == 0 && getRepeatMode() != RepeatMode.ALL)) { + play(index); + } else { + if(index == 0) { + index = size(); + } + + play(index - 1); + } + } + + public synchronized void next() { + // Delete podcast if fully listened to + if(currentPlaying != null && currentPlaying.getSong() instanceof PodcastEpisode) { + int duration = getPlayerDuration(); + + // Make sure > 90% of the way through + int cutoffPoint = (int)(duration * 0.90); + if(duration > 0 && cachedPosition > cutoffPoint) { + toDelete.add(currentPlaying); + } + } + + int index = getCurrentPlayingIndex(); + int nextPlayingIndex = getNextPlayingIndex(); + // Make sure to actually go to next when repeat song is on + if(index == nextPlayingIndex) { + nextPlayingIndex++; + } + if (index != -1 && nextPlayingIndex < size()) { + if(nextPlaying != null && downloadList.get(nextPlayingIndex) == nextPlaying && nextPlayerState == PlayerState.PREPARED && remoteState == RemoteControlState.LOCAL) { + if(mediaPlayer.isPlaying()) { + mediaPlayer.stop(); + } + mediaPlayer.setOnErrorListener(null); + mediaPlayer.setOnCompletionListener(null); + mediaPlayer.reset(); + playNext(true); + } else { + play(nextPlayingIndex); + } + } + } + + private void onSongCompleted() { + play(getNextPlayingIndex()); + } + + public synchronized void pause() { + try { + if (playerState == STARTED) { + if (remoteState != RemoteControlState.LOCAL) { + remoteController.stop(); + } else { + mediaPlayer.pause(); + } + setPlayerState(PAUSED); + } + } catch (Exception x) { + handleError(x); + } + } + + public synchronized void stop() { + try { + if (playerState == STARTED) { + if (remoteState != RemoteControlState.LOCAL) { + remoteController.stop(); + } else { + mediaPlayer.pause(); + } + setPlayerState(STOPPED); + } else if(playerState == PAUSED) { + setPlayerState(STOPPED); + } + } catch(Exception x) { + handleError(x); + } + } + + public synchronized void start() { + try { + if (remoteState != RemoteControlState.LOCAL) { + remoteController.start(); + } else { + mediaPlayer.start(); + } + setPlayerState(STARTED); + } catch (Exception x) { + handleError(x); + } + } + + public synchronized void reset() { + if (bufferTask != null) { + bufferTask.cancel(); + } + try { + // Only set to idle if it's not being killed to start RemoteController + if(remoteState == RemoteControlState.LOCAL) { + setPlayerState(IDLE); + } + mediaPlayer.setOnErrorListener(null); + mediaPlayer.setOnCompletionListener(null); + mediaPlayer.reset(); + } catch (Exception x) { + handleError(x); + } + } + + public int getPlayerPosition() { + try { + if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING) { + return 0; + } + if (remoteState != RemoteControlState.LOCAL) { + return remoteController.getRemotePosition() * 1000; + } else { + return cachedPosition; + } + } catch (Exception x) { + handleError(x); + return 0; + } + } + + 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; + } + + 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, (currentPlaying != null) ? currentPlaying.getSong() : null, 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(1000L); + } + 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; + } + + public void setSuggestedPlaylistName(String name, String id) { + this.suggestedPlaylistName = name; + this.suggestedPlaylistId = id; + } + + public String getSuggestedPlaylistName() { + return suggestedPlaylistName; + } + + public String getSuggestedPlaylistId() { + return suggestedPlaylistId; + } + + public boolean getEqualizerAvailable() { + return equalizerAvailable; + } + + public boolean getVisualizerAvailable() { + return visualizerAvailable; + } + + public EqualizerController getEqualizerController() { + if (equalizerAvailable && equalizerController == null) { + equalizerController = new EqualizerController(this, mediaPlayer); + if (!equalizerController.isAvailable()) { + equalizerController = null; + } else { + equalizerController.loadSettings(); + } + } + return equalizerController; + } + + public VisualizerController getVisualizerController() { + if (visualizerAvailable && visualizerController == null) { + visualizerController = new VisualizerController(this, mediaPlayer); + if (!visualizerController.isAvailable()) { + visualizerController = null; + } + } + return visualizerController; + } + + public MediaRouteSelector getRemoteSelector() { + return mediaRouter.getSelector(); + } + + public boolean isRemoteEnabled() { + return remoteState != RemoteControlState.LOCAL; + } + + public void setRemoteEnabled(RemoteControlState newState) { + if(instance != null) { + setRemoteEnabled(newState, null); + } + } + public void setRemoteEnabled(RemoteControlState newState, Object ref) { + setRemoteState(newState, ref); + + RouteInfo info = mediaRouter.getSelectedRoute(); + String routeId = info.getId(); + + SharedPreferences.Editor editor = Util.getPreferences(this).edit(); + editor.putInt(Constants.PREFERENCES_KEY_CONTROL_MODE, newState.getValue()); + editor.putString(Constants.PREFERENCES_KEY_CONTROL_ID, routeId); + editor.commit(); + } + private void setRemoteState(RemoteControlState newState, Object ref) { + setRemoteState(newState, ref, null); + } + private void setRemoteState(final RemoteControlState newState, final Object ref, final String routeId) { + if(remoteController != null) { + remoteController.stop(); + setPlayerState(PlayerState.IDLE); + remoteController.shutdown(); + remoteController = null; + } + + remoteState = newState; + switch(newState) { + case JUKEBOX_SERVER: + remoteController = new JukeboxController(this, handler); + break; + case CHROMECAST: + // TODO: Fix case where starting up with chromecast set + if(ref == null) { + remoteState = RemoteControlState.LOCAL; + break; + } + remoteController = (RemoteController) ref; + break; + case LOCAL: default: + break; + } + + if (remoteState != RemoteControlState.LOCAL) { + reset(); + + // Cancel current download, if necessary. + if (currentDownloading != null) { + currentDownloading.cancelDownload(); + } + } + + SharedPreferences prefs = Util.getPreferences(this); + if(currentPlaying != null && prefs.getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false)) { + Util.showPlayingNotification(this, this, handler, currentPlaying.getSong()); + } else { + Util.hidePlayingNotification(this, this, handler); + } + + if(routeId != null) { + handler.post(new Runnable() { + @Override + public void run() { + RouteInfo info = mediaRouter.getRouteForId(routeId); + if(info == null) { + setRemoteState(RemoteControlState.LOCAL, null); + } else if(newState == RemoteControlState.CHROMECAST) { + RemoteController controller = mediaRouter.getRemoteController(info); + if(controller != null) { + setRemoteState(RemoteControlState.CHROMECAST, controller); + } + } + } + }); + } + } + + public void setRemoteVolume(boolean up) { + remoteController.setVolume(up); + } + + public void startRemoteScan() { + mediaRouter.startScan(); + } + + public void stopRemoteScan() { + mediaRouter.stopScan(); + } + + private synchronized void bufferAndPlay() { + bufferAndPlay(0); + } + private synchronized void bufferAndPlay(int position) { + if(playerState != PREPARED) { + reset(); + + bufferTask = new BufferTask(currentPlaying, position); + bufferTask.start(); + } else { + doPlay(currentPlaying, position, 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 (DownloadService.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); + } + } + + // Only call when starting, setPlayerState(PAUSED) already calls this + if(start) { + 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.setOnErrorListener(null); + nextMediaPlayer.reset(); + nextMediaPlayer.release(); + nextMediaPlayer = null; + } + + // Exit when using remote controllers + if(remoteState != RemoteControlState.LOCAL) { + return; + } + + nextMediaPlayer = new MediaPlayer(); + nextMediaPlayer.setWakeMode(DownloadService.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(); + + // Finished loading, delete when list is cleared + if(downloadFile.getSong() instanceof PodcastEpisode) { + toDelete.add(downloadFile); + } + } else { + // If file is not completely downloaded, restart the playback from the current position. + synchronized (DownloadService.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(); + } + } + } + } + }); + } + + public void setSleepTimerDuration(int duration){ + timerDuration = duration; + } + + 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); + } + + public void stopSleepTimer() { + if(sleepTimer != null){ + sleepTimer.cancel(); + sleepTimer.purge(); + } + sleepTimer = null; + } + + public boolean getSleepTimer() { + return sleepTimer != null; + } - void download(Bookmark bookmark); - void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle); - void downloadBackground(List<MusicDirectory.Entry> songs, boolean save); + public void setVolume(float volume) { + if(mediaPlayer != null) { + mediaPlayer.setVolume(volume, volume); + } + } - void setShufflePlayEnabled(boolean enabled); + 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; + } - boolean isShufflePlayEnabled(); + int currentPlayingIndex = getCurrentPlayingIndex(); + DownloadFile movedSong = list.remove(from); + list.add(to, movedSong); + if(remoteState != RemoteControlState.LOCAL && mainList) { + updateJukeboxPlaylist(); + } else if(mainList && (movedSong == nextPlaying || (currentPlayingIndex + 1) == to)) { + // Moving next playing or moving a song to be next playing + setNextPlaying(); + } + } - void shuffle(); + private void handleError(Exception x) { + Log.w(TAG, "Media player error: " + x, x); + if(mediaPlayer != null) { + mediaPlayer.reset(); + } + setPlayerState(IDLE); + } + private void handleErrorNext(Exception x) { + Log.w(TAG, "Next Media player error: " + x, x); + nextMediaPlayer.reset(); + setNextPlayerState(IDLE); + } - RepeatMode getRepeatMode(); + protected synchronized void checkDownloads() { + if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable()) { + return; + } - void setRepeatMode(RepeatMode repeatMode); + if (shufflePlay) { + checkShufflePlay(); + } - boolean getKeepScreenOn(); + if (remoteState != RemoteControlState.LOCAL || !Util.isNetworkConnected(this) || Util.isOffline(this)) { + return; + } - void setKeepScreenOn(boolean screenOn); + if (downloadList.isEmpty() && backgroundDownloadList.isEmpty()) { + return; + } - boolean getShowVisualization(); + // Need to download current playing? + if (currentPlaying != null && currentPlaying != currentDownloading && !currentPlaying.isWorkDone()) { + // Cancel current download, if necessary. + if (currentDownloading != null) { + currentDownloading.cancelDownload(); + } - void setShowVisualization(boolean showVisualization); + currentDownloading = currentPlaying; + currentDownloading.download(); + cleanupCandidates.add(currentDownloading); + } - void clear(); - - void clearBackground(); + // Find a suitable target for download. + else if (currentDownloading == null || currentDownloading.isWorkDone() || currentDownloading.isFailed() && (!downloadList.isEmpty() || !backgroundDownloadList.isEmpty())) { + currentDownloading = null; + int n = size(); - void clearIncomplete(); + int preloaded = 0; - int size(); - - void remove(int which); + 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() && !downloadFile.isFailedMax()) { + 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++; + } - void remove(DownloadFile downloadFile); + i = (i + 1) % n; + } while (i != start); + } - List<DownloadFile> getSongs(); - - List<DownloadFile> getDownloads(); - - List<DownloadFile> getBackgroundDownloads(); + 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 { + if(!downloadFile.isFailedMax()) { + currentDownloading = downloadFile; + currentDownloading.download(); + cleanupCandidates.add(currentDownloading); + break; + } + } + } + } + } - int getCurrentPlayingIndex(); + if(!backgroundDownloadList.isEmpty()) { + DownloadFile speedFile = null; + // Updating existing notification + if(downloadOngoing) { + // Changing download, use speed of last DownloadFile + if(revision != downloadRevision && lastDownloaded != null) { + speedFile = lastDownloaded; + } else { + // Updated mid-download + speedFile = currentDownloading; + } + } + Util.showDownloadingNotification(this, currentDownloading, backgroundDownloadList.size(), speedFile); + downloadRevision = revision; + lastDownloaded = currentDownloading; + downloadOngoing = true; + } else if(backgroundDownloadList.isEmpty() && downloadOngoing) { + Util.hideDownloadingNotification(this); + downloadOngoing = false; + lastDownloaded = null; + } - DownloadFile getCurrentPlaying(); + // Delete obsolete .partial and .complete files. + cleanup(); + } - DownloadFile getCurrentDownloading(); + private synchronized void checkShufflePlay() { - void play(int index); + // 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(); - void seekTo(int position); + long revisionBefore = revision; - void previous(); + // 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++; + } + } - void next(); + int currIndex = currentPlaying == null ? 0 : getCurrentPlayingIndex(); - void pause(); - - void stop(); + // 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++; + } + } + currentPlayingIndex = downloadList.indexOf(currentPlaying); - void start(); + if (revisionBefore != revision) { + updateJukeboxPlaylist(); + } - void reset(); + if (wasEmpty && !downloadList.isEmpty()) { + play(0); + } + } - PlayerState getPlayerState(); + public long getDownloadListUpdateRevision() { + return revision; + } - int getPlayerPosition(); + 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(); + } + } + } + } - int getPlayerDuration(); + private class BufferTask extends CancellableTask { + private final DownloadFile downloadFile; + private final int position; + private final long expectedFileSize; + private final File partialFile; - void delete(List<MusicDirectory.Entry> songs); + public BufferTask(DownloadFile downloadFile, int position) { + this.downloadFile = downloadFile; + this.position = position; + partialFile = downloadFile.getPartialFile(); - void unpin(List<MusicDirectory.Entry> songs); + // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to. + int bitRate = downloadFile.getBitRate(); + long byteCount = Math.max(100000, bitRate * 1024L / 8L * 5L); - DownloadFile forSong(MusicDirectory.Entry song); + // 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; + } - long getDownloadListUpdateRevision(); + @Override + public void execute() { + setPlayerState(DOWNLOADING); - void setSuggestedPlaylistName(String name, String id); + while (!bufferComplete()) { + Util.sleepQuietly(1000L); + if (isCancelled()) { + return; + } + } + doPlay(downloadFile, position, true); + } - String getSuggestedPlaylistName(); - - String getSuggestedPlaylistId(); - - boolean getEqualizerAvailable(); + private boolean bufferComplete() { + boolean completeFileAvailable = downloadFile.isWorkDone(); + long size = partialFile.length(); - boolean getVisualizerAvailable(); + Log.i(TAG, "Buffering " + partialFile + " (" + size + "/" + expectedFileSize + ", " + completeFileAvailable + ")"); + return completeFileAvailable || size >= expectedFileSize; + } - EqualizerController getEqualizerController(); + @Override + public String toString() { + return "BufferTask (" + downloadFile + ")"; + } + } - VisualizerController getVisualizerController(); + private class CheckCompletionTask extends CancellableTask { + private final DownloadFile downloadFile; + private final File partialFile; - MediaRouteSelector getRemoteSelector(); + public CheckCompletionTask(DownloadFile downloadFile) { + setNextPlayerState(PlayerState.IDLE); + this.downloadFile = downloadFile; + if(downloadFile != null) { + partialFile = downloadFile.getPartialFile(); + } else { + partialFile = null; + } + } - boolean isRemoteEnabled(); + @Override + public void execute() { + if(downloadFile == null) { + return; + } - void setRemoteEnabled(RemoteControlState newState); + // Do an initial sleep so this prepare can't compete with main prepare + Util.sleepQuietly(5000L); + while (!bufferComplete()) { + Util.sleepQuietly(5000L); + if (isCancelled()) { + return; + } + } - void setRemoteVolume(boolean up); + // Start the setup of the next media player + mediaPlayerHandler.post(new Runnable() { + public void run() { + setupNext(downloadFile); + } + }); + } - void startRemoteScan(); + private boolean bufferComplete() { + boolean completeFileAvailable = downloadFile.isWorkDone(); + Log.i(TAG, "Buffering next " + partialFile + " (" + partialFile.length() + ")"); + return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED); + } - void stopRemoteScan(); - - void setSleepTimerDuration(int duration); - - void startSleepTimer(); - - void stopSleepTimer(); - - boolean getSleepTimer(); - - void setVolume(float volume); - - void swap(boolean mainList, int from, int to); + @Override + public String toString() { + return "CheckCompletionTask (" + downloadFile + ")"; + } + } } diff --git a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java b/src/github/daneren2005/dsub/service/DownloadServiceImpl.java deleted file mode 100644 index d28c5abf..00000000 --- a/src/github/daneren2005/dsub/service/DownloadServiceImpl.java +++ /dev/null @@ -1,1780 +0,0 @@ -/* - 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 android.support.v7.media.MediaRouter.RouteInfo; -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.Bookmark; -import github.daneren2005.dsub.domain.MusicDirectory; -import github.daneren2005.dsub.domain.PlayerState; -import github.daneren2005.dsub.domain.PodcastEpisode; -import github.daneren2005.dsub.domain.RemoteControlState; -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.MediaRouteManager; -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.support.v7.media.MediaRouteSelector; -import android.support.v7.media.MediaRouter; -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"; - public static final String CANCEL_DOWNLOADS = "github.daneren2005.dsub.CANCEL_DOWNLOADS"; - - 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 List<DownloadFile> toDelete = 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 RemoteController remoteController; - private DownloadFile currentPlaying; - private int currentPlayingIndex = -1; - 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 long downloadRevision; - private boolean downloadOngoing = false; - private DownloadFile lastDownloaded = null; - - private static boolean equalizerAvailable; - private static boolean visualizerAvailable; - private EqualizerController equalizerController; - private VisualizerController visualizerController; - private boolean showVisualization; - private RemoteControlState remoteState = RemoteControlState.LOCAL; - private PositionCache positionCache; - private StreamProxy proxy; - - private Timer sleepTimer; - private int timerDuration; - private boolean autoPlayStart = false; - - private MediaRouteManager mediaRouter; - - 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); - - mediaRouter = new MediaRouteManager(this); - - instance = this; - lifecycleSupport.onCreate(); - - if(prefs.getBoolean(Constants.PREFERENCES_EQUALIZER_ON, false)) { - getEqualizerController(); - } - if(prefs.getBoolean(Constants.PREFERENCES_VISUALIZER_ON, false)) { - getVisualizerController(); - showVisualization = true; - } - } - - @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(); - } - if(remoteController != null) { - remoteController.stop(); - remoteController.shutdown(); - } - mediaRouter.destroy(); - Util.hidePlayingNotification(this, this, handler); - Util.hideDownloadingNotification(this); - } - - public static DownloadService getInstance() { - return instance; - } - - @Override - public IBinder onBind(Intent intent) { - return binder; - } - - @Override - public synchronized void download(Bookmark bookmark) { - clear(); - DownloadFile downloadFile = new DownloadFile(this, bookmark.getEntry(), false); - downloadList.add(downloadFile); - revision++; - updateJukeboxPlaylist(); - play(0, true, bookmark.getPosition()); - lifecycleSupport.serializeDownloadQueue(); - } - - @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) { - if(song != null) { - DownloadFile downloadFile = new DownloadFile(this, song, save); - downloadList.add(getCurrentPlayingIndex() + offset, downloadFile); - offset++; - } - } - setNextPlaying(); - 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); - currentPlayingIndex = 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); - if(!downloadFile.isWorkDone() || (downloadFile.shouldSave() && !downloadFile.isSaved())) { - // Only add to list if there is work to be done - backgroundDownloadList.add(downloadFile); - } - } - revision++; - - checkDownloads(); - lifecycleSupport.serializeDownloadQueue(); - } - - private void updateJukeboxPlaylist() { - if (remoteState != RemoteControlState.LOCAL) { - remoteController.updatePlaylist(); - } - } - - public void restore(List<MusicDirectory.Entry> songs, List<MusicDirectory.Entry> toDelete, int currentPlayingIndex, int currentPlayingPosition) { - SharedPreferences prefs = Util.getPreferences(this); - remoteState = RemoteControlState.values()[prefs.getInt(Constants.PREFERENCES_KEY_CONTROL_MODE, 0)]; - if(remoteState != RemoteControlState.LOCAL) { - String id = prefs.getString(Constants.PREFERENCES_KEY_CONTROL_ID, null); - setRemoteState(remoteState, null, id); - } - 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() && remoteState == RemoteControlState.LOCAL) { - doPlay(currentPlaying, currentPlayingPosition, autoPlayStart); - } - autoPlayStart = false; - } - - if(toDelete != null) { - for(MusicDirectory.Entry entry: toDelete) { - this.toDelete.add(forSong(entry)); - } - } - } - - @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); - currentPlayingIndex = downloadList.indexOf(currentPlaying); - if (currentPlaying != null) { - downloadList.remove(getCurrentPlayingIndex()); - downloadList.add(0, currentPlaying); - currentPlayingIndex = 0; - } - 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; - SharedPreferences.Editor editor = Util.getPreferences(this).edit(); - editor.putBoolean(Constants.PREFERENCES_VISUALIZER_ON, showVisualization); - editor.commit(); - } - - @Override - public synchronized DownloadFile forSong(MusicDirectory.Entry song) { - DownloadFile returnFile = null; - for (DownloadFile downloadFile : downloadList) { - if (downloadFile.getSong().equals(song)) { - if(((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone())) { - // If downloading, return immediately - return downloadFile; - } else { - // Otherwise, check to make sure there isn't a background download going on first - returnFile = downloadFile; - } - } - } - for (DownloadFile downloadFile : backgroundDownloadList) { - if (downloadFile.getSong().equals(song)) { - return downloadFile; - } - } - - if(returnFile != null) { - return returnFile; - } - - 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(); - Util.hideDownloadingNotification(this); - } - - @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) { - // Delete podcast if fully listened to - if(currentPlaying != null && currentPlaying.getSong() instanceof PodcastEpisode) { - int duration = getPlayerDuration(); - - // Make sure > 90% of the way through - int cutoffPoint = (int)(duration * 0.90); - if(duration > 0 && cachedPosition > cutoffPoint) { - currentPlaying.delete(); - } - } - for(DownloadFile podcast: toDelete) { - podcast.delete(); - } - toDelete.clear(); - - 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); - currentPlayingIndex = downloadList.indexOf(currentPlaying); - } - - @Override - public synchronized void remove(DownloadFile downloadFile) { - if (downloadFile == currentDownloading) { - currentDownloading.cancelDownload(); - currentDownloading = null; - } - if (downloadFile == currentPlaying) { - reset(); - setCurrentPlaying(null, false); - } - downloadList.remove(downloadFile); - currentPlayingIndex = downloadList.indexOf(currentPlaying); - 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) { - currentPlayingIndex = -1; - } else { - currentPlayingIndex = downloadList.indexOf(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 = getNextPlayingIndex(); - - 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 int getCurrentPlayingIndex() { - return currentPlayingIndex; - } - private int getNextPlayingIndex() { - 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; - } - } - return index; - } - - @Override - public DownloadFile getCurrentPlaying() { - return currentPlaying; - } - - @Override - public DownloadFile getCurrentDownloading() { - return currentDownloading; - } - - @Override - public List<DownloadFile> getSongs() { - return downloadList; - } - - public List<DownloadFile> getToDelete() { return toDelete; } - - @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) { - play(index, start, 0); - } - private synchronized void play(int index, boolean start, int position) { - 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 (remoteState != RemoteControlState.LOCAL) { - remoteController.changeTrack(index, downloadList.get(index)); - setPlayerState(STARTED); - } else { - bufferAndPlay(position); - } - } - if (remoteState == RemoteControlState.LOCAL) { - 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 if(!nextMediaPlayer.isPlaying()) { - Log.w(TAG, "nextSetup lied about it's state!"); - 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 (remoteState != RemoteControlState.LOCAL) { - remoteController.changePosition(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 && getRepeatMode() != RepeatMode.ALL)) { - play(index); - } else { - if(index == 0) { - index = size(); - } - - play(index - 1); - } - } - - @Override - public synchronized void next() { - // Delete podcast if fully listened to - if(currentPlaying != null && currentPlaying.getSong() instanceof PodcastEpisode) { - int duration = getPlayerDuration(); - - // Make sure > 90% of the way through - int cutoffPoint = (int)(duration * 0.90); - if(duration > 0 && cachedPosition > cutoffPoint) { - toDelete.add(currentPlaying); - } - } - - int index = getCurrentPlayingIndex(); - int nextPlayingIndex = getNextPlayingIndex(); - // Make sure to actually go to next when repeat song is on - if(index == nextPlayingIndex) { - nextPlayingIndex++; - } - if (index != -1 && nextPlayingIndex < size()) { - if(nextPlaying != null && downloadList.get(nextPlayingIndex) == nextPlaying && nextPlayerState == PlayerState.PREPARED && remoteState == RemoteControlState.LOCAL) { - if(mediaPlayer.isPlaying()) { - mediaPlayer.stop(); - } - mediaPlayer.setOnErrorListener(null); - mediaPlayer.setOnCompletionListener(null); - mediaPlayer.reset(); - playNext(true); - } else { - play(nextPlayingIndex); - } - } - } - - private void onSongCompleted() { - play(getNextPlayingIndex()); - } - - @Override - public synchronized void pause() { - try { - if (playerState == STARTED) { - if (remoteState != RemoteControlState.LOCAL) { - remoteController.stop(); - } else { - mediaPlayer.pause(); - } - setPlayerState(PAUSED); - } - } catch (Exception x) { - handleError(x); - } - } - - @Override - public synchronized void stop() { - try { - if (playerState == STARTED) { - if (remoteState != RemoteControlState.LOCAL) { - remoteController.stop(); - } else { - mediaPlayer.pause(); - } - setPlayerState(STOPPED); - } else if(playerState == PAUSED) { - setPlayerState(STOPPED); - } - } catch(Exception x) { - handleError(x); - } - } - - @Override - public synchronized void start() { - try { - if (remoteState != RemoteControlState.LOCAL) { - remoteController.start(); - } else { - mediaPlayer.start(); - } - setPlayerState(STARTED); - } catch (Exception x) { - handleError(x); - } - } - - @Override - public synchronized void reset() { - if (bufferTask != null) { - bufferTask.cancel(); - } - try { - // Only set to idle if it's not being killed to start RemoteController - if(remoteState == RemoteControlState.LOCAL) { - setPlayerState(IDLE); - } - mediaPlayer.setOnErrorListener(null); - mediaPlayer.setOnCompletionListener(null); - mediaPlayer.reset(); - } catch (Exception x) { - handleError(x); - } - } - - @Override - public int getPlayerPosition() { - try { - if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING) { - return 0; - } - if (remoteState != RemoteControlState.LOCAL) { - return remoteController.getRemotePosition() * 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, (currentPlaying != null) ? currentPlaying.getSong() : null, 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(1000L); - } - 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 MediaRouteSelector getRemoteSelector() { - return mediaRouter.getSelector(); - } - - @Override - public boolean isRemoteEnabled() { - return remoteState != RemoteControlState.LOCAL; - } - - @Override - public void setRemoteEnabled(RemoteControlState newState) { - if(instance != null) { - setRemoteEnabled(newState, null); - } - } - public void setRemoteEnabled(RemoteControlState newState, Object ref) { - setRemoteState(newState, ref); - - RouteInfo info = mediaRouter.getSelectedRoute(); - String routeId = info.getId(); - - SharedPreferences.Editor editor = Util.getPreferences(this).edit(); - editor.putInt(Constants.PREFERENCES_KEY_CONTROL_MODE, newState.getValue()); - editor.putString(Constants.PREFERENCES_KEY_CONTROL_ID, routeId); - editor.commit(); - } - private void setRemoteState(RemoteControlState newState, Object ref) { - setRemoteState(newState, ref, null); - } - private void setRemoteState(final RemoteControlState newState, final Object ref, final String routeId) { - if(remoteController != null) { - remoteController.stop(); - setPlayerState(PlayerState.IDLE); - remoteController.shutdown(); - remoteController = null; - } - - remoteState = newState; - switch(newState) { - case JUKEBOX_SERVER: - remoteController = new JukeboxController(this, handler); - break; - case CHROMECAST: - // TODO: Fix case where starting up with chromecast set - if(ref == null) { - remoteState = RemoteControlState.LOCAL; - break; - } - remoteController = (RemoteController) ref; - break; - case LOCAL: default: - break; - } - - if (remoteState != RemoteControlState.LOCAL) { - reset(); - - // Cancel current download, if necessary. - if (currentDownloading != null) { - currentDownloading.cancelDownload(); - } - } - - SharedPreferences prefs = Util.getPreferences(this); - if(currentPlaying != null && prefs.getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false)) { - Util.showPlayingNotification(this, this, handler, currentPlaying.getSong()); - } else { - Util.hidePlayingNotification(this, this, handler); - } - - if(routeId != null) { - handler.post(new Runnable() { - @Override - public void run() { - RouteInfo info = mediaRouter.getRouteForId(routeId); - if(info == null) { - setRemoteState(RemoteControlState.LOCAL, null); - } else if(newState == RemoteControlState.CHROMECAST) { - RemoteController controller = mediaRouter.getRemoteController(info); - if(controller != null) { - setRemoteState(RemoteControlState.CHROMECAST, controller); - } - } - } - }); - } - } - - @Override - public void setRemoteVolume(boolean up) { - remoteController.setVolume(up); - } - - @Override - public void startRemoteScan() { - mediaRouter.startScan(); - } - - @Override - public void stopRemoteScan() { - mediaRouter.stopScan(); - } - - private synchronized void bufferAndPlay() { - bufferAndPlay(0); - } - private synchronized void bufferAndPlay(int position) { - if(playerState != PREPARED) { - reset(); - - bufferTask = new BufferTask(currentPlaying, position); - bufferTask.start(); - } else { - doPlay(currentPlaying, position, 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); - } - } - - // Only call when starting, setPlayerState(PAUSED) already calls this - if(start) { - 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.setOnErrorListener(null); - nextMediaPlayer.reset(); - nextMediaPlayer.release(); - nextMediaPlayer = null; - } - - // Exit when using remote controllers - if(remoteState != RemoteControlState.LOCAL) { - return; - } - - 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(); - - // Finished loading, delete when list is cleared - if(downloadFile.getSong() instanceof PodcastEpisode) { - toDelete.add(downloadFile); - } - } else { - // 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(remoteState != RemoteControlState.LOCAL && 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); - if(mediaPlayer != null) { - 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 (remoteState != RemoteControlState.LOCAL || !Util.isNetworkConnected(this) || Util.isOffline(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() && !downloadFile.isFailedMax()) { - 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 { - if(!downloadFile.isFailedMax()) { - currentDownloading = downloadFile; - currentDownloading.download(); - cleanupCandidates.add(currentDownloading); - break; - } - } - } - } - } - - if(!backgroundDownloadList.isEmpty()) { - DownloadFile speedFile = null; - // Updating existing notification - if(downloadOngoing) { - // Changing download, use speed of last DownloadFile - if(revision != downloadRevision && lastDownloaded != null) { - speedFile = lastDownloaded; - } else { - // Updated mid-download - speedFile = currentDownloading; - } - } - Util.showDownloadingNotification(this, currentDownloading, backgroundDownloadList.size(), speedFile); - downloadRevision = revision; - lastDownloaded = currentDownloading; - downloadOngoing = true; - } else if(backgroundDownloadList.isEmpty() && downloadOngoing) { - Util.hideDownloadingNotification(this); - downloadOngoing = false; - lastDownloaded = null; - } - - // 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++; - } - } - currentPlayingIndex = downloadList.indexOf(currentPlaying); - - 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(); - - // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to. - int bitRate = downloadFile.getBitRate(); - long byteCount = Math.max(100000, bitRate * 1024L / 8L * 5L); - - // 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 + ")"; - } - } -} diff --git a/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java b/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java index cd876621..1e123ef3 100644 --- a/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java +++ b/src/github/daneren2005/dsub/service/DownloadServiceLifecycleSupport.java @@ -33,8 +33,6 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.media.RemoteControlClient; -import android.os.AsyncTask; -import android.os.Build; import android.os.Handler; import android.os.Looper; import android.telephony.PhoneStateListener; @@ -56,7 +54,7 @@ public class DownloadServiceLifecycleSupport { private static final String TAG = DownloadServiceLifecycleSupport.class.getSimpleName(); private static final String FILENAME_DOWNLOADS_SER = "downloadstate2.ser"; - private final DownloadServiceImpl downloadService; + private final DownloadService downloadService; private Looper eventLooper; private Handler eventHandler; private ScheduledExecutorService executorService; @@ -79,17 +77,17 @@ public class DownloadServiceLifecycleSupport { public void run() { String action = intent.getAction(); Log.i(TAG, "intentReceiver.onReceive: " + action); - if (DownloadServiceImpl.CMD_PLAY.equals(action)) { + if (DownloadService.CMD_PLAY.equals(action)) { downloadService.play(); - } else if (DownloadServiceImpl.CMD_NEXT.equals(action)) { + } else if (DownloadService.CMD_NEXT.equals(action)) { downloadService.next(); - } else if (DownloadServiceImpl.CMD_PREVIOUS.equals(action)) { + } else if (DownloadService.CMD_PREVIOUS.equals(action)) { downloadService.previous(); - } else if (DownloadServiceImpl.CMD_TOGGLEPAUSE.equals(action)) { + } else if (DownloadService.CMD_TOGGLEPAUSE.equals(action)) { downloadService.togglePlayPause(); - } else if (DownloadServiceImpl.CMD_PAUSE.equals(action)) { + } else if (DownloadService.CMD_PAUSE.equals(action)) { downloadService.pause(); - } else if (DownloadServiceImpl.CMD_STOP.equals(action)) { + } else if (DownloadService.CMD_STOP.equals(action)) { downloadService.pause(); downloadService.seekTo(0); } @@ -99,7 +97,7 @@ public class DownloadServiceLifecycleSupport { }; - public DownloadServiceLifecycleSupport(DownloadServiceImpl downloadService) { + public DownloadServiceLifecycleSupport(DownloadService downloadService) { this.downloadService = downloadService; } @@ -189,13 +187,13 @@ public class DownloadServiceLifecycleSupport { // Register the handler for outside intents. IntentFilter commandFilter = new IntentFilter(); - commandFilter.addAction(DownloadServiceImpl.CMD_PLAY); - commandFilter.addAction(DownloadServiceImpl.CMD_TOGGLEPAUSE); - commandFilter.addAction(DownloadServiceImpl.CMD_PAUSE); - commandFilter.addAction(DownloadServiceImpl.CMD_STOP); - commandFilter.addAction(DownloadServiceImpl.CMD_PREVIOUS); - commandFilter.addAction(DownloadServiceImpl.CMD_NEXT); - commandFilter.addAction(DownloadServiceImpl.CANCEL_DOWNLOADS); + commandFilter.addAction(DownloadService.CMD_PLAY); + commandFilter.addAction(DownloadService.CMD_TOGGLEPAUSE); + commandFilter.addAction(DownloadService.CMD_PAUSE); + commandFilter.addAction(DownloadService.CMD_STOP); + commandFilter.addAction(DownloadService.CMD_PREVIOUS); + commandFilter.addAction(DownloadService.CMD_NEXT); + commandFilter.addAction(DownloadService.CANCEL_DOWNLOADS); downloadService.registerReceiver(intentReceiver, commandFilter); new CacheCleaner(downloadService, downloadService).clean(); @@ -219,7 +217,7 @@ public class DownloadServiceLifecycleSupport { } } else { String action = intent.getAction(); - if(DownloadServiceImpl.CANCEL_DOWNLOADS.equals(action)) { + if(DownloadService.CANCEL_DOWNLOADS.equals(action)) { downloadService.clearBackground(); } } diff --git a/src/github/daneren2005/dsub/service/JukeboxController.java b/src/github/daneren2005/dsub/service/JukeboxController.java index faace3ae..eff2da0b 100644 --- a/src/github/daneren2005/dsub/service/JukeboxController.java +++ b/src/github/daneren2005/dsub/service/JukeboxController.java @@ -26,10 +26,8 @@ import github.daneren2005.dsub.service.parser.SubsonicRESTException; import github.daneren2005.dsub.util.Util; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.concurrent.Executors; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -48,7 +46,7 @@ public class JukeboxController extends RemoteController { private RemoteStatus jukeboxStatus; private float gain = 0.5f; - public JukeboxController(DownloadServiceImpl downloadService, Handler handler) { + public JukeboxController(DownloadService downloadService, Handler handler) { this.downloadService = downloadService; this.handler = handler; new Thread() { diff --git a/src/github/daneren2005/dsub/service/OfflineMusicService.java b/src/github/daneren2005/dsub/service/OfflineMusicService.java index a3cdd437..b054eaad 100644 --- a/src/github/daneren2005/dsub/service/OfflineMusicService.java +++ b/src/github/daneren2005/dsub/service/OfflineMusicService.java @@ -404,7 +404,7 @@ public class OfflineMusicService extends RESTMusicService { @Override public MusicDirectory getPlaylist(boolean refresh, String id, String name, Context context, ProgressListener progressListener) throws Exception { - DownloadService downloadService = DownloadServiceImpl.getInstance(); + DownloadService downloadService = DownloadService.getInstance(); if (downloadService == null) { return new MusicDirectory(); } diff --git a/src/github/daneren2005/dsub/service/RemoteController.java b/src/github/daneren2005/dsub/service/RemoteController.java index f2b8f20d..ec50be51 100644 --- a/src/github/daneren2005/dsub/service/RemoteController.java +++ b/src/github/daneren2005/dsub/service/RemoteController.java @@ -35,7 +35,7 @@ import github.daneren2005.dsub.domain.RemoteStatus; public abstract class RemoteController { private static final String TAG = RemoteController.class.getSimpleName(); - protected DownloadServiceImpl downloadService; + protected DownloadService downloadService; private VolumeToast volumeToast; public abstract void start(); diff --git a/src/github/daneren2005/dsub/util/MediaRouteManager.java b/src/github/daneren2005/dsub/util/MediaRouteManager.java index a1bd9b6b..4a124402 100644 --- a/src/github/daneren2005/dsub/util/MediaRouteManager.java +++ b/src/github/daneren2005/dsub/util/MediaRouteManager.java @@ -19,14 +19,13 @@ import android.support.v7.media.MediaControlIntent; import android.support.v7.media.MediaRouteProvider; import android.support.v7.media.MediaRouteSelector; import android.support.v7.media.MediaRouter; -import android.util.Log; import java.util.ArrayList; import java.util.List; import github.daneren2005.dsub.domain.RemoteControlState; import github.daneren2005.dsub.provider.JukeboxRouteProvider; -import github.daneren2005.dsub.service.DownloadServiceImpl; +import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.service.RemoteController; import github.daneren2005.dsub.util.compat.CastCompat; @@ -39,7 +38,7 @@ public class MediaRouteManager extends MediaRouter.Callback { private static final String TAG = MediaRouteManager.class.getSimpleName(); private static boolean castAvailable = false; - private DownloadServiceImpl downloadService; + private DownloadService downloadService; private MediaRouter router; private MediaRouteSelector selector; private List<MediaRouteProvider> providers = new ArrayList<MediaRouteProvider>(); @@ -53,7 +52,7 @@ public class MediaRouteManager extends MediaRouter.Callback { } } - public MediaRouteManager(DownloadServiceImpl downloadService) { + public MediaRouteManager(DownloadService downloadService) { this.downloadService = downloadService; router = MediaRouter.getInstance(downloadService); addProviders(); diff --git a/src/github/daneren2005/dsub/util/Util.java b/src/github/daneren2005/dsub/util/Util.java index e2f2d02f..6140444d 100644 --- a/src/github/daneren2005/dsub/util/Util.java +++ b/src/github/daneren2005/dsub/util/Util.java @@ -38,7 +38,6 @@ import android.media.AudioManager; import android.media.AudioManager.OnAudioFocusChangeListener; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Build; import android.os.Environment; @@ -65,7 +64,6 @@ import github.daneren2005.dsub.provider.DSubWidgetProvider; import github.daneren2005.dsub.receiver.MediaButtonIntentReceiver; import github.daneren2005.dsub.service.DownloadFile; import github.daneren2005.dsub.service.DownloadService; -import github.daneren2005.dsub.service.DownloadServiceImpl; import org.apache.http.HttpEntity; @@ -83,8 +81,6 @@ import java.lang.reflect.Method; import java.security.MessageDigest; import java.text.DecimalFormat; import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -971,7 +967,7 @@ public final class Util { ((TextView)dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } - public static void showPlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler, MusicDirectory.Entry song) { + public static void showPlayingNotification(final Context context, final DownloadService downloadService, Handler handler, MusicDirectory.Entry song) { // Set the icon, scrolling text and timestamp final Notification notification = new Notification(R.drawable.stat_notify_playing, song.getTitle(), System.currentTimeMillis()); notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; @@ -1059,7 +1055,7 @@ public final class Util { if(previous > 0) { Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS"); - prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + prevIntent.setComponent(new ComponentName(context, DownloadService.class)); prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); rv.setOnClickPendingIntent(previous, pendingIntent); @@ -1067,13 +1063,13 @@ public final class Util { if(pause > 0) { if(playing) { Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE"); - pauseIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + pauseIntent.setComponent(new ComponentName(context, DownloadService.class)); pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0); rv.setOnClickPendingIntent(pause, pendingIntent); } else { Intent prevIntent = new Intent("KEYCODE_MEDIA_START"); - prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + prevIntent.setComponent(new ComponentName(context, DownloadService.class)); prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)); pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); rv.setOnClickPendingIntent(pause, pendingIntent); @@ -1081,21 +1077,21 @@ public final class Util { } if(next > 0) { Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT"); - nextIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + nextIntent.setComponent(new ComponentName(context, DownloadService.class)); nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT)); pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0); rv.setOnClickPendingIntent(next, pendingIntent); } if(close > 0) { Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP"); - prevIntent.setComponent(new ComponentName(context, DownloadServiceImpl.class)); + prevIntent.setComponent(new ComponentName(context, DownloadService.class)); prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP)); pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); rv.setOnClickPendingIntent(close, pendingIntent); } } - public static void hidePlayingNotification(final Context context, final DownloadServiceImpl downloadService, Handler handler) { + public static void hidePlayingNotification(final Context context, final DownloadService downloadService, Handler handler) { // Remove notification and remove the service from the foreground handler.post(new Runnable() { @Override @@ -1109,8 +1105,8 @@ public final class Util { } public static void showDownloadingNotification(final Context context, DownloadFile file, int size, DownloadFile speedFile) { - Intent cancelIntent = new Intent(context, DownloadServiceImpl.class); - cancelIntent.setAction(DownloadServiceImpl.CANCEL_DOWNLOADS); + Intent cancelIntent = new Intent(context, DownloadService.class); + cancelIntent.setAction(DownloadService.CANCEL_DOWNLOADS); PendingIntent cancelPI = PendingIntent.getService(context, 0, cancelIntent, 0); String currentDownloading, currentSize, speed; @@ -1236,7 +1232,7 @@ public final class Util { hasFocus = true; audioManager.requestAudioFocus(new OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { - DownloadServiceImpl downloadService = (DownloadServiceImpl)context; + DownloadService downloadService = (DownloadService)context; if((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !downloadService.isRemoteEnabled()) { if(downloadService.getPlayerState() == PlayerState.STARTED) { SharedPreferences prefs = getPreferences(context); @@ -1271,7 +1267,7 @@ public final class Util { * <p>Broadcasts the given song info as the new song being played.</p> */ public static void broadcastNewTrackInfo(Context context, MusicDirectory.Entry song) { - DownloadService downloadService = (DownloadServiceImpl)context; + DownloadService downloadService = (DownloadService)context; Intent intent = new Intent(EVENT_META_CHANGED); Intent avrcpIntent = new Intent(AVRCP_METADATA_CHANGED); @@ -1331,7 +1327,7 @@ public final class Util { private static void addTrackInfo(Context context, MusicDirectory.Entry song, Intent intent) { if (song != null) { - DownloadService downloadService = (DownloadServiceImpl)context; + DownloadService downloadService = (DownloadService)context; File albumArtFile = FileUtil.getAlbumArtFile(context, song); intent.putExtra("track", song.getTitle()); diff --git a/src/github/daneren2005/dsub/util/compat/CastCompat.java b/src/github/daneren2005/dsub/util/compat/CastCompat.java index 31581816..ab64bca9 100644 --- a/src/github/daneren2005/dsub/util/compat/CastCompat.java +++ b/src/github/daneren2005/dsub/util/compat/CastCompat.java @@ -21,7 +21,7 @@ import com.google.android.gms.cast.CastDevice; import com.google.android.gms.cast.CastMediaControlIntent; import github.daneren2005.dsub.service.ChromeCastController; -import github.daneren2005.dsub.service.DownloadServiceImpl; +import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.service.RemoteController; /** @@ -42,7 +42,7 @@ public final class CastCompat { // Calling here forces class initialization. } - public static RemoteController getController(DownloadServiceImpl downloadService, MediaRouter.RouteInfo info) { + public static RemoteController getController(DownloadService downloadService, MediaRouter.RouteInfo info) { CastDevice device = CastDevice.getFromBundle(info.getExtras()); if(device != null) { return new ChromeCastController(downloadService, device); diff --git a/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java b/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java index 3bf06b82..56378a25 100644 --- a/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java +++ b/src/github/daneren2005/dsub/util/compat/RemoteControlClientICS.java @@ -1,7 +1,7 @@ package github.daneren2005.dsub.util.compat; import github.daneren2005.dsub.domain.MusicDirectory; -import github.daneren2005.dsub.service.DownloadServiceImpl; +import github.daneren2005.dsub.service.DownloadService; import github.daneren2005.dsub.util.ImageLoader; import android.annotation.TargetApi; import android.app.PendingIntent; @@ -11,10 +11,8 @@ import android.content.Intent; import android.media.AudioManager; import android.media.MediaMetadataRetriever; import android.media.RemoteControlClient; -import android.util.Log; import github.daneren2005.dsub.activity.SubsonicActivity; -import github.daneren2005.dsub.service.DownloadService; @TargetApi(14) public class RemoteControlClientICS extends RemoteControlClientHelper { @@ -22,10 +20,10 @@ public class RemoteControlClientICS extends RemoteControlClientHelper { protected RemoteControlClient mRemoteControl; protected ImageLoader imageLoader; - protected DownloadServiceImpl downloadService; + protected DownloadService downloadService; public void register(final Context context, final ComponentName mediaButtonReceiverComponent) { - downloadService = (DownloadServiceImpl) context; + downloadService = (DownloadService) context; AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); // build the PendingIntent for the remote control client diff --git a/src/github/daneren2005/dsub/view/SongView.java b/src/github/daneren2005/dsub/view/SongView.java index 3c1ee842..2176b768 100644 --- a/src/github/daneren2005/dsub/view/SongView.java +++ b/src/github/daneren2005/dsub/view/SongView.java @@ -20,8 +20,6 @@ package github.daneren2005.dsub.view; import android.content.Context; import android.content.res.TypedArray; -import android.media.MediaMetadataRetriever; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.*; @@ -29,12 +27,10 @@ import github.daneren2005.dsub.R; import github.daneren2005.dsub.domain.MusicDirectory; import github.daneren2005.dsub.domain.PodcastEpisode; import github.daneren2005.dsub.service.DownloadService; -import github.daneren2005.dsub.service.DownloadServiceImpl; import github.daneren2005.dsub.service.DownloadFile; import github.daneren2005.dsub.util.Util; import java.io.File; -import java.text.DateFormat; /** * Used to display songs in a {@code ListView}. @@ -150,7 +146,7 @@ public class SongView extends UpdateView implements Checkable { @Override protected void updateBackground() { if (downloadService == null) { - downloadService = DownloadServiceImpl.getInstance(); + downloadService = DownloadService.getInstance(); if(downloadService == null) { return; } diff --git a/src/github/daneren2005/dsub/view/VisualizerView.java b/src/github/daneren2005/dsub/view/VisualizerView.java index 53ebc2ec..aa921930 100644 --- a/src/github/daneren2005/dsub/view/VisualizerView.java +++ b/src/github/daneren2005/dsub/view/VisualizerView.java @@ -23,12 +23,10 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.media.audiofx.Visualizer; -import android.util.AttributeSet; import android.view.View; import github.daneren2005.dsub.audiofx.VisualizerController; import github.daneren2005.dsub.domain.PlayerState; import github.daneren2005.dsub.service.DownloadService; -import github.daneren2005.dsub.service.DownloadServiceImpl; /** * A simple class that draws waveform data received from a @@ -92,7 +90,7 @@ public class VisualizerView extends View { } private VisualizerController getVizualiser() { - DownloadService downloadService = DownloadServiceImpl.getInstance(); + DownloadService downloadService = DownloadService.getInstance(); VisualizerController visualizerController = downloadService == null ? null : downloadService.getVisualizerController(); return visualizerController; } @@ -109,7 +107,7 @@ public class VisualizerView extends View { if (!active) { return; } - DownloadService downloadService = DownloadServiceImpl.getInstance(); + DownloadService downloadService = DownloadService.getInstance(); if (downloadService != null && downloadService.getPlayerState() != PlayerState.STARTED) { return; } |