diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml
index f34f2342..93d6ce3e 100644
--- a/ultrasonic/src/main/AndroidManifest.xml
+++ b/ultrasonic/src/main/AndroidManifest.xml
@@ -118,7 +118,7 @@
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java
index d9665a79..c5a4c53a 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java
@@ -36,6 +36,7 @@ import android.widget.TextView;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.service.DownloadService;
import org.moire.ultrasonic.service.DownloadServiceImpl;
+import org.moire.ultrasonic.service.MediaPlayerService;
import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory;
import org.moire.ultrasonic.util.Constants;
@@ -476,7 +477,7 @@ public class MainActivity extends SubsonicTabActivity
private void exit()
{
- stopService(new Intent(this, DownloadServiceImpl.class));
+ DownloadServiceImpl.getInstance().onCommand(new Intent(this, MediaPlayerService.class));
Util.unregisterMediaButtonEventReceiver(this);
finish();
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java
index a3e7954e..43712579 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java
@@ -97,8 +97,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
applyTheme();
super.onCreate(bundle);
- // This should always succeed as it is called when Ultrasonic is in the foreground
- startService(new Intent(this, DownloadServiceImpl.class));
+ if (DownloadServiceImpl.getInstance() == null) new DownloadServiceImpl(getApplicationContext());
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (bundle != null)
@@ -764,30 +763,17 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
public DownloadService getDownloadService()
{
- // If service is not available, request it to start and wait for it.
- for (int i = 0; i < 5; i++)
+ DownloadService downloadService = DownloadServiceImpl.getInstance();
+
+ if (downloadService != null)
{
- DownloadService downloadService = DownloadServiceImpl.getInstance();
-
- if (downloadService != null)
- {
- return downloadService;
- }
-
- Log.w(TAG, "DownloadService not running. Attempting to start it.");
-
- try
- {
- startService(new Intent(this, DownloadServiceImpl.class));
- }
- catch (IllegalStateException exception)
- {
- Log.w(TAG, "getDownloadService couldn't start DownloadServiceImpl because the application was in the background.");
- return null;
- }
- Util.sleepQuietly(50L);
+ return downloadService;
}
+ Log.w(TAG, "DownloadService not running. Attempting to start it.");
+
+ new DownloadServiceImpl(getApplicationContext());
+
return DownloadServiceImpl.getInstance();
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java
index d4e986c6..8a834026 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java
@@ -19,6 +19,7 @@ import org.moire.ultrasonic.activity.MainActivity;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.service.DownloadService;
import org.moire.ultrasonic.service.DownloadServiceImpl;
+import org.moire.ultrasonic.service.MediaPlayerService;
import org.moire.ultrasonic.util.FileUtil;
public class UltraSonicAppWidgetProvider extends AppWidgetProvider
@@ -69,11 +70,11 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider
/**
* Handle a change notification coming over from {@link DownloadService}
*/
- public void notifyChange(Context context, DownloadService service, boolean playing, boolean setAlbum)
+ public void notifyChange(Context context, MusicDirectory.Entry currentSong, boolean playing, boolean setAlbum)
{
if (hasInstances(context))
{
- performUpdate(context, service, null, playing, setAlbum);
+ performUpdate(context, currentSong, null, playing, setAlbum);
}
}
@@ -96,15 +97,14 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider
/**
* Update all active widget instances by pushing changes
*/
- private void performUpdate(Context context, DownloadService service, int[] appWidgetIds, boolean playing, boolean setAlbum)
+ private void performUpdate(Context context, MusicDirectory.Entry currentSong, int[] appWidgetIds, boolean playing, boolean setAlbum)
{
final Resources res = context.getResources();
final RemoteViews views = new RemoteViews(context.getPackageName(), this.layoutId);
- MusicDirectory.Entry currentPlaying = service.getCurrentPlaying() == null ? null : service.getCurrentPlaying().getSong();
- String title = currentPlaying == null ? null : currentPlaying.getTitle();
- String artist = currentPlaying == null ? null : currentPlaying.getArtist();
- String album = currentPlaying == null ? null : currentPlaying.getAlbum();
+ String title = currentSong == null ? null : currentSong.getTitle();
+ String artist = currentSong == null ? null : currentSong.getArtist();
+ String album = currentSong == null ? null : currentSong.getAlbum();
CharSequence errorState = null;
// Show error message?
@@ -117,7 +117,7 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider
{
errorState = res.getText(R.string.widget_sdcard_missing);
}
- else if (currentPlaying == null)
+ else if (currentSong == null)
{
errorState = res.getText(R.string.widget_initial_text);
}
@@ -157,7 +157,7 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider
// Set the cover art
try
{
- Bitmap bitmap = currentPlaying == null ? null : FileUtil.getAlbumArtBitmap(context, currentPlaying, 240, true);
+ Bitmap bitmap = currentSong == null ? null : FileUtil.getAlbumArtBitmap(context, currentSong, 240, true);
if (bitmap == null)
{
@@ -176,7 +176,7 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider
}
// Link actions buttons to intents
- linkButtons(context, views, currentPlaying != null);
+ linkButtons(context, views, currentSong != null);
pushUpdate(context, appWidgetIds, views);
}
@@ -200,19 +200,19 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider
// Emulate media button clicks.
intent = new Intent("1");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_play, pendingIntent);
intent = new Intent("2"); // 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, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_next, pendingIntent);
intent = new Intent("3"); // 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, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java
index 171fe2ff..09593d9e 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java
@@ -25,9 +25,9 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.Log;
-import android.view.KeyEvent;
import org.moire.ultrasonic.service.DownloadServiceImpl;
+import org.moire.ultrasonic.service.MediaPlayerService;
import org.moire.ultrasonic.util.Util;
/**
@@ -57,42 +57,23 @@ public class MediaButtonIntentReceiver extends BroadcastReceiver
Parcelable event = (Parcelable) extras.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, MediaPlayerService.class);
serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
try
{
- context.startService(serviceIntent);
- }
- catch (IllegalStateException exception)
- {
- Log.i(TAG, "MediaButtonIntentReceiver couldn't start DownloadServiceImpl because the application was in the background.");
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
- {
- KeyEvent keyEvent = (KeyEvent) event;
- if (keyEvent.getAction() == KeyEvent.ACTION_DOWN && keyEvent.getRepeatCount() == 0)
- {
- int keyCode = keyEvent.getKeyCode();
- if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE ||
- keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
- keyCode == KeyEvent.KEYCODE_MEDIA_PLAY)
- {
- // TODO: The only time it is OK to start DownloadServiceImpl as a foreground service is when we now it will display its notification.
- // When DownloadServiceImpl is refactored to a proper foreground service, this can be removed.
- context.startForegroundService(serviceIntent);
- Log.i(TAG, "MediaButtonIntentReceiver started DownloadServiceImpl as foreground service");
- }
- }
- }
- }
+ if (DownloadServiceImpl.getInstance() == null) new DownloadServiceImpl(context);
+ DownloadServiceImpl.getInstance().onCommand(serviceIntent);
- try
- {
if (isOrderedBroadcast())
{
abortBroadcast();
}
}
+ catch (IllegalStateException exception)
+ {
+ Log.w(TAG, "MediaButtonIntentReceiver couldn't start DownloadServiceImpl because the application was in the background.");
+ }
catch (Exception x)
{
// Ignored.
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java
index 3c7d9ef4..22e25e15 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java
@@ -441,10 +441,7 @@ public class DownloadFile
new CacheCleaner(context, DownloadServiceImpl.getInstance()).cleanSpace();
- if (DownloadServiceImpl.getInstance() != null)
- {
- ((DownloadServiceImpl) DownloadServiceImpl.getInstance()).checkDownloads();
- }
+ MediaPlayerService.checkDownloads(context);
}
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java
index 309eb8e4..47b37406 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java
@@ -18,37 +18,12 @@
*/
package org.moire.ultrasonic.service;
-import android.annotation.SuppressLint;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.media.AudioManager;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaPlayer;
-import android.media.RemoteControlClient;
-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 androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationManagerCompat;
import android.util.Log;
-import android.view.View;
-import android.widget.RemoteViews;
-import android.widget.SeekBar;
import org.koin.java.standalone.KoinJavaComponent;
-import org.moire.ultrasonic.R;
-import org.moire.ultrasonic.activity.DownloadActivity;
-import org.moire.ultrasonic.activity.SubsonicTabActivity;
import org.moire.ultrasonic.audiofx.EqualizerController;
import org.moire.ultrasonic.audiofx.VisualizerController;
import org.moire.ultrasonic.domain.MusicDirectory;
@@ -58,41 +33,22 @@ import org.moire.ultrasonic.domain.RepeatMode;
import org.moire.ultrasonic.domain.UserInfo;
import org.moire.ultrasonic.featureflags.Feature;
import org.moire.ultrasonic.featureflags.FeatureStorage;
-import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x1;
-import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x2;
-import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x3;
-import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x4;
-import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
-import org.moire.ultrasonic.util.CancellableTask;
-import org.moire.ultrasonic.util.Constants;
-import org.moire.ultrasonic.util.FileUtil;
import org.moire.ultrasonic.util.LRUCache;
import org.moire.ultrasonic.util.ShufflePlayBuffer;
-import org.moire.ultrasonic.util.SimpleServiceBinder;
-import org.moire.ultrasonic.util.StreamProxy;
import org.moire.ultrasonic.util.Util;
-import java.io.File;
-import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import static org.moire.ultrasonic.domain.PlayerState.COMPLETED;
-import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
-import static org.moire.ultrasonic.domain.PlayerState.IDLE;
-import static org.moire.ultrasonic.domain.PlayerState.PAUSED;
-import static org.moire.ultrasonic.domain.PlayerState.PREPARED;
-import static org.moire.ultrasonic.domain.PlayerState.PREPARING;
-import static org.moire.ultrasonic.domain.PlayerState.STARTED;
-import static org.moire.ultrasonic.domain.PlayerState.STOPPED;
+import static org.moire.ultrasonic.service.MediaPlayerService.playerState;
/**
* @author Sindre Mehus, Joshua Bahnsen
* @version $Id$
*/
-public class DownloadServiceImpl extends Service implements DownloadService
+public class DownloadServiceImpl implements DownloadService
{
private static final String TAG = DownloadServiceImpl.class.getSimpleName();
@@ -103,264 +59,51 @@ public class DownloadServiceImpl extends Service implements DownloadService
public static final String CMD_PREVIOUS = "org.moire.ultrasonic.CMD_PREVIOUS";
public static final String CMD_NEXT = "org.moire.ultrasonic.CMD_NEXT";
- private static final String NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic";
- private static final String NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service";
- private static final int NOTIFICATION_ID = 3033;
-
- private final IBinder binder = new SimpleServiceBinder(this);
- private Looper mediaPlayerLooper;
- private MediaPlayer mediaPlayer;
- private MediaPlayer nextMediaPlayer;
- private boolean nextSetup;
- private final List downloadList = new ArrayList();
- private final List backgroundDownloadList = new ArrayList();
- private final Handler handler = new Handler();
- private Handler mediaPlayerHandler;
- private final DownloadServiceLifecycleSupport lifecycleSupport = new DownloadServiceLifecycleSupport(this);
- private final ShufflePlayBuffer shufflePlayBuffer = new ShufflePlayBuffer(this);
+ private DownloadServiceLifecycleSupport lifecycleSupport;
private final LRUCache downloadFileCache = new LRUCache(100);
- private final List cleanupCandidates = new ArrayList();
- private final Scrobbler scrobbler = new Scrobbler();
- private final JukeboxService jukeboxService = new JukeboxService(this);
- private DownloadFile currentPlaying;
- private DownloadFile nextPlaying;
- private DownloadFile currentDownloading;
- private CancellableTask bufferTask;
- private CancellableTask nextPlayingTask;
- private PlayerState playerState = IDLE;
- private PlayerState nextPlayerState = IDLE;
- private boolean shufflePlay;
- private long revision;
- private static DownloadService instance;
+ private static DownloadServiceImpl instance;
private String suggestedPlaylistName;
- private PowerManager.WakeLock wakeLock;
private boolean keepScreenOn;
- private int cachedPosition;
- private static boolean equalizerAvailable;
- private static boolean visualizerAvailable;
- private EqualizerController equalizerController;
- private VisualizerController visualizerController;
private boolean showVisualization;
private boolean jukeboxEnabled;
- private PositionCache positionCache;
- private StreamProxy proxy;
- public RemoteControlClient remoteControlClient;
- private AudioManager audioManager;
- private int secondaryProgress = -1;
private boolean autoPlayStart;
- private final static int lockScreenBitmapSize = 500;
- private boolean isInForeground = false;
- private NotificationCompat.Builder notificationBuilder;
+ private Context context;
- static
+ public DownloadServiceImpl(Context context)
{
- try
- {
- EqualizerController.checkAvailable();
- equalizerAvailable = true;
- }
- catch (Throwable t)
- {
- equalizerAvailable = false;
- }
- }
+ this.context = context;
- static
- {
- try
- {
- VisualizerController.checkAvailable();
- visualizerAvailable = true;
- }
- catch (Throwable t)
- {
- visualizerAvailable = false;
- }
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onCreate()
- {
- super.onCreate();
-
- new Thread(new Runnable()
- {
- @Override
- public void run()
- {
- Thread.currentThread().setName("DownloadServiceImpl");
-
- Looper.prepare();
-
- if (mediaPlayer != null)
- {
- mediaPlayer.release();
- }
-
- 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(String.format("MediaPlayer error: %d (%d)", 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();
-
- audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
- setUpRemoteControlClient();
-
- if (equalizerAvailable)
- {
- equalizerController = new EqualizerController(this, mediaPlayer);
- if (!equalizerController.isAvailable())
- {
- equalizerController = null;
- }
- else
- {
- equalizerController.loadSettings();
- }
- }
- if (visualizerAvailable)
- {
- visualizerController = new VisualizerController(mediaPlayer);
- if (!visualizerController.isAvailable())
- {
- visualizerController = null;
- }
- }
-
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
- wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
- wakeLock.setReferenceCounted(false);
+ // TODO: refactor
+ MediaPlayerService.shufflePlayBuffer = new ShufflePlayBuffer(context);
+ MediaPlayerService.jukeboxService = new JukeboxService(context, this);
instance = this;
+ lifecycleSupport = new DownloadServiceLifecycleSupport(context,this);
lifecycleSupport.onCreate();
-
- // Create Notification Channel
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- //The suggested importance of a startForeground service notification is IMPORTANCE_LOW
- NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
- channel.setLightColor(android.R.color.holo_blue_dark);
- channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
- NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- manager.createNotificationChannel(channel);
- }
-
- // We should use a single notification builder, otherwise the notification may not be updated
- notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
+ MediaPlayerService.lifecycleSupport = lifecycleSupport;
Log.i(TAG, "DownloadServiceImpl created");
}
- @Override
- public int onStartCommand(Intent intent, int flags, int startId)
+ public void onCommand(Intent intent)
{
- super.onStartCommand(intent, flags, startId);
-
lifecycleSupport.onStart(intent);
- Log.i(TAG, "DownloadServiceImpl started with intent");
- return START_NOT_STICKY;
+ Log.i(TAG, "DownloadServiceImpl received intent");
}
- @Override
- public void onDestroy()
- {
- super.onDestroy();
-
- try
- {
- instance = null;
- lifecycleSupport.onDestroy();
- mediaPlayer.release();
-
- if (nextMediaPlayer != null)
- {
- nextMediaPlayer.release();
- }
-
- mediaPlayerLooper.quit();
- shufflePlayBuffer.shutdown();
-
- if (equalizerController != null)
- {
- equalizerController.release();
- }
-
- if (visualizerController != null)
- {
- visualizerController.release();
- }
-
- if (bufferTask != null)
- {
- bufferTask.cancel();
- }
-
- if (nextPlayingTask != null)
- {
- nextPlayingTask.cancel();
- }
-
- 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);
-
- audioManager.unregisterRemoteControlClient(remoteControlClient);
- clearRemoteControl();
-
- wakeLock.release();
- }
- catch (Throwable ignored)
- {
- }
-
- Log.i(TAG, "DownloadServiceImpl stopped");
- }
-
- public static DownloadService getInstance()
+ public static DownloadServiceImpl getInstance()
{
return instance;
}
- @Override
- public IBinder onBind(Intent intent)
- {
- return binder;
- }
-
@Override
public synchronized void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, boolean newPlaylist)
{
- shufflePlay = false;
+ MediaPlayerService.shufflePlay = false;
int offset = 1;
if (songs.isEmpty())
@@ -370,7 +113,7 @@ public class DownloadServiceImpl extends Service implements DownloadService
if (newPlaylist)
{
- downloadList.clear();
+ MediaPlayerService.downloadList.clear();
}
if (playNext)
@@ -382,12 +125,12 @@ public class DownloadServiceImpl extends Service implements DownloadService
for (MusicDirectory.Entry song : songs)
{
- DownloadFile downloadFile = new DownloadFile(this, song, save);
- downloadList.add(getCurrentPlayingIndex() + offset, downloadFile);
+ DownloadFile downloadFile = new DownloadFile(context, song, save);
+ MediaPlayerService.downloadList.add(getCurrentPlayingIndex() + offset, downloadFile);
offset++;
}
- revision++;
+ MediaPlayerService.revision++;
}
else
{
@@ -396,19 +139,20 @@ public class DownloadServiceImpl extends Service implements DownloadService
for (MusicDirectory.Entry song : songs)
{
- DownloadFile downloadFile = new DownloadFile(this, song, save);
- downloadList.add(downloadFile);
+ DownloadFile downloadFile = new DownloadFile(context, song, save);
+ MediaPlayerService.downloadList.add(downloadFile);
}
if (!autoplay && (size - 1) == index)
{
- setNextPlaying();
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
}
- revision++;
+ MediaPlayerService.revision++;
}
- updateJukeboxPlaylist();
+ MediaPlayerService.updateJukeboxPlaylist();
if (shuffle) shuffle();
@@ -418,13 +162,13 @@ public class DownloadServiceImpl extends Service implements DownloadService
}
else
{
- if (currentPlaying == null)
+ if (MediaPlayerService.currentPlaying == null)
{
- currentPlaying = downloadList.get(0);
- currentPlaying.setPlaying(true);
+ MediaPlayerService.currentPlaying = MediaPlayerService.downloadList.get(0);
+ MediaPlayerService.currentPlaying.setPlaying(true);
}
- checkDownloads();
+ MediaPlayerService.checkDownloads(context);
}
lifecycleSupport.serializeDownloadQueue();
@@ -435,24 +179,16 @@ public class DownloadServiceImpl extends Service implements DownloadService
{
for (MusicDirectory.Entry song : songs)
{
- DownloadFile downloadFile = new DownloadFile(this, song, save);
- backgroundDownloadList.add(downloadFile);
+ DownloadFile downloadFile = new DownloadFile(context, song, save);
+ MediaPlayerService.backgroundDownloadList.add(downloadFile);
}
- revision++;
+ MediaPlayerService.revision++;
- checkDownloads();
+ MediaPlayerService.checkDownloads(context);
lifecycleSupport.serializeDownloadQueue();
}
- private void updateJukeboxPlaylist()
- {
- if (jukeboxEnabled)
- {
- jukeboxService.updatePlaylist();
- }
- }
-
@Override
public void restore(List songs, int currentPlayingIndex, int currentPlayingPosition, boolean autoPlay, boolean newPlaylist)
{
@@ -460,24 +196,19 @@ public class DownloadServiceImpl extends Service implements DownloadService
if (currentPlayingIndex != -1)
{
- while (mediaPlayer == null)
- {
- Util.sleepQuietly(50L);
- }
+ MediaPlayerService.getInstance(context).play(currentPlayingIndex, autoPlayStart);
- play(currentPlayingIndex, autoPlayStart);
-
- if (currentPlaying != null)
+ if (MediaPlayerService.currentPlaying != null)
{
if (autoPlay && jukeboxEnabled)
{
- jukeboxService.skip(getCurrentPlayingIndex(), currentPlayingPosition / 1000);
+ MediaPlayerService.jukeboxService.skip(getCurrentPlayingIndex(), currentPlayingPosition / 1000);
}
else
{
- if (currentPlaying.isCompleteFileAvailable())
+ if (MediaPlayerService.currentPlaying.isCompleteFileAvailable())
{
- doPlay(currentPlaying, currentPlayingPosition, autoPlay);
+ MediaPlayerService.getInstance(context).doPlay(MediaPlayerService.currentPlaying, currentPlayingPosition, autoPlay);
}
}
}
@@ -486,61 +217,82 @@ public class DownloadServiceImpl extends Service implements DownloadService
}
}
+ public synchronized void setCurrentPlaying(DownloadFile currentPlaying)
+ {
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setCurrentPlaying(currentPlaying);
+ }
+
+ public synchronized void setCurrentPlaying(int index)
+ {
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setCurrentPlaying(index);
+ }
+
+ public synchronized void setPlayerState(PlayerState state)
+ {
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setPlayerState(state);
+ }
+
@Override
public void stopJukeboxService()
{
- jukeboxService.stopJukeboxService();
+ MediaPlayerService.jukeboxService.stopJukeboxService();
}
@Override
public void startJukeboxService()
{
- jukeboxService.startJukeboxService();
+ MediaPlayerService.jukeboxService.startJukeboxService();
}
@Override
public synchronized void setShufflePlayEnabled(boolean enabled)
{
- shufflePlay = enabled;
- if (shufflePlay)
+ MediaPlayerService.shufflePlay = enabled;
+ if (MediaPlayerService.shufflePlay)
{
clear();
- checkDownloads();
+ MediaPlayerService.checkDownloads(context);
}
}
@Override
public boolean isShufflePlayEnabled()
{
- return shufflePlay;
+ return MediaPlayerService.shufflePlay;
}
@Override
public synchronized void shuffle()
{
- Collections.shuffle(downloadList);
- if (currentPlaying != null)
+ Collections.shuffle(MediaPlayerService.downloadList);
+ if (MediaPlayerService.currentPlaying != null)
{
- downloadList.remove(getCurrentPlayingIndex());
- downloadList.add(0, currentPlaying);
+ MediaPlayerService.downloadList.remove(getCurrentPlayingIndex());
+ MediaPlayerService.downloadList.add(0, MediaPlayerService.currentPlaying);
}
- revision++;
+ MediaPlayerService.revision++;
lifecycleSupport.serializeDownloadQueue();
- updateJukeboxPlaylist();
- setNextPlaying();
+ MediaPlayerService.updateJukeboxPlaylist();
+
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
}
@Override
public RepeatMode getRepeatMode()
{
- return Util.getRepeatMode(this);
+ return Util.getRepeatMode(context);
}
@Override
public void setRepeatMode(RepeatMode repeatMode)
{
- Util.setRepeatMode(this, repeatMode);
- setNextPlaying();
+ Util.setRepeatMode(context, repeatMode);
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
}
@Override
@@ -570,14 +322,14 @@ public class DownloadServiceImpl extends Service implements DownloadService
@Override
public synchronized DownloadFile forSong(MusicDirectory.Entry song)
{
- for (DownloadFile downloadFile : downloadList)
+ for (DownloadFile downloadFile : MediaPlayerService.downloadList)
{
if (downloadFile.getSong().equals(song) && ((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone()))
{
return downloadFile;
}
}
- for (DownloadFile downloadFile : backgroundDownloadList)
+ for (DownloadFile downloadFile : MediaPlayerService.backgroundDownloadList)
{
if (downloadFile.getSong().equals(song))
{
@@ -588,7 +340,7 @@ public class DownloadServiceImpl extends Service implements DownloadService
DownloadFile downloadFile = downloadFileCache.get(song);
if (downloadFile == null)
{
- downloadFile = new DownloadFile(this, song, false);
+ downloadFile = new DownloadFile(context, song, false);
downloadFileCache.put(song, downloadFile);
}
return downloadFile;
@@ -597,25 +349,30 @@ public class DownloadServiceImpl extends Service implements DownloadService
@Override
public synchronized void clear()
{
- clear(true);
+ MediaPlayerService.clear(true);
+ }
+
+ public synchronized void clear(boolean serialize)
+ {
+ MediaPlayerService.clear(serialize);
}
@Override
public synchronized void clearBackground()
{
- if (currentDownloading != null && backgroundDownloadList.contains(currentDownloading))
+ if (MediaPlayerService.currentDownloading != null && MediaPlayerService.backgroundDownloadList.contains(MediaPlayerService.currentDownloading))
{
- currentDownloading.cancelDownload();
- currentDownloading = null;
+ MediaPlayerService.currentDownloading.cancelDownload();
+ MediaPlayerService.currentDownloading = null;
}
- backgroundDownloadList.clear();
+ MediaPlayerService.backgroundDownloadList.clear();
}
@Override
public synchronized void clearIncomplete()
{
reset();
- Iterator iterator = downloadList.iterator();
+ Iterator iterator = MediaPlayerService.downloadList.iterator();
while (iterator.hasNext())
{
@@ -627,62 +384,43 @@ public class DownloadServiceImpl extends Service implements DownloadService
}
lifecycleSupport.serializeDownloadQueue();
- updateJukeboxPlaylist();
+ MediaPlayerService.updateJukeboxPlaylist();
}
@Override
public synchronized int size()
{
- return downloadList.size();
- }
-
- public synchronized void clear(boolean serialize)
- {
- reset();
- downloadList.clear();
- revision++;
- if (currentDownloading != null)
- {
- currentDownloading.cancelDownload();
- currentDownloading = null;
- }
- setCurrentPlaying(null);
-
- if (serialize)
- {
- lifecycleSupport.serializeDownloadQueue();
- }
- updateJukeboxPlaylist();
- setNextPlaying();
+ return MediaPlayerService.downloadList.size();
}
@Override
public synchronized void remove(int which)
{
- downloadList.remove(which);
+ MediaPlayerService.downloadList.remove(which);
}
@Override
public synchronized void remove(DownloadFile downloadFile)
{
- if (downloadFile == currentDownloading)
+ if (downloadFile == MediaPlayerService.currentDownloading)
{
- currentDownloading.cancelDownload();
- currentDownloading = null;
+ MediaPlayerService.currentDownloading.cancelDownload();
+ MediaPlayerService.currentDownloading = null;
}
- if (downloadFile == currentPlaying)
+ if (downloadFile == MediaPlayerService.currentPlaying)
{
reset();
setCurrentPlaying(null);
}
- downloadList.remove(downloadFile);
- backgroundDownloadList.remove(downloadFile);
- revision++;
+ MediaPlayerService.downloadList.remove(downloadFile);
+ MediaPlayerService.backgroundDownloadList.remove(downloadFile);
+ MediaPlayerService.revision++;
lifecycleSupport.serializeDownloadQueue();
- updateJukeboxPlaylist();
- if (downloadFile == nextPlaying)
+ MediaPlayerService.updateJukeboxPlaylist();
+ if (downloadFile == MediaPlayerService.nextPlaying)
{
- setNextPlaying();
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
}
}
@@ -704,133 +442,28 @@ public class DownloadServiceImpl extends Service implements DownloadService
}
}
- synchronized void setCurrentPlaying(int currentPlayingIndex)
- {
- try
- {
- setCurrentPlaying(downloadList.get(currentPlayingIndex));
- }
- catch (IndexOutOfBoundsException x)
- {
- // Ignored
- }
- }
-
- synchronized void setCurrentPlaying(DownloadFile currentPlaying)
- {
- this.currentPlaying = currentPlaying;
-
- if (currentPlaying != null)
- {
- Util.broadcastNewTrackInfo(this, currentPlaying.getSong());
- Util.broadcastA2dpMetaDataChange(this, instance);
- }
- else
- {
- Util.broadcastNewTrackInfo(this, null);
- Util.broadcastA2dpMetaDataChange(this, null);
- }
-
- updateRemoteControl();
-
- // Update widget
- UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(this, this, playerState == PlayerState.STARTED, false);
- UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(this, this, playerState == PlayerState.STARTED, true);
- UltraSonicAppWidgetProvider4x3.getInstance().notifyChange(this, this, playerState == PlayerState.STARTED, false);
- UltraSonicAppWidgetProvider4x4.getInstance().notifyChange(this, this, playerState == PlayerState.STARTED, false);
- SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
-
- if (currentPlaying != null)
- {
- if (tabInstance != null) {
- updateNotification();
- tabInstance.showNowPlaying();
- }
- }
- else
- {
- if (tabInstance != null)
- {
- stopForeground(true);
- clearRemoteControl();
- isInForeground = false;
- tabInstance.hideNowPlaying();
- }
- }
- }
-
- synchronized void setNextPlaying()
- {
- boolean gaplessPlayback = Util.getGaplessPlaybackPreference(DownloadServiceImpl.this);
-
- if (!gaplessPlayback)
- {
- nextPlaying = null;
- nextPlayerState = IDLE;
- return;
- }
-
- int index = getCurrentPlayingIndex();
-
- if (index != -1)
- {
- switch (getRepeatMode())
- {
- case OFF:
- index += 1;
- break;
- case ALL:
- index = (index + 1) % size();
- break;
- case SINGLE:
- break;
- default:
- break;
- }
- }
-
- nextSetup = false;
- if (nextPlayingTask != null)
- {
- nextPlayingTask.cancel();
- nextPlayingTask = null;
- }
-
- if (index < size() && index != -1)
- {
- nextPlaying = downloadList.get(index);
- nextPlayingTask = new CheckCompletionTask(nextPlaying);
- nextPlayingTask.start();
- }
- else
- {
- nextPlaying = null;
- setNextPlayerState(IDLE);
- }
- }
-
@Override
public synchronized int getCurrentPlayingIndex()
{
- return downloadList.indexOf(currentPlaying);
+ return MediaPlayerService.downloadList.indexOf(MediaPlayerService.currentPlaying);
}
@Override
public DownloadFile getCurrentPlaying()
{
- return currentPlaying;
+ return MediaPlayerService.currentPlaying;
}
@Override
public DownloadFile getCurrentDownloading()
{
- return currentDownloading;
+ return MediaPlayerService.currentDownloading;
}
@Override
public List getSongs()
{
- return downloadList;
+ return MediaPlayerService.downloadList;
}
@Override
@@ -838,7 +471,7 @@ public class DownloadServiceImpl extends Service implements DownloadService
{
long totalDuration = 0;
- for (DownloadFile downloadFile : downloadList)
+ for (DownloadFile downloadFile : MediaPlayerService.downloadList)
{
Entry entry = downloadFile.getSong();
@@ -863,98 +496,26 @@ public class DownloadServiceImpl extends Service implements DownloadService
public synchronized List getDownloads()
{
List temp = new ArrayList();
- temp.addAll(downloadList);
- temp.addAll(backgroundDownloadList);
+ temp.addAll(MediaPlayerService.downloadList);
+ temp.addAll(MediaPlayerService.backgroundDownloadList);
return temp;
}
@Override
public List 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);
- }
+ return MediaPlayerService.backgroundDownloadList;
}
@Override
public synchronized void play(int index)
{
- play(index, true);
+ MediaPlayerService.getInstance(context).play(index, true);
}
- private synchronized void play(int index, boolean start)
+ public synchronized void play()
{
- updateRemoteControl();
-
- if (index < 0 || index >= size())
- {
- resetPlayback();
- }
- else
- {
- if (nextPlayingTask != null)
- {
- nextPlayingTask.cancel();
- nextPlayingTask = null;
- }
-
- setCurrentPlaying(index);
-
- if (start)
- {
- if (jukeboxEnabled)
- {
- jukeboxService.skip(getCurrentPlayingIndex(), 0);
- setPlayerState(STARTED);
- }
- else
- {
- bufferAndPlay();
- }
- }
-
- checkDownloads();
- setNextPlaying();
- }
- }
-
- private synchronized void resetPlayback()
- {
- reset();
- setCurrentPlaying(null);
- lifecycleSupport.serializeDownloadQueue();
- }
-
- private synchronized void playNext()
- {
- MediaPlayer tmp = mediaPlayer;
- mediaPlayer = nextMediaPlayer;
- nextMediaPlayer = tmp;
- setCurrentPlaying(nextPlaying);
- 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;
- }
+ MediaPlayerService.getInstance(context).play();
}
/**
@@ -963,42 +524,14 @@ public class DownloadServiceImpl extends Service implements DownloadService
@Override
public synchronized void togglePlayPause()
{
- if (playerState == PAUSED || playerState == COMPLETED || playerState == STOPPED)
- {
- start();
- }
- else if (playerState == IDLE)
- {
- autoPlayStart = true;
- play();
- }
- else if (playerState == STARTED)
- {
- pause();
- }
+ if (playerState == PlayerState.IDLE) autoPlayStart = true;
+ MediaPlayerService.getInstance(context).togglePlayPause();
}
@Override
public synchronized void seekTo(int position)
{
- try
- {
- if (jukeboxEnabled)
- {
- jukeboxService.skip(getCurrentPlayingIndex(), position / 1000);
- }
- else
- {
- mediaPlayer.seekTo(position);
- cachedPosition = position;
-
- updateRemoteControl();
- }
- }
- catch (Exception x)
- {
- handleError(x);
- }
+ MediaPlayerService.getInstance(context).seekTo(position);
}
@Override
@@ -1031,193 +564,53 @@ public class DownloadServiceImpl extends Service implements DownloadService
}
}
- private void onSongCompleted()
- {
- int index = getCurrentPlayingIndex();
-
- if (currentPlaying != null)
- {
- final Entry song = currentPlaying.getSong();
-
- if (song != null && song.getBookmarkPosition() > 0 && Util.getShouldClearBookmark(this))
- {
- MusicService musicService = MusicServiceFactory.getMusicService(DownloadServiceImpl.this);
- try
- {
- musicService.deleteBookmark(song.getId(), DownloadServiceImpl.this, null);
- }
- catch (Exception ignored)
- {
-
- }
- }
- }
-
- if (index != -1)
- {
- switch (getRepeatMode())
- {
- case OFF:
- if (index + 1 < 0 || index + 1 >= size())
- {
- if (Util.getShouldClearPlaylist(this))
- {
- clear();
- }
-
- resetPlayback();
- break;
- }
-
- play(index + 1);
- break;
- case ALL:
- play((index + 1) % size());
- break;
- case SINGLE:
- play(index);
- break;
- default:
- break;
- }
- }
- }
-
@Override
public synchronized void pause()
{
- try
- {
- if (playerState == STARTED)
- {
- if (jukeboxEnabled)
- {
- jukeboxService.stop();
- }
- else
- {
- mediaPlayer.pause();
- }
- setPlayerState(PAUSED);
- }
- }
- catch (Exception x)
- {
- handleError(x);
- }
+ MediaPlayerService.getInstance(context).pause();
}
@Override
public synchronized void stop()
{
- try
- {
- if (playerState == STARTED)
- {
- if (jukeboxEnabled)
- {
- jukeboxService.stop();
- }
- else
- {
- mediaPlayer.pause();
- }
- setPlayerState(STOPPED);
- }
- else
- {
- setPlayerState(STOPPED);
- }
- }
- catch (Exception x)
- {
- handleError(x);
- }
+ MediaPlayerService.getInstance(context).stop();
}
@Override
public synchronized void start()
{
- try
- {
- if (jukeboxEnabled)
- {
- jukeboxService.start();
- }
- else
- {
- mediaPlayer.start();
- }
- setPlayerState(STARTED);
- }
- catch (Exception x)
- {
- handleError(x);
- }
+ MediaPlayerService.getInstance(context).start();
}
@Override
public synchronized void reset()
{
- if (bufferTask != null)
- {
- bufferTask.cancel();
- }
- try
- {
- setPlayerState(IDLE);
- mediaPlayer.setOnErrorListener(null);
- mediaPlayer.setOnCompletionListener(null);
- mediaPlayer.reset();
- }
- catch (Exception x)
- {
- handleError(x);
- }
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.reset();
}
@Override
public synchronized int getPlayerPosition()
{
- try
- {
- if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING)
- {
- return 0;
- }
-
- return jukeboxEnabled ? jukeboxService.getPositionSeconds() * 1000 : cachedPosition;
- }
- catch (Exception x)
- {
- handleError(x);
- return 0;
- }
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService == null) return 0;
+ return mediaPlayerService.getPlayerPosition();
}
@Override
public synchronized int getPlayerDuration()
{
- if (currentPlaying != null)
+ if (MediaPlayerService.currentPlaying != null)
{
- Integer duration = currentPlaying.getSong().getDuration();
+ Integer duration = MediaPlayerService.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;
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService == null) return 0;
+ return mediaPlayerService.getPlayerDuration();
}
@Override
@@ -1226,106 +619,6 @@ public class DownloadServiceImpl extends Service implements DownloadService
return playerState;
}
- synchronized void setPlayerState(PlayerState playerState)
- {
- Log.i(TAG, String.format("%s -> %s (%s)", this.playerState.name(), playerState.name(), currentPlaying));
-
- this.playerState = playerState;
-
- if (this.playerState == PAUSED)
- {
- lifecycleSupport.serializeDownloadQueue();
- }
-
- if (this.playerState == PlayerState.STARTED)
- {
- Util.requestAudioFocus(this);
- }
-
- boolean showWhenPaused = (this.playerState != PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(this));
- boolean show = this.playerState == PlayerState.STARTED || showWhenPaused;
-
- Util.broadcastPlaybackStatusChange(this, this.playerState);
- Util.broadcastA2dpPlayStatusChange(this, this.playerState, instance);
-
- if (this.playerState == PlayerState.STARTED || this.playerState == PlayerState.PAUSED)
- {
- // Set remote control
- updateRemoteControl();
- }
-
- // Update widget
- UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(this, this, this.playerState == PlayerState.STARTED, false);
- UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(this, this, this.playerState == PlayerState.STARTED, true);
- UltraSonicAppWidgetProvider4x3.getInstance().notifyChange(this, this, this.playerState == PlayerState.STARTED, false);
- UltraSonicAppWidgetProvider4x4.getInstance().notifyChange(this, this, this.playerState == PlayerState.STARTED, false);
- SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
-
- if (show)
- {
- if (tabInstance != null)
- {
- // Only update notification is player state is one that will change the icon
- if (this.playerState == PlayerState.STARTED || this.playerState == PlayerState.PAUSED)
- {
- updateNotification();
- tabInstance.showNowPlaying();
- }
- }
- }
- else
- {
- if (tabInstance != null)
- {
- stopForeground(true);
- clearRemoteControl();
- isInForeground = false;
- tabInstance.hideNowPlaying();
- }
- }
-
- if (this.playerState == STARTED)
- {
- scrobbler.scrobble(this, currentPlaying, false);
- }
- else if (this.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 void setPlayerStateCompleted()
- {
- Log.i(TAG, String.format("%s -> %s (%s)", 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, String.format("Next: %s -> %s (%s)", this.nextPlayerState.name(), playerState.name(), nextPlaying));
- this.nextPlayerState = playerState;
- }
-
@Override
public void setSuggestedPlaylistName(String name)
{
@@ -1339,47 +632,28 @@ public class DownloadServiceImpl extends Service implements DownloadService
}
@Override
- public boolean getEqualizerAvailable()
- {
- return equalizerAvailable;
- }
+ public boolean getEqualizerAvailable() { return MediaPlayerService.equalizerAvailable; }
@Override
public boolean getVisualizerAvailable()
{
- return visualizerAvailable;
+ return MediaPlayerService.visualizerAvailable;
}
@Override
public EqualizerController getEqualizerController()
{
- if (equalizerAvailable && equalizerController == null)
- {
- equalizerController = new EqualizerController(this, mediaPlayer);
- if (!equalizerController.isAvailable())
- {
- equalizerController = null;
- }
- else
- {
- equalizerController.loadSettings();
- }
- }
- return equalizerController;
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService == null) return null;
+ return mediaPlayerService.getEqualizerController();
}
@Override
public VisualizerController getVisualizerController()
{
- if (visualizerAvailable && visualizerController == null)
- {
- visualizerController = new VisualizerController(mediaPlayer);
- if (!visualizerController.isAvailable())
- {
- visualizerController = null;
- }
- }
- return visualizerController;
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService == null) return null;
+ return mediaPlayerService.getVisualizerController();
}
@Override
@@ -1391,12 +665,12 @@ public class DownloadServiceImpl extends Service implements DownloadService
@Override
public boolean isJukeboxAvailable()
{
- MusicService musicService = MusicServiceFactory.getMusicService(DownloadServiceImpl.this);
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
try
{
- String username = Util.getUserName(DownloadServiceImpl.this, Util.getActiveServer(DownloadServiceImpl.this));
- UserInfo user = musicService.getUser(username, DownloadServiceImpl.this, null);
+ String username = Util.getUserName(context, Util.getActiveServer(context));
+ UserInfo user = musicService.getUser(username, context, null);
return user.getJukeboxRole();
}
catch (Exception e)
@@ -1410,12 +684,12 @@ public class DownloadServiceImpl extends Service implements DownloadService
@Override
public boolean isSharingAvailable()
{
- MusicService musicService = MusicServiceFactory.getMusicService(DownloadServiceImpl.this);
+ MusicService musicService = MusicServiceFactory.getMusicService(context);
try
{
- String username = Util.getUserName(DownloadServiceImpl.this, Util.getActiveServer(DownloadServiceImpl.this));
- UserInfo user = musicService.getUser(username, DownloadServiceImpl.this, null);
+ String username = Util.getUserName(context, Util.getActiveServer(context));
+ UserInfo user = musicService.getUser(username, context, null);
return user.getShareRole();
}
catch (Exception e)
@@ -1430,445 +704,43 @@ public class DownloadServiceImpl extends Service implements DownloadService
public void setJukeboxEnabled(boolean jukeboxEnabled)
{
this.jukeboxEnabled = jukeboxEnabled;
- jukeboxService.setEnabled(jukeboxEnabled);
+ MediaPlayerService.jukeboxService.setEnabled(jukeboxEnabled);
if (jukeboxEnabled)
{
- jukeboxService.startJukeboxService();
+ MediaPlayerService.jukeboxService.startJukeboxService();
reset();
// Cancel current download, if necessary.
- if (currentDownloading != null)
+ if (MediaPlayerService.currentDownloading != null)
{
- currentDownloading.cancelDownload();
+ MediaPlayerService.currentDownloading.cancelDownload();
}
}
else
{
- jukeboxService.stopJukeboxService();
+ MediaPlayerService.jukeboxService.stopJukeboxService();
}
}
@Override
public void adjustJukeboxVolume(boolean up)
{
- jukeboxService.adjustVolume(up);
- }
-
- @SuppressLint("NewApi")
- public void setUpRemoteControlClient()
- {
- if (!Util.isLockScreenEnabled(this)) return;
-
- ComponentName componentName = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
-
- if (remoteControlClient == null)
- {
- final Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- mediaButtonIntent.setComponent(componentName);
- PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
- remoteControlClient = new RemoteControlClient(broadcast);
- audioManager.registerRemoteControlClient(remoteControlClient);
-
- // Flags for the media transport control that this client supports.
- int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
- RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
- RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
- RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
- RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
- RemoteControlClient.FLAG_KEY_MEDIA_STOP;
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
- {
- flags |= RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
-
- remoteControlClient.setOnGetPlaybackPositionListener(new RemoteControlClient.OnGetPlaybackPositionListener()
- {
- @Override
- public long onGetPlaybackPosition()
- {
- return mediaPlayer.getCurrentPosition();
- }
- });
-
- remoteControlClient.setPlaybackPositionUpdateListener(new RemoteControlClient.OnPlaybackPositionUpdateListener()
- {
- @Override
- public void onPlaybackPositionUpdate(long newPositionMs)
- {
- seekTo((int) newPositionMs);
- }
- });
- }
-
- remoteControlClient.setTransportControlFlags(flags);
- }
- }
-
- private void clearRemoteControl()
- {
- if (remoteControlClient != null)
- {
- remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
- audioManager.unregisterRemoteControlClient(remoteControlClient);
- remoteControlClient = null;
- }
- }
-
- private void updateRemoteControl()
- {
- if (!Util.isLockScreenEnabled(this))
- {
- clearRemoteControl();
- return;
- }
-
- if (remoteControlClient != null)
- {
- audioManager.unregisterRemoteControlClient(remoteControlClient);
- audioManager.registerRemoteControlClient(remoteControlClient);
- }
- else
- {
- setUpRemoteControlClient();
- }
-
- Log.i(TAG, String.format("In updateRemoteControl, playerState: %s [%d]", playerState, getPlayerPosition()));
-
- switch (playerState)
- {
- case STARTED:
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
- {
- remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
- }
- else
- {
- remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING, getPlayerPosition(), 1.0f);
- }
- break;
- default:
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
- {
- remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
- }
- else
- {
- remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED, getPlayerPosition(), 1.0f);
- }
- break;
- }
-
- if (currentPlaying != null)
- {
- MusicDirectory.Entry currentSong = currentPlaying.getSong();
-
- Bitmap lockScreenBitmap = FileUtil.getAlbumArtBitmap(this, currentSong, Util.getMinDisplayMetric(this), true);
-
- String artist = currentSong.getArtist();
- String album = currentSong.getAlbum();
- String title = currentSong.getTitle();
- Integer currentSongDuration = currentSong.getDuration();
- Long duration = 0L;
-
- if (currentSongDuration != null) duration = (long) currentSongDuration * 1000;
-
- remoteControlClient.editMetadata(true).putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, artist).putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, artist).putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, album).putString(MediaMetadataRetriever.METADATA_KEY_TITLE, title).putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration)
- .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, lockScreenBitmap).apply();
- }
- }
-
- private synchronized void bufferAndPlay()
- {
- if (playerState != PREPARED)
- {
- reset();
-
- bufferTask = new BufferTask(currentPlaying, 0);
- bufferTask.start();
- }
- else
- {
- doPlay(currentPlaying, 0, true);
- }
- }
-
- private synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start)
- {
- try
- {
- downloadFile.setPlaying(false);
- //downloadFile.setPlaying(true);
- final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
- boolean partial = file.equals(downloadFile.getPartialFile());
- downloadFile.updateModificationDate();
-
- mediaPlayer.setOnCompletionListener(null);
- secondaryProgress = -1; // Ensure seeking in non StreamProxy playback works
- mediaPlayer.reset();
- setPlayerState(IDLE);
- mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- String dataSource = file.getPath();
-
- if (partial)
- {
- 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, String.format("Data Source: %s", dataSource));
- }
- else if (proxy != null)
- {
- proxy.stop();
- proxy = null;
- }
-
- Log.i(TAG, "Preparing media player");
- mediaPlayer.setDataSource(dataSource);
- setPlayerState(PREPARING);
-
- mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener()
- {
- @Override
- public void onBufferingUpdate(MediaPlayer mp, int percent)
- {
- SeekBar progressBar = DownloadActivity.getProgressBar();
- MusicDirectory.Entry song = downloadFile.getSong();
-
- if (percent == 100)
- {
- if (progressBar != null)
- {
- progressBar.setSecondaryProgress(100 * progressBar.getMax());
- }
-
- mp.setOnBufferingUpdateListener(null);
- }
- else if (progressBar != null && song.getTranscodedContentType() == null && Util.getMaxBitRate(DownloadServiceImpl.this) == 0)
- {
- secondaryProgress = (int) (((double) percent / (double) 100) * progressBar.getMax());
- progressBar.setSecondaryProgress(secondaryProgress);
- }
- }
- });
-
- mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
- {
- @Override
- public void onPrepared(MediaPlayer mp)
- {
- Log.i(TAG, "Media player prepared");
-
- setPlayerState(PREPARED);
-
- SeekBar progressBar = DownloadActivity.getProgressBar();
-
- if (progressBar != null && downloadFile.isWorkDone())
- {
- // Populate seek bar secondary progress if we have a complete file for consistency
- DownloadActivity.getProgressBar().setSecondaryProgress(100 * progressBar.getMax());
- }
-
- synchronized (DownloadServiceImpl.this)
- {
- if (position != 0)
- {
- Log.i(TAG, String.format("Restarting player from position %d", position));
- seekTo(position);
- }
- cachedPosition = position;
-
- if (start)
- {
- mediaPlayer.start();
- setPlayerState(STARTED);
- }
- else
- {
- setPlayerState(PAUSED);
- }
- }
-
- lifecycleSupport.serializeDownloadQueue();
- }
- });
-
- setupHandlers(downloadFile, partial);
-
- mediaPlayer.prepareAsync();
- }
- catch (Exception x)
- {
- handleError(x);
- }
- }
-
- private synchronized void setupNext(final DownloadFile downloadFile)
- {
- try
- {
- final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
-
- if (nextMediaPlayer != null)
- {
- nextMediaPlayer.setOnCompletionListener(null);
- nextMediaPlayer.release();
- nextMediaPlayer = null;
- }
-
- nextMediaPlayer = new MediaPlayer();
- nextMediaPlayer.setWakeMode(DownloadServiceImpl.this, PowerManager.PARTIAL_WAKE_LOCK);
-
- try
- {
- nextMediaPlayer.setAudioSessionId(mediaPlayer.getAudioSessionId());
- }
- catch (Throwable e)
- {
- nextMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- }
-
- nextMediaPlayer.setDataSource(file.getPath());
- setNextPlayerState(PREPARING);
-
- nextMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
- {
- @Override
- @SuppressLint("NewApi")
- public void onPrepared(MediaPlayer mp)
- {
- try
- {
- setNextPlayerState(PREPARED);
-
- if (Util.getGaplessPlaybackPreference(DownloadServiceImpl.this) && 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()
- {
- @Override
- public boolean onError(MediaPlayer mediaPlayer, int what, int extra)
- {
- Log.w(TAG, String.format("Error on playing next (%d, %d): %s", what, extra, downloadFile));
- return true;
- }
- });
-
- nextMediaPlayer.prepareAsync();
- }
- catch (Exception x)
- {
- handleErrorNext(x);
- }
- }
-
- private void setupHandlers(final DownloadFile downloadFile, final boolean isPartial)
- {
- mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener()
- {
- @Override
- public boolean onError(MediaPlayer mediaPlayer, int what, int extra)
- {
- Log.w(TAG, String.format("Error on playing file (%d, %d): %s", what, extra, downloadFile));
- int pos = cachedPosition;
- reset();
- downloadFile.setPlaying(false);
- doPlay(downloadFile, pos, true);
- downloadFile.setPlaying(true);
- return true;
- }
- });
-
- final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
-
- 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);
-
- int pos = cachedPosition;
- Log.i(TAG, String.format("Ending position %d of %d", pos, duration));
-
- if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 1000)))
- {
- setPlayerStateCompleted();
-
- if (Util.getGaplessPlaybackPreference(DownloadServiceImpl.this) && nextPlaying != null && nextPlayerState == PlayerState.PREPARED)
- {
- if (!nextSetup)
- {
- playNext();
- }
- else
- {
- nextSetup = false;
- playNext();
- }
- }
- else
- {
- onSongCompleted();
- }
-
- return;
- }
-
- synchronized (DownloadServiceImpl.this)
- {
- if (downloadFile.isWorkDone())
- {
- // Complete was called early even though file is fully buffered
- Log.i(TAG, String.format("Requesting restart from %d of %d", pos, duration));
- reset();
- downloadFile.setPlaying(false);
- doPlay(downloadFile, pos, true);
- downloadFile.setPlaying(true);
- }
- else
- {
- Log.i(TAG, String.format("Requesting restart from %d of %d", pos, duration));
- reset();
- bufferTask = new BufferTask(downloadFile, pos);
- bufferTask.start();
- }
- }
- }
- });
+ MediaPlayerService.jukeboxService.adjustVolume(up);
}
@Override
public void setVolume(float volume)
{
- if (mediaPlayer != null)
- {
- mediaPlayer.setVolume(volume, volume);
- }
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setVolume(volume);
}
@Override
public synchronized void swap(boolean mainList, int from, int to)
{
- List list = mainList ? downloadList : backgroundDownloadList;
+ List list = mainList ? MediaPlayerService.downloadList : MediaPlayerService.backgroundDownloadList;
int max = list.size();
if (to >= max)
@@ -1886,332 +758,38 @@ public class DownloadServiceImpl extends Service implements DownloadService
if (jukeboxEnabled && mainList)
{
- updateJukeboxPlaylist();
+ MediaPlayerService.updateJukeboxPlaylist();
}
- else if (mainList && (movedSong == nextPlaying || (currentPlayingIndex + 1) == to))
+ else if (mainList && (movedSong == MediaPlayerService.nextPlaying || (currentPlayingIndex + 1) == to))
{
// Moving next playing or moving a song to be next playing
- setNextPlaying();
- }
- }
-
- private void handleError(Exception x)
- {
- Log.w(TAG, String.format("Media player error: %s", x), x);
-
- try
- {
- mediaPlayer.reset();
- }
- catch (Exception ex)
- {
- Log.w(TAG, String.format("Exception encountered when resetting media player: %s", ex), ex);
- }
-
- setPlayerState(IDLE);
- }
-
- private void handleErrorNext(Exception x)
- {
- Log.w(TAG, String.format("Next Media player error: %s", x), x);
- nextMediaPlayer.reset();
- setNextPlayerState(IDLE);
- }
-
- protected synchronized void checkDownloads()
- {
- if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable())
- {
- return;
- }
-
- if (shufflePlay)
- {
- checkShufflePlay();
- }
-
- if (jukeboxEnabled || !Util.isNetworkConnected(this))
- {
- return;
- }
-
- if (downloadList.isEmpty() && backgroundDownloadList.isEmpty())
- {
- return;
- }
-
- // Need to download current playing?
- if (currentPlaying != null && currentPlaying != currentDownloading && !currentPlaying.isWorkDone())
- {
- // Cancel current download, if necessary.
- if (currentDownloading != null)
- {
- currentDownloading.cancelDownload();
- }
-
- currentDownloading = currentPlaying;
- currentDownloading.download();
- cleanupCandidates.add(currentDownloading);
- }
-
- // Find a suitable target for download.
- else
- {
- if (currentDownloading == null || currentDownloading.isWorkDone() || currentDownloading.isFailed() && (!downloadList.isEmpty() || !backgroundDownloadList.isEmpty()))
- {
- currentDownloading = null;
- int n = size();
-
- int preloaded = 0;
-
- if (n != 0)
- {
- int start = currentPlaying == null ? 0 : getCurrentPlayingIndex();
- if (start == -1)
- {
- start = 0;
- }
- int i = start;
- do
- {
- DownloadFile downloadFile = downloadList.get(i);
- if (!downloadFile.isWorkDone())
- {
- if (downloadFile.shouldSave() || preloaded < Util.getPreloadCount(this))
- {
- currentDownloading = downloadFile;
- currentDownloading.download();
- cleanupCandidates.add(currentDownloading);
- if (i == (start + 1))
- {
- setNextPlayerState(DOWNLOADING);
- }
- break;
- }
- }
- else if (currentPlaying != downloadFile)
- {
- preloaded++;
- }
-
- i = (i + 1) % n;
- } while (i != start);
- }
-
- if ((preloaded + 1 == n || preloaded >= Util.getPreloadCount(this) || downloadList.isEmpty()) && !backgroundDownloadList.isEmpty())
- {
- for (int i = 0; i < backgroundDownloadList.size(); i++)
- {
- DownloadFile downloadFile = backgroundDownloadList.get(i);
- if (downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved()))
- {
- if (Util.getShouldScanMedia(this))
- {
- Util.scanMedia(this, downloadFile.getCompleteFile());
- }
-
- // Don't need to keep list like active song list
- backgroundDownloadList.remove(i);
- revision++;
- i--;
- }
- else
- {
- currentDownloading = downloadFile;
- currentDownloading.download();
- cleanupCandidates.add(currentDownloading);
- break;
- }
- }
- }
- }
- }
-
- // Delete obsolete .partial and .complete files.
- cleanup();
- }
-
- private synchronized void checkShufflePlay()
- {
- // Get users desired random playlist size
- int listSize = Util.getMaxSongs(this);
- boolean wasEmpty = downloadList.isEmpty();
-
- long revisionBefore = revision;
-
- // First, ensure that list is at least 20 songs long.
- int size = size();
- if (size < listSize)
- {
- for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size))
- {
- DownloadFile downloadFile = new DownloadFile(this, song, false);
- downloadList.add(downloadFile);
- revision++;
- }
- }
-
- int currIndex = currentPlaying == null ? 0 : getCurrentPlayingIndex();
-
- // Only shift playlist if playing song #5 or later.
- if (currIndex > 4)
- {
- int songsToShift = currIndex - 2;
- for (MusicDirectory.Entry song : shufflePlayBuffer.get(songsToShift))
- {
- downloadList.add(new DownloadFile(this, song, false));
- downloadList.get(0).cancelDownload();
- downloadList.remove(0);
- revision++;
- }
- }
-
- if (revisionBefore != revision)
- {
- updateJukeboxPlaylist();
- }
-
- if (wasEmpty && !downloadList.isEmpty())
- {
- play(0);
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
}
}
@Override
public long getDownloadListUpdateRevision()
{
- return revision;
- }
-
- private synchronized void cleanup()
- {
- Iterator iterator = cleanupCandidates.iterator();
- while (iterator.hasNext())
- {
- DownloadFile downloadFile = iterator.next();
- if (downloadFile != currentPlaying && downloadFile != currentDownloading)
- {
- if (downloadFile.cleanup())
- {
- iterator.remove();
- }
- }
- }
+ return MediaPlayerService.revision;
}
@Override
public void updateNotification()
{
- if (Util.isNotificationEnabled(this)) {
- if (isInForeground == true) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification());
- }
- else {
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(this);
- notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification());
- }
- Log.w(TAG, "--- Updated notification");
- }
- else {
- startForeground(NOTIFICATION_ID, buildForegroundNotification());
- isInForeground = true;
- Log.w(TAG, "--- Created Foreground notification");
- }
- }
+ MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
+ if (mediaPlayerService != null) mediaPlayerService.updateNotification();
}
- @SuppressWarnings("IconColors")
- private Notification buildForegroundNotification() {
- notificationBuilder.setSmallIcon(R.drawable.ic_stat_ultrasonic);
-
- notificationBuilder.setAutoCancel(false);
- notificationBuilder.setOngoing(true);
- notificationBuilder.setOnlyAlertOnce(true);
- notificationBuilder.setWhen(System.currentTimeMillis());
- notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
- notificationBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
-
- RemoteViews contentView = new RemoteViews(this.getPackageName(), R.layout.notification);
- Util.linkButtons(this, contentView, false);
- RemoteViews bigView = new RemoteViews(this.getPackageName(), R.layout.notification_large);
- Util.linkButtons(this, bigView, false);
-
- notificationBuilder.setContent(contentView);
-
- Intent notificationIntent = new Intent(this, DownloadActivity.class);
- notificationBuilder.setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, 0));
-
- if (playerState == PlayerState.PAUSED || playerState == PlayerState.IDLE) {
- contentView.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark);
- bigView.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark);
- } else if (playerState == PlayerState.STARTED) {
- contentView.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark);
- bigView.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark);
- }
-
- if (currentPlaying != null) {
- final Entry song = currentPlaying.getSong();
- final String title = song.getTitle();
- final String text = song.getArtist();
- final String album = song.getAlbum();
- final int rating = song.getUserRating() == null ? 0 : song.getUserRating();
- final int imageSize = Util.getNotificationImageSize(this);
-
- try {
- final Bitmap nowPlayingImage = FileUtil.getAlbumArtBitmap(this, currentPlaying.getSong(), imageSize, true);
- if (nowPlayingImage == null) {
- contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
- bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
- } else {
- contentView.setImageViewBitmap(R.id.notification_image, nowPlayingImage);
- bigView.setImageViewBitmap(R.id.notification_image, nowPlayingImage);
- }
- } catch (Exception x) {
- Log.w(TAG, "Failed to get notification cover art", x);
- contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
- bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
- }
-
-
- contentView.setTextViewText(R.id.trackname, title);
- bigView.setTextViewText(R.id.trackname, title);
- contentView.setTextViewText(R.id.artist, text);
- bigView.setTextViewText(R.id.artist, text);
- contentView.setTextViewText(R.id.album, album);
- bigView.setTextViewText(R.id.album, album);
-
- boolean useFiveStarRating = KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING);
- if (!useFiveStarRating)
- bigView.setViewVisibility(R.id.notification_rating, View.INVISIBLE);
- else {
- bigView.setImageViewResource(R.id.notification_five_star_1, rating > 0 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
- bigView.setImageViewResource(R.id.notification_five_star_2, rating > 1 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
- bigView.setImageViewResource(R.id.notification_five_star_3, rating > 2 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
- bigView.setImageViewResource(R.id.notification_five_star_4, rating > 3 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
- bigView.setImageViewResource(R.id.notification_five_star_5, rating > 4 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
- }
- }
-
- Notification notification = notificationBuilder.build();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- notification.bigContentView = bigView;
- }
-
- return notification;
- }
-
public void setSongRating(final int rating)
{
if (!KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING))
return;
- if (currentPlaying == null)
+ if (MediaPlayerService.currentPlaying == null)
return;
- final Entry song = currentPlaying.getSong();
+ final Entry song = MediaPlayerService.currentPlaying.getSong();
song.setUserRating(rating);
new Thread(new Runnable()
@@ -2219,11 +797,11 @@ public class DownloadServiceImpl extends Service implements DownloadService
@Override
public void run()
{
- final MusicService musicService = MusicServiceFactory.getMusicService(DownloadServiceImpl.this);
+ final MusicService musicService = MusicServiceFactory.getMusicService(context);
try
{
- musicService.setRating(song.getId(), rating, DownloadServiceImpl.this, null);
+ musicService.setRating(song.getId(), rating, context, null);
}
catch (Exception e)
{
@@ -2235,164 +813,8 @@ public class DownloadServiceImpl extends Service implements DownloadService
updateNotification();
}
- private class BufferTask extends CancellableTask
+ private void handleError(Exception x)
{
- 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();
-
- long bufferLength = Util.getBufferLength(DownloadServiceImpl.this);
-
- if (bufferLength == 0)
- {
- // Set to seconds in a day, basically infinity
- bufferLength = 86400L;
- }
-
- // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to.
- int bitRate = downloadFile.getBitRate();
- long byteCount = Math.max(100000, bitRate * 1024L / 8L * bufferLength);
-
- // Find out how large the file should grow before resuming playback.
- Log.i(TAG, String.format("Buffering from position %d and bitrate %d", position, bitRate));
- expectedFileSize = (position * bitRate / 8) + byteCount;
- }
-
- @Override
- public void execute()
- {
- setPlayerState(DOWNLOADING);
-
- while (!bufferComplete() && !Util.isOffline(DownloadServiceImpl.this))
- {
- Util.sleepQuietly(1000L);
- if (isCancelled())
- {
- return;
- }
- }
- doPlay(downloadFile, position, true);
- }
-
- private boolean bufferComplete()
- {
- boolean completeFileAvailable = downloadFile.isWorkDone();
- long size = partialFile.length();
-
- Log.i(TAG, String.format("Buffering %s (%d/%d, %s)", partialFile, size, expectedFileSize, completeFileAvailable));
- return completeFileAvailable || size >= expectedFileSize;
- }
-
- @Override
- public String toString()
- {
- return String.format("BufferTask (%s)", downloadFile);
- }
- }
-
- private class PositionCache implements Runnable
- {
- boolean isRunning = true;
-
- public void stop()
- {
- isRunning = false;
- }
-
- @Override
- public void run()
- {
- Thread.currentThread().setName("PositionCache");
-
- // Stop checking position before the song reaches completion
- while (isRunning)
- {
- try
- {
- if (mediaPlayer != null && playerState == STARTED)
- {
- cachedPosition = mediaPlayer.getCurrentPosition();
- }
-
- Util.sleepQuietly(25L);
- }
- catch (Exception e)
- {
- Log.w(TAG, "Crashed getting current position", e);
- isRunning = false;
- positionCache = null;
- }
- }
- }
- }
-
- private class CheckCompletionTask extends CancellableTask
- {
- private final DownloadFile downloadFile;
- private final File partialFile;
-
- public CheckCompletionTask(DownloadFile downloadFile)
- {
- super();
- setNextPlayerState(PlayerState.IDLE);
-
- this.downloadFile = downloadFile;
-
- partialFile = downloadFile != null ? downloadFile.getPartialFile() : null;
- }
-
- @Override
- public void execute()
- {
- Thread.currentThread().setName("CheckCompletionTask");
-
- 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()
- {
- @Override
- public void run()
- {
- setupNext(downloadFile);
- }
- });
- }
-
- private boolean bufferComplete()
- {
- boolean completeFileAvailable = downloadFile.isWorkDone();
- Log.i(TAG, String.format("Buffering next %s (%d)", partialFile, partialFile.length()));
- return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED);
- }
-
- @Override
- public String toString()
- {
- return String.format("CheckCompletionTask (%s)", downloadFile);
- }
+ Log.w(TAG, String.format("Media player error: %s", x), x);
}
}
\ No newline at end of file
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java
index 265bce86..4cbcef74 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java
@@ -67,6 +67,7 @@ public class DownloadServiceLifecycleSupport
private boolean externalStorageAvailable = true;
private Lock lock = new ReentrantLock();
private final AtomicBoolean setup = new AtomicBoolean(false);
+ private Context context;
/**
* This receiver manages the intent that could come from other applications.
@@ -107,9 +108,10 @@ public class DownloadServiceLifecycleSupport
};
- public DownloadServiceLifecycleSupport(DownloadServiceImpl downloadService)
+ public DownloadServiceLifecycleSupport(Context context, DownloadServiceImpl downloadService)
{
this.downloadService = downloadService;
+ this.context = context;
}
public void onCreate()
@@ -121,7 +123,7 @@ public class DownloadServiceLifecycleSupport
{
try
{
- downloadService.checkDownloads();
+ MediaPlayerService.checkDownloads(context);
}
catch (Throwable x)
{
@@ -156,10 +158,10 @@ public class DownloadServiceLifecycleSupport
IntentFilter ejectFilter = new IntentFilter(Intent.ACTION_MEDIA_EJECT);
ejectFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
ejectFilter.addDataScheme("file");
- downloadService.registerReceiver(ejectEventReceiver, ejectFilter);
+ context.registerReceiver(ejectEventReceiver, ejectFilter);
// React to media buttons.
- Util.registerMediaButtonEventReceiver(downloadService);
+ Util.registerMediaButtonEventReceiver(context);
// Pause temporarily on incoming phone calls.
//phoneStateListener = new MyPhoneStateListener();
@@ -174,20 +176,20 @@ public class DownloadServiceLifecycleSupport
commandFilter.addAction(DownloadServiceImpl.CMD_STOP);
commandFilter.addAction(DownloadServiceImpl.CMD_PREVIOUS);
commandFilter.addAction(DownloadServiceImpl.CMD_NEXT);
- downloadService.registerReceiver(intentReceiver, commandFilter);
+ context.registerReceiver(intentReceiver, commandFilter);
- int instance = Util.getActiveServer(downloadService);
- downloadService.setJukeboxEnabled(Util.getJukeboxEnabled(downloadService, instance));
+ int instance = Util.getActiveServer(context);
+ downloadService.setJukeboxEnabled(Util.getJukeboxEnabled(context, instance));
deserializeDownloadQueue();
- new CacheCleaner(downloadService, downloadService).clean();
+ new CacheCleaner(context, downloadService).clean();
}
private void registerHeadsetReceiver() {
// Pause when headset is unplugged.
- final SharedPreferences sp = Util.getPreferences(downloadService);
- final String spKey = downloadService
+ final SharedPreferences sp = Util.getPreferences(context);
+ final String spKey = context
.getString(R.string.settings_playback_resume_play_on_headphones_plug);
headsetEventReceiver = new BroadcastReceiver() {
@@ -218,7 +220,7 @@ public class DownloadServiceLifecycleSupport
IntentFilter headsetIntentFilter = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ?
new IntentFilter(AudioManager.ACTION_HEADSET_PLUG) :
new IntentFilter(Intent.ACTION_HEADSET_PLUG);
- downloadService.registerReceiver(headsetEventReceiver, headsetIntentFilter);
+ context.registerReceiver(headsetEventReceiver, headsetIntentFilter);
}
public void onStart(Intent intent)
@@ -238,12 +240,9 @@ public class DownloadServiceLifecycleSupport
executorService.shutdown();
serializeDownloadQueueNow();
downloadService.clear(false);
- downloadService.unregisterReceiver(ejectEventReceiver);
- downloadService.unregisterReceiver(headsetEventReceiver);
- downloadService.unregisterReceiver(intentReceiver);
-
- //TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE);
- //telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
+ context.unregisterReceiver(ejectEventReceiver);
+ context.unregisterReceiver(headsetEventReceiver);
+ context.unregisterReceiver(intentReceiver);
}
public boolean isExternalStorageAvailable()
@@ -273,7 +272,7 @@ public class DownloadServiceLifecycleSupport
state.currentPlayingPosition = downloadService.getPlayerPosition();
Log.i(TAG, String.format("Serialized currentPlayingIndex: %d, currentPlayingPosition: %d", state.currentPlayingIndex, state.currentPlayingPosition));
- FileUtil.serialize(downloadService, state, FILENAME_DOWNLOADS_SER);
+ FileUtil.serialize(context, state, FILENAME_DOWNLOADS_SER);
}
private void deserializeDownloadQueue()
@@ -283,7 +282,7 @@ public class DownloadServiceLifecycleSupport
private void deserializeDownloadQueueNow()
{
- State state = FileUtil.deserialize(downloadService, FILENAME_DOWNLOADS_SER);
+ State state = FileUtil.deserialize(context, FILENAME_DOWNLOADS_SER);
if (state == null)
{
return;
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java
index 4d85b0b6..cc321cb3 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java
@@ -67,6 +67,7 @@ public class JukeboxService
private VolumeToast volumeToast;
private AtomicBoolean running = new AtomicBoolean();
private Thread serviceThread;
+ private Context context;
// TODO: Report warning if queue fills up.
// TODO: Create shutdown method?
@@ -74,8 +75,9 @@ public class JukeboxService
// TODO: Persist RC state?
// TODO: Minimize status updates.
- public JukeboxService(DownloadServiceImpl downloadService)
+ public JukeboxService(Context context, DownloadServiceImpl downloadService)
{
+ this.context = context;
this.downloadService = downloadService;
}
@@ -149,7 +151,7 @@ public class JukeboxService
try
{
- if (!Util.isOffline(downloadService))
+ if (!Util.isOffline(context))
{
task = tasks.take();
JukeboxStatus status = task.execute();
@@ -212,7 +214,7 @@ public class JukeboxService
@Override
public void run()
{
- Util.toast(downloadService, resourceId, false);
+ Util.toast(context, resourceId, false);
}
});
@@ -282,14 +284,14 @@ public class JukeboxService
if (volumeToast == null)
{
- volumeToast = new VolumeToast(downloadService);
+ volumeToast = new VolumeToast(context);
}
volumeToast.setVolume(gain);
}
private MusicService getMusicService()
{
- return MusicServiceFactory.getMusicService(downloadService);
+ return MusicServiceFactory.getMusicService(context);
}
public int getPositionSeconds()
@@ -380,7 +382,7 @@ public class JukeboxService
@Override
JukeboxStatus execute() throws Exception
{
- return getMusicService().getJukeboxStatus(downloadService, null);
+ return getMusicService().getJukeboxStatus(context, null);
}
}
@@ -396,7 +398,7 @@ public class JukeboxService
@Override
JukeboxStatus execute() throws Exception
{
- return getMusicService().updateJukeboxPlaylist(ids, downloadService, null);
+ return getMusicService().updateJukeboxPlaylist(ids, context, null);
}
}
@@ -414,7 +416,7 @@ public class JukeboxService
@Override
JukeboxStatus execute() throws Exception
{
- return getMusicService().skipJukebox(index, offsetSeconds, downloadService, null);
+ return getMusicService().skipJukebox(index, offsetSeconds, context, null);
}
}
@@ -423,7 +425,7 @@ public class JukeboxService
@Override
JukeboxStatus execute() throws Exception
{
- return getMusicService().stopJukebox(downloadService, null);
+ return getMusicService().stopJukebox(context, null);
}
}
@@ -432,7 +434,7 @@ public class JukeboxService
@Override
JukeboxStatus execute() throws Exception
{
- return getMusicService().startJukebox(downloadService, null);
+ return getMusicService().startJukebox(context, null);
}
}
@@ -449,7 +451,7 @@ public class JukeboxService
@Override
JukeboxStatus execute() throws Exception
{
- return getMusicService().setJukeboxGain(gain, downloadService, null);
+ return getMusicService().setJukeboxGain(gain, context, null);
}
}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java
new file mode 100644
index 00000000..0d44ab69
--- /dev/null
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java
@@ -0,0 +1,1792 @@
+package org.moire.ultrasonic.service;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaPlayer;
+import android.media.RemoteControlClient;
+import android.media.audiofx.AudioEffect;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.RemoteViews;
+import android.widget.SeekBar;
+
+import androidx.annotation.Nullable;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationManagerCompat;
+
+import org.koin.java.standalone.KoinJavaComponent;
+import org.moire.ultrasonic.R;
+import org.moire.ultrasonic.activity.DownloadActivity;
+import org.moire.ultrasonic.activity.SubsonicTabActivity;
+import org.moire.ultrasonic.audiofx.EqualizerController;
+import org.moire.ultrasonic.audiofx.VisualizerController;
+import org.moire.ultrasonic.domain.MusicDirectory;
+import org.moire.ultrasonic.domain.PlayerState;
+import org.moire.ultrasonic.domain.RepeatMode;
+import org.moire.ultrasonic.featureflags.Feature;
+import org.moire.ultrasonic.featureflags.FeatureStorage;
+import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x1;
+import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x2;
+import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x3;
+import org.moire.ultrasonic.provider.UltraSonicAppWidgetProvider4x4;
+import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
+import org.moire.ultrasonic.util.CancellableTask;
+import org.moire.ultrasonic.util.Constants;
+import org.moire.ultrasonic.util.FileUtil;
+import org.moire.ultrasonic.util.ShufflePlayBuffer;
+import org.moire.ultrasonic.util.SimpleServiceBinder;
+import org.moire.ultrasonic.util.StreamProxy;
+import org.moire.ultrasonic.util.Util;
+
+import java.io.File;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.moire.ultrasonic.domain.PlayerState.COMPLETED;
+import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
+import static org.moire.ultrasonic.domain.PlayerState.IDLE;
+import static org.moire.ultrasonic.domain.PlayerState.PAUSED;
+import static org.moire.ultrasonic.domain.PlayerState.PREPARED;
+import static org.moire.ultrasonic.domain.PlayerState.PREPARING;
+import static org.moire.ultrasonic.domain.PlayerState.STARTED;
+import static org.moire.ultrasonic.domain.PlayerState.STOPPED;
+
+public class MediaPlayerService extends Service
+{
+ private static final String TAG = MediaPlayerService.class.getSimpleName();
+ private static final String NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic";
+ private static final String NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service";
+ private static final int NOTIFICATION_ID = 3033;
+
+ private static MediaPlayerService instance = null;
+
+ public static boolean equalizerAvailable;
+ public static boolean visualizerAvailable;
+ public static final List downloadList = new ArrayList();
+ public static final List backgroundDownloadList = new ArrayList();
+
+ private final IBinder binder = new SimpleServiceBinder<>(this);
+ private PowerManager.WakeLock wakeLock;
+
+ private Looper mediaPlayerLooper;
+ private MediaPlayer mediaPlayer;
+ private MediaPlayer nextMediaPlayer;
+ private Handler mediaPlayerHandler;
+ private AudioManager audioManager;
+
+ public RemoteControlClient remoteControlClient;
+ private EqualizerController equalizerController;
+ private VisualizerController visualizerController;
+ public static ShufflePlayBuffer shufflePlayBuffer;
+ private final Scrobbler scrobbler = new Scrobbler();
+ private CancellableTask bufferTask;
+ public static DownloadFile currentPlaying;
+ public static DownloadFile currentDownloading;
+ public static DownloadFile nextPlaying;
+
+ public static boolean jukeboxEnabled;
+ public static JukeboxService jukeboxService;
+ public static DownloadServiceLifecycleSupport lifecycleSupport;
+
+ public static int cachedPosition;
+ private PositionCache positionCache;
+ private StreamProxy proxy;
+
+ private static boolean nextSetup;
+ private static CancellableTask nextPlayingTask;
+ public static PlayerState playerState = IDLE;
+ public static PlayerState nextPlayerState = IDLE;
+ private boolean isInForeground = false;
+ private static final List cleanupCandidates = new ArrayList();
+ public static boolean shufflePlay;
+ public static long revision;
+ private int secondaryProgress = -1;
+
+ private NotificationCompat.Builder notificationBuilder;
+
+ static
+ {
+ try
+ {
+ EqualizerController.checkAvailable();
+ equalizerAvailable = true;
+ }
+ catch (Throwable t)
+ {
+ equalizerAvailable = false;
+ }
+ }
+
+ static
+ {
+ try
+ {
+ VisualizerController.checkAvailable();
+ visualizerAvailable = true;
+ }
+ catch (Throwable t)
+ {
+ visualizerAvailable = false;
+ }
+ }
+
+ public static synchronized int size() { return downloadList.size(); }
+ public RepeatMode getRepeatMode() { return Util.getRepeatMode(this); }
+
+ public static MediaPlayerService getInstance(Context context)
+ {
+ for (int i = 0; i < 5; i++)
+ {
+ if (instance != null) return instance;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
+ {
+ context.startForegroundService(new Intent(context, MediaPlayerService.class));
+ }
+ else
+ {
+ context.startService(new Intent(context, MediaPlayerService.class));
+ }
+
+ Util.sleepQuietly(50L);
+ }
+
+ return instance;
+ }
+
+ public static MediaPlayerService getRunningInstance()
+ {
+ return instance;
+ }
+
+ @Nullable
+ @Override
+ public IBinder onBind(Intent intent)
+ {
+ return binder;
+ }
+
+ @Override
+ public void onCreate()
+ {
+ super.onCreate();
+
+ new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ Thread.currentThread().setName("MediaPlayerService");
+
+ Looper.prepare();
+
+ if (mediaPlayer != null)
+ {
+ mediaPlayer.release();
+ }
+
+ mediaPlayer = new MediaPlayer();
+ mediaPlayer.setWakeMode(MediaPlayerService.this, PowerManager.PARTIAL_WAKE_LOCK);
+
+ mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener()
+ {
+ @Override
+ public boolean onError(MediaPlayer mediaPlayer, int what, int more)
+ {
+ handleError(new Exception(String.format("MediaPlayer error: %d (%d)", 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();
+
+ audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
+ setUpRemoteControlClient();
+
+ if (equalizerAvailable)
+ {
+ equalizerController = new EqualizerController(this, mediaPlayer);
+ if (!equalizerController.isAvailable())
+ {
+ equalizerController = null;
+ }
+ else
+ {
+ equalizerController.loadSettings();
+ }
+ }
+
+ if (visualizerAvailable)
+ {
+ visualizerController = new VisualizerController(mediaPlayer);
+ if (!visualizerController.isAvailable())
+ {
+ visualizerController = null;
+ }
+ }
+
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName());
+ wakeLock.setReferenceCounted(false);
+
+ // Create Notification Channel
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ //The suggested importance of a startForeground service notification is IMPORTANCE_LOW
+ NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
+ channel.setLightColor(android.R.color.holo_blue_dark);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+ NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ manager.createNotificationChannel(channel);
+ }
+
+ // We should use a single notification builder, otherwise the notification may not be updated
+ notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);
+
+ instance = this;
+
+ Log.i(TAG, "MediaPlayerService created");
+ }
+
+ @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;
+
+ reset();
+ try
+ {
+ mediaPlayer.release();
+
+ if (nextMediaPlayer != null)
+ {
+ nextMediaPlayer.release();
+ }
+
+ mediaPlayerLooper.quit();
+ shufflePlayBuffer.shutdown();
+
+ if (equalizerController != null)
+ {
+ equalizerController.release();
+ }
+
+ if (visualizerController != null)
+ {
+ visualizerController.release();
+ }
+
+ if (bufferTask != null)
+ {
+ bufferTask.cancel();
+ }
+
+ if (nextPlayingTask != null)
+ {
+ nextPlayingTask.cancel();
+ }
+
+ 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);
+
+ audioManager.unregisterRemoteControlClient(remoteControlClient);
+ clearRemoteControl();
+
+ wakeLock.release();
+ }
+ catch (Throwable ignored)
+ {
+ }
+
+ Log.i(TAG, "MediaPlayerService stopped");
+ }
+
+ 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(mediaPlayer);
+ if (!visualizerController.isAvailable())
+ {
+ visualizerController = null;
+ }
+ }
+ return visualizerController;
+ }
+
+ public void setUpRemoteControlClient()
+ {
+ if (!Util.isLockScreenEnabled(this)) return;
+
+ ComponentName componentName = new ComponentName(getPackageName(), MediaButtonIntentReceiver.class.getName());
+
+ if (remoteControlClient == null)
+ {
+ final Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ mediaButtonIntent.setComponent(componentName);
+ PendingIntent broadcast = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ remoteControlClient = new RemoteControlClient(broadcast);
+ audioManager.registerRemoteControlClient(remoteControlClient);
+
+ // Flags for the media transport control that this client supports.
+ int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS |
+ RemoteControlClient.FLAG_KEY_MEDIA_NEXT |
+ RemoteControlClient.FLAG_KEY_MEDIA_PLAY |
+ RemoteControlClient.FLAG_KEY_MEDIA_PAUSE |
+ RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE |
+ RemoteControlClient.FLAG_KEY_MEDIA_STOP;
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
+ {
+ flags |= RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE;
+
+ remoteControlClient.setOnGetPlaybackPositionListener(new RemoteControlClient.OnGetPlaybackPositionListener()
+ {
+ @Override
+ public long onGetPlaybackPosition()
+ {
+ return mediaPlayer.getCurrentPosition();
+ }
+ });
+
+ remoteControlClient.setPlaybackPositionUpdateListener(new RemoteControlClient.OnPlaybackPositionUpdateListener()
+ {
+ @Override
+ public void onPlaybackPositionUpdate(long newPositionMs)
+ {
+ seekTo((int) newPositionMs);
+ }
+ });
+ }
+
+ remoteControlClient.setTransportControlFlags(flags);
+ }
+ }
+
+ private void clearRemoteControl()
+ {
+ if (remoteControlClient != null)
+ {
+ remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED);
+ audioManager.unregisterRemoteControlClient(remoteControlClient);
+ remoteControlClient = null;
+ }
+ }
+
+ private void updateRemoteControl()
+ {
+ if (!Util.isLockScreenEnabled(this))
+ {
+ clearRemoteControl();
+ return;
+ }
+
+ if (remoteControlClient != null)
+ {
+ audioManager.unregisterRemoteControlClient(remoteControlClient);
+ audioManager.registerRemoteControlClient(remoteControlClient);
+ }
+ else
+ {
+ setUpRemoteControlClient();
+ }
+
+ Log.i(TAG, String.format("In updateRemoteControl, playerState: %s [%d]", playerState, getPlayerPosition()));
+
+ switch (playerState)
+ {
+ case STARTED:
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
+ {
+ remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING);
+ }
+ else
+ {
+ remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING, getPlayerPosition(), 1.0f);
+ }
+ break;
+ default:
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
+ {
+ remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED);
+ }
+ else
+ {
+ remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PAUSED, getPlayerPosition(), 1.0f);
+ }
+ break;
+ }
+
+ if (currentPlaying != null)
+ {
+ MusicDirectory.Entry currentSong = currentPlaying.getSong();
+
+ Bitmap lockScreenBitmap = FileUtil.getAlbumArtBitmap(this, currentSong, Util.getMinDisplayMetric(this), true);
+
+ String artist = currentSong.getArtist();
+ String album = currentSong.getAlbum();
+ String title = currentSong.getTitle();
+ Integer currentSongDuration = currentSong.getDuration();
+ Long duration = 0L;
+
+ if (currentSongDuration != null) duration = (long) currentSongDuration * 1000;
+
+ remoteControlClient.editMetadata(true).putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, artist).putString(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST, artist).putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, album).putString(MediaMetadataRetriever.METADATA_KEY_TITLE, title).putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration)
+ .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, lockScreenBitmap).apply();
+ }
+ }
+
+ public synchronized void seekTo(int position)
+ {
+ try
+ {
+ if (jukeboxEnabled)
+ {
+ jukeboxService.skip(getCurrentPlayingIndex(), position / 1000);
+ }
+ else
+ {
+ mediaPlayer.seekTo(position);
+ cachedPosition = position;
+
+ updateRemoteControl();
+ }
+ }
+ catch (Exception x)
+ {
+ handleError(x);
+ }
+ }
+
+ public synchronized int getPlayerPosition()
+ {
+ try
+ {
+ if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING)
+ {
+ return 0;
+ }
+
+ return jukeboxEnabled ? jukeboxService.getPositionSeconds() * 1000 : cachedPosition;
+ }
+ catch (Exception x)
+ {
+ handleError(x);
+ return 0;
+ }
+ }
+
+ public synchronized int getPlayerDuration()
+ {
+ if (MediaPlayerService.currentPlaying != null)
+ {
+ Integer duration = MediaPlayerService.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 static synchronized int getCurrentPlayingIndex()
+ {
+ return downloadList.indexOf(currentPlaying);
+ }
+
+ public synchronized void setCurrentPlaying(int currentPlayingIndex)
+ {
+ try
+ {
+ setCurrentPlaying(downloadList.get(currentPlayingIndex));
+ }
+ catch (IndexOutOfBoundsException x)
+ {
+ // Ignored
+ }
+ }
+
+ public synchronized void setCurrentPlaying(DownloadFile currentPlaying)
+ {
+ MediaPlayerService.currentPlaying = currentPlaying;
+
+ if (currentPlaying != null)
+ {
+ Util.broadcastNewTrackInfo(this, currentPlaying.getSong());
+ Util.broadcastA2dpPlayStatusChange(this, playerState, currentPlaying.getSong(), downloadList.size() + backgroundDownloadList.size(), downloadList.indexOf(currentPlaying) + 1, getPlayerPosition());
+ }
+ else
+ {
+ Util.broadcastNewTrackInfo(this, null);
+ Util.broadcastA2dpMetaDataChange(this, null);
+ }
+
+ // Update widget
+ UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(this, currentPlaying.getSong(), playerState == PlayerState.STARTED, false);
+ UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(this, currentPlaying.getSong(), playerState == PlayerState.STARTED, true);
+ UltraSonicAppWidgetProvider4x3.getInstance().notifyChange(this, currentPlaying.getSong(), playerState == PlayerState.STARTED, false);
+ UltraSonicAppWidgetProvider4x4.getInstance().notifyChange(this, currentPlaying.getSong(), playerState == PlayerState.STARTED, false);
+
+ updateRemoteControl();
+ SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
+
+ if (currentPlaying != null)
+ {
+ if (tabInstance != null) {
+ updateNotification();
+ tabInstance.showNowPlaying();
+ }
+ }
+ else
+ {
+ if (tabInstance != null)
+ {
+ tabInstance.hideNowPlaying();
+ stopForeground(true);
+ isInForeground = false;
+ stopSelf();
+ }
+ }
+ }
+
+ public synchronized void setNextPlaying()
+ {
+ boolean gaplessPlayback = Util.getGaplessPlaybackPreference(this);
+
+ if (!gaplessPlayback)
+ {
+ nextPlaying = null;
+ nextPlayerState = IDLE;
+ return;
+ }
+
+ int index = getCurrentPlayingIndex();
+
+ if (index != -1)
+ {
+ switch (getRepeatMode())
+ {
+ case OFF:
+ index += 1;
+ break;
+ case ALL:
+ index = (index + 1) % size();
+ break;
+ case SINGLE:
+ break;
+ default:
+ break;
+ }
+ }
+
+ nextSetup = false;
+ if (nextPlayingTask != null)
+ {
+ nextPlayingTask.cancel();
+ nextPlayingTask = null;
+ }
+
+ if (index < size() && index != -1)
+ {
+ nextPlaying = downloadList.get(index);
+ nextPlayingTask = new CheckCompletionTask(nextPlaying);
+ nextPlayingTask.start();
+ }
+ else
+ {
+ nextPlaying = null;
+ setNextPlayerState(IDLE);
+ }
+ }
+
+ public synchronized void togglePlayPause()
+ {
+ if (playerState == PAUSED || playerState == COMPLETED || playerState == STOPPED)
+ {
+ start();
+ }
+ else if (playerState == IDLE)
+ {
+ play();
+ }
+ else if (playerState == STARTED)
+ {
+ pause();
+ }
+ }
+
+ public void setVolume(float volume)
+ {
+ if (mediaPlayer != null)
+ {
+ mediaPlayer.setVolume(volume, volume);
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ public synchronized void play(int index, boolean start)
+ {
+ updateRemoteControl();
+
+ if (index < 0 || index >= size())
+ {
+ resetPlayback();
+ }
+ else
+ {
+ if (nextPlayingTask != null)
+ {
+ nextPlayingTask.cancel();
+ nextPlayingTask = null;
+ }
+
+ setCurrentPlaying(index);
+
+ if (start)
+ {
+ if (jukeboxEnabled)
+ {
+ jukeboxService.skip(getCurrentPlayingIndex(), 0);
+ setPlayerState(STARTED);
+ }
+ else
+ {
+ bufferAndPlay();
+ }
+ }
+
+ checkDownloads(this);
+ setNextPlaying();
+ }
+ }
+
+ private synchronized void resetPlayback()
+ {
+ reset();
+ setCurrentPlaying(null);
+ lifecycleSupport.serializeDownloadQueue();
+ }
+
+ public synchronized void reset()
+ {
+ if (bufferTask != null)
+ {
+ bufferTask.cancel();
+ }
+ try
+ {
+ setPlayerState(IDLE);
+ mediaPlayer.setOnErrorListener(null);
+ mediaPlayer.setOnCompletionListener(null);
+ mediaPlayer.reset();
+ }
+ catch (Exception x)
+ {
+ handleError(x);
+ }
+ }
+
+ private synchronized void playNext()
+ {
+ MediaPlayer tmp = mediaPlayer;
+ mediaPlayer = nextMediaPlayer;
+ nextMediaPlayer = tmp;
+ setCurrentPlaying(nextPlaying);
+ 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;
+ }
+ }
+
+ public synchronized void pause()
+ {
+ try
+ {
+ if (playerState == STARTED)
+ {
+ if (jukeboxEnabled)
+ {
+ jukeboxService.stop();
+ }
+ else
+ {
+ mediaPlayer.pause();
+ }
+ setPlayerState(PAUSED);
+ }
+ }
+ catch (Exception x)
+ {
+ handleError(x);
+ }
+ }
+
+ public synchronized void stop()
+ {
+ try
+ {
+ if (playerState == STARTED)
+ {
+ if (jukeboxEnabled)
+ {
+ jukeboxService.stop();
+ }
+ else
+ {
+ mediaPlayer.pause();
+ }
+ }
+ setPlayerState(STOPPED);
+ }
+ catch (Exception x)
+ {
+ handleError(x);
+ }
+ }
+
+ public synchronized void start()
+ {
+ try
+ {
+ if (jukeboxEnabled)
+ {
+ jukeboxService.start();
+ }
+ else
+ {
+ mediaPlayer.start();
+ }
+ setPlayerState(STARTED);
+ }
+ catch (Exception x)
+ {
+ handleError(x);
+ }
+ }
+
+ private synchronized void bufferAndPlay()
+ {
+ if (playerState != PREPARED)
+ {
+ reset();
+
+ bufferTask = new BufferTask(currentPlaying, 0);
+ bufferTask.start();
+ }
+ else
+ {
+ doPlay(currentPlaying, 0, true);
+ }
+ }
+
+ public synchronized void setPlayerState(PlayerState playerState)
+ {
+ Log.i(TAG, String.format("%s -> %s (%s)", playerState.name(), playerState.name(), currentPlaying));
+
+ MediaPlayerService.playerState = playerState;
+
+ if (playerState == PAUSED)
+ {
+ lifecycleSupport.serializeDownloadQueue();
+ }
+
+ if (playerState == PlayerState.STARTED)
+ {
+ Util.requestAudioFocus(this);
+ }
+
+ boolean showWhenPaused = (playerState != PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(this));
+ boolean show = playerState == PlayerState.STARTED || showWhenPaused;
+
+ Util.broadcastPlaybackStatusChange(this, playerState);
+ Util.broadcastA2dpPlayStatusChange(this, playerState, currentPlaying.getSong(), downloadList.size() + backgroundDownloadList.size(), downloadList.indexOf(currentPlaying) + 1, getPlayerPosition());
+
+ if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)
+ {
+ // Set remote control
+ updateRemoteControl();
+ }
+
+ // Update widget
+ UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(this, currentPlaying.getSong(), playerState == PlayerState.STARTED, false);
+ UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(this, currentPlaying.getSong(), playerState == PlayerState.STARTED, true);
+ UltraSonicAppWidgetProvider4x3.getInstance().notifyChange(this, currentPlaying.getSong(), playerState == PlayerState.STARTED, false);
+ UltraSonicAppWidgetProvider4x4.getInstance().notifyChange(this, currentPlaying.getSong(), playerState == PlayerState.STARTED, false);
+ SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
+
+ if (show)
+ {
+ if (tabInstance != null)
+ {
+ // Only update notification is player state is one that will change the icon
+ if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)
+ {
+ updateNotification();
+ tabInstance.showNowPlaying();
+ }
+ }
+ }
+ else
+ {
+ if (tabInstance != null)
+ {
+ stopForeground(true);
+ isInForeground = false;
+ tabInstance.hideNowPlaying();
+ stopSelf();
+ }
+ }
+
+ 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 void setPlayerStateCompleted()
+ {
+ Log.i(TAG, String.format("%s -> %s (%s)", playerState.name(), PlayerState.COMPLETED, currentPlaying));
+ playerState = PlayerState.COMPLETED;
+
+ if (positionCache != null)
+ {
+ positionCache.stop();
+ positionCache = null;
+ }
+
+ scrobbler.scrobble(this, currentPlaying, true);
+ }
+
+ private static synchronized void setNextPlayerState(PlayerState playerState)
+ {
+ Log.i(TAG, String.format("Next: %s -> %s (%s)", nextPlayerState.name(), playerState.name(), nextPlaying));
+ nextPlayerState = playerState;
+ }
+
+ public synchronized void doPlay(final DownloadFile downloadFile, final int position, final boolean start)
+ {
+ try
+ {
+ downloadFile.setPlaying(false);
+ //downloadFile.setPlaying(true);
+ final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
+ boolean partial = file.equals(downloadFile.getPartialFile());
+ downloadFile.updateModificationDate();
+
+ mediaPlayer.setOnCompletionListener(null);
+ secondaryProgress = -1; // Ensure seeking in non StreamProxy playback works
+ mediaPlayer.reset();
+ setPlayerState(IDLE);
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ String dataSource = file.getPath();
+
+ if (partial)
+ {
+ if (proxy == null)
+ {
+ proxy = new StreamProxy(new Supplier() {
+ @Override
+ public DownloadFile get() { return currentPlaying; }
+ });
+ proxy.start();
+ }
+
+ dataSource = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), URLEncoder.encode(dataSource, Constants.UTF_8));
+ Log.i(TAG, String.format("Data Source: %s", dataSource));
+ }
+ else if (proxy != null)
+ {
+ proxy.stop();
+ proxy = null;
+ }
+
+ Log.i(TAG, "Preparing media player");
+ mediaPlayer.setDataSource(dataSource);
+ setPlayerState(PREPARING);
+
+ mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener()
+ {
+ @Override
+ public void onBufferingUpdate(MediaPlayer mp, int percent)
+ {
+ SeekBar progressBar = DownloadActivity.getProgressBar();
+ MusicDirectory.Entry song = downloadFile.getSong();
+
+ if (percent == 100)
+ {
+ if (progressBar != null)
+ {
+ progressBar.setSecondaryProgress(100 * progressBar.getMax());
+ }
+
+ mp.setOnBufferingUpdateListener(null);
+ }
+ else if (progressBar != null && song.getTranscodedContentType() == null && Util.getMaxBitRate(MediaPlayerService.this) == 0)
+ {
+ secondaryProgress = (int) (((double) percent / (double) 100) * progressBar.getMax());
+ progressBar.setSecondaryProgress(secondaryProgress);
+ }
+ }
+ });
+
+ mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener()
+ {
+ @Override
+ public void onPrepared(MediaPlayer mp)
+ {
+ Log.i(TAG, "Media player prepared");
+
+ setPlayerState(PREPARED);
+
+ SeekBar progressBar = DownloadActivity.getProgressBar();
+
+ if (progressBar != null && downloadFile.isWorkDone())
+ {
+ // Populate seek bar secondary progress if we have a complete file for consistency
+ DownloadActivity.getProgressBar().setSecondaryProgress(100 * progressBar.getMax());
+ }
+
+ synchronized (MediaPlayerService.this)
+ {
+ if (position != 0)
+ {
+ Log.i(TAG, String.format("Restarting player from position %d", position));
+ seekTo(position);
+ }
+ cachedPosition = position;
+
+ if (start)
+ {
+ mediaPlayer.start();
+ setPlayerState(STARTED);
+ }
+ else
+ {
+ setPlayerState(PAUSED);
+ }
+ }
+
+ lifecycleSupport.serializeDownloadQueue();
+ }
+ });
+
+ setupHandlers(downloadFile, partial);
+
+ mediaPlayer.prepareAsync();
+ }
+ catch (Exception x)
+ {
+ handleError(x);
+ }
+ }
+
+ private synchronized void setupNext(final DownloadFile downloadFile)
+ {
+ try
+ {
+ final File file = downloadFile.isCompleteFileAvailable() ? downloadFile.getCompleteFile() : downloadFile.getPartialFile();
+
+ if (nextMediaPlayer != null)
+ {
+ nextMediaPlayer.setOnCompletionListener(null);
+ nextMediaPlayer.release();
+ nextMediaPlayer = null;
+ }
+
+ nextMediaPlayer = new MediaPlayer();
+ nextMediaPlayer.setWakeMode(MediaPlayerService.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()
+ {
+ @Override
+ @SuppressLint("NewApi")
+ public void onPrepared(MediaPlayer mp)
+ {
+ try
+ {
+ setNextPlayerState(PREPARED);
+
+ if (Util.getGaplessPlaybackPreference(MediaPlayerService.this) && 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()
+ {
+ @Override
+ public boolean onError(MediaPlayer mediaPlayer, int what, int extra)
+ {
+ Log.w(TAG, String.format("Error on playing next (%d, %d): %s", what, extra, downloadFile));
+ return true;
+ }
+ });
+
+ nextMediaPlayer.prepareAsync();
+ }
+ catch (Exception x)
+ {
+ handleErrorNext(x);
+ }
+ }
+
+ private void setupHandlers(final DownloadFile downloadFile, final boolean isPartial)
+ {
+ mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener()
+ {
+ @Override
+ public boolean onError(MediaPlayer mediaPlayer, int what, int extra)
+ {
+ Log.w(TAG, String.format("Error on playing file (%d, %d): %s", what, extra, downloadFile));
+ int pos = cachedPosition;
+ reset();
+ downloadFile.setPlaying(false);
+ doPlay(downloadFile, pos, true);
+ downloadFile.setPlaying(true);
+ return true;
+ }
+ });
+
+ final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000;
+
+ 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);
+
+ int pos = cachedPosition;
+ Log.i(TAG, String.format("Ending position %d of %d", pos, duration));
+
+ if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 1000)))
+ {
+ setPlayerStateCompleted();
+
+ if (Util.getGaplessPlaybackPreference(MediaPlayerService.this) && nextPlaying != null && nextPlayerState == PlayerState.PREPARED)
+ {
+ if (!nextSetup)
+ {
+ playNext();
+ }
+ else
+ {
+ nextSetup = false;
+ playNext();
+ }
+ }
+ else
+ {
+ onSongCompleted();
+ }
+
+ return;
+ }
+
+ synchronized (this)
+ {
+ if (downloadFile.isWorkDone())
+ {
+ // Complete was called early even though file is fully buffered
+ Log.i(TAG, String.format("Requesting restart from %d of %d", pos, duration));
+ reset();
+ downloadFile.setPlaying(false);
+ doPlay(downloadFile, pos, true);
+ downloadFile.setPlaying(true);
+ }
+ else
+ {
+ Log.i(TAG, String.format("Requesting restart from %d of %d", pos, duration));
+ reset();
+ bufferTask = new BufferTask(downloadFile, pos);
+ bufferTask.start();
+ }
+ }
+ }
+ });
+ }
+
+ private void onSongCompleted()
+ {
+ int index = getCurrentPlayingIndex();
+
+ if (currentPlaying != null)
+ {
+ final MusicDirectory.Entry song = currentPlaying.getSong();
+
+ if (song != null && song.getBookmarkPosition() > 0 && Util.getShouldClearBookmark(this))
+ {
+ MusicService musicService = MusicServiceFactory.getMusicService(this);
+ try
+ {
+ musicService.deleteBookmark(song.getId(), this, null);
+ }
+ catch (Exception ignored)
+ {
+
+ }
+ }
+ }
+
+ if (index != -1)
+ {
+ switch (getRepeatMode())
+ {
+ case OFF:
+ if (index + 1 < 0 || index + 1 >= size())
+ {
+ if (Util.getShouldClearPlaylist(this))
+ {
+ clear(true);
+ }
+
+ resetPlayback();
+ break;
+ }
+
+ play(index + 1);
+ break;
+ case ALL:
+ play((index + 1) % size());
+ break;
+ case SINGLE:
+ play(index);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public static synchronized void clear(boolean serialize)
+ {
+ MediaPlayerService mediaPlayerService = getRunningInstance();
+
+ if (mediaPlayerService != null) mediaPlayerService.reset();
+ downloadList.clear();
+ revision++;
+ if (currentDownloading != null)
+ {
+ currentDownloading.cancelDownload();
+ currentDownloading = null;
+ }
+ if (mediaPlayerService != null)
+ {
+ mediaPlayerService.setCurrentPlaying(null);
+ updateJukeboxPlaylist();
+ mediaPlayerService.setNextPlaying();
+ }
+
+ if (serialize)
+ {
+ lifecycleSupport.serializeDownloadQueue();
+ }
+ }
+
+ protected static synchronized void checkDownloads(Context context)
+ {
+ if (!Util.isExternalStoragePresent() || !lifecycleSupport.isExternalStorageAvailable())
+ {
+ return;
+ }
+
+ if (shufflePlay)
+ {
+ checkShufflePlay(context);
+ }
+
+ if (jukeboxEnabled || !Util.isNetworkConnected(context))
+ {
+ return;
+ }
+
+ if (MediaPlayerService.downloadList.isEmpty() && MediaPlayerService.backgroundDownloadList.isEmpty())
+ {
+ return;
+ }
+
+ // Need to download current playing?
+ if (MediaPlayerService.currentPlaying != null && MediaPlayerService.currentPlaying != MediaPlayerService.currentDownloading && !MediaPlayerService.currentPlaying.isWorkDone())
+ {
+ // Cancel current download, if necessary.
+ if (MediaPlayerService.currentDownloading != null)
+ {
+ MediaPlayerService.currentDownloading.cancelDownload();
+ }
+
+ MediaPlayerService.currentDownloading = MediaPlayerService.currentPlaying;
+ MediaPlayerService.currentDownloading.download();
+ cleanupCandidates.add(MediaPlayerService.currentDownloading);
+ }
+
+ // Find a suitable target for download.
+ else
+ {
+ if (MediaPlayerService.currentDownloading == null ||
+ MediaPlayerService.currentDownloading.isWorkDone() ||
+ MediaPlayerService.currentDownloading.isFailed() &&
+ (!MediaPlayerService.downloadList.isEmpty() || !MediaPlayerService.backgroundDownloadList.isEmpty()))
+ {
+ MediaPlayerService.currentDownloading = null;
+ int n = size();
+
+ int preloaded = 0;
+
+ if (n != 0)
+ {
+ int start = MediaPlayerService.currentPlaying == null ? 0 : getCurrentPlayingIndex();
+ if (start == -1)
+ {
+ start = 0;
+ }
+ int i = start;
+ do
+ {
+ DownloadFile downloadFile = MediaPlayerService.downloadList.get(i);
+ if (!downloadFile.isWorkDone())
+ {
+ if (downloadFile.shouldSave() || preloaded < Util.getPreloadCount(context))
+ {
+ MediaPlayerService.currentDownloading = downloadFile;
+ MediaPlayerService.currentDownloading.download();
+ cleanupCandidates.add(MediaPlayerService.currentDownloading);
+ if (i == (start + 1))
+ {
+ setNextPlayerState(DOWNLOADING);
+ }
+ break;
+ }
+ }
+ else if (MediaPlayerService.currentPlaying != downloadFile)
+ {
+ preloaded++;
+ }
+
+ i = (i + 1) % n;
+ } while (i != start);
+ }
+
+ if ((preloaded + 1 == n || preloaded >= Util.getPreloadCount(context) || MediaPlayerService.downloadList.isEmpty()) && !MediaPlayerService.backgroundDownloadList.isEmpty())
+ {
+ for (int i = 0; i < MediaPlayerService.backgroundDownloadList.size(); i++)
+ {
+ DownloadFile downloadFile = MediaPlayerService.backgroundDownloadList.get(i);
+ if (downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved()))
+ {
+ if (Util.getShouldScanMedia(context))
+ {
+ Util.scanMedia(context, downloadFile.getCompleteFile());
+ }
+
+ // Don't need to keep list like active song list
+ MediaPlayerService.backgroundDownloadList.remove(i);
+ revision++;
+ i--;
+ }
+ else
+ {
+ MediaPlayerService.currentDownloading = downloadFile;
+ MediaPlayerService.currentDownloading.download();
+ cleanupCandidates.add(MediaPlayerService.currentDownloading);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Delete obsolete .partial and .complete files.
+ cleanup(context);
+ }
+
+ private static synchronized void checkShufflePlay(Context context)
+ {
+ // Get users desired random playlist size
+ int listSize = Util.getMaxSongs(context);
+ boolean wasEmpty = MediaPlayerService.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 : MediaPlayerService.shufflePlayBuffer.get(listSize - size))
+ {
+ DownloadFile downloadFile = new DownloadFile(context, song, false);
+ MediaPlayerService.downloadList.add(downloadFile);
+ revision++;
+ }
+ }
+
+ int currIndex = MediaPlayerService.currentPlaying == null ? 0 : getCurrentPlayingIndex();
+
+ // Only shift playlist if playing song #5 or later.
+ if (currIndex > 4)
+ {
+ int songsToShift = currIndex - 2;
+ for (MusicDirectory.Entry song : MediaPlayerService.shufflePlayBuffer.get(songsToShift))
+ {
+ MediaPlayerService.downloadList.add(new DownloadFile(context, song, false));
+ MediaPlayerService.downloadList.get(0).cancelDownload();
+ MediaPlayerService.downloadList.remove(0);
+ revision++;
+ }
+ }
+
+ if (revisionBefore != revision)
+ {
+ getInstance(context).updateJukeboxPlaylist();
+ }
+
+ if (wasEmpty && !MediaPlayerService.downloadList.isEmpty())
+ {
+ getInstance(context).play(0);
+ }
+ }
+
+ public static void updateJukeboxPlaylist()
+ {
+ if (jukeboxEnabled)
+ {
+ jukeboxService.updatePlaylist();
+ }
+ }
+
+ private static synchronized void cleanup(Context context)
+ {
+ Iterator iterator = cleanupCandidates.iterator();
+ while (iterator.hasNext())
+ {
+ DownloadFile downloadFile = iterator.next();
+ if (downloadFile != MediaPlayerService.currentPlaying && downloadFile != MediaPlayerService.currentDownloading)
+ {
+ if (downloadFile.cleanup())
+ {
+ iterator.remove();
+ }
+ }
+ }
+ }
+
+ public void updateNotification()
+ {
+ if (Util.isNotificationEnabled(this)) {
+ if (isInForeground == true) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification());
+ }
+ else {
+ final NotificationManagerCompat notificationManager =
+ NotificationManagerCompat.from(this);
+ notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification());
+ }
+ Log.w(TAG, "--- Updated notification");
+ }
+ else {
+ startForeground(NOTIFICATION_ID, buildForegroundNotification());
+ isInForeground = true;
+ Log.w(TAG, "--- Created Foreground notification");
+ }
+ }
+ }
+
+ @SuppressWarnings("IconColors")
+ private Notification buildForegroundNotification() {
+ notificationBuilder.setSmallIcon(R.drawable.ic_stat_ultrasonic);
+
+ notificationBuilder.setAutoCancel(false);
+ notificationBuilder.setOngoing(true);
+ notificationBuilder.setOnlyAlertOnce(true);
+ notificationBuilder.setWhen(System.currentTimeMillis());
+ notificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
+ notificationBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
+
+ RemoteViews contentView = new RemoteViews(this.getPackageName(), R.layout.notification);
+ Util.linkButtons(this, contentView, false);
+ RemoteViews bigView = new RemoteViews(this.getPackageName(), R.layout.notification_large);
+ Util.linkButtons(this, bigView, false);
+
+ notificationBuilder.setContent(contentView);
+
+ Intent notificationIntent = new Intent(this, DownloadActivity.class);
+ notificationBuilder.setContentIntent(PendingIntent.getActivity(this, 0, notificationIntent, 0));
+
+ if (playerState == PlayerState.PAUSED || playerState == PlayerState.IDLE) {
+ contentView.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark);
+ bigView.setImageViewResource(R.id.control_play, R.drawable.media_start_normal_dark);
+ } else if (playerState == PlayerState.STARTED) {
+ contentView.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark);
+ bigView.setImageViewResource(R.id.control_play, R.drawable.media_pause_normal_dark);
+ }
+
+ if (currentPlaying != null) {
+ final MusicDirectory.Entry song = currentPlaying.getSong();
+ final String title = song.getTitle();
+ final String text = song.getArtist();
+ final String album = song.getAlbum();
+ final int rating = song.getUserRating() == null ? 0 : song.getUserRating();
+ final int imageSize = Util.getNotificationImageSize(this);
+
+ try {
+ final Bitmap nowPlayingImage = FileUtil.getAlbumArtBitmap(this, currentPlaying.getSong(), imageSize, true);
+ if (nowPlayingImage == null) {
+ contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
+ bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
+ } else {
+ contentView.setImageViewBitmap(R.id.notification_image, nowPlayingImage);
+ bigView.setImageViewBitmap(R.id.notification_image, nowPlayingImage);
+ }
+ } catch (Exception x) {
+ Log.w(TAG, "Failed to get notification cover art", x);
+ contentView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
+ bigView.setImageViewResource(R.id.notification_image, R.drawable.unknown_album);
+ }
+
+
+ contentView.setTextViewText(R.id.trackname, title);
+ bigView.setTextViewText(R.id.trackname, title);
+ contentView.setTextViewText(R.id.artist, text);
+ bigView.setTextViewText(R.id.artist, text);
+ contentView.setTextViewText(R.id.album, album);
+ bigView.setTextViewText(R.id.album, album);
+
+ boolean useFiveStarRating = KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING);
+ if (!useFiveStarRating)
+ bigView.setViewVisibility(R.id.notification_rating, View.INVISIBLE);
+ else {
+ bigView.setImageViewResource(R.id.notification_five_star_1, rating > 0 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
+ bigView.setImageViewResource(R.id.notification_five_star_2, rating > 1 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
+ bigView.setImageViewResource(R.id.notification_five_star_3, rating > 2 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
+ bigView.setImageViewResource(R.id.notification_five_star_4, rating > 3 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
+ bigView.setImageViewResource(R.id.notification_five_star_5, rating > 4 ? R.drawable.ic_star_full_dark : R.drawable.ic_star_hollow_dark);
+ }
+ }
+
+ Notification notification = notificationBuilder.build();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ notification.bigContentView = bigView;
+ }
+
+ return notification;
+ }
+
+ private class PositionCache implements Runnable
+ {
+ boolean isRunning = true;
+
+ public void stop()
+ {
+ isRunning = false;
+ }
+
+ @Override
+ public void run()
+ {
+ Thread.currentThread().setName("PositionCache");
+
+ // Stop checking position before the song reaches completion
+ while (isRunning)
+ {
+ try
+ {
+ if (mediaPlayer != null && playerState == STARTED)
+ {
+ cachedPosition = mediaPlayer.getCurrentPosition();
+ }
+
+ Util.sleepQuietly(25L);
+ }
+ catch (Exception e)
+ {
+ Log.w(TAG, "Crashed getting current position", e);
+ isRunning = false;
+ positionCache = null;
+ }
+ }
+ }
+ }
+
+ private void handleError(Exception x)
+ {
+ Log.w(TAG, String.format("Media player error: %s", x), x);
+
+ try
+ {
+ mediaPlayer.reset();
+ }
+ catch (Exception ex)
+ {
+ Log.w(TAG, String.format("Exception encountered when resetting media player: %s", ex), ex);
+ }
+ }
+
+ private void handleErrorNext(Exception x)
+ {
+ Log.w(TAG, String.format("Next Media player error: %s", x), x);
+ nextMediaPlayer.reset();
+ }
+
+ 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();
+
+ long bufferLength = Util.getBufferLength(MediaPlayerService.this);
+
+ if (bufferLength == 0)
+ {
+ // Set to seconds in a day, basically infinity
+ bufferLength = 86400L;
+ }
+
+ // Calculate roughly how many bytes BUFFER_LENGTH_SECONDS corresponds to.
+ int bitRate = downloadFile.getBitRate();
+ long byteCount = Math.max(100000, bitRate * 1024L / 8L * bufferLength);
+
+ // Find out how large the file should grow before resuming playback.
+ Log.i(TAG, String.format("Buffering from position %d and bitrate %d", position, bitRate));
+ expectedFileSize = (position * bitRate / 8) + byteCount;
+ }
+
+ @Override
+ public void execute()
+ {
+ setPlayerState(DOWNLOADING);
+
+ while (!bufferComplete() && !Util.isOffline(MediaPlayerService.this))
+ {
+ Util.sleepQuietly(1000L);
+ if (isCancelled())
+ {
+ return;
+ }
+ }
+ doPlay(downloadFile, position, true);
+ }
+
+ private boolean bufferComplete()
+ {
+ boolean completeFileAvailable = downloadFile.isWorkDone();
+ long size = partialFile.length();
+
+ Log.i(TAG, String.format("Buffering %s (%d/%d, %s)", partialFile, size, expectedFileSize, completeFileAvailable));
+ return completeFileAvailable || size >= expectedFileSize;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("BufferTask (%s)", downloadFile);
+ }
+ }
+
+ private class CheckCompletionTask extends CancellableTask
+ {
+ private final DownloadFile downloadFile;
+ private final File partialFile;
+
+ public CheckCompletionTask(DownloadFile downloadFile)
+ {
+ super();
+ setNextPlayerState(PlayerState.IDLE);
+
+ this.downloadFile = downloadFile;
+
+ partialFile = downloadFile != null ? downloadFile.getPartialFile() : null;
+ }
+
+ @Override
+ public void execute()
+ {
+ Thread.currentThread().setName("CheckCompletionTask");
+
+ 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()
+ {
+ @Override
+ public void run()
+ {
+ setupNext(downloadFile);
+ }
+ });
+ }
+
+ private boolean bufferComplete()
+ {
+ boolean completeFileAvailable = downloadFile.isWorkDone();
+ Log.i(TAG, String.format("Buffering next %s (%d)", partialFile, partialFile.length()));
+ return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("CheckCompletionTask (%s)", downloadFile);
+ }
+
+ }
+}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Supplier.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Supplier.java
new file mode 100644
index 00000000..9a4a072c
--- /dev/null
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Supplier.java
@@ -0,0 +1,6 @@
+package org.moire.ultrasonic.service;
+
+public abstract class Supplier
+{
+ public abstract T get();
+}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java
index 5e8c5d4a..34df0285 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java
@@ -4,7 +4,7 @@ import android.util.Log;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.service.DownloadFile;
-import org.moire.ultrasonic.service.DownloadService;
+import org.moire.ultrasonic.service.Supplier;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
@@ -32,9 +32,9 @@ public class StreamProxy implements Runnable
private boolean isRunning;
private ServerSocket socket;
private int port;
- private DownloadService downloadService;
+ private Supplier currentPlaying;
- public StreamProxy(DownloadService downloadService)
+ public StreamProxy(Supplier currentPlaying)
{
// Create listening socket
@@ -43,7 +43,7 @@ public class StreamProxy implements Runnable
socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[]{127, 0, 0, 1}));
socket.setSoTimeout(5000);
port = socket.getLocalPort();
- this.downloadService = downloadService;
+ this.currentPlaying = currentPlaying;
}
catch (UnknownHostException e)
{ // impossible
@@ -170,7 +170,7 @@ public class StreamProxy implements Runnable
public void run()
{
Log.i(TAG, "Streaming song in background");
- DownloadFile downloadFile = downloadService.getCurrentPlaying();
+ DownloadFile downloadFile = currentPlaying.get();
MusicDirectory.Entry song = downloadFile.getSong();
long fileSize = downloadFile.getBitRate() * ((song.getDuration() != null) ? song.getDuration() : 0) * 1000 / 8;
Log.i(TAG, String.format("Streaming fileSize: %d", fileSize));
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java
index 4c455236..26a2870a 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java
@@ -56,6 +56,7 @@ import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
import org.moire.ultrasonic.service.DownloadFile;
import org.moire.ultrasonic.service.DownloadService;
import org.moire.ultrasonic.service.DownloadServiceImpl;
+import org.moire.ultrasonic.service.MediaPlayerService;
import org.moire.ultrasonic.service.MusicServiceFactory;
import java.io.*;
@@ -1045,38 +1046,31 @@ public class Util extends DownloadActivity
context.sendBroadcast(avrcpIntent);
}
- public static void broadcastA2dpPlayStatusChange(Context context, PlayerState state, DownloadService downloadService)
+ public static void broadcastA2dpPlayStatusChange(Context context, PlayerState state, Entry currentSong, Integer listSize, Integer id, Integer playerPosition)
{
- if (!Util.getShouldSendBluetoothNotifications(context) || downloadService == null)
+ if (!Util.getShouldSendBluetoothNotifications(context))
{
return;
}
- DownloadFile currentPlaying = downloadService.getCurrentPlaying();
-
- if (currentPlaying != null)
+ if (currentSong != null)
{
Intent avrcpIntent = new Intent(CM_AVRCP_PLAYSTATE_CHANGED);
- Entry song = currentPlaying.getSong();
-
- if (song == null)
+ if (currentSong == null)
{
return;
}
- if (song != currentSong)
+ if (currentSong != currentSong)
{
- currentSong = song;
+ Util.currentSong = currentSong;
}
- String title = song.getTitle();
- String artist = song.getArtist();
- String album = song.getAlbum();
- Integer duration = song.getDuration();
- Integer listSize = downloadService.getDownloads().size();
- Integer id = downloadService.getCurrentPlayingIndex() + 1;
- Integer playerPosition = downloadService.getPlayerPosition();
+ String title = currentSong.getTitle();
+ String artist = currentSong.getArtist();
+ String album = currentSong.getAlbum();
+ Integer duration = currentSong.getDuration();
avrcpIntent.putExtra("track", title);
avrcpIntent.putExtra("track_name", title);
@@ -1089,7 +1083,7 @@ public class Util extends DownloadActivity
if (Util.getShouldSendBluetoothAlbumArt(context))
{
- File albumArtFile = FileUtil.getAlbumArtFile(context, song);
+ File albumArtFile = FileUtil.getAlbumArtFile(context, currentSong);
avrcpIntent.putExtra("coverart", albumArtFile.getAbsolutePath());
avrcpIntent.putExtra("cover", albumArtFile.getAbsolutePath());
}
@@ -1290,55 +1284,55 @@ public class Util extends DownloadActivity
// Emulate media button clicks.
intent = new Intent("1");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_play, pendingIntent);
intent = new Intent("2");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_next, pendingIntent);
intent = new Intent("3");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
intent = new Intent("4");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.control_stop, pendingIntent);
intent = new Intent("RATE_1");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_1));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_1, pendingIntent);
intent = new Intent("RATE_2");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_2));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_2, pendingIntent);
intent = new Intent("RATE_3");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_3));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_3, pendingIntent);
intent = new Intent("RATE_4");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_4));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_4, pendingIntent);
intent = new Intent("RATE_5");
- intent.setComponent(new ComponentName(context, DownloadServiceImpl.class));
+ intent.setComponent(new ComponentName(context, MediaPlayerService.class));
intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_5));
pendingIntent = PendingIntent.getService(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.notification_five_star_5, pendingIntent);