From 13b987791e164169191460188e12a11b372d2962 Mon Sep 17 00:00:00 2001 From: Nite Date: Sun, 21 Jun 2020 09:31:38 +0200 Subject: [PATCH 01/25] Started DownloadServiceImpl refactor --- ultrasonic/src/main/AndroidManifest.xml | 2 +- .../ultrasonic/activity/MainActivity.java | 3 +- .../activity/SubsonicTabActivity.java | 32 +- .../provider/UltraSonicAppWidgetProvider.java | 26 +- .../receiver/MediaButtonIntentReceiver.java | 35 +- .../ultrasonic/service/DownloadFile.java | 5 +- .../service/DownloadServiceImpl.java | 1912 ++--------------- .../DownloadServiceLifecycleSupport.java | 37 +- .../ultrasonic/service/JukeboxService.java | 24 +- .../service/MediaPlayerService.java | 1792 +++++++++++++++ .../moire/ultrasonic/service/Supplier.java | 6 + .../moire/ultrasonic/util/StreamProxy.java | 10 +- .../java/org/moire/ultrasonic/util/Util.java | 48 +- 13 files changed, 2056 insertions(+), 1876 deletions(-) create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/Supplier.java 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); From 53628dde54c076f7a6269f1e10071b5bc61440b5 Mon Sep 17 00:00:00 2001 From: Nite Date: Mon, 22 Jun 2020 18:35:58 +0200 Subject: [PATCH 02/25] Started to use Koin, refactored lifecycleSupport and Intent handling --- .../activity/EqualizerActivity.java | 15 +- .../ultrasonic/activity/MainActivity.java | 9 +- .../activity/SelectPlaylistActivity.java | 2 +- .../activity/SubsonicTabActivity.java | 48 +-- .../ultrasonic/fragment/SettingsFragment.java | 9 +- .../provider/UltraSonicAppWidgetProvider.java | 23 +- .../receiver/A2dpIntentReceiver.java | 24 +- .../receiver/BluetoothIntentReceiver.java | 4 +- .../receiver/MediaButtonIntentReceiver.java | 23 +- .../moire/ultrasonic/service/Consumer.java | 6 + .../ultrasonic/service/DownloadFile.java | 4 +- .../service/DownloadQueueSerializer.java | 105 ++++++ .../service/DownloadServiceImpl.java | 313 +++++++++-------- .../DownloadServiceLifecycleSupport.java | 329 ++++-------------- .../service/ExternalStorageMonitor.java | 55 +++ .../ultrasonic/service/JukeboxService.java | 32 +- .../service/MediaPlayerService.java | 94 +++-- .../service/OfflineMusicService.java | 6 - .../org/moire/ultrasonic/service/State.java | 16 + .../moire/ultrasonic/util/CacheCleaner.java | 24 +- .../org/moire/ultrasonic/util/Constants.java | 11 + .../java/org/moire/ultrasonic/util/Util.java | 55 +-- .../org/moire/ultrasonic/view/SongView.java | 25 +- .../moire/ultrasonic/view/VisualizerView.java | 15 +- .../moire/ultrasonic/di/MusicServiceModule.kt | 12 +- 25 files changed, 651 insertions(+), 608 deletions(-) create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadQueueSerializer.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/State.java diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java index 58047244..329bdf79 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java @@ -38,6 +38,10 @@ import org.moire.ultrasonic.service.DownloadServiceImpl; import java.util.HashMap; import java.util.Map; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + /** * Equalizer controls. * @@ -52,6 +56,8 @@ public class EqualizerActivity extends ResultActivity private EqualizerController equalizerController; private Equalizer equalizer; + private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + @Override public void onCreate(Bundle bundle) { @@ -123,14 +129,7 @@ public class EqualizerActivity extends ResultActivity private void setup() { - DownloadService instance = DownloadServiceImpl.getInstance(); - - if (instance == null) - { - return; - } - - equalizerController = instance.getEqualizerController(); + equalizerController = downloadServiceImpl.getValue().getEqualizerController(); equalizer = equalizerController.getEqualizer(); initEqualizer(); 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 c5a4c53a..1b053e19 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,8 @@ 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.DownloadServiceLifecycleSupport; +import org.moire.ultrasonic.service.ExternalStorageMonitor; import org.moire.ultrasonic.service.MediaPlayerService; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; @@ -47,7 +49,10 @@ import org.moire.ultrasonic.util.Util; import java.util.Collections; +import kotlin.Lazy; + import static java.util.Arrays.asList; +import static org.koin.java.standalone.KoinJavaComponent.inject; public class MainActivity extends SubsonicTabActivity { @@ -68,6 +73,8 @@ public class MainActivity extends SubsonicTabActivity private static boolean infoDialogDisplayed; private static boolean shouldUseId3; + private Lazy lifecycleSupport = inject(DownloadServiceLifecycleSupport.class); + /** * Called when the activity is first created. */ @@ -477,7 +484,7 @@ public class MainActivity extends SubsonicTabActivity private void exit() { - DownloadServiceImpl.getInstance().onCommand(new Intent(this, MediaPlayerService.class)); + lifecycleSupport.getValue().onDestroy(); Util.unregisterMediaButtonEventReceiver(this); finish(); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java index 7af8157d..0ba31f04 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectPlaylistActivity.java @@ -126,7 +126,7 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt List playlists = musicService.getPlaylists(refresh, SelectPlaylistActivity.this, this); if (!Util.isOffline(SelectPlaylistActivity.this)) - new CacheCleaner(SelectPlaylistActivity.this, getDownloadService()).cleanPlaylists(playlists); + new CacheCleaner(SelectPlaylistActivity.this).cleanPlaylists(playlists); return playlists; } 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 43712579..c26f30de 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -29,7 +29,6 @@ import android.media.AudioManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Environment; import androidx.appcompat.app.ActionBar; import android.util.Log; import android.view.*; @@ -39,6 +38,7 @@ import android.widget.*; import net.simonvt.menudrawer.MenuDrawer; import net.simonvt.menudrawer.Position; import org.koin.java.standalone.KoinJavaComponent; +import static org.koin.java.standalone.KoinJavaComponent.inject; import org.moire.ultrasonic.R; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicDirectory.Entry; @@ -56,6 +56,8 @@ import java.io.PrintWriter; import java.util.*; import java.util.regex.Pattern; +import kotlin.Lazy; + /** * @author Sindre Mehus */ @@ -74,6 +76,9 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen private static final String STATE_ACTIVE_POSITION = "org.moire.ultrasonic.activePosition"; private static final int DIALOG_ASK_FOR_SHARE_DETAILS = 102; + private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy lifecycleSupport = inject(DownloadServiceLifecycleSupport.class); + public MenuDrawer menuDrawer; private int activePosition = 1; private int menuActiveViewId; @@ -97,7 +102,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen applyTheme(); super.onCreate(bundle); - if (DownloadServiceImpl.getInstance() == null) new DownloadServiceImpl(getApplicationContext()); setVolumeControlStream(AudioManager.STREAM_MUSIC); if (bundle != null) @@ -155,6 +159,8 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen instance = this; Util.registerMediaButtonEventReceiver(this); + // Lifecycle support's constructor registers some event receivers so it should be created early + lifecycleSupport.getValue(); // Make sure to update theme if (theme != null && !theme.equals(Util.getTheme(this))) @@ -256,27 +262,22 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen if (nowPlayingView != null) { - final DownloadService downloadService = DownloadServiceImpl.getInstance(); + PlayerState playerState = downloadServiceImpl.getValue().getPlayerState(); - if (downloadService != null) + if (playerState.equals(PlayerState.PAUSED) || playerState.equals(PlayerState.STARTED)) { - PlayerState playerState = downloadService.getPlayerState(); + DownloadFile file = downloadServiceImpl.getValue().getCurrentPlaying(); - if (playerState.equals(PlayerState.PAUSED) || playerState.equals(PlayerState.STARTED)) + if (file != null) { - DownloadFile file = downloadService.getCurrentPlaying(); - - if (file != null) - { - final Entry song = file.getSong(); - showNowPlaying(SubsonicTabActivity.this, downloadService, song, playerState); - } - } - else - { - hideNowPlaying(); + final Entry song = file.getSong(); + showNowPlaying(SubsonicTabActivity.this, downloadServiceImpl.getValue(), song, playerState); } } + else + { + hideNowPlaying(); + } } return null; @@ -763,18 +764,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen public DownloadService getDownloadService() { - DownloadService downloadService = DownloadServiceImpl.getInstance(); - - if (downloadService != null) - { - return downloadService; - } - - Log.w(TAG, "DownloadService not running. Attempting to start it."); - - new DownloadServiceImpl(getApplicationContext()); - - return DownloadServiceImpl.getInstance(); + return downloadServiceImpl.getValue(); } protected void warnIfNetworkOrStorageUnavailable() diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java index 864d8a14..57e780ae 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java @@ -22,6 +22,10 @@ import org.moire.ultrasonic.util.*; import java.io.File; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + /** * Shows main app settings. */ @@ -62,6 +66,8 @@ public class SettingsFragment extends PreferenceFragment private SharedPreferences settings; private int activeServers; + private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -401,7 +407,6 @@ public class SettingsFragment extends PreferenceFragment } // Clear download queue. - DownloadService downloadService = DownloadServiceImpl.getInstance(); - downloadService.clear(); + downloadServiceImpl.getValue().clear(); } } 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 8a834026..e88db061 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java @@ -18,8 +18,7 @@ import org.moire.ultrasonic.activity.DownloadActivity; 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.Constants; import org.moire.ultrasonic.util.FileUtil; public class UltraSonicAppWidgetProvider extends AppWidgetProvider @@ -194,27 +193,27 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider Intent intent = new Intent(context, playerActive ? DownloadActivity.class : MainActivity.class); intent.setAction("android.intent.action.MAIN"); intent.addCategory("android.intent.category.LAUNCHER"); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 10, intent, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.appwidget_coverart, pendingIntent); views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent); // Emulate media button clicks. - intent = new Intent("1"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 11, 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, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 12, 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, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 13, intent, 0); views.setOnClickPendingIntent(R.id.control_previous, pendingIntent); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java index 721f0eb1..85ce1782 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java @@ -8,28 +8,24 @@ import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.service.DownloadService; import org.moire.ultrasonic.service.DownloadServiceImpl; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + public class A2dpIntentReceiver extends BroadcastReceiver { - private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse"; + private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); @Override public void onReceive(Context context, Intent intent) { - - DownloadService downloadService = DownloadServiceImpl.getInstance(); - - if (downloadService == null) + if (downloadServiceImpl.getValue().getCurrentPlaying() == null) { return; } - if (downloadService.getCurrentPlaying() == null) - { - return; - } - - Entry song = downloadService.getCurrentPlaying().getSong(); + Entry song = downloadServiceImpl.getValue().getCurrentPlaying().getSong(); if (song == null) { @@ -39,8 +35,8 @@ public class A2dpIntentReceiver extends BroadcastReceiver Intent avrcpIntent = new Intent(PLAYSTATUS_RESPONSE); Integer duration = song.getDuration(); - Integer playerPosition = downloadService.getPlayerPosition(); - Integer listSize = downloadService.getDownloads().size(); + Integer playerPosition = downloadServiceImpl.getValue().getPlayerPosition(); + Integer listSize = downloadServiceImpl.getValue().getDownloads().size(); if (duration != null) { @@ -50,7 +46,7 @@ public class A2dpIntentReceiver extends BroadcastReceiver avrcpIntent.putExtra("position", (long) playerPosition); avrcpIntent.putExtra("ListSize", (long) listSize); - switch (downloadService.getPlayerState()) + switch (downloadServiceImpl.getValue().getPlayerState()) { case STARTED: avrcpIntent.putExtra("playing", true); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/BluetoothIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/BluetoothIntentReceiver.java index 4a731322..4cf7bfe2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/BluetoothIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/BluetoothIntentReceiver.java @@ -24,7 +24,7 @@ import android.content.Context; import android.content.Intent; import android.util.Log; -import org.moire.ultrasonic.service.DownloadServiceImpl; +import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.Util; /** @@ -71,7 +71,7 @@ public class BluetoothIntentReceiver extends BroadcastReceiver if (disconnected) { Log.i(TAG, "Disconnected from Bluetooth device, requesting pause."); - context.sendBroadcast(new Intent(DownloadServiceImpl.CMD_PAUSE)); + context.sendBroadcast(new Intent(Constants.CMD_PAUSE)); } } } 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 09593d9e..2315dc3e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java @@ -21,22 +21,25 @@ package org.moire.ultrasonic.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.util.Log; -import org.moire.ultrasonic.service.DownloadServiceImpl; -import org.moire.ultrasonic.service.MediaPlayerService; +import org.moire.ultrasonic.service.DownloadServiceLifecycleSupport; +import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.Util; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + /** * @author Sindre Mehus */ public class MediaButtonIntentReceiver extends BroadcastReceiver { - private static final String TAG = MediaButtonIntentReceiver.class.getSimpleName(); + private Lazy lifecycleSupport = inject(DownloadServiceLifecycleSupport.class); @Override public void onReceive(Context context, Intent intent) @@ -57,23 +60,17 @@ 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, MediaPlayerService.class); - serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); - try { - if (DownloadServiceImpl.getInstance() == null) new DownloadServiceImpl(context); - DownloadServiceImpl.getInstance().onCommand(serviceIntent); + Intent serviceIntent = new Intent(Constants.CMD_PROCESS_KEYCODE); + serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); + lifecycleSupport.getValue().receiveIntent(serviceIntent); 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/Consumer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java new file mode 100644 index 00000000..4fd5c473 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java @@ -0,0 +1,6 @@ +package org.moire.ultrasonic.service; + +public abstract class Consumer +{ + public abstract void accept(T t); +} 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 22e25e15..2d4c7646 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java @@ -37,11 +37,13 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; +import kotlin.Lazy; import kotlin.Pair; import static android.content.Context.POWER_SERVICE; import static android.os.PowerManager.ON_AFTER_RELEASE; import static android.os.PowerManager.SCREEN_DIM_WAKE_LOCK; +import static org.koin.java.standalone.KoinJavaComponent.inject; /** * @author Sindre Mehus @@ -439,7 +441,7 @@ public class DownloadFile wifiLock.release(); } - new CacheCleaner(context, DownloadServiceImpl.getInstance()).cleanSpace(); + new CacheCleaner(context).cleanSpace(); MediaPlayerService.checkDownloads(context); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadQueueSerializer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadQueueSerializer.java new file mode 100644 index 00000000..570ab067 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadQueueSerializer.java @@ -0,0 +1,105 @@ +package org.moire.ultrasonic.service; + +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; + +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.FileUtil; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class DownloadQueueSerializer +{ + private static final String TAG = DownloadQueueSerializer.class.getSimpleName(); + + public final Lock lock = new ReentrantLock(); + public final AtomicBoolean setup = new AtomicBoolean(false); + private Context context; + + public DownloadQueueSerializer(Context context) + { + this.context = context; + } + + public void serializeDownloadQueue(Iterable songs, int currentPlayingIndex, int currentPlayingPosition) + { + if (!setup.get()) + { + return; + } + + new SerializeTask().execute(songs, currentPlayingIndex, currentPlayingPosition); + } + + public void serializeDownloadQueueNow(Iterable songs, int currentPlayingIndex, int currentPlayingPosition) + { + State state = new State(); + for (DownloadFile downloadFile : songs) + { + state.songs.add(downloadFile.getSong()); + } + state.currentPlayingIndex = currentPlayingIndex; + state.currentPlayingPosition = currentPlayingPosition; + + Log.i(TAG, String.format("Serialized currentPlayingIndex: %d, currentPlayingPosition: %d", state.currentPlayingIndex, state.currentPlayingPosition)); + FileUtil.serialize(context, state, Constants.FILENAME_DOWNLOADS_SER); + } + + public void deserializeDownloadQueue(Consumer afterDeserialized) + { + new DeserializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, afterDeserialized); + } + + public void deserializeDownloadQueueNow(Consumer afterDeserialized) + { + State state = FileUtil.deserialize(context, Constants.FILENAME_DOWNLOADS_SER); + if (state == null) return; + Log.i(TAG, "Deserialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition); + afterDeserialized.accept(state); + } + + private class SerializeTask extends AsyncTask + { + @Override + protected Void doInBackground(Object... params) + { + if (lock.tryLock()) + { + try + { + Thread.currentThread().setName("SerializeTask"); + serializeDownloadQueueNow((Iterable)params[0], (int)params[1], (int)params[2]); + } + finally + { + lock.unlock(); + } + } + return null; + } + } + + private class DeserializeTask extends AsyncTask + { + @Override + protected Void doInBackground(Object... params) + { + try + { + Thread.currentThread().setName("DeserializeTask"); + lock.lock(); + deserializeDownloadQueueNow((Consumer)params[0]); + setup.set(true); + } + finally + { + lock.unlock(); + } + + return null; + } + } +} 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 47b37406..ba3e1de0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java @@ -20,7 +20,6 @@ package org.moire.ultrasonic.service; import android.content.Context; import android.content.Intent; -import android.os.PowerManager; import android.util.Log; import org.koin.java.standalone.KoinJavaComponent; @@ -42,6 +41,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; import static org.moire.ultrasonic.service.MediaPlayerService.playerState; /** @@ -52,26 +54,18 @@ public class DownloadServiceImpl implements DownloadService { private static final String TAG = DownloadServiceImpl.class.getSimpleName(); - public static final String CMD_PLAY = "org.moire.ultrasonic.CMD_PLAY"; - public static final String CMD_TOGGLEPAUSE = "org.moire.ultrasonic.CMD_TOGGLEPAUSE"; - public static final String CMD_PAUSE = "org.moire.ultrasonic.CMD_PAUSE"; - public static final String CMD_STOP = "org.moire.ultrasonic.CMD_STOP"; - public static final String CMD_PREVIOUS = "org.moire.ultrasonic.CMD_PREVIOUS"; - public static final String CMD_NEXT = "org.moire.ultrasonic.CMD_NEXT"; + private final LRUCache downloadFileCache = new LRUCache<>(100); - private DownloadServiceLifecycleSupport lifecycleSupport; - - private final LRUCache downloadFileCache = new LRUCache(100); - - private static DownloadServiceImpl instance; private String suggestedPlaylistName; private boolean keepScreenOn; private boolean showVisualization; - private boolean jukeboxEnabled; private boolean autoPlayStart; private Context context; + public Lazy jukeboxService = inject(JukeboxService.class); + private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); + private Lazy externalStorageMonitor = inject(ExternalStorageMonitor.class); public DownloadServiceImpl(Context context) { @@ -79,25 +73,152 @@ public class DownloadServiceImpl implements DownloadService // TODO: refactor MediaPlayerService.shufflePlayBuffer = new ShufflePlayBuffer(context); - MediaPlayerService.jukeboxService = new JukeboxService(context, this); + externalStorageMonitor.getValue().onCreate(new Runnable() { + @Override + public void run() { + reset(); + } + }); - instance = this; - lifecycleSupport = new DownloadServiceLifecycleSupport(context,this); - lifecycleSupport.onCreate(); - MediaPlayerService.lifecycleSupport = lifecycleSupport; + int instance = Util.getActiveServer(context); + setJukeboxEnabled(Util.getJukeboxEnabled(context, instance)); Log.i(TAG, "DownloadServiceImpl created"); } - public void onCommand(Intent intent) + public void onDestroy() { - lifecycleSupport.onStart(intent); - Log.i(TAG, "DownloadServiceImpl received intent"); + externalStorageMonitor.getValue().onDestroy(); + context.stopService(new Intent(context, MediaPlayerService.class)); + Log.i(TAG, "DownloadServiceImpl destroyed"); } - public static DownloadServiceImpl getInstance() + private void executeOnStartedMediaPlayerService(final Consumer taskToExecute) { - return instance; + Thread t = new Thread() + { + public void run() + { + MediaPlayerService instance = MediaPlayerService.getInstance(context); + taskToExecute.accept(instance); + } + }; + t.start(); + } + + @Override + public void restore(List songs, final int currentPlayingIndex, final int currentPlayingPosition, final boolean autoPlay, boolean newPlaylist) + { + download(songs, false, false, false, false, newPlaylist); + + if (currentPlayingIndex != -1) + { + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.play(currentPlayingIndex, autoPlayStart); + } + }); + + if (MediaPlayerService.currentPlaying != null) + { + if (autoPlay && jukeboxService.getValue().isEnabled()) + { + jukeboxService.getValue().skip(getCurrentPlayingIndex(), currentPlayingPosition / 1000); + } + else + { + if (MediaPlayerService.currentPlaying.isCompleteFileAvailable()) + { + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.doPlay(MediaPlayerService.currentPlaying, currentPlayingPosition, autoPlay); + } + }); + } + } + } + + autoPlayStart = false; + } + } + + @Override + public synchronized void play(final int index) + { + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.play(index, true); + } + }); + } + + public synchronized void play() + { + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.play(); + } + }); + } + + @Override + public synchronized void togglePlayPause() + { + if (playerState == PlayerState.IDLE) autoPlayStart = true; + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.togglePlayPause(); + } + }); + } + + @Override + public synchronized void seekTo(final int position) + { + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.seekTo(position); + } + }); + } + + @Override + public synchronized void pause() + { + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.pause(); + } + }); + } + + @Override + public synchronized void start() + { + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.start(); + } + }); + } + + @Override + public synchronized void stop() + { + executeOnStartedMediaPlayerService(new Consumer() { + @Override + public void accept(MediaPlayerService mediaPlayerService) { + mediaPlayerService.stop(); + } + }); } @Override @@ -129,8 +250,6 @@ public class DownloadServiceImpl implements DownloadService MediaPlayerService.downloadList.add(getCurrentPlayingIndex() + offset, downloadFile); offset++; } - - MediaPlayerService.revision++; } else { @@ -149,10 +268,10 @@ public class DownloadServiceImpl implements DownloadService if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); } - MediaPlayerService.revision++; } + MediaPlayerService.revision++; - MediaPlayerService.updateJukeboxPlaylist(); + jukeboxService.getValue().updatePlaylist(); if (shuffle) shuffle(); @@ -171,7 +290,7 @@ public class DownloadServiceImpl implements DownloadService MediaPlayerService.checkDownloads(context); } - lifecycleSupport.serializeDownloadQueue(); + downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); } @Override @@ -186,35 +305,7 @@ public class DownloadServiceImpl implements DownloadService MediaPlayerService.revision++; MediaPlayerService.checkDownloads(context); - lifecycleSupport.serializeDownloadQueue(); - } - - @Override - public void restore(List songs, int currentPlayingIndex, int currentPlayingPosition, boolean autoPlay, boolean newPlaylist) - { - download(songs, false, false, false, false, newPlaylist); - - if (currentPlayingIndex != -1) - { - MediaPlayerService.getInstance(context).play(currentPlayingIndex, autoPlayStart); - - if (MediaPlayerService.currentPlaying != null) - { - if (autoPlay && jukeboxEnabled) - { - MediaPlayerService.jukeboxService.skip(getCurrentPlayingIndex(), currentPlayingPosition / 1000); - } - else - { - if (MediaPlayerService.currentPlaying.isCompleteFileAvailable()) - { - MediaPlayerService.getInstance(context).doPlay(MediaPlayerService.currentPlaying, currentPlayingPosition, autoPlay); - } - } - } - - autoPlayStart = false; - } + downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); } public synchronized void setCurrentPlaying(DownloadFile currentPlaying) @@ -238,13 +329,13 @@ public class DownloadServiceImpl implements DownloadService @Override public void stopJukeboxService() { - MediaPlayerService.jukeboxService.stopJukeboxService(); + jukeboxService.getValue().stopJukeboxService(); } @Override public void startJukeboxService() { - MediaPlayerService.jukeboxService.startJukeboxService(); + jukeboxService.getValue().startJukeboxService(); } @Override @@ -274,8 +365,8 @@ public class DownloadServiceImpl implements DownloadService MediaPlayerService.downloadList.add(0, MediaPlayerService.currentPlaying); } MediaPlayerService.revision++; - lifecycleSupport.serializeDownloadQueue(); - MediaPlayerService.updateJukeboxPlaylist(); + downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + jukeboxService.getValue().updatePlaylist(); MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); @@ -349,12 +440,17 @@ public class DownloadServiceImpl implements DownloadService @Override public synchronized void clear() { - MediaPlayerService.clear(true); + clear(true); } public synchronized void clear(boolean serialize) { MediaPlayerService.clear(serialize); + jukeboxService.getValue().updatePlaylist(); + if (serialize) + { + downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + } } @Override @@ -383,8 +479,8 @@ public class DownloadServiceImpl implements DownloadService } } - lifecycleSupport.serializeDownloadQueue(); - MediaPlayerService.updateJukeboxPlaylist(); + downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + jukeboxService.getValue().updatePlaylist(); } @Override @@ -415,8 +511,8 @@ public class DownloadServiceImpl implements DownloadService MediaPlayerService.downloadList.remove(downloadFile); MediaPlayerService.backgroundDownloadList.remove(downloadFile); MediaPlayerService.revision++; - lifecycleSupport.serializeDownloadQueue(); - MediaPlayerService.updateJukeboxPlaylist(); + downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + jukeboxService.getValue().updatePlaylist(); if (downloadFile == MediaPlayerService.nextPlaying) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); @@ -461,10 +557,7 @@ public class DownloadServiceImpl implements DownloadService } @Override - public List getSongs() - { - return MediaPlayerService.downloadList; - } + public List getSongs() { return MediaPlayerService.downloadList; } @Override public long getDownloadListDuration() @@ -495,7 +588,7 @@ public class DownloadServiceImpl implements DownloadService @Override public synchronized List getDownloads() { - List temp = new ArrayList(); + List temp = new ArrayList<>(); temp.addAll(MediaPlayerService.downloadList); temp.addAll(MediaPlayerService.backgroundDownloadList); return temp; @@ -507,33 +600,6 @@ public class DownloadServiceImpl implements DownloadService return MediaPlayerService.backgroundDownloadList; } - @Override - public synchronized void play(int index) - { - MediaPlayerService.getInstance(context).play(index, true); - } - - public synchronized void play() - { - MediaPlayerService.getInstance(context).play(); - } - - /** - * Plays or resumes the playback, depending on the current player state. - */ - @Override - public synchronized void togglePlayPause() - { - if (playerState == PlayerState.IDLE) autoPlayStart = true; - MediaPlayerService.getInstance(context).togglePlayPause(); - } - - @Override - public synchronized void seekTo(int position) - { - MediaPlayerService.getInstance(context).seekTo(position); - } - @Override public synchronized void previous() { @@ -564,24 +630,6 @@ public class DownloadServiceImpl implements DownloadService } } - @Override - public synchronized void pause() - { - MediaPlayerService.getInstance(context).pause(); - } - - @Override - public synchronized void stop() - { - MediaPlayerService.getInstance(context).stop(); - } - - @Override - public synchronized void start() - { - MediaPlayerService.getInstance(context).start(); - } - @Override public synchronized void reset() { @@ -659,18 +707,16 @@ public class DownloadServiceImpl implements DownloadService @Override public boolean isJukeboxEnabled() { - return jukeboxEnabled; + return jukeboxService.getValue().isEnabled(); } @Override public boolean isJukeboxAvailable() { - MusicService musicService = MusicServiceFactory.getMusicService(context); - try { String username = Util.getUserName(context, Util.getActiveServer(context)); - UserInfo user = musicService.getUser(username, context, null); + UserInfo user = MusicServiceFactory.getMusicService(context).getUser(username, context, null); return user.getJukeboxRole(); } catch (Exception e) @@ -684,12 +730,10 @@ public class DownloadServiceImpl implements DownloadService @Override public boolean isSharingAvailable() { - MusicService musicService = MusicServiceFactory.getMusicService(context); - try { String username = Util.getUserName(context, Util.getActiveServer(context)); - UserInfo user = musicService.getUser(username, context, null); + UserInfo user = MusicServiceFactory.getMusicService(context).getUser(username, context, null); return user.getShareRole(); } catch (Exception e) @@ -703,12 +747,12 @@ public class DownloadServiceImpl implements DownloadService @Override public void setJukeboxEnabled(boolean jukeboxEnabled) { - this.jukeboxEnabled = jukeboxEnabled; - MediaPlayerService.jukeboxService.setEnabled(jukeboxEnabled); + jukeboxService.getValue().setEnabled(jukeboxEnabled); + setPlayerState(PlayerState.IDLE); if (jukeboxEnabled) { - MediaPlayerService.jukeboxService.startJukeboxService(); + jukeboxService.getValue().startJukeboxService(); reset(); @@ -720,14 +764,14 @@ public class DownloadServiceImpl implements DownloadService } else { - MediaPlayerService.jukeboxService.stopJukeboxService(); + jukeboxService.getValue().stopJukeboxService(); } } @Override public void adjustJukeboxVolume(boolean up) { - MediaPlayerService.jukeboxService.adjustVolume(up); + jukeboxService.getValue().adjustVolume(up); } @Override @@ -756,9 +800,9 @@ public class DownloadServiceImpl implements DownloadService DownloadFile movedSong = list.remove(from); list.add(to, movedSong); - if (jukeboxEnabled && mainList) + if (jukeboxService.getValue().isEnabled() && mainList) { - MediaPlayerService.updateJukeboxPlaylist(); + jukeboxService.getValue().updatePlaylist(); } else if (mainList && (movedSong == MediaPlayerService.nextPlaying || (currentPlayingIndex + 1) == to)) { @@ -797,11 +841,9 @@ public class DownloadServiceImpl implements DownloadService @Override public void run() { - final MusicService musicService = MusicServiceFactory.getMusicService(context); - try { - musicService.setRating(song.getId(), rating, context, null); + MusicServiceFactory.getMusicService(context).setRating(song.getId(), rating, context, null); } catch (Exception e) { @@ -812,9 +854,4 @@ public class DownloadServiceImpl implements DownloadService updateNotification(); } - - private void handleError(Exception x) - { - 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 4cbcef74..a8ce6468 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java @@ -25,168 +25,80 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.media.AudioManager; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; import android.util.Log; import android.view.KeyEvent; import org.moire.ultrasonic.R; -import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.PlayerState; import org.moire.ultrasonic.util.CacheCleaner; -import org.moire.ultrasonic.util.FileUtil; +import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.Util; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; /** * @author Sindre Mehus */ public class DownloadServiceLifecycleSupport { - private static final String TAG = DownloadServiceLifecycleSupport.class.getSimpleName(); - private static final String FILENAME_DOWNLOADS_SER = "downloadstate.ser"; - private final DownloadServiceImpl downloadService; - private ScheduledExecutorService executorService; + private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); + private final DownloadServiceImpl downloadService; // From DI + private BroadcastReceiver headsetEventReceiver; - private BroadcastReceiver ejectEventReceiver; - private PhoneStateListener phoneStateListener; - 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. - */ - private BroadcastReceiver intentReceiver = new BroadcastReceiver() - { - @Override - public void onReceive(Context context, Intent intent) - { - String action = intent.getAction(); - Log.i(TAG, "intentReceiver.onReceive: " + action); - if (DownloadServiceImpl.CMD_PLAY.equals(action)) - { - downloadService.play(); - } - else if (DownloadServiceImpl.CMD_NEXT.equals(action)) - { - downloadService.next(); - } - else if (DownloadServiceImpl.CMD_PREVIOUS.equals(action)) - { - downloadService.previous(); - } - else if (DownloadServiceImpl.CMD_TOGGLEPAUSE.equals(action)) - { - downloadService.togglePlayPause(); - } - else if (DownloadServiceImpl.CMD_PAUSE.equals(action)) - { - downloadService.pause(); - } - else if (DownloadServiceImpl.CMD_STOP.equals(action)) - { - downloadService.pause(); - downloadService.seekTo(0); - } - } - }; - - - public DownloadServiceLifecycleSupport(Context context, DownloadServiceImpl downloadService) + public DownloadServiceLifecycleSupport(Context context, final DownloadServiceImpl downloadService) { this.downloadService = downloadService; this.context = context; - } - public void onCreate() - { - Runnable downloadChecker = new Runnable() - { - @Override - public void run() - { - try - { - MediaPlayerService.checkDownloads(context); - } - catch (Throwable x) - { - Log.e(TAG, "checkDownloads() failed.", x); - } - } - }; - - executorService = Executors.newSingleThreadScheduledExecutor(); - executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS); - - registerHeadsetReceiver(); - - // Stop when SD card is ejected. - ejectEventReceiver = new BroadcastReceiver() - { - @Override - public void onReceive(Context context, Intent intent) - { - externalStorageAvailable = Intent.ACTION_MEDIA_MOUNTED.equals(intent.getAction()); - if (!externalStorageAvailable) - { - Log.i(TAG, "External media is ejecting. Stopping playback."); - downloadService.reset(); - } - else - { - Log.i(TAG, "External media is available."); - } - } - }; - IntentFilter ejectFilter = new IntentFilter(Intent.ACTION_MEDIA_EJECT); - ejectFilter.addAction(Intent.ACTION_MEDIA_MOUNTED); - ejectFilter.addDataScheme("file"); - context.registerReceiver(ejectEventReceiver, ejectFilter); + registerHeadsetReceiver(); // React to media buttons. Util.registerMediaButtonEventReceiver(context); - // Pause temporarily on incoming phone calls. - //phoneStateListener = new MyPhoneStateListener(); - //TelephonyManager telephonyManager = (TelephonyManager) downloadService.getSystemService(Context.TELEPHONY_SERVICE); - //telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); - // Register the handler for outside intents. IntentFilter commandFilter = new IntentFilter(); - commandFilter.addAction(DownloadServiceImpl.CMD_PLAY); - commandFilter.addAction(DownloadServiceImpl.CMD_TOGGLEPAUSE); - commandFilter.addAction(DownloadServiceImpl.CMD_PAUSE); - commandFilter.addAction(DownloadServiceImpl.CMD_STOP); - commandFilter.addAction(DownloadServiceImpl.CMD_PREVIOUS); - commandFilter.addAction(DownloadServiceImpl.CMD_NEXT); + commandFilter.addAction(Constants.CMD_PLAY); + commandFilter.addAction(Constants.CMD_TOGGLEPAUSE); + commandFilter.addAction(Constants.CMD_PAUSE); + commandFilter.addAction(Constants.CMD_STOP); + commandFilter.addAction(Constants.CMD_PREVIOUS); + commandFilter.addAction(Constants.CMD_NEXT); + commandFilter.addAction(Constants.CMD_PROCESS_KEYCODE); context.registerReceiver(intentReceiver, commandFilter); - int instance = Util.getActiveServer(context); - downloadService.setJukeboxEnabled(Util.getJukeboxEnabled(context, instance)); + downloadQueueSerializer.getValue().deserializeDownloadQueue(new Consumer() { + @Override + public void accept(State state) { + downloadService.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, false, false); - deserializeDownloadQueue(); + // Work-around: Serialize again, as the restore() method creates a serialization without current playing info. + downloadQueueSerializer.getValue().serializeDownloadQueue(downloadService.getSongs(), + downloadService.getCurrentPlayingIndex(), downloadService.getPlayerPosition()); + } + }); - new CacheCleaner(context, downloadService).clean(); + new CacheCleaner(context).clean(); + Log.i(TAG, "LifecycleSupport created"); } - private void registerHeadsetReceiver() { + public void onDestroy() + { + downloadService.clear(false); + context.unregisterReceiver(headsetEventReceiver); + context.unregisterReceiver(intentReceiver); + downloadService.onDestroy(); + Log.i(TAG, "LifecycleSupport destroyed"); + } + + private void registerHeadsetReceiver() { // Pause when headset is unplugged. final SharedPreferences sp = Util.getPreferences(context); final String spKey = context @@ -223,8 +135,9 @@ public class DownloadServiceLifecycleSupport context.registerReceiver(headsetEventReceiver, headsetIntentFilter); } - public void onStart(Intent intent) + public void receiveIntent(Intent intent) { + Log.i(TAG, "Received intent"); if (intent != null && intent.getExtras() != null) { KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT); @@ -235,66 +148,6 @@ public class DownloadServiceLifecycleSupport } } - public void onDestroy() - { - executorService.shutdown(); - serializeDownloadQueueNow(); - downloadService.clear(false); - context.unregisterReceiver(ejectEventReceiver); - context.unregisterReceiver(headsetEventReceiver); - context.unregisterReceiver(intentReceiver); - } - - public boolean isExternalStorageAvailable() - { - return externalStorageAvailable; - } - - public void serializeDownloadQueue() - { - if (!setup.get()) - { - return; - } - - new SerializeTask().execute(); - } - - public void serializeDownloadQueueNow() - { - Iterable songs = new ArrayList(downloadService.getSongs()); - State state = new State(); - for (DownloadFile downloadFile : songs) - { - state.songs.add(downloadFile.getSong()); - } - state.currentPlayingIndex = downloadService.getCurrentPlayingIndex(); - state.currentPlayingPosition = downloadService.getPlayerPosition(); - - Log.i(TAG, String.format("Serialized currentPlayingIndex: %d, currentPlayingPosition: %d", state.currentPlayingIndex, state.currentPlayingPosition)); - FileUtil.serialize(context, state, FILENAME_DOWNLOADS_SER); - } - - private void deserializeDownloadQueue() - { - new DeserializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void deserializeDownloadQueueNow() - { - State state = FileUtil.deserialize(context, FILENAME_DOWNLOADS_SER); - if (state == null) - { - return; - } - Log.i(TAG, "Deserialized currentPlayingIndex: " + state.currentPlayingIndex + ", currentPlayingPosition: " + state.currentPlayingPosition); - // TODO: here the autoPlay = false creates problems when Ultrasonic is started by a Play MediaButton as the player won't start this way. - downloadService.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, false, false); - - // Work-around: Serialize again, as the restore() method creates a serialization without current playing info. - serializeDownloadQueue(); - } - private void handleKeyEvent(KeyEvent event) { if (event.getAction() != KeyEvent.ACTION_DOWN || event.getRepeatCount() > 0) @@ -321,11 +174,7 @@ public class DownloadServiceLifecycleSupport downloadService.stop(); break; case KeyEvent.KEYCODE_MEDIA_PLAY: - if (downloadService.getPlayerState() == PlayerState.IDLE) - { - downloadService.play(); - } - else if (downloadService.getPlayerState() != PlayerState.STARTED) + if (downloadService.getPlayerState() != PlayerState.STARTED) { downloadService.start(); } @@ -354,87 +203,39 @@ public class DownloadServiceLifecycleSupport } /** - * Logic taken from packages/apps/Music. Will pause when an incoming - * call rings or if a call (incoming or outgoing) is connected. + * This receiver manages the intent that could come from other applications. */ - private class MyPhoneStateListener extends PhoneStateListener + private BroadcastReceiver intentReceiver = new BroadcastReceiver() { - private boolean resumeAfterCall; - @Override - public void onCallStateChanged(int state, String incomingNumber) + public void onReceive(Context context, Intent intent) { - switch (state) + String action = intent.getAction(); + if (action == null) return; + Log.i(TAG, "intentReceiver.onReceive: " + action); + + switch(action) { - case TelephonyManager.CALL_STATE_RINGING: - case TelephonyManager.CALL_STATE_OFFHOOK: - if (downloadService.getPlayerState() == PlayerState.STARTED && !downloadService.isJukeboxEnabled()) - { - resumeAfterCall = true; - downloadService.pause(); - } + case Constants.CMD_PLAY: + downloadService.play(); break; - case TelephonyManager.CALL_STATE_IDLE: - if (resumeAfterCall) - { - resumeAfterCall = false; - downloadService.start(); - } + case Constants.CMD_NEXT: + downloadService.next(); break; - default: + case Constants.CMD_PREVIOUS: + downloadService.previous(); + break; + case Constants.CMD_TOGGLEPAUSE: + downloadService.togglePlayPause(); + break; + case Constants.CMD_STOP: + downloadService.pause(); + downloadService.seekTo(0); + break; + case Constants.CMD_PROCESS_KEYCODE: + receiveIntent(intent); break; } } - } - - private static class State implements Serializable - { - private static final long serialVersionUID = -6346438781062572270L; - - private List songs = new ArrayList(); - private int currentPlayingIndex; - private int currentPlayingPosition; - } - - private class SerializeTask extends AsyncTask - { - @Override - protected Void doInBackground(Void... params) - { - if (lock.tryLock()) - { - try - { - Thread.currentThread().setName("SerializeTask"); - serializeDownloadQueueNow(); - } - finally - { - lock.unlock(); - } - } - return null; - } - } - - private class DeserializeTask extends AsyncTask - { - @Override - protected Void doInBackground(Void... params) - { - try - { - Thread.currentThread().setName("DeserializeTask"); - lock.lock(); - deserializeDownloadQueueNow(); - setup.set(true); - } - finally - { - lock.unlock(); - } - - return null; - } - } + }; } \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java new file mode 100644 index 00000000..489eb39b --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java @@ -0,0 +1,55 @@ +package org.moire.ultrasonic.service; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +public class ExternalStorageMonitor +{ + private static final String TAG = ExternalStorageMonitor.class.getSimpleName(); + + private Context context; + private BroadcastReceiver ejectEventReceiver; + private boolean externalStorageAvailable = true; + + public ExternalStorageMonitor(Context context) + { + this.context = context; + } + + public void onCreate(final Runnable ejectedCallback) + { + // Stop when SD card is ejected. + ejectEventReceiver = new BroadcastReceiver() + { + @Override + public void onReceive(Context context, Intent intent) + { + externalStorageAvailable = Intent.ACTION_MEDIA_MOUNTED.equals(intent.getAction()); + if (!externalStorageAvailable) + { + Log.i(TAG, "External media is ejecting. Stopping playback."); + ejectedCallback.run(); + } + else + { + Log.i(TAG, "External media is available."); + } + } + }; + + IntentFilter ejectFilter = new IntentFilter(Intent.ACTION_MEDIA_EJECT); + ejectFilter.addAction(Intent.ACTION_MEDIA_MOUNTED); + ejectFilter.addDataScheme("file"); + context.registerReceiver(ejectEventReceiver, ejectFilter); + } + + public void onDestroy() + { + context.unregisterReceiver(ejectEventReceiver); + } + + public boolean isExternalStorageAvailable() { return externalStorageAvailable; } +} 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 cc321cb3..afbda458 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java @@ -44,6 +44,10 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + /** * Provides an asynchronous interface to the remote jukebox on the Subsonic server. * @@ -52,13 +56,10 @@ import java.util.concurrent.atomic.AtomicLong; */ public class JukeboxService { - private static final String TAG = JukeboxService.class.getSimpleName(); private static final long STATUS_UPDATE_INTERVAL_SECONDS = 5L; - private final Handler handler = new Handler(); private final TaskQueue tasks = new TaskQueue(); - private final DownloadServiceImpl downloadService; private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture statusUpdateFuture; private final AtomicLong timeOfLastUpdate = new AtomicLong(); @@ -67,18 +68,20 @@ public class JukeboxService private VolumeToast volumeToast; private AtomicBoolean running = new AtomicBoolean(); private Thread serviceThread; + private boolean enabled = false; private Context context; + private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + // TODO: Report warning if queue fills up. // TODO: Create shutdown method? // TODO: Disable repeat. // TODO: Persist RC state? // TODO: Minimize status updates. - public JukeboxService(Context context, DownloadServiceImpl downloadService) + public JukeboxService(Context context) { this.context = context; - this.downloadService = downloadService; } public void startJukeboxService() @@ -179,9 +182,9 @@ public class JukeboxService // Track change? Integer index = jukeboxStatus.getCurrentPlayingIndex(); - if (index != null && index != -1 && index != downloadService.getCurrentPlayingIndex()) + if (index != null && index != -1 && index != downloadServiceImpl.getValue().getCurrentPlayingIndex()) { - downloadService.setCurrentPlaying(index); + downloadServiceImpl.getValue().setCurrentPlaying(index); } } @@ -209,7 +212,7 @@ public class JukeboxService { Log.w(TAG, x.toString()); - handler.post(new Runnable() + new Handler().post(new Runnable() { @Override public void run() @@ -218,17 +221,19 @@ public class JukeboxService } }); - downloadService.setJukeboxEnabled(false); + downloadServiceImpl.getValue().setJukeboxEnabled(false); } public void updatePlaylist() { + if (!enabled) return; + tasks.remove(Skip.class); tasks.remove(Stop.class); tasks.remove(Start.class); List ids = new ArrayList(); - for (DownloadFile file : downloadService.getDownloads()) + for (DownloadFile file : downloadServiceImpl.getValue().getDownloads()) { ids.add(file.getSong().getId()); } @@ -250,7 +255,7 @@ public class JukeboxService } tasks.add(new Skip(index, offsetSeconds)); - downloadService.setPlayerState(PlayerState.STARTED); + downloadServiceImpl.getValue().setPlayerState(PlayerState.STARTED); } public void stop() @@ -320,8 +325,11 @@ public class JukeboxService } stop(); + } - downloadService.setPlayerState(PlayerState.IDLE); + public boolean isEnabled() + { + return enabled; } private static class TaskQueue diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index 0d44ab69..2797f967 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -32,6 +32,7 @@ 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.MainActivity; import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.audiofx.EqualizerController; import org.moire.ultrasonic.audiofx.VisualizerController; @@ -58,7 +59,13 @@ import java.net.URLEncoder; import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; import static org.moire.ultrasonic.domain.PlayerState.COMPLETED; import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING; import static org.moire.ultrasonic.domain.PlayerState.IDLE; @@ -101,9 +108,11 @@ public class MediaPlayerService extends Service public static DownloadFile currentDownloading; public static DownloadFile nextPlaying; - public static boolean jukeboxEnabled; - public static JukeboxService jukeboxService; - public static DownloadServiceLifecycleSupport lifecycleSupport; + public Lazy jukeboxService = inject(JukeboxService.class); + private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); + private Lazy externalStorageMonitor = inject(ExternalStorageMonitor.class); + + private ScheduledExecutorService executorService; public static int cachedPosition; private PositionCache positionCache; @@ -233,6 +242,25 @@ public class MediaPlayerService extends Service } }).start(); + Runnable downloadChecker = new Runnable() + { + @Override + public void run() + { + try + { + MediaPlayerService.checkDownloads(MediaPlayerService.this); + } + catch (Throwable x) + { + Log.e(TAG, "checkDownloads() failed.", x); + } + } + }; + + executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS); + audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); setUpRemoteControlClient(); @@ -274,7 +302,8 @@ public class MediaPlayerService extends Service // We should use a single notification builder, otherwise the notification may not be updated notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID); - + // Update notification early. It is better to show an empty one temporarily than waiting too long and letting Android kill the app + updateNotification(); instance = this; Log.i(TAG, "MediaPlayerService created"); @@ -284,7 +313,6 @@ public class MediaPlayerService extends Service public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); - lifecycleSupport.onStart(intent); return START_NOT_STICKY; } @@ -296,6 +324,8 @@ public class MediaPlayerService extends Service instance = null; reset(); + executorService.shutdown(); + try { mediaPlayer.release(); @@ -501,9 +531,9 @@ public class MediaPlayerService extends Service { try { - if (jukeboxEnabled) + if (jukeboxService.getValue().isEnabled()) { - jukeboxService.skip(getCurrentPlayingIndex(), position / 1000); + jukeboxService.getValue().skip(getCurrentPlayingIndex(), position / 1000); } else { @@ -528,7 +558,7 @@ public class MediaPlayerService extends Service return 0; } - return jukeboxEnabled ? jukeboxService.getPositionSeconds() * 1000 : cachedPosition; + return jukeboxService.getValue().isEnabled() ? jukeboxService.getValue().getPositionSeconds() * 1000 : cachedPosition; } catch (Exception x) { @@ -736,9 +766,9 @@ public class MediaPlayerService extends Service if (start) { - if (jukeboxEnabled) + if (jukeboxService.getValue().isEnabled()) { - jukeboxService.skip(getCurrentPlayingIndex(), 0); + jukeboxService.getValue().skip(getCurrentPlayingIndex(), 0); setPlayerState(STARTED); } else @@ -756,7 +786,7 @@ public class MediaPlayerService extends Service { reset(); setCurrentPlaying(null); - lifecycleSupport.serializeDownloadQueue(); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); } public synchronized void reset() @@ -802,9 +832,9 @@ public class MediaPlayerService extends Service { if (playerState == STARTED) { - if (jukeboxEnabled) + if (jukeboxService.getValue().isEnabled()) { - jukeboxService.stop(); + jukeboxService.getValue().stop(); } else { @@ -825,9 +855,9 @@ public class MediaPlayerService extends Service { if (playerState == STARTED) { - if (jukeboxEnabled) + if (jukeboxService.getValue().isEnabled()) { - jukeboxService.stop(); + jukeboxService.getValue().stop(); } else { @@ -846,9 +876,9 @@ public class MediaPlayerService extends Service { try { - if (jukeboxEnabled) + if (jukeboxService.getValue().isEnabled()) { - jukeboxService.start(); + jukeboxService.getValue().start(); } else { @@ -885,7 +915,7 @@ public class MediaPlayerService extends Service if (playerState == PAUSED) { - lifecycleSupport.serializeDownloadQueue(); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); } if (playerState == PlayerState.STARTED) @@ -1080,7 +1110,7 @@ public class MediaPlayerService extends Service } } - lifecycleSupport.serializeDownloadQueue(); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); } }); @@ -1274,6 +1304,8 @@ public class MediaPlayerService extends Service if (Util.getShouldClearPlaylist(this)) { clear(true); + jukeboxService.getValue().updatePlaylist(); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); } resetPlayback(); @@ -1294,6 +1326,8 @@ public class MediaPlayerService extends Service } } + // TODO: Serialization was originally here, removed for static. refactor this and but back + // downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); public static synchronized void clear(boolean serialize) { MediaPlayerService mediaPlayerService = getRunningInstance(); @@ -1309,19 +1343,14 @@ public class MediaPlayerService extends Service 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()) + // TODO: refactor inject + if (!Util.isExternalStoragePresent() || !inject(ExternalStorageMonitor.class).getValue().isExternalStorageAvailable()) { return; } @@ -1331,7 +1360,8 @@ public class MediaPlayerService extends Service checkShufflePlay(context); } - if (jukeboxEnabled || !Util.isNetworkConnected(context)) + // TODO: This inject is ugly, refactor + if (inject(JukeboxService.class).getValue().isEnabled() || !Util.isNetworkConnected(context)) { return; } @@ -1472,7 +1502,7 @@ public class MediaPlayerService extends Service if (revisionBefore != revision) { - getInstance(context).updateJukeboxPlaylist(); + getInstance(context).jukeboxService.getValue().updatePlaylist(); } if (wasEmpty && !MediaPlayerService.downloadList.isEmpty()) @@ -1481,14 +1511,6 @@ public class MediaPlayerService extends Service } } - public static void updateJukeboxPlaylist() - { - if (jukeboxEnabled) - { - jukeboxService.updatePlaylist(); - } - } - private static synchronized void cleanup(Context context) { Iterator iterator = cleanupCandidates.iterator(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java index b1ab7bf8..97e3d125 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java @@ -584,12 +584,6 @@ public class OfflineMusicService extends RESTMusicService @Override public MusicDirectory getPlaylist(String id, String name, Context context, ProgressListener progressListener) throws Exception { - DownloadService downloadService = DownloadServiceImpl.getInstance(); - if (downloadService == null) - { - return new MusicDirectory(); - } - Reader reader = null; BufferedReader buffer = null; try diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/State.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/State.java new file mode 100644 index 00000000..b0c19e90 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/State.java @@ -0,0 +1,16 @@ +package org.moire.ultrasonic.service; + +import org.moire.ultrasonic.domain.MusicDirectory; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class State implements Serializable +{ + public static final long serialVersionUID = -6346438781062572270L; + + public List songs = new ArrayList(); + public int currentPlayingIndex; + public int currentPlayingPosition; +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java index 9d1edf4f..83ccad56 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java @@ -8,6 +8,7 @@ import android.util.Log; import org.moire.ultrasonic.domain.Playlist; import org.moire.ultrasonic.service.DownloadFile; import org.moire.ultrasonic.service.DownloadService; +import org.moire.ultrasonic.service.DownloadServiceImpl; import java.io.File; import java.util.ArrayList; @@ -19,6 +20,10 @@ import java.util.List; import java.util.Set; import java.util.SortedSet; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + /** * @author Sindre Mehus * @version $Id$ @@ -30,12 +35,11 @@ public class CacheCleaner private static final long MIN_FREE_SPACE = 500 * 1024L * 1024L; private final Context context; - private final DownloadService downloadService; + private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); - public CacheCleaner(Context context, DownloadService downloadService) + public CacheCleaner(Context context) { this.context = context; - this.downloadService = downloadService; } public void clean() @@ -219,7 +223,7 @@ public class CacheCleaner { Set filesToNotDelete = new HashSet(5); - for (DownloadFile downloadFile : downloadService.getDownloads()) + for (DownloadFile downloadFile : downloadServiceImpl.getValue().getDownloads()) { filesToNotDelete.add(downloadFile.getPartialFile()); filesToNotDelete.add(downloadFile.getCompleteFile()); @@ -234,12 +238,6 @@ public class CacheCleaner @Override protected Void doInBackground(Void... params) { - if (downloadService == null) - { - Log.e(TAG, "DownloadService not set. Aborting cache cleaning."); - return null; - } - try { Thread.currentThread().setName("BackgroundCleanup"); @@ -268,12 +266,6 @@ public class CacheCleaner @Override protected Void doInBackground(Void... params) { - if (downloadService == null) - { - Log.e(TAG, "DownloadService not set. Aborting cache cleaning."); - return null; - } - try { Thread.currentThread().setName("BackgroundSpaceCleanup"); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java index 827891fb..303bc680 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Constants.java @@ -60,6 +60,15 @@ public final class Constants public static final String INTENT_EXTRA_NAME_IS_ALBUM = "subsonic.isalbum"; public static final String INTENT_EXTRA_NAME_VIDEOS = "subsonic.videos"; + // Names for Intent Actions + public static final String CMD_PROCESS_KEYCODE = "org.moire.ultrasonic.CMD_PROCESS_KEYCODE"; + public static final String CMD_PLAY = "org.moire.ultrasonic.CMD_PLAY"; + public static final String CMD_TOGGLEPAUSE = "org.moire.ultrasonic.CMD_TOGGLEPAUSE"; + public static final String CMD_PAUSE = "org.moire.ultrasonic.CMD_PAUSE"; + public static final String CMD_STOP = "org.moire.ultrasonic.CMD_STOP"; + public static final String CMD_PREVIOUS = "org.moire.ultrasonic.CMD_PREVIOUS"; + public static final String CMD_NEXT = "org.moire.ultrasonic.CMD_NEXT"; + // Notification IDs. public static final int NOTIFICATION_ID_PLAYING = 100; @@ -140,6 +149,8 @@ public final class Constants // URL for project donations. public static final String DONATION_URL = "http://www.subsonic.org/pages/premium.jsp"; + public static final String FILENAME_DOWNLOADS_SER = "downloadstate.ser"; + public static final String ALBUM_ART_FILE = "folder.jpeg"; public static final String STARRED = "starred"; public static final String ALPHABETICAL_BY_NAME = "alphabeticalByName"; 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 26a2870a..da8b1d9e 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.DownloadServiceLifecycleSupport; import org.moire.ultrasonic.service.MediaPlayerService; import org.moire.ultrasonic.service.MusicServiceFactory; @@ -1283,58 +1284,58 @@ public class Util extends DownloadActivity views.setOnClickPendingIntent(R.id.appwidget_top, pendingIntent); // Emulate media button clicks. - intent = new Intent("1"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 1, intent, 0); views.setOnClickPendingIntent(R.id.control_play, pendingIntent); - intent = new Intent("2"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 2, intent, 0); views.setOnClickPendingIntent(R.id.control_next, pendingIntent); - intent = new Intent("3"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 3, intent, 0); views.setOnClickPendingIntent(R.id.control_previous, pendingIntent); - intent = new Intent("4"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 4, intent, 0); views.setOnClickPendingIntent(R.id.control_stop, pendingIntent); - intent = new Intent("RATE_1"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_1)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 5, intent, 0); views.setOnClickPendingIntent(R.id.notification_five_star_1, pendingIntent); - intent = new Intent("RATE_2"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_2)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 6, intent, 0); views.setOnClickPendingIntent(R.id.notification_five_star_2, pendingIntent); - intent = new Intent("RATE_3"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_3)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 7, intent, 0); views.setOnClickPendingIntent(R.id.notification_five_star_3, pendingIntent); - intent = new Intent("RATE_4"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_4)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 8, intent, 0); views.setOnClickPendingIntent(R.id.notification_five_star_4, pendingIntent); - intent = new Intent("RATE_5"); - intent.setComponent(new ComponentName(context, MediaPlayerService.class)); + intent = new Intent(Constants.CMD_PROCESS_KEYCODE); + intent.setPackage(context.getPackageName()); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_5)); - pendingIntent = PendingIntent.getService(context, 0, intent, 0); + pendingIntent = PendingIntent.getBroadcast(context, 9, intent, 0); views.setOnClickPendingIntent(R.id.notification_five_star_5, pendingIntent); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java index 24460f67..5014c71b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java @@ -46,6 +46,10 @@ import org.moire.ultrasonic.util.VideoPlayerType; import java.io.File; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + /** * Used to display songs in a {@code ListView}. * @@ -72,13 +76,14 @@ public class SongView extends UpdateView implements Checkable private ImageType leftImageType; private ImageType rightImageType; private Drawable rightImage; - private DownloadService downloadService; private DownloadFile downloadFile; private boolean playing; private EntryAdapter.SongViewHolder viewHolder; private boolean maximized = false; private boolean useFiveStarRating; + private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + public SongView(Context context) { super(context); @@ -164,10 +169,7 @@ public class SongView extends UpdateView implements Checkable this.song = song; - if (downloadService != null) - { - this.downloadFile = downloadService.forSong(song); - } + this.downloadFile = downloadServiceImpl.getValue().forSong(song); StringBuilder artist = new StringBuilder(60); @@ -311,10 +313,6 @@ public class SongView extends UpdateView implements Checkable @Override protected void updateBackground() { - if (downloadService == null) - { - downloadService = DownloadServiceImpl.getInstance(); - } } @Override @@ -322,12 +320,7 @@ public class SongView extends UpdateView implements Checkable { updateBackground(); - if (downloadService == null) - { - return; - } - - downloadFile = downloadService.forSong(this.song); + downloadFile = downloadServiceImpl.getValue().forSong(this.song); File partialFile = downloadFile.getPartialFile(); if (downloadFile.isWorkDone()) @@ -417,7 +410,7 @@ public class SongView extends UpdateView implements Checkable viewHolder.fiveStar4.setImageDrawable(rating > 3 ? starDrawable : starHollowDrawable); viewHolder.fiveStar5.setImageDrawable(rating > 4 ? starDrawable : starHollowDrawable); - boolean playing = downloadService.getCurrentPlaying() == downloadFile; + boolean playing = downloadServiceImpl.getValue().getCurrentPlaying() == downloadFile; if (playing) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java index c8aafa47..92332344 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java @@ -30,6 +30,10 @@ import org.moire.ultrasonic.domain.PlayerState; import org.moire.ultrasonic.service.DownloadService; import org.moire.ultrasonic.service.DownloadServiceImpl; +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + /** * A simple class that draws waveform data received from a * {@link Visualizer.OnDataCaptureListener#onWaveFormDataCapture} @@ -39,7 +43,6 @@ import org.moire.ultrasonic.service.DownloadServiceImpl; */ public class VisualizerView extends View { - private static final int PREFERRED_CAPTURE_RATE_MILLIHERTZ = 20000; private final Paint paint = new Paint(); @@ -48,6 +51,8 @@ public class VisualizerView extends View private float[] points; private boolean active; + private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + public VisualizerView(Context context) { super(context); @@ -97,10 +102,9 @@ public class VisualizerView extends View invalidate(); } - private static Visualizer getVizualizer() + private Visualizer getVizualizer() { - DownloadService downloadService = DownloadServiceImpl.getInstance(); - VisualizerController visualizerController = downloadService == null ? null : downloadService.getVisualizerController(); + VisualizerController visualizerController = downloadServiceImpl.getValue().getVisualizerController(); return visualizerController == null ? null : visualizerController.getVisualizer(); } @@ -120,8 +124,7 @@ public class VisualizerView extends View return; } - DownloadService downloadService = DownloadServiceImpl.getInstance(); - if (downloadService != null && downloadService.getPlayerState() != PlayerState.STARTED) + if (downloadServiceImpl.getValue().getPlayerState() != PlayerState.STARTED) { return; } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 193e34d9..eb07d759 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -3,6 +3,7 @@ package org.moire.ultrasonic.di import android.content.SharedPreferences import android.util.Log +import org.koin.android.ext.koin.androidContext import kotlin.math.abs import org.koin.dsl.module.module import org.moire.ultrasonic.BuildConfig @@ -11,10 +12,7 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration import org.moire.ultrasonic.api.subsonic.di.subsonicApiModule import org.moire.ultrasonic.cache.PermanentFileStorage -import org.moire.ultrasonic.service.CachedMusicService -import org.moire.ultrasonic.service.MusicService -import org.moire.ultrasonic.service.OfflineMusicService -import org.moire.ultrasonic.service.RESTMusicService +import org.moire.ultrasonic.service.* import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader import org.moire.ultrasonic.util.Constants @@ -113,4 +111,10 @@ val musicServiceModule = module(MUSIC_SERVICE_CONTEXT) { } single { SubsonicImageLoader(getProperty(DiProperties.APP_CONTEXT), get()) } + + single { DownloadServiceImpl(androidContext()) } + single { JukeboxService(androidContext()) } + single { DownloadServiceLifecycleSupport(androidContext(), get())} + single { DownloadQueueSerializer(androidContext())} + single { ExternalStorageMonitor(androidContext())} } From 0820763c7fba7554c0cea6ebde401632929770e9 Mon Sep 17 00:00:00 2001 From: Nite Date: Tue, 23 Jun 2020 18:40:44 +0200 Subject: [PATCH 03/25] Refactored playback related things --- .../ultrasonic/activity/BookmarkActivity.java | 2 +- .../ultrasonic/activity/DownloadActivity.java | 36 +- .../ultrasonic/activity/SearchActivity.java | 2 +- .../activity/SelectAlbumActivity.java | 2 +- .../activity/SubsonicTabActivity.java | 5 +- .../receiver/A2dpIntentReceiver.java | 17 +- .../moire/ultrasonic/service/BiConsumer.java | 6 + .../ultrasonic/service/DownloadFile.java | 5 +- .../ultrasonic/service/DownloadService.java | 24 - .../service/DownloadServiceImpl.java | 325 +--- .../DownloadServiceLifecycleSupport.java | 10 +- .../moire/ultrasonic/service/Downloader.java | 454 +++++ .../ultrasonic/service/JukeboxService.java | 13 +- .../service/MediaPlayerService.java | 1588 +++-------------- .../org/moire/ultrasonic/service/Player.java | 1070 +++++++++++ .../moire/ultrasonic/util/CacheCleaner.java | 7 +- .../ultrasonic/util/ShufflePlayBuffer.java | 20 +- .../java/org/moire/ultrasonic/util/Util.java | 18 +- .../org/moire/ultrasonic/view/SongView.java | 11 +- .../moire/ultrasonic/di/MusicServiceModule.kt | 14 +- 20 files changed, 1903 insertions(+), 1726 deletions(-) create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/BiConsumer.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java index a7b7a52d..703422ec 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java @@ -310,7 +310,7 @@ public class BookmarkActivity extends SubsonicTabActivity for (MusicDirectory.Entry song : selection) { - DownloadFile downloadFile = getDownloadService().forSong(song); + DownloadFile downloadFile = downloader.getValue().getDownloadFileForSong(song); if (downloadFile.isWorkDone()) { deleteEnabled = true; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java index 28db6a4a..2cc55a02 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java @@ -293,7 +293,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Boolean doInBackground() throws Throwable { - if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1) + if (downloader.getValue().getCurrentPlayingIndex() < downloader.getValue().downloadList.size() - 1) { getDownloadService().next(); return true; @@ -567,7 +567,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi final DownloadService downloadService = getDownloadService(); - if (downloadService == null || downloadService.getCurrentPlaying() == null) + if (downloadService == null || player.getValue().currentPlaying == null) { playlistFlipper.setDisplayedChild(1); } @@ -632,7 +632,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi } } - final DownloadFile currentDownloading = getDownloadService().getCurrentDownloading(); + final DownloadFile currentDownloading = downloader.getValue().currentDownloading; for (int i = 0; i < count; i++) { if (currentDownloading == playlistView.getItemAtPosition(i)) @@ -782,7 +782,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (downloadService != null) { - DownloadFile downloadFile = downloadService.getCurrentPlaying(); + DownloadFile downloadFile = player.getValue().currentPlaying; if (downloadFile != null) { @@ -1019,7 +1019,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi onDownloadListChanged(); return true; case R.id.menu_item_save_playlist: - if (!getDownloadService().getSongs().isEmpty()) + if (!downloader.getValue().downloadList.isEmpty()) { showDialog(DIALOG_SAVE_PLAYLIST); } @@ -1142,7 +1142,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (downloadService != null) { - List downloadServiceSongs = downloadService.getSongs(); + List downloadServiceSongs = downloader.getValue().downloadList; if (downloadServiceSongs != null) { @@ -1175,12 +1175,12 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return; } - if (currentRevision != getDownloadService().getDownloadListUpdateRevision()) + if (currentRevision != downloader.getValue().getDownloadListUpdateRevision()) { onDownloadListChanged(); } - if (currentPlaying != getDownloadService().getCurrentPlaying()) + if (currentPlaying != player.getValue().currentPlaying) { onCurrentChanged(); } @@ -1199,7 +1199,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi protected Void doInBackground() throws Throwable { final List entries = new LinkedList(); - for (final DownloadFile downloadFile : getDownloadService().getSongs()) + for (final DownloadFile downloadFile : downloader.getValue().downloadList) { entries.add(downloadFile.getSong()); } @@ -1254,7 +1254,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi { warnIfNetworkOrStorageUnavailable(); - final int current = service.getCurrentPlayingIndex(); + final int current = downloader.getValue().getCurrentPlayingIndex(); if (current == -1) { @@ -1275,7 +1275,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return; } - final List list = downloadService.getSongs(); + final List list = downloader.getValue().downloadList; emptyTextView.setText(R.string.download_empty); final SongListAdapter adapter = new SongListAdapter(this, list); @@ -1313,7 +1313,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return; } - DownloadFile currentPlaying = downloadService.getCurrentPlaying(); + DownloadFile currentPlaying = player.getValue().currentPlaying; if (currentPlaying == item) { @@ -1333,7 +1333,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi }); emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE); - currentRevision = downloadService.getDownloadListUpdateRevision(); + currentRevision = downloader.getValue().getDownloadListUpdateRevision(); switch (downloadService.getRepeatMode()) { @@ -1360,13 +1360,13 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return; } - currentPlaying = downloadService.getCurrentPlaying(); + currentPlaying = player.getValue().currentPlaying; scrollToCurrent(); - long totalDuration = downloadService.getDownloadListDuration(); - long totalSongs = downloadService.getSongs().size(); - int currentSongIndex = downloadService.getCurrentPlayingIndex() + 1; + long totalDuration = downloader.getValue().getDownloadListDuration(); + long totalSongs = downloader.getValue().downloadList.size(); + int currentSongIndex = downloader.getValue().getCurrentPlayingIndex() + 1; String duration = Util.formatTotalDuration(totalDuration); @@ -1580,7 +1580,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (e1X - e2X > swipeDistance && absX > swipeVelocity) { warnIfNetworkOrStorageUnavailable(); - if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) + if (downloader.getValue().getCurrentPlayingIndex() < downloader.getValue().downloadList.size() - 1) { downloadService.next(); onCurrentChanged(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java index 6ed7e942..fb0b542b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java @@ -520,7 +520,7 @@ public class SearchActivity extends SubsonicTabActivity if (autoplay) { - downloadService.play(downloadService.size() - 1); + downloadService.play(downloader.getValue().downloadList.size() - 1); } Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1)); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java index d975160e..76ed4e51 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java @@ -1024,7 +1024,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity for (MusicDirectory.Entry song : selection) { - DownloadFile downloadFile = getDownloadService().forSong(song); + DownloadFile downloadFile = downloader.getValue().getDownloadFileForSong(song); if (downloadFile.isWorkDone()) { deleteEnabled = true; 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 c26f30de..3084b4a6 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -78,6 +78,9 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); private Lazy lifecycleSupport = inject(DownloadServiceLifecycleSupport.class); + protected Lazy downloader = inject(Downloader.class); + protected Lazy player = inject(Player.class); + public MenuDrawer menuDrawer; private int activePosition = 1; @@ -266,7 +269,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen if (playerState.equals(PlayerState.PAUSED) || playerState.equals(PlayerState.STARTED)) { - DownloadFile file = downloadServiceImpl.getValue().getCurrentPlaying(); + DownloadFile file = player.getValue().currentPlaying; if (file != null) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java index 85ce1782..214436c7 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java @@ -5,8 +5,9 @@ import android.content.Context; import android.content.Intent; import org.moire.ultrasonic.domain.MusicDirectory.Entry; -import org.moire.ultrasonic.service.DownloadService; import org.moire.ultrasonic.service.DownloadServiceImpl; +import org.moire.ultrasonic.service.Downloader; +import org.moire.ultrasonic.service.Player; import kotlin.Lazy; @@ -16,16 +17,18 @@ public class A2dpIntentReceiver extends BroadcastReceiver { private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse"; private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy downloader = inject(Downloader.class); + protected Lazy player = inject(Player.class); @Override public void onReceive(Context context, Intent intent) { - if (downloadServiceImpl.getValue().getCurrentPlaying() == null) + if (player.getValue().currentPlaying == null) { return; } - Entry song = downloadServiceImpl.getValue().getCurrentPlaying().getSong(); + Entry song = player.getValue().currentPlaying.getSong(); if (song == null) { @@ -35,8 +38,8 @@ public class A2dpIntentReceiver extends BroadcastReceiver Intent avrcpIntent = new Intent(PLAYSTATUS_RESPONSE); Integer duration = song.getDuration(); - Integer playerPosition = downloadServiceImpl.getValue().getPlayerPosition(); - Integer listSize = downloadServiceImpl.getValue().getDownloads().size(); + int playerPosition = downloadServiceImpl.getValue().getPlayerPosition(); + int listSize = downloader.getValue().getDownloads().size(); if (duration != null) { @@ -52,11 +55,7 @@ public class A2dpIntentReceiver extends BroadcastReceiver avrcpIntent.putExtra("playing", true); break; case STOPPED: - avrcpIntent.putExtra("playing", false); - break; case PAUSED: - avrcpIntent.putExtra("playing", false); - break; case COMPLETED: avrcpIntent.putExtra("playing", false); break; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/BiConsumer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/BiConsumer.java new file mode 100644 index 00000000..537ef5f4 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/BiConsumer.java @@ -0,0 +1,6 @@ +package org.moire.ultrasonic.service; + +public abstract class BiConsumer +{ + public abstract void accept(T t, U u); +} 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 2d4c7646..2d0c7b90 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java @@ -68,6 +68,9 @@ public class DownloadFile private volatile boolean saveWhenDone; private volatile boolean completeWhenDone; + private Lazy downloader = inject(Downloader.class); + + public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) { super(); @@ -443,7 +446,7 @@ public class DownloadFile new CacheCleaner(context).cleanSpace(); - MediaPlayerService.checkDownloads(context); + downloader.getValue().checkDownloads(); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadService.java index 9e69b722..b11eaaf6 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadService.java @@ -61,30 +61,10 @@ public interface DownloadService void clear(); - void clearBackground(); - void clearIncomplete(); - int size(); - - void remove(int which); - void remove(DownloadFile downloadFile); - long getDownloadListDuration(); - - List getSongs(); - - List getDownloads(); - - List getBackgroundDownloads(); - - int getCurrentPlayingIndex(); - - DownloadFile getCurrentPlaying(); - - DownloadFile getCurrentDownloading(); - void play(int index); void seekTo(int position); @@ -111,10 +91,6 @@ public interface DownloadService void unpin(List songs); - DownloadFile forSong(Entry song); - - long getDownloadListUpdateRevision(); - void setSuggestedPlaylistName(String name); String getSuggestedPlaylistName(); 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 ba3e1de0..b4262ec7 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java @@ -32,19 +32,15 @@ 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.util.LRUCache; import org.moire.ultrasonic.util.ShufflePlayBuffer; import org.moire.ultrasonic.util.Util; -import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; import java.util.List; import kotlin.Lazy; import static org.koin.java.standalone.KoinJavaComponent.inject; -import static org.moire.ultrasonic.service.MediaPlayerService.playerState; /** * @author Sindre Mehus, Joshua Bahnsen @@ -54,8 +50,6 @@ public class DownloadServiceImpl implements DownloadService { private static final String TAG = DownloadServiceImpl.class.getSimpleName(); - private final LRUCache downloadFileCache = new LRUCache<>(100); - private String suggestedPlaylistName; private boolean keepScreenOn; @@ -66,13 +60,18 @@ public class DownloadServiceImpl implements DownloadService public Lazy jukeboxService = inject(JukeboxService.class); private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); private Lazy externalStorageMonitor = inject(ExternalStorageMonitor.class); + private final Downloader downloader; + private final ShufflePlayBuffer shufflePlayBuffer; + private final Player player; - public DownloadServiceImpl(Context context) + public DownloadServiceImpl(Context context, Downloader downloader, ShufflePlayBuffer shufflePlayBuffer, + Player player) { this.context = context; + this.downloader = downloader; + this.shufflePlayBuffer = shufflePlayBuffer; + this.player = player; - // TODO: refactor - MediaPlayerService.shufflePlayBuffer = new ShufflePlayBuffer(context); externalStorageMonitor.getValue().onCreate(new Runnable() { @Override public void run() { @@ -120,26 +119,25 @@ public class DownloadServiceImpl implements DownloadService } }); - if (MediaPlayerService.currentPlaying != null) + if (player.currentPlaying != null) { if (autoPlay && jukeboxService.getValue().isEnabled()) { - jukeboxService.getValue().skip(getCurrentPlayingIndex(), currentPlayingPosition / 1000); + jukeboxService.getValue().skip(downloader.getCurrentPlayingIndex(), currentPlayingPosition / 1000); } else { - if (MediaPlayerService.currentPlaying.isCompleteFileAvailable()) + if (player.currentPlaying.isCompleteFileAvailable()) { executeOnStartedMediaPlayerService(new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { - mediaPlayerService.doPlay(MediaPlayerService.currentPlaying, currentPlayingPosition, autoPlay); + player.doPlay(player.currentPlaying, currentPlayingPosition, autoPlay); } }); } } } - autoPlayStart = false; } } @@ -168,7 +166,7 @@ public class DownloadServiceImpl implements DownloadService @Override public synchronized void togglePlayPause() { - if (playerState == PlayerState.IDLE) autoPlayStart = true; + if (player.playerState == PlayerState.IDLE) autoPlayStart = true; executeOnStartedMediaPlayerService(new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { @@ -222,96 +220,42 @@ public class DownloadServiceImpl implements DownloadService } @Override - public synchronized void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, boolean newPlaylist) + public synchronized void download(List songs, boolean save, boolean autoPlay, boolean playNext, boolean shuffle, boolean newPlaylist) { - MediaPlayerService.shufflePlay = false; - int offset = 1; - - if (songs.isEmpty()) - { - return; - } - - if (newPlaylist) - { - MediaPlayerService.downloadList.clear(); - } - - if (playNext) - { - if (autoplay && getCurrentPlayingIndex() >= 0) - { - offset = 0; - } - - for (MusicDirectory.Entry song : songs) - { - DownloadFile downloadFile = new DownloadFile(context, song, save); - MediaPlayerService.downloadList.add(getCurrentPlayingIndex() + offset, downloadFile); - offset++; - } - } - else - { - int size = size(); - int index = getCurrentPlayingIndex(); - - for (MusicDirectory.Entry song : songs) - { - DownloadFile downloadFile = new DownloadFile(context, song, save); - MediaPlayerService.downloadList.add(downloadFile); - } - - if (!autoplay && (size - 1) == index) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); - } - - } - MediaPlayerService.revision++; - + downloader.download(songs, save, autoPlay, playNext, newPlaylist); jukeboxService.getValue().updatePlaylist(); if (shuffle) shuffle(); - if (autoplay) + if (!playNext && !autoPlay && (downloader.downloadList.size() - 1) == downloader.getCurrentPlayingIndex()) + { + MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); + if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); + } + + if (autoPlay) { play(0); } else { - if (MediaPlayerService.currentPlaying == null) - { - MediaPlayerService.currentPlaying = MediaPlayerService.downloadList.get(0); - MediaPlayerService.currentPlaying.setPlaying(true); - } - - MediaPlayerService.checkDownloads(context); + downloader.setFirstPlaying(); } - downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } @Override public synchronized void downloadBackground(List songs, boolean save) { - for (MusicDirectory.Entry song : songs) - { - DownloadFile downloadFile = new DownloadFile(context, song, save); - MediaPlayerService.backgroundDownloadList.add(downloadFile); - } - - MediaPlayerService.revision++; - - MediaPlayerService.checkDownloads(context); - downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + downloader.downloadBackground(songs, save); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } public synchronized void setCurrentPlaying(DownloadFile currentPlaying) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setCurrentPlaying(currentPlaying); + if (mediaPlayerService != null) player.setCurrentPlaying(currentPlaying); } public synchronized void setCurrentPlaying(int index) @@ -323,7 +267,7 @@ public class DownloadServiceImpl implements DownloadService public synchronized void setPlayerState(PlayerState state) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setPlayerState(state); + if (mediaPlayerService != null) player.setPlayerState(state); } @Override @@ -341,31 +285,26 @@ public class DownloadServiceImpl implements DownloadService @Override public synchronized void setShufflePlayEnabled(boolean enabled) { - MediaPlayerService.shufflePlay = enabled; - if (MediaPlayerService.shufflePlay) + shufflePlayBuffer.isEnabled = enabled; + if (enabled) { clear(); - MediaPlayerService.checkDownloads(context); + downloader.checkDownloads(); } } @Override public boolean isShufflePlayEnabled() { - return MediaPlayerService.shufflePlay; + return shufflePlayBuffer.isEnabled; } @Override public synchronized void shuffle() { - Collections.shuffle(MediaPlayerService.downloadList); - if (MediaPlayerService.currentPlaying != null) - { - MediaPlayerService.downloadList.remove(getCurrentPlayingIndex()); - MediaPlayerService.downloadList.add(0, MediaPlayerService.currentPlaying); - } - MediaPlayerService.revision++; - downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + downloader.shuffle(); + + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); jukeboxService.getValue().updatePlaylist(); MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); @@ -410,33 +349,6 @@ public class DownloadServiceImpl implements DownloadService this.showVisualization = showVisualization; } - @Override - public synchronized DownloadFile forSong(MusicDirectory.Entry song) - { - for (DownloadFile downloadFile : MediaPlayerService.downloadList) - { - if (downloadFile.getSong().equals(song) && ((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone())) - { - return downloadFile; - } - } - for (DownloadFile downloadFile : MediaPlayerService.backgroundDownloadList) - { - if (downloadFile.getSong().equals(song)) - { - return downloadFile; - } - } - - DownloadFile downloadFile = downloadFileCache.get(song); - if (downloadFile == null) - { - downloadFile = new DownloadFile(context, song, false); - downloadFileCache.put(song, downloadFile); - } - return downloadFile; - } - @Override public synchronized void clear() { @@ -445,30 +357,17 @@ public class DownloadServiceImpl implements DownloadService public synchronized void clear(boolean serialize) { - MediaPlayerService.clear(serialize); - jukeboxService.getValue().updatePlaylist(); - if (serialize) - { - downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); - } - } + MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); + if (mediaPlayerService != null) mediaPlayerService.clear(serialize); - @Override - public synchronized void clearBackground() - { - if (MediaPlayerService.currentDownloading != null && MediaPlayerService.backgroundDownloadList.contains(MediaPlayerService.currentDownloading)) - { - MediaPlayerService.currentDownloading.cancelDownload(); - MediaPlayerService.currentDownloading = null; - } - MediaPlayerService.backgroundDownloadList.clear(); + jukeboxService.getValue().updatePlaylist(); } @Override public synchronized void clearIncomplete() { reset(); - Iterator iterator = MediaPlayerService.downloadList.iterator(); + Iterator iterator = downloader.downloadList.iterator(); while (iterator.hasNext()) { @@ -479,41 +378,25 @@ public class DownloadServiceImpl implements DownloadService } } - downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); jukeboxService.getValue().updatePlaylist(); } - @Override - public synchronized int size() - { - return MediaPlayerService.downloadList.size(); - } - - @Override - public synchronized void remove(int which) - { - MediaPlayerService.downloadList.remove(which); - } - @Override public synchronized void remove(DownloadFile downloadFile) { - if (downloadFile == MediaPlayerService.currentDownloading) - { - MediaPlayerService.currentDownloading.cancelDownload(); - MediaPlayerService.currentDownloading = null; - } - if (downloadFile == MediaPlayerService.currentPlaying) + if (downloadFile == player.currentPlaying) { reset(); setCurrentPlaying(null); } - MediaPlayerService.downloadList.remove(downloadFile); - MediaPlayerService.backgroundDownloadList.remove(downloadFile); - MediaPlayerService.revision++; - downloadQueueSerializer.getValue().serializeDownloadQueue(getSongs(), getCurrentPlayingIndex(), getPlayerPosition()); + + downloader.removeDownloadFile(downloadFile); + + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); jukeboxService.getValue().updatePlaylist(); - if (downloadFile == MediaPlayerService.nextPlaying) + + if (downloadFile == player.nextPlaying) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); @@ -525,7 +408,7 @@ public class DownloadServiceImpl implements DownloadService { for (MusicDirectory.Entry song : songs) { - forSong(song).delete(); + downloader.getDownloadFileForSong(song).delete(); } } @@ -534,76 +417,14 @@ public class DownloadServiceImpl implements DownloadService { for (MusicDirectory.Entry song : songs) { - forSong(song).unpin(); + downloader.getDownloadFileForSong(song).unpin(); } } - @Override - public synchronized int getCurrentPlayingIndex() - { - return MediaPlayerService.downloadList.indexOf(MediaPlayerService.currentPlaying); - } - - @Override - public DownloadFile getCurrentPlaying() - { - return MediaPlayerService.currentPlaying; - } - - @Override - public DownloadFile getCurrentDownloading() - { - return MediaPlayerService.currentDownloading; - } - - @Override - public List getSongs() { return MediaPlayerService.downloadList; } - - @Override - public long getDownloadListDuration() - { - long totalDuration = 0; - - for (DownloadFile downloadFile : MediaPlayerService.downloadList) - { - Entry entry = downloadFile.getSong(); - - if (!entry.isDirectory()) - { - if (entry.getArtist() != null) - { - Integer duration = entry.getDuration(); - - if (duration != null) - { - totalDuration += duration; - } - } - } - } - - return totalDuration; - } - - @Override - public synchronized List getDownloads() - { - List temp = new ArrayList<>(); - temp.addAll(MediaPlayerService.downloadList); - temp.addAll(MediaPlayerService.backgroundDownloadList); - return temp; - } - - @Override - public List getBackgroundDownloads() - { - return MediaPlayerService.backgroundDownloadList; - } - @Override public synchronized void previous() { - int index = getCurrentPlayingIndex(); + int index = downloader.getCurrentPlayingIndex(); if (index == -1) { return; @@ -623,7 +444,7 @@ public class DownloadServiceImpl implements DownloadService @Override public synchronized void next() { - int index = getCurrentPlayingIndex(); + int index = downloader.getCurrentPlayingIndex(); if (index != -1) { play(index + 1); @@ -634,7 +455,7 @@ public class DownloadServiceImpl implements DownloadService public synchronized void reset() { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.reset(); + if (mediaPlayerService != null) player.reset(); } @Override @@ -648,24 +469,22 @@ public class DownloadServiceImpl implements DownloadService @Override public synchronized int getPlayerDuration() { - if (MediaPlayerService.currentPlaying != null) + if (player.currentPlaying != null) { - Integer duration = MediaPlayerService.currentPlaying.getSong().getDuration(); + Integer duration = player.currentPlaying.getSong().getDuration(); if (duration != null) { return duration * 1000; } } + MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService == null) return 0; return mediaPlayerService.getPlayerDuration(); } @Override - public PlayerState getPlayerState() - { - return playerState; - } + public PlayerState getPlayerState() { return player.playerState; } @Override public void setSuggestedPlaylistName(String name) @@ -680,12 +499,12 @@ public class DownloadServiceImpl implements DownloadService } @Override - public boolean getEqualizerAvailable() { return MediaPlayerService.equalizerAvailable; } + public boolean getEqualizerAvailable() { return player.equalizerAvailable; } @Override public boolean getVisualizerAvailable() { - return MediaPlayerService.visualizerAvailable; + return player.visualizerAvailable; } @Override @@ -693,7 +512,7 @@ public class DownloadServiceImpl implements DownloadService { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService == null) return null; - return mediaPlayerService.getEqualizerController(); + return player.getEqualizerController(); } @Override @@ -701,7 +520,7 @@ public class DownloadServiceImpl implements DownloadService { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService == null) return null; - return mediaPlayerService.getVisualizerController(); + return player.getVisualizerController(); } @Override @@ -757,9 +576,9 @@ public class DownloadServiceImpl implements DownloadService reset(); // Cancel current download, if necessary. - if (MediaPlayerService.currentDownloading != null) + if (downloader.currentDownloading != null) { - MediaPlayerService.currentDownloading.cancelDownload(); + downloader.currentDownloading.cancelDownload(); } } else @@ -778,13 +597,13 @@ public class DownloadServiceImpl implements DownloadService public void setVolume(float volume) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setVolume(volume); + if (mediaPlayerService != null) player.setVolume(volume); } @Override public synchronized void swap(boolean mainList, int from, int to) { - List list = mainList ? MediaPlayerService.downloadList : MediaPlayerService.backgroundDownloadList; + List list = mainList ? downloader.downloadList : downloader.backgroundDownloadList; int max = list.size(); if (to >= max) @@ -796,7 +615,7 @@ public class DownloadServiceImpl implements DownloadService to = 0; } - int currentPlayingIndex = getCurrentPlayingIndex(); + int currentPlayingIndex = downloader.getCurrentPlayingIndex(); DownloadFile movedSong = list.remove(from); list.add(to, movedSong); @@ -804,7 +623,7 @@ public class DownloadServiceImpl implements DownloadService { jukeboxService.getValue().updatePlaylist(); } - else if (mainList && (movedSong == MediaPlayerService.nextPlaying || (currentPlayingIndex + 1) == to)) + else if (mainList && (movedSong == player.nextPlaying || (currentPlayingIndex + 1) == to)) { // Moving next playing or moving a song to be next playing MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); @@ -812,17 +631,11 @@ public class DownloadServiceImpl implements DownloadService } } - @Override - public long getDownloadListUpdateRevision() - { - return MediaPlayerService.revision; - } - @Override public void updateNotification() { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.updateNotification(); + if (mediaPlayerService != null) mediaPlayerService.updateNotification(player.playerState, player.currentPlaying); } public void setSongRating(final int rating) @@ -830,10 +643,10 @@ public class DownloadServiceImpl implements DownloadService if (!KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING)) return; - if (MediaPlayerService.currentPlaying == null) + if (player.currentPlaying == null) return; - final Entry song = MediaPlayerService.currentPlaying.getSong(); + final Entry song = player.currentPlaying.getSong(); song.setUserRating(rating); new Thread(new Runnable() 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 a8ce6468..407a2fd2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java @@ -49,14 +49,16 @@ public class DownloadServiceLifecycleSupport private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); private final DownloadServiceImpl downloadService; // From DI + private final Downloader downloader; // From DI private BroadcastReceiver headsetEventReceiver; private Context context; - public DownloadServiceLifecycleSupport(Context context, final DownloadServiceImpl downloadService) + public DownloadServiceLifecycleSupport(Context context, final DownloadServiceImpl downloadService, final Downloader downloader) { this.downloadService = downloadService; this.context = context; + this.downloader = downloader; registerHeadsetReceiver(); @@ -80,8 +82,8 @@ public class DownloadServiceLifecycleSupport downloadService.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, false, false); // Work-around: Serialize again, as the restore() method creates a serialization without current playing info. - downloadQueueSerializer.getValue().serializeDownloadQueue(downloadService.getSongs(), - downloadService.getCurrentPlayingIndex(), downloadService.getPlayerPosition()); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, + downloader.getCurrentPlayingIndex(), downloadService.getPlayerPosition()); } }); @@ -165,7 +167,7 @@ public class DownloadServiceLifecycleSupport downloadService.previous(); break; case KeyEvent.KEYCODE_MEDIA_NEXT: - if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) + if (downloader.getCurrentPlayingIndex() < downloader.downloadList.size() - 1) { downloadService.next(); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java new file mode 100644 index 00000000..3b694012 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java @@ -0,0 +1,454 @@ +package org.moire.ultrasonic.service; + +import android.content.Context; +import android.util.Log; + +import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.util.LRUCache; +import org.moire.ultrasonic.util.ShufflePlayBuffer; +import org.moire.ultrasonic.util.Util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; +import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING; +import static org.moire.ultrasonic.domain.PlayerState.STARTED; + +public class Downloader +{ + private static final String TAG = Downloader.class.getSimpleName(); + + private final ShufflePlayBuffer shufflePlayBuffer; + private final ExternalStorageMonitor externalStorageMonitor; + private final Player player; + public Lazy jukeboxService = inject(JukeboxService.class); + + public final List downloadList = new ArrayList<>(); + public final List backgroundDownloadList = new ArrayList<>(); + private final List cleanupCandidates = new ArrayList<>(); + private final LRUCache downloadFileCache = new LRUCache<>(100); + + public DownloadFile currentDownloading; + public static long revision; + + private ScheduledExecutorService executorService; + private Context context; + + public Downloader(Context context, ShufflePlayBuffer shufflePlayBuffer, ExternalStorageMonitor externalStorageMonitor, + Player player) + { + this.context = context; + this.shufflePlayBuffer = shufflePlayBuffer; + this.externalStorageMonitor = externalStorageMonitor; + this.player = player; + } + + public void onCreate() + { + Runnable downloadChecker = new Runnable() + { + @Override + public void run() + { + try + { + checkDownloads(); + } + catch (Throwable x) + { + Log.e(TAG, "checkDownloads() failed.", x); + } + } + }; + + executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS); + Log.i(TAG, "Downloader created"); + + } + + public void onDestroy() + { + executorService.shutdown(); + Log.i(TAG, "Downloader destroyed"); + } + + protected synchronized void checkDownloads() + { + if (!Util.isExternalStoragePresent() || !externalStorageMonitor.isExternalStorageAvailable()) + { + return; + } + + if (shufflePlayBuffer.isEnabled) + { + checkShufflePlay(context); + } + + if (jukeboxService.getValue().isEnabled() || !Util.isNetworkConnected(context)) + { + return; + } + + if (downloadList.isEmpty() && backgroundDownloadList.isEmpty()) + { + return; + } + + // Need to download current playing? + if (player.currentPlaying != null && player.currentPlaying != currentDownloading && !player.currentPlaying.isWorkDone()) + { + // Cancel current download, if necessary. + if (currentDownloading != null) + { + currentDownloading.cancelDownload(); + } + + currentDownloading = player.currentPlaying; + currentDownloading.download(); + cleanupCandidates.add(currentDownloading); + + // Delete obsolete .partial and .complete files. + cleanup(); + return; + } + + // Find a suitable target for download. + if (currentDownloading != null && + !currentDownloading.isWorkDone() && + (!currentDownloading.isFailed() || (downloadList.isEmpty() && backgroundDownloadList.isEmpty()))) + { + cleanup(); + return; + } + + // There is a target to download + currentDownloading = null; + int n = downloadList.size(); + + int preloaded = 0; + + if (n != 0) + { + int start = player.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(context)) + { + currentDownloading = downloadFile; + currentDownloading.download(); + cleanupCandidates.add(currentDownloading); + if (i == (start + 1)) + { + player.setNextPlayerState(DOWNLOADING); + } + break; + } + } + else if (player.currentPlaying != downloadFile) + { + preloaded++; + } + + i = (i + 1) % n; + } while (i != start); + } + + if ((preloaded + 1 == n || preloaded >= Util.getPreloadCount(context) || 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(context)) + { + Util.scanMedia(context, 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(); + } + + public synchronized int getCurrentPlayingIndex() + { + return downloadList.indexOf(player.currentPlaying); + } + + public long getDownloadListDuration() + { + long totalDuration = 0; + + for (DownloadFile downloadFile : downloadList) + { + MusicDirectory.Entry entry = downloadFile.getSong(); + + if (!entry.isDirectory()) + { + if (entry.getArtist() != null) + { + Integer duration = entry.getDuration(); + + if (duration != null) + { + totalDuration += duration; + } + } + } + } + + return totalDuration; + } + + public synchronized List getDownloads() + { + List temp = new ArrayList<>(); + temp.addAll(downloadList); + temp.addAll(backgroundDownloadList); + return temp; + } + + public List getBackgroundDownloads() + { + return backgroundDownloadList; + } + + public long getDownloadListUpdateRevision() + { + return revision; + } + + public synchronized void clear() + { + downloadList.clear(); + revision++; + if (currentDownloading != null) + { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + } + + public synchronized void clearBackground() + { + if (currentDownloading != null && backgroundDownloadList.contains(currentDownloading)) + { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + backgroundDownloadList.clear(); + } + + public synchronized void removeDownloadFile(DownloadFile downloadFile) + { + if (downloadFile == currentDownloading) + { + currentDownloading.cancelDownload(); + currentDownloading = null; + } + + downloadList.remove(downloadFile); + backgroundDownloadList.remove(downloadFile); + revision++; + } + + public synchronized void download(List songs, boolean save, boolean autoPlay, boolean playNext, boolean newPlaylist) + { + shufflePlayBuffer.isEnabled = false; + int offset = 1; + + if (songs.isEmpty()) + { + return; + } + + if (newPlaylist) + { + downloadList.clear(); + } + + if (playNext) + { + if (autoPlay && getCurrentPlayingIndex() >= 0) + { + offset = 0; + } + + for (MusicDirectory.Entry song : songs) + { + DownloadFile downloadFile = new DownloadFile(context, song, save); + downloadList.add(getCurrentPlayingIndex() + offset, downloadFile); + offset++; + } + } + else + { + for (MusicDirectory.Entry song : songs) + { + DownloadFile downloadFile = new DownloadFile(context, song, save); + downloadList.add(downloadFile); + } + } + revision++; + } + + public synchronized void downloadBackground(List songs, boolean save) + { + for (MusicDirectory.Entry song : songs) + { + DownloadFile downloadFile = new DownloadFile(context, song, save); + backgroundDownloadList.add(downloadFile); + } + + revision++; + + checkDownloads(); + } + + public synchronized void shuffle() + { + Collections.shuffle(downloadList); + if (player.currentPlaying != null) + { + downloadList.remove(getCurrentPlayingIndex()); + downloadList.add(0, player.currentPlaying); + } + revision++; + } + + public synchronized void setFirstPlaying() + { + if (player.currentPlaying == null) + { + player.currentPlaying = downloadList.get(0); + player.currentPlaying.setPlaying(true); + } + + checkDownloads(); + } + + public synchronized DownloadFile getDownloadFileForSong(MusicDirectory.Entry song) + { + for (DownloadFile downloadFile : downloadList) + { + if (downloadFile.getSong().equals(song) && ((downloadFile.isDownloading() && !downloadFile.isDownloadCancelled() && downloadFile.getPartialFile().exists()) || downloadFile.isWorkDone())) + { + return downloadFile; + } + } + for (DownloadFile downloadFile : backgroundDownloadList) + { + if (downloadFile.getSong().equals(song)) + { + return downloadFile; + } + } + + DownloadFile downloadFile = downloadFileCache.get(song); + if (downloadFile == null) + { + downloadFile = new DownloadFile(context, song, false); + downloadFileCache.put(song, downloadFile); + } + return downloadFile; + } + + private synchronized void cleanup() + { + Iterator iterator = cleanupCandidates.iterator(); + while (iterator.hasNext()) + { + DownloadFile downloadFile = iterator.next(); + if (downloadFile != player.currentPlaying && downloadFile != currentDownloading) + { + if (downloadFile.cleanup()) + { + iterator.remove(); + } + } + } + } + + private synchronized void checkShufflePlay(Context context) + { + // Get users desired random playlist size + int listSize = Util.getMaxSongs(context); + boolean wasEmpty = downloadList.isEmpty(); + + long revisionBefore = revision; + + // First, ensure that list is at least 20 songs long. + int size = downloadList.size(); + if (size < listSize) + { + for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size)) + { + DownloadFile downloadFile = new DownloadFile(context, song, false); + downloadList.add(downloadFile); + revision++; + } + } + + int currIndex = player.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(context, song, false)); + downloadList.get(0).cancelDownload(); + downloadList.remove(0); + revision++; + } + } + + if (revisionBefore != revision) + { + jukeboxService.getValue().updatePlaylist(); + } + + if (wasEmpty && !downloadList.isEmpty()) + { + if (jukeboxService.getValue().isEnabled()) + { + jukeboxService.getValue().skip(0, 0); + player.setPlayerState(STARTED); + } + else + { + player.play(downloadList.get(0)); + } + } + } +} 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 afbda458..449e6a43 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java @@ -71,7 +71,9 @@ public class JukeboxService private boolean enabled = false; private Context context; + // TODO: These create circular references, try to refactor private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private final Downloader downloader; // TODO: Report warning if queue fills up. // TODO: Create shutdown method? @@ -79,9 +81,10 @@ public class JukeboxService // TODO: Persist RC state? // TODO: Minimize status updates. - public JukeboxService(Context context) + public JukeboxService(Context context, Downloader downloader) { this.context = context; + this.downloader = downloader; } public void startJukeboxService() @@ -182,7 +185,7 @@ public class JukeboxService // Track change? Integer index = jukeboxStatus.getCurrentPlayingIndex(); - if (index != null && index != -1 && index != downloadServiceImpl.getValue().getCurrentPlayingIndex()) + if (index != null && index != -1 && index != downloader.getCurrentPlayingIndex()) { downloadServiceImpl.getValue().setCurrentPlaying(index); } @@ -232,8 +235,8 @@ public class JukeboxService tasks.remove(Stop.class); tasks.remove(Start.class); - List ids = new ArrayList(); - for (DownloadFile file : downloadServiceImpl.getValue().getDownloads()) + List ids = new ArrayList<>(); + for (DownloadFile file : downloader.getDownloads()) { ids.add(file.getSong().getId()); } @@ -334,7 +337,7 @@ public class JukeboxService private static class TaskQueue { - private final LinkedBlockingQueue queue = new LinkedBlockingQueue(); + private final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); void add(JukeboxTask jukeboxTask) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index 2797f967..a093bd5b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -1,29 +1,19 @@ 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; @@ -32,10 +22,7 @@ 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.MainActivity; 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; @@ -45,24 +32,11 @@ 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 java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - import kotlin.Lazy; import static org.koin.java.standalone.KoinJavaComponent.inject; @@ -70,7 +44,6 @@ 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; @@ -84,79 +57,18 @@ public class MediaPlayerService extends Service 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 Lazy jukeboxService = inject(JukeboxService.class); private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); - private Lazy externalStorageMonitor = inject(ExternalStorageMonitor.class); + private Lazy shufflePlayBuffer = inject(ShufflePlayBuffer.class); + private Lazy downloader = inject(Downloader.class); + private Lazy player = inject(Player.class); - private ScheduledExecutorService executorService; - - 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) @@ -197,98 +109,26 @@ public class MediaPlayerService extends Service { super.onCreate(); - new Thread(new Runnable() - { + downloader.getValue().onCreate(); + shufflePlayBuffer.getValue().onCreate(); + + player.getValue().onCreate(); + setupOnCurrentPlayingChangedHandler(); + setupOnPlayerStateChangedHandler(); + setupOnSongCompletedHandler(); + player.getValue().onPrepared = 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(); - - Runnable downloadChecker = new Runnable() - { - @Override - public void run() - { - try - { - MediaPlayerService.checkDownloads(MediaPlayerService.this); - } - catch (Throwable x) - { - Log.e(TAG, "checkDownloads() failed.", x); - } + public void run() { + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.getValue().downloadList, + downloader.getValue().getCurrentPlayingIndex(), getPlayerPosition()); } }; - - executorService = Executors.newSingleThreadScheduledExecutor(); - executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS); - - audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); - setUpRemoteControlClient(); - - if (equalizerAvailable) - { - equalizerController = new EqualizerController(this, mediaPlayer); - if (!equalizerController.isAvailable()) - { - equalizerController = null; + player.getValue().onNextSongRequested = new Runnable() { + @Override + public void run() { + setNextPlaying(); } - 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) { @@ -303,7 +143,7 @@ public class MediaPlayerService extends Service // We should use a single notification builder, otherwise the notification may not be updated notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID); // Update notification early. It is better to show an empty one temporarily than waiting too long and letting Android kill the app - updateNotification(); + updateNotification(IDLE, null); instance = this; Log.i(TAG, "MediaPlayerService created"); @@ -323,50 +163,11 @@ public class MediaPlayerService extends Service instance = null; - reset(); - executorService.shutdown(); - 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(); + player.getValue().onDestroy(); + shufflePlayBuffer.getValue().onDestroy(); + downloader.getValue().onDestroy(); } catch (Throwable ignored) { @@ -375,232 +176,39 @@ public class MediaPlayerService extends Service Log.i(TAG, "MediaPlayerService stopped"); } - public EqualizerController getEqualizerController() + public synchronized void seekTo(int position) { - if (equalizerAvailable && equalizerController == null) + if (jukeboxService.getValue().isEnabled()) { - 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); + jukeboxService.getValue().skip(downloader.getValue().getCurrentPlayingIndex(), position / 1000); } 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 (jukeboxService.getValue().isEnabled()) - { - jukeboxService.getValue().skip(getCurrentPlayingIndex(), position / 1000); - } - else - { - mediaPlayer.seekTo(position); - cachedPosition = position; - - updateRemoteControl(); - } - } - catch (Exception x) - { - handleError(x); + player.getValue().seekTo(position); } } public synchronized int getPlayerPosition() { - try + if (player.getValue().playerState == IDLE || player.getValue().playerState == DOWNLOADING || player.getValue().playerState == PREPARING) { - if (playerState == IDLE || playerState == DOWNLOADING || playerState == PREPARING) - { - return 0; - } - - return jukeboxService.getValue().isEnabled() ? jukeboxService.getValue().getPositionSeconds() * 1000 : cachedPosition; - } - catch (Exception x) - { - handleError(x); return 0; } + + return jukeboxService.getValue().isEnabled() ? jukeboxService.getValue().getPositionSeconds() * 1000 : + player.getValue().getPlayerPosition(); } 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); + return player.getValue().getPlayerDuration(); } public synchronized void setCurrentPlaying(int currentPlayingIndex) { try { - setCurrentPlaying(downloadList.get(currentPlayingIndex)); + player.getValue().setCurrentPlaying(downloader.getValue().downloadList.get(currentPlayingIndex)); } catch (IndexOutOfBoundsException x) { @@ -608,47 +216,53 @@ public class MediaPlayerService extends Service } } - public synchronized void setCurrentPlaying(DownloadFile currentPlaying) + public void setupOnCurrentPlayingChangedHandler() { - MediaPlayerService.currentPlaying = currentPlaying; + player.getValue().onCurrentPlayingChanged = new Consumer() { + @Override + public void accept(DownloadFile currentPlaying) { + if (currentPlaying != null) + { + Util.broadcastNewTrackInfo(MediaPlayerService.this, currentPlaying.getSong()); + Util.broadcastA2dpMetaDataChange(MediaPlayerService.this, getPlayerPosition(), currentPlaying, + downloader.getValue().getDownloads().size(), downloader.getValue().getCurrentPlayingIndex() + 1); + } + else + { + Util.broadcastNewTrackInfo(MediaPlayerService.this, null); + Util.broadcastA2dpMetaDataChange(MediaPlayerService.this, getPlayerPosition(), null, + downloader.getValue().getDownloads().size(), downloader.getValue().getCurrentPlayingIndex() + 1); + } - 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 + PlayerState playerState = player.getValue().playerState; + MusicDirectory.Entry song = currentPlaying == null? null : currentPlaying.getSong(); + UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); + UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true); + UltraSonicAppWidgetProvider4x3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); + UltraSonicAppWidgetProvider4x4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); - // 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(); - updateRemoteControl(); - SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance(); - - if (currentPlaying != null) - { - if (tabInstance != null) { - updateNotification(); - tabInstance.showNowPlaying(); + if (currentPlaying != null) + { + if (tabInstance != null) { + updateNotification(player.getValue().playerState, currentPlaying); + tabInstance.showNowPlaying(); + } + } + else + { + if (tabInstance != null) + { + tabInstance.hideNowPlaying(); + stopForeground(true); + isInForeground = false; + stopSelf(); + } + } } - } - else - { - if (tabInstance != null) - { - tabInstance.hideNowPlaying(); - stopForeground(true); - isInForeground = false; - stopSelf(); - } - } + }; } public synchronized void setNextPlaying() @@ -657,12 +271,11 @@ public class MediaPlayerService extends Service if (!gaplessPlayback) { - nextPlaying = null; - nextPlayerState = IDLE; + player.getValue().setNextPlaying(null); return; } - int index = getCurrentPlayingIndex(); + int index = downloader.getValue().getCurrentPlayingIndex(); if (index != -1) { @@ -672,7 +285,7 @@ public class MediaPlayerService extends Service index += 1; break; case ALL: - index = (index + 1) % size(); + index = (index + 1) % downloader.getValue().downloadList.size(); break; case SINGLE: break; @@ -681,56 +294,40 @@ public class MediaPlayerService extends Service } } - nextSetup = false; - if (nextPlayingTask != null) - { - nextPlayingTask.cancel(); - nextPlayingTask = null; - } + player.getValue().clearNextPlaying(); - if (index < size() && index != -1) + if (index < downloader.getValue().downloadList.size() && index != -1) { - nextPlaying = downloadList.get(index); - nextPlayingTask = new CheckCompletionTask(nextPlaying); - nextPlayingTask.start(); + player.getValue().setNextPlaying(downloader.getValue().downloadList.get(index)); } else { - nextPlaying = null; - setNextPlayerState(IDLE); + player.getValue().setNextPlaying(null); } } public synchronized void togglePlayPause() { - if (playerState == PAUSED || playerState == COMPLETED || playerState == STOPPED) + if (player.getValue().playerState == PAUSED || player.getValue().playerState == COMPLETED || player.getValue().playerState == STOPPED) { start(); } - else if (playerState == IDLE) + else if (player.getValue().playerState == IDLE) { play(); } - else if (playerState == STARTED) + else if (player.getValue().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(); + int current = downloader.getValue().getCurrentPlayingIndex(); if (current == -1) { play(0); @@ -748,802 +345,233 @@ public class MediaPlayerService extends Service public synchronized void play(int index, boolean start) { - updateRemoteControl(); - - if (index < 0 || index >= size()) + if (index < 0 || index >= downloader.getValue().downloadList.size()) { resetPlayback(); } else { - if (nextPlayingTask != null) - { - nextPlayingTask.cancel(); - nextPlayingTask = null; - } - - setCurrentPlaying(index); - if (start) { if (jukeboxService.getValue().isEnabled()) { - jukeboxService.getValue().skip(getCurrentPlayingIndex(), 0); - setPlayerState(STARTED); + jukeboxService.getValue().skip(index, 0); + player.getValue().setPlayerState(STARTED); } else { - bufferAndPlay(); + player.getValue().play(downloader.getValue().downloadList.get(index)); } } - checkDownloads(this); + downloader.getValue().checkDownloads(); setNextPlaying(); } } private synchronized void resetPlayback() { - reset(); - setCurrentPlaying(null); - downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); - } - - 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; - } + player.getValue().reset(); + player.getValue().setCurrentPlaying(null); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.getValue().downloadList, + downloader.getValue().getCurrentPlayingIndex(), getPlayerPosition()); } public synchronized void pause() { - try + if (player.getValue().playerState == STARTED) { - if (playerState == STARTED) + if (jukeboxService.getValue().isEnabled()) { - if (jukeboxService.getValue().isEnabled()) - { - jukeboxService.getValue().stop(); - } - else - { - mediaPlayer.pause(); - } - setPlayerState(PAUSED); + jukeboxService.getValue().stop(); } - } - catch (Exception x) - { - handleError(x); + else + { + player.getValue().pause(); + } + player.getValue().setPlayerState(PAUSED); } } public synchronized void stop() { - try + if (player.getValue().playerState == STARTED) { - if (playerState == STARTED) + if (jukeboxService.getValue().isEnabled()) { - if (jukeboxService.getValue().isEnabled()) - { - jukeboxService.getValue().stop(); - } - else - { - mediaPlayer.pause(); - } + jukeboxService.getValue().stop(); + } + else + { + player.getValue().pause(); } - setPlayerState(STOPPED); - } - catch (Exception x) - { - handleError(x); } + player.getValue().setPlayerState(STOPPED); } public synchronized void start() { - try + if (jukeboxService.getValue().isEnabled()) { - if (jukeboxService.getValue().isEnabled()) - { - jukeboxService.getValue().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(); + jukeboxService.getValue().start(); } else { - doPlay(currentPlaying, 0, true); + player.getValue().start(); } + player.getValue().setPlayerState(STARTED); } - public synchronized void setPlayerState(PlayerState playerState) + public void setupOnPlayerStateChangedHandler() { - Log.i(TAG, String.format("%s -> %s (%s)", playerState.name(), playerState.name(), currentPlaying)); - - MediaPlayerService.playerState = playerState; - - if (playerState == PAUSED) - { - downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); - } - - 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); - } - } - - downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); - } - }); - - 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() - { + player.getValue().onPlayerStateChanged = new BiConsumer() { @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; + public void accept(PlayerState playerState, DownloadFile currentPlaying) { + if (playerState == PAUSED) + { + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.getValue().downloadList, downloader.getValue().getCurrentPlayingIndex(), getPlayerPosition()); + } + + boolean showWhenPaused = (playerState != PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(MediaPlayerService.this)); + boolean show = playerState == PlayerState.STARTED || showWhenPaused; + + Util.broadcastPlaybackStatusChange(MediaPlayerService.this, playerState); + Util.broadcastA2dpPlayStatusChange(MediaPlayerService.this, playerState, currentPlaying.getSong(), + downloader.getValue().downloadList.size() + downloader.getValue().backgroundDownloadList.size(), + downloader.getValue().downloadList.indexOf(currentPlaying) + 1, getPlayerPosition()); + + MusicDirectory.Entry song = currentPlaying.getSong(); + // Update widget + UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); + UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true); + UltraSonicAppWidgetProvider4x3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); + UltraSonicAppWidgetProvider4x4.getInstance().notifyChange(MediaPlayerService.this, song, 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(playerState, currentPlaying); + tabInstance.showNowPlaying(); + } + } + } + else + { + if (tabInstance != null) + { + stopForeground(true); + isInForeground = false; + tabInstance.hideNowPlaying(); + stopSelf(); + } + } + + if (playerState == STARTED) + { + scrobbler.scrobble(MediaPlayerService.this, currentPlaying, false); + } + else if (playerState == COMPLETED) + { + scrobbler.scrobble(MediaPlayerService.this, currentPlaying, true); + } } - }); + }; + } - final int duration = downloadFile.getSong().getDuration() == null ? 0 : downloadFile.getSong().getDuration() * 1000; - - mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() - { + private void setupOnSongCompletedHandler() + { + player.getValue().onSongCompleted = new Consumer() { @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); + public void accept(DownloadFile currentPlaying) { + int index = downloader.getValue().getCurrentPlayingIndex(); - int pos = cachedPosition; - Log.i(TAG, String.format("Ending position %d of %d", pos, duration)); - - if (!isPartial || (downloadFile.isWorkDone() && (Math.abs(duration - pos) < 1000))) + if (currentPlaying != null) { - setPlayerStateCompleted(); + final MusicDirectory.Entry song = currentPlaying.getSong(); - if (Util.getGaplessPlaybackPreference(MediaPlayerService.this) && nextPlaying != null && nextPlayerState == PlayerState.PREPARED) + if (song != null && song.getBookmarkPosition() > 0 && Util.getShouldClearBookmark(MediaPlayerService.this)) { - if (!nextSetup) + MusicService musicService = MusicServiceFactory.getMusicService(MediaPlayerService.this); + try { - playNext(); + musicService.deleteBookmark(song.getId(), MediaPlayerService.this, null); } - else + catch (Exception ignored) { - nextSetup = false; - playNext(); + } } - else - { - onSongCompleted(); - } - - return; } - synchronized (this) + if (index != -1) { - if (downloadFile.isWorkDone()) + switch (getRepeatMode()) { - // 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); - jukeboxService.getValue().updatePlaylist(); - downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); - } - - resetPlayback(); - break; - } - - play(index + 1); - break; - case ALL: - play((index + 1) % size()); - break; - case SINGLE: - play(index); - break; - default: - break; - } - } - } - - // TODO: Serialization was originally here, removed for static. refactor this and but back - // downloadQueueSerializer.getValue().serializeDownloadQueue(downloadList, getCurrentPlayingIndex(), getPlayerPosition()); - 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); - mediaPlayerService.setNextPlaying(); - } - } - - protected static synchronized void checkDownloads(Context context) - { - // TODO: refactor inject - if (!Util.isExternalStoragePresent() || !inject(ExternalStorageMonitor.class).getValue().isExternalStorageAvailable()) - { - return; - } - - if (shufflePlay) - { - checkShufflePlay(context); - } - - // TODO: This inject is ugly, refactor - if (inject(JukeboxService.class).getValue().isEnabled() || !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)) + case OFF: + if (index + 1 < 0 || index + 1 >= downloader.getValue().downloadList.size()) { - MediaPlayerService.currentDownloading = downloadFile; - MediaPlayerService.currentDownloading.download(); - cleanupCandidates.add(MediaPlayerService.currentDownloading); - if (i == (start + 1)) + if (Util.getShouldClearPlaylist(MediaPlayerService.this)) { - setNextPlayerState(DOWNLOADING); + clear(true); + jukeboxService.getValue().updatePlaylist(); } + + resetPlayback(); 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); + play(index + 1); + break; + case ALL: + play((index + 1) % downloader.getValue().downloadList.size()); + break; + case SINGLE: + play(index); + break; + default: break; - } } } } - } - - // Delete obsolete .partial and .complete files. - cleanup(context); + }; } - private static synchronized void checkShufflePlay(Context context) + public synchronized void clear(boolean serialize) { - // Get users desired random playlist size - int listSize = Util.getMaxSongs(context); - boolean wasEmpty = MediaPlayerService.downloadList.isEmpty(); + player.getValue().reset(); + downloader.getValue().clear(); + player.getValue().setCurrentPlaying(null); - long revisionBefore = revision; + setNextPlaying(); - // 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).jukeboxService.getValue().updatePlaylist(); - } - - if (wasEmpty && !MediaPlayerService.downloadList.isEmpty()) - { - getInstance(context).play(0); + if (serialize) { + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.getValue().downloadList, + downloader.getValue().getCurrentPlayingIndex(), getPlayerPosition()); } } - 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() + public void updateNotification(PlayerState playerState, DownloadFile currentPlaying) { 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()); + notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying)); } else { final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); - notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification()); + notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying)); } Log.w(TAG, "--- Updated notification"); } else { - startForeground(NOTIFICATION_ID, buildForegroundNotification()); + startForeground(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying)); isInForeground = true; Log.w(TAG, "--- Created Foreground notification"); } @@ -1551,7 +579,7 @@ public class MediaPlayerService extends Service } @SuppressWarnings("IconColors") - private Notification buildForegroundNotification() { + private Notification buildForegroundNotification(PlayerState playerState, DownloadFile currentPlaying) { notificationBuilder.setSmallIcon(R.drawable.ic_stat_ultrasonic); notificationBuilder.setAutoCancel(false); @@ -1629,186 +657,4 @@ public class MediaPlayerService extends Service 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/Player.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java new file mode 100644 index 00000000..6922dc91 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java @@ -0,0 +1,1070 @@ +package org.moire.ultrasonic.service; + +import android.annotation.SuppressLint; +import android.app.PendingIntent; +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.Looper; +import android.os.PowerManager; +import android.util.Log; +import android.widget.SeekBar; + +import org.moire.ultrasonic.activity.DownloadActivity; +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.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.StreamProxy; +import org.moire.ultrasonic.util.Util; + +import java.io.File; +import java.net.URLEncoder; + +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; + +public class Player +{ + private static final String TAG = Player.class.getSimpleName(); + private final Context context; + + private PowerManager.WakeLock wakeLock; + public DownloadFile currentPlaying; + public DownloadFile nextPlaying; + private static boolean nextSetup; + private static CancellableTask nextPlayingTask; + private MediaPlayer mediaPlayer; + + private MediaPlayer nextMediaPlayer; + private Looper mediaPlayerLooper; + private Handler mediaPlayerHandler; + public static int cachedPosition; + private StreamProxy proxy; + + public PlayerState playerState = IDLE; + public PlayerState nextPlayerState = IDLE; + + private AudioManager audioManager; + public RemoteControlClient remoteControlClient; + + public static boolean equalizerAvailable; + public static boolean visualizerAvailable; + private EqualizerController equalizerController; + private VisualizerController visualizerController; + private CancellableTask bufferTask; + private PositionCache positionCache; + private int secondaryProgress = -1; + + public Consumer onCurrentPlayingChanged; + public Consumer onSongCompleted; + public BiConsumer onPlayerStateChanged; + public Runnable onPrepared; + public Runnable onNextSongRequested; + + static + { + try + { + EqualizerController.checkAvailable(); + equalizerAvailable = true; + } + catch (Throwable t) + { + equalizerAvailable = false; + } + } + + static + { + try + { + VisualizerController.checkAvailable(); + visualizerAvailable = true; + } + catch (Throwable t) + { + visualizerAvailable = false; + } + } + + public Player(Context context) + { + this.context = context; + } + + public void 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(context, 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, context.getPackageName()); + context.sendBroadcast(i); + } + catch (Throwable e) + { + // Froyo or lower + } + + mediaPlayerLooper = Looper.myLooper(); + mediaPlayerHandler = new Handler(mediaPlayerLooper); + Looper.loop(); + } + }).start(); + + PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getName()); + wakeLock.setReferenceCounted(false); + + audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + + setUpRemoteControlClient(); + + if (equalizerAvailable) + { + equalizerController = new EqualizerController(context, mediaPlayer); + if (!equalizerController.isAvailable()) + { + equalizerController = null; + } + else + { + equalizerController.loadSettings(); + } + } + + if (visualizerAvailable) + { + visualizerController = new VisualizerController(mediaPlayer); + if (!visualizerController.isAvailable()) + { + visualizerController = null; + } + } + + Log.i(TAG, "Player created"); + } + + public void onDestroy() + { + reset(); + + try + { + mediaPlayer.release(); + if (nextMediaPlayer != null) + { + nextMediaPlayer.release(); + } + + mediaPlayerLooper.quit(); + + 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, context.getPackageName()); + context.sendBroadcast(i); + + audioManager.unregisterRemoteControlClient(remoteControlClient); + clearRemoteControl(); + wakeLock.release(); + } + catch (Throwable ignored) + { + } + + Log.i(TAG, "Player destroyed"); + } + + public EqualizerController getEqualizerController() + { + if (equalizerAvailable && equalizerController == null) + { + equalizerController = new EqualizerController(context, 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 synchronized void setPlayerState(PlayerState playerState) + { + Log.i(TAG, String.format("%s -> %s (%s)", playerState.name(), playerState.name(), currentPlaying)); + + this.playerState = playerState; + + if (playerState == PlayerState.STARTED) + { + Util.requestAudioFocus(context); + } + + if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) + { + updateRemoteControl(); + } + + onPlayerStateChanged.accept(playerState, currentPlaying); + + 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; + } + } + + public synchronized void setCurrentPlaying(DownloadFile currentPlaying) + { + this.currentPlaying = currentPlaying; + updateRemoteControl(); + onCurrentPlayingChanged.accept(currentPlaying); + } + + public synchronized void setNextPlaying(DownloadFile nextToPlay) + { + if (nextToPlay == null) + { + nextPlaying = null; + setNextPlayerState(IDLE); + return; + } + + nextPlaying = nextToPlay; + nextPlayingTask = new CheckCompletionTask(nextPlaying); + nextPlayingTask.start(); + } + + public synchronized void clearNextPlaying() + { + nextSetup = false; + nextPlaying = null; + if (nextPlayingTask != null) + { + nextPlayingTask.cancel(); + nextPlayingTask = null; + } + } + + public synchronized void setNextPlayerState(PlayerState playerState) + { + Log.i(TAG, String.format("Next: %s -> %s (%s)", nextPlayerState.name(), playerState.name(), nextPlaying)); + nextPlayerState = playerState; + } + + public synchronized void bufferAndPlay() + { + if (playerState != PREPARED) + { + reset(); + + bufferTask = new BufferTask(currentPlaying, 0); + bufferTask.start(); + } + else + { + doPlay(currentPlaying, 0, true); + } + } + + public synchronized void play(DownloadFile fileToPlay) + { + if (nextPlayingTask != null) + { + nextPlayingTask.cancel(); + nextPlayingTask = null; + } + + updateRemoteControl(); + setCurrentPlaying(fileToPlay); + bufferAndPlay(); + } + + public synchronized void playNext() + { + MediaPlayer tmp = mediaPlayer; + mediaPlayer = nextMediaPlayer; + nextMediaPlayer = tmp; + setCurrentPlaying(nextPlaying); + setPlayerState(PlayerState.STARTED); + setupHandlers(currentPlaying, false); + onNextSongRequested.run(); + + // 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 + { + mediaPlayer.pause(); + } + catch (Exception x) + { + handleError(x); + } + } + + public synchronized void start() + { + try + { + mediaPlayer.start(); + } + catch (Exception x) + { + handleError(x); + } + } + + public void updateRemoteControl() + { + if (!Util.isLockScreenEnabled(context)) + { + 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(context, currentSong, Util.getMinDisplayMetric(context), 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 void clearRemoteControl() + { + if (remoteControlClient != null) + { + remoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_STOPPED); + audioManager.unregisterRemoteControlClient(remoteControlClient); + remoteControlClient = null; + } + } + + public void setUpRemoteControlClient() + { + if (!Util.isLockScreenEnabled(context)) return; + + ComponentName componentName = new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName()); + + if (remoteControlClient == null) + { + final Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + mediaButtonIntent.setComponent(componentName); + PendingIntent broadcast = PendingIntent.getBroadcast(context, 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); + } + } + + public synchronized void seekTo(int position) + { + try + { + 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 cachedPosition; + } + catch (Exception x) + { + handleError(x); + return 0; + } + } + + public synchronized int getPlayerDuration() + { + if (currentPlaying != null) + { + Integer duration = currentPlaying.getSong().getDuration(); + if (duration != null) + { + return duration * 1000; + } + } + if (playerState != IDLE && playerState != DOWNLOADING && playerState != PlayerState.PREPARING) + { + try + { + return mediaPlayer.getDuration(); + } + catch (Exception x) + { + handleError(x); + } + } + return 0; + } + + public void setVolume(float volume) + { + if (mediaPlayer != null) + { + mediaPlayer.setVolume(volume, volume); + } + } + + 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(context) == 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 (Player.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); + } + } + + onPrepared.run(); + } + }); + + 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(context, 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(context) && 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))) + { + setPlayerState(COMPLETED); + + if (Util.getGaplessPlaybackPreference(context) && nextPlaying != null && nextPlayerState == PlayerState.PREPARED) + { + if (!nextSetup) + { + playNext(); + } + else + { + nextSetup = false; + playNext(); + } + } + else + { + onSongCompleted.accept(currentPlaying); + } + + 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(); + } + } + } + }); + } + + 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 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(context); + + 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(context)) + { + 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); + } + + } + + 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(); + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java index 83ccad56..cc6a83a1 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java @@ -7,8 +7,7 @@ import android.util.Log; import org.moire.ultrasonic.domain.Playlist; import org.moire.ultrasonic.service.DownloadFile; -import org.moire.ultrasonic.service.DownloadService; -import org.moire.ultrasonic.service.DownloadServiceImpl; +import org.moire.ultrasonic.service.Downloader; import java.io.File; import java.util.ArrayList; @@ -35,7 +34,7 @@ public class CacheCleaner private static final long MIN_FREE_SPACE = 500 * 1024L * 1024L; private final Context context; - private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy downloader = inject(Downloader.class); public CacheCleaner(Context context) { @@ -223,7 +222,7 @@ public class CacheCleaner { Set filesToNotDelete = new HashSet(5); - for (DownloadFile downloadFile : downloadServiceImpl.getValue().getDownloads()) + for (DownloadFile downloadFile : downloader.getValue().getDownloads()) { filesToNotDelete.add(downloadFile.getPartialFile()); filesToNotDelete.add(downloadFile.getCompleteFile()); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java index 8b476acd..07aef6b7 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java @@ -37,19 +37,24 @@ import java.util.concurrent.TimeUnit; */ public class ShufflePlayBuffer { - private static final String TAG = ShufflePlayBuffer.class.getSimpleName(); private static final int CAPACITY = 50; private static final int REFILL_THRESHOLD = 40; - private final ScheduledExecutorService executorService; private final List buffer = new ArrayList(); private final Context context; + private ScheduledExecutorService executorService; private int currentServer; + public boolean isEnabled = false; + public ShufflePlayBuffer(Context context) { this.context = context; + } + + public void onCreate() + { executorService = Executors.newSingleThreadScheduledExecutor(); Runnable runnable = new Runnable() { @@ -62,6 +67,11 @@ public class ShufflePlayBuffer executorService.scheduleWithFixedDelay(runnable, 1, 10, TimeUnit.SECONDS); } + public void onDestroy() + { + executorService.shutdown(); + } + public List get(int size) { clearBufferIfNecessary(); @@ -78,13 +88,9 @@ public class ShufflePlayBuffer return result; } - public void shutdown() - { - executorService.shutdown(); - } - private void refill() { + if (!isEnabled) return; // Check if active server has changed. clearBufferIfNecessary(); 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 da8b1d9e..1bc31fe3 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -57,6 +57,7 @@ import org.moire.ultrasonic.service.DownloadFile; import org.moire.ultrasonic.service.DownloadService; import org.moire.ultrasonic.service.DownloadServiceImpl; import org.moire.ultrasonic.service.DownloadServiceLifecycleSupport; +import org.moire.ultrasonic.service.Downloader; import org.moire.ultrasonic.service.MediaPlayerService; import org.moire.ultrasonic.service.MusicServiceFactory; @@ -960,7 +961,7 @@ public class Util extends DownloadActivity context.sendBroadcast(intent); } - public static void broadcastA2dpMetaDataChange(Context context, DownloadService downloadService) + public static void broadcastA2dpMetaDataChange(Context context, int playerPosition, DownloadFile currentPlaying, int listSize, int id) { if (!Util.getShouldSendBluetoothNotifications(context)) { @@ -970,17 +971,9 @@ public class Util extends DownloadActivity Entry song = null; Intent avrcpIntent = new Intent(CM_AVRCP_METADATA_CHANGED); - if (downloadService != null) - { - DownloadFile entry = downloadService.getCurrentPlaying(); + if (currentPlaying != null) song = currentPlaying.getSong(); - if (entry != null) - { - song = entry.getSong(); - } - } - - if (downloadService == null || song == null) + if (song == null) { avrcpIntent.putExtra("track", ""); avrcpIntent.putExtra("track_name", ""); @@ -1013,9 +1006,6 @@ public class Util extends DownloadActivity 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(); avrcpIntent.putExtra("track", title); avrcpIntent.putExtra("track_name", title); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java index 5014c71b..5c617dbc 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java @@ -39,8 +39,10 @@ import org.moire.ultrasonic.featureflags.FeatureStorage; import org.moire.ultrasonic.service.DownloadFile; import org.moire.ultrasonic.service.DownloadService; import org.moire.ultrasonic.service.DownloadServiceImpl; +import org.moire.ultrasonic.service.Downloader; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; +import org.moire.ultrasonic.service.Player; import org.moire.ultrasonic.util.Util; import org.moire.ultrasonic.util.VideoPlayerType; @@ -82,7 +84,8 @@ public class SongView extends UpdateView implements Checkable private boolean maximized = false; private boolean useFiveStarRating; - private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy downloader = inject(Downloader.class); + protected Lazy player = inject(Player.class); public SongView(Context context) { @@ -169,7 +172,7 @@ public class SongView extends UpdateView implements Checkable this.song = song; - this.downloadFile = downloadServiceImpl.getValue().forSong(song); + this.downloadFile = downloader.getValue().getDownloadFileForSong(song); StringBuilder artist = new StringBuilder(60); @@ -320,7 +323,7 @@ public class SongView extends UpdateView implements Checkable { updateBackground(); - downloadFile = downloadServiceImpl.getValue().forSong(this.song); + downloadFile = downloader.getValue().getDownloadFileForSong(this.song); File partialFile = downloadFile.getPartialFile(); if (downloadFile.isWorkDone()) @@ -410,7 +413,7 @@ public class SongView extends UpdateView implements Checkable viewHolder.fiveStar4.setImageDrawable(rating > 3 ? starDrawable : starHollowDrawable); viewHolder.fiveStar5.setImageDrawable(rating > 4 ? starDrawable : starHollowDrawable); - boolean playing = downloadServiceImpl.getValue().getCurrentPlaying() == downloadFile; + boolean playing = player.getValue().currentPlaying == downloadFile; if (playing) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index eb07d759..7072738d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -15,6 +15,7 @@ import org.moire.ultrasonic.cache.PermanentFileStorage import org.moire.ultrasonic.service.* import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader import org.moire.ultrasonic.util.Constants +import org.moire.ultrasonic.util.ShufflePlayBuffer internal const val MUSIC_SERVICE_CONTEXT = "CurrentMusicService" internal const val ONLINE_MUSIC_SERVICE = "OnlineMusicService" @@ -112,9 +113,12 @@ val musicServiceModule = module(MUSIC_SERVICE_CONTEXT) { single { SubsonicImageLoader(getProperty(DiProperties.APP_CONTEXT), get()) } - single { DownloadServiceImpl(androidContext()) } - single { JukeboxService(androidContext()) } - single { DownloadServiceLifecycleSupport(androidContext(), get())} - single { DownloadQueueSerializer(androidContext())} - single { ExternalStorageMonitor(androidContext())} + single { DownloadServiceImpl(androidContext(), get(), get(), get()) } + single { JukeboxService(androidContext(), get()) } + single { DownloadServiceLifecycleSupport(androidContext(), get(), get()) } + single { DownloadQueueSerializer(androidContext()) } + single { ExternalStorageMonitor(androidContext()) } + single { ShufflePlayBuffer(androidContext()) } + single { Downloader(androidContext(), get(), get(), get()) } + single { Player(androidContext()) } } From c3be251e8be6ddf3ff014f03577f58e11e9b1566 Mon Sep 17 00:00:00 2001 From: Nite Date: Tue, 23 Jun 2020 19:27:04 +0200 Subject: [PATCH 04/25] Restored minor fixes --- .../service/DownloadServiceLifecycleSupport.java | 7 ++++++- .../org/moire/ultrasonic/service/MediaPlayerService.java | 2 ++ .../src/main/java/org/moire/ultrasonic/service/Player.java | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) 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 407a2fd2..a63829a9 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java @@ -79,6 +79,7 @@ public class DownloadServiceLifecycleSupport downloadQueueSerializer.getValue().deserializeDownloadQueue(new Consumer() { @Override public void accept(State state) { + // TODO: here the autoPlay = false creates problems when Ultrasonic is started by a Play MediaButton as the player won't start this way. downloadService.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, false, false); // Work-around: Serialize again, as the restore() method creates a serialization without current playing info. @@ -176,7 +177,11 @@ public class DownloadServiceLifecycleSupport downloadService.stop(); break; case KeyEvent.KEYCODE_MEDIA_PLAY: - if (downloadService.getPlayerState() != PlayerState.STARTED) + if (downloadService.getPlayerState() == PlayerState.IDLE) + { + downloadService.play(); + } + else if (downloadService.getPlayerState() != PlayerState.STARTED) { downloadService.start(); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index a093bd5b..82b0d7a4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -257,6 +257,7 @@ public class MediaPlayerService extends Service { tabInstance.hideNowPlaying(); stopForeground(true); + player.getValue().clearRemoteControl(); isInForeground = false; stopSelf(); } @@ -465,6 +466,7 @@ public class MediaPlayerService extends Service if (tabInstance != null) { stopForeground(true); + player.getValue().clearRemoteControl(); isInForeground = false; tabInstance.hideNowPlaying(); stopSelf(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java index 6922dc91..eb1001a6 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java @@ -478,7 +478,7 @@ public class Player } } - private void clearRemoteControl() + public void clearRemoteControl() { if (remoteControlClient != null) { From 8c60e0991450063788bd2c7f07095400f723b373 Mon Sep 17 00:00:00 2001 From: Nite Date: Thu, 25 Jun 2020 11:58:09 +0200 Subject: [PATCH 05/25] Fixed player threading, other minor fixes --- .../service/DownloadServiceImpl.java | 5 +- .../moire/ultrasonic/service/Downloader.java | 2 +- .../service/MediaPlayerService.java | 142 +++++++++--------- .../org/moire/ultrasonic/service/Player.java | 61 ++++++-- 4 files changed, 127 insertions(+), 83 deletions(-) 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 b4262ec7..8b7b2795 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java @@ -20,6 +20,7 @@ package org.moire.ultrasonic.service; import android.content.Context; import android.content.Intent; +import android.os.Handler; import android.util.Log; import org.koin.java.standalone.KoinJavaComponent; @@ -106,7 +107,7 @@ public class DownloadServiceImpl implements DownloadService } @Override - public void restore(List songs, final int currentPlayingIndex, final int currentPlayingPosition, final boolean autoPlay, boolean newPlaylist) + public synchronized void restore(List songs, final int currentPlayingIndex, final int currentPlayingPosition, final boolean autoPlay, boolean newPlaylist) { download(songs, false, false, false, false, newPlaylist); @@ -318,7 +319,7 @@ public class DownloadServiceImpl implements DownloadService } @Override - public void setRepeatMode(RepeatMode repeatMode) + public synchronized void setRepeatMode(RepeatMode repeatMode) { Util.setRepeatMode(context, repeatMode); MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java index 3b694012..f5f18df4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java @@ -347,7 +347,7 @@ public class Downloader public synchronized void setFirstPlaying() { - if (player.currentPlaying == null) + if (player.currentPlaying == null && downloadList.size() > 0) { player.currentPlaying = downloadList.get(0); player.currentPlaying.setPlaying(true); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index 82b0d7a4..3e2b15f2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -8,7 +8,6 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.media.MediaPlayer; import android.os.Build; import android.os.IBinder; import android.util.Log; @@ -62,9 +61,12 @@ public class MediaPlayerService extends Service public Lazy jukeboxService = inject(JukeboxService.class); private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); - private Lazy shufflePlayBuffer = inject(ShufflePlayBuffer.class); - private Lazy downloader = inject(Downloader.class); - private Lazy player = inject(Player.class); + private Lazy shufflePlayBufferLazy = inject(ShufflePlayBuffer.class); + private Lazy downloaderLazy = inject(Downloader.class); + private Lazy playerLazy = inject(Player.class); + private Player player; + private Downloader downloader; + private ShufflePlayBuffer shufflePlayBuffer; private boolean isInForeground = false; private NotificationCompat.Builder notificationBuilder; @@ -109,21 +111,25 @@ public class MediaPlayerService extends Service { super.onCreate(); - downloader.getValue().onCreate(); - shufflePlayBuffer.getValue().onCreate(); + downloader = downloaderLazy.getValue(); + player = playerLazy.getValue(); + shufflePlayBuffer = shufflePlayBufferLazy.getValue(); - player.getValue().onCreate(); + downloader.onCreate(); + shufflePlayBuffer.onCreate(); + + player.onCreate(); setupOnCurrentPlayingChangedHandler(); setupOnPlayerStateChangedHandler(); setupOnSongCompletedHandler(); - player.getValue().onPrepared = new Runnable() { + player.onPrepared = new Runnable() { @Override public void run() { - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.getValue().downloadList, - downloader.getValue().getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, + downloader.getCurrentPlayingIndex(), getPlayerPosition()); } }; - player.getValue().onNextSongRequested = new Runnable() { + player.onNextSongRequested = new Runnable() { @Override public void run() { setNextPlaying(); @@ -165,9 +171,9 @@ public class MediaPlayerService extends Service try { - player.getValue().onDestroy(); - shufflePlayBuffer.getValue().onDestroy(); - downloader.getValue().onDestroy(); + player.onDestroy(); + shufflePlayBuffer.onDestroy(); + downloader.onDestroy(); } catch (Throwable ignored) { @@ -180,35 +186,35 @@ public class MediaPlayerService extends Service { if (jukeboxService.getValue().isEnabled()) { - jukeboxService.getValue().skip(downloader.getValue().getCurrentPlayingIndex(), position / 1000); + jukeboxService.getValue().skip(downloader.getCurrentPlayingIndex(), position / 1000); } else { - player.getValue().seekTo(position); + player.seekTo(position); } } public synchronized int getPlayerPosition() { - if (player.getValue().playerState == IDLE || player.getValue().playerState == DOWNLOADING || player.getValue().playerState == PREPARING) + if (player.playerState == IDLE || player.playerState == DOWNLOADING || player.playerState == PREPARING) { return 0; } return jukeboxService.getValue().isEnabled() ? jukeboxService.getValue().getPositionSeconds() * 1000 : - player.getValue().getPlayerPosition(); + player.getPlayerPosition(); } public synchronized int getPlayerDuration() { - return player.getValue().getPlayerDuration(); + return player.getPlayerDuration(); } public synchronized void setCurrentPlaying(int currentPlayingIndex) { try { - player.getValue().setCurrentPlaying(downloader.getValue().downloadList.get(currentPlayingIndex)); + player.setCurrentPlaying(downloader.downloadList.get(currentPlayingIndex)); } catch (IndexOutOfBoundsException x) { @@ -218,24 +224,24 @@ public class MediaPlayerService extends Service public void setupOnCurrentPlayingChangedHandler() { - player.getValue().onCurrentPlayingChanged = new Consumer() { + player.onCurrentPlayingChanged = new Consumer() { @Override public void accept(DownloadFile currentPlaying) { if (currentPlaying != null) { Util.broadcastNewTrackInfo(MediaPlayerService.this, currentPlaying.getSong()); Util.broadcastA2dpMetaDataChange(MediaPlayerService.this, getPlayerPosition(), currentPlaying, - downloader.getValue().getDownloads().size(), downloader.getValue().getCurrentPlayingIndex() + 1); + downloader.getDownloads().size(), downloader.getCurrentPlayingIndex() + 1); } else { Util.broadcastNewTrackInfo(MediaPlayerService.this, null); Util.broadcastA2dpMetaDataChange(MediaPlayerService.this, getPlayerPosition(), null, - downloader.getValue().getDownloads().size(), downloader.getValue().getCurrentPlayingIndex() + 1); + downloader.getDownloads().size(), downloader.getCurrentPlayingIndex() + 1); } // Update widget - PlayerState playerState = player.getValue().playerState; + PlayerState playerState = player.playerState; MusicDirectory.Entry song = currentPlaying == null? null : currentPlaying.getSong(); UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true); @@ -247,7 +253,7 @@ public class MediaPlayerService extends Service if (currentPlaying != null) { if (tabInstance != null) { - updateNotification(player.getValue().playerState, currentPlaying); + updateNotification(player.playerState, currentPlaying); tabInstance.showNowPlaying(); } } @@ -257,7 +263,7 @@ public class MediaPlayerService extends Service { tabInstance.hideNowPlaying(); stopForeground(true); - player.getValue().clearRemoteControl(); + player.clearRemoteControl(); isInForeground = false; stopSelf(); } @@ -272,11 +278,11 @@ public class MediaPlayerService extends Service if (!gaplessPlayback) { - player.getValue().setNextPlaying(null); + player.setNextPlaying(null); return; } - int index = downloader.getValue().getCurrentPlayingIndex(); + int index = downloader.getCurrentPlayingIndex(); if (index != -1) { @@ -286,7 +292,7 @@ public class MediaPlayerService extends Service index += 1; break; case ALL: - index = (index + 1) % downloader.getValue().downloadList.size(); + index = (index + 1) % downloader.downloadList.size(); break; case SINGLE: break; @@ -295,29 +301,29 @@ public class MediaPlayerService extends Service } } - player.getValue().clearNextPlaying(); + player.clearNextPlaying(); - if (index < downloader.getValue().downloadList.size() && index != -1) + if (index < downloader.downloadList.size() && index != -1) { - player.getValue().setNextPlaying(downloader.getValue().downloadList.get(index)); + player.setNextPlaying(downloader.downloadList.get(index)); } else { - player.getValue().setNextPlaying(null); + player.setNextPlaying(null); } } public synchronized void togglePlayPause() { - if (player.getValue().playerState == PAUSED || player.getValue().playerState == COMPLETED || player.getValue().playerState == STOPPED) + if (player.playerState == PAUSED || player.playerState == COMPLETED || player.playerState == STOPPED) { start(); } - else if (player.getValue().playerState == IDLE) + else if (player.playerState == IDLE) { play(); } - else if (player.getValue().playerState == STARTED) + else if (player.playerState == STARTED) { pause(); } @@ -328,7 +334,7 @@ public class MediaPlayerService extends Service */ public synchronized void play() { - int current = downloader.getValue().getCurrentPlayingIndex(); + int current = downloader.getCurrentPlayingIndex(); if (current == -1) { play(0); @@ -346,7 +352,8 @@ public class MediaPlayerService extends Service public synchronized void play(int index, boolean start) { - if (index < 0 || index >= downloader.getValue().downloadList.size()) + Log.v(TAG, String.format("play requested for %d", index)); + if (index < 0 || index >= downloader.downloadList.size()) { resetPlayback(); } @@ -357,30 +364,30 @@ public class MediaPlayerService extends Service if (jukeboxService.getValue().isEnabled()) { jukeboxService.getValue().skip(index, 0); - player.getValue().setPlayerState(STARTED); + player.setPlayerState(STARTED); } else { - player.getValue().play(downloader.getValue().downloadList.get(index)); + player.play(downloader.downloadList.get(index)); } } - downloader.getValue().checkDownloads(); + downloader.checkDownloads(); setNextPlaying(); } } private synchronized void resetPlayback() { - player.getValue().reset(); - player.getValue().setCurrentPlaying(null); - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.getValue().downloadList, - downloader.getValue().getCurrentPlayingIndex(), getPlayerPosition()); + player.reset(); + player.setCurrentPlaying(null); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, + downloader.getCurrentPlayingIndex(), getPlayerPosition()); } public synchronized void pause() { - if (player.getValue().playerState == STARTED) + if (player.playerState == STARTED) { if (jukeboxService.getValue().isEnabled()) { @@ -388,15 +395,15 @@ public class MediaPlayerService extends Service } else { - player.getValue().pause(); + player.pause(); } - player.getValue().setPlayerState(PAUSED); + player.setPlayerState(PAUSED); } } public synchronized void stop() { - if (player.getValue().playerState == STARTED) + if (player.playerState == STARTED) { if (jukeboxService.getValue().isEnabled()) { @@ -404,10 +411,10 @@ public class MediaPlayerService extends Service } else { - player.getValue().pause(); + player.pause(); } } - player.getValue().setPlayerState(STOPPED); + player.setPlayerState(STOPPED); } public synchronized void start() @@ -418,19 +425,19 @@ public class MediaPlayerService extends Service } else { - player.getValue().start(); + player.start(); } - player.getValue().setPlayerState(STARTED); + player.setPlayerState(STARTED); } public void setupOnPlayerStateChangedHandler() { - player.getValue().onPlayerStateChanged = new BiConsumer() { + player.onPlayerStateChanged = new BiConsumer() { @Override public void accept(PlayerState playerState, DownloadFile currentPlaying) { if (playerState == PAUSED) { - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.getValue().downloadList, downloader.getValue().getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } boolean showWhenPaused = (playerState != PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(MediaPlayerService.this)); @@ -438,8 +445,8 @@ public class MediaPlayerService extends Service Util.broadcastPlaybackStatusChange(MediaPlayerService.this, playerState); Util.broadcastA2dpPlayStatusChange(MediaPlayerService.this, playerState, currentPlaying.getSong(), - downloader.getValue().downloadList.size() + downloader.getValue().backgroundDownloadList.size(), - downloader.getValue().downloadList.indexOf(currentPlaying) + 1, getPlayerPosition()); + downloader.downloadList.size() + downloader.backgroundDownloadList.size(), + downloader.downloadList.indexOf(currentPlaying) + 1, getPlayerPosition()); MusicDirectory.Entry song = currentPlaying.getSong(); // Update widget @@ -466,7 +473,7 @@ public class MediaPlayerService extends Service if (tabInstance != null) { stopForeground(true); - player.getValue().clearRemoteControl(); + player.clearRemoteControl(); isInForeground = false; tabInstance.hideNowPlaying(); stopSelf(); @@ -487,10 +494,10 @@ public class MediaPlayerService extends Service private void setupOnSongCompletedHandler() { - player.getValue().onSongCompleted = new Consumer() { + player.onSongCompleted = new Consumer() { @Override public void accept(DownloadFile currentPlaying) { - int index = downloader.getValue().getCurrentPlayingIndex(); + int index = downloader.getCurrentPlayingIndex(); if (currentPlaying != null) { @@ -515,7 +522,7 @@ public class MediaPlayerService extends Service switch (getRepeatMode()) { case OFF: - if (index + 1 < 0 || index + 1 >= downloader.getValue().downloadList.size()) + if (index + 1 < 0 || index + 1 >= downloader.downloadList.size()) { if (Util.getShouldClearPlaylist(MediaPlayerService.this)) { @@ -530,7 +537,7 @@ public class MediaPlayerService extends Service play(index + 1); break; case ALL: - play((index + 1) % downloader.getValue().downloadList.size()); + play((index + 1) % downloader.downloadList.size()); break; case SINGLE: play(index); @@ -545,15 +552,15 @@ public class MediaPlayerService extends Service public synchronized void clear(boolean serialize) { - player.getValue().reset(); - downloader.getValue().clear(); - player.getValue().setCurrentPlaying(null); + player.reset(); + downloader.clear(); + player.setCurrentPlaying(null); setNextPlaying(); if (serialize) { - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.getValue().downloadList, - downloader.getValue().getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, + downloader.getCurrentPlayingIndex(), getPlayerPosition()); } } @@ -632,7 +639,6 @@ public class MediaPlayerService extends Service 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); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java index eb1001a6..def1bb07 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java @@ -117,7 +117,7 @@ public class Player @Override public void run() { - Thread.currentThread().setName("MediaPlayerService"); + Thread.currentThread().setName("MediaPlayerThread"); Looper.prepare(); @@ -270,9 +270,9 @@ public class Player return visualizerController; } - public synchronized void setPlayerState(PlayerState playerState) + public synchronized void setPlayerState(final PlayerState playerState) { - Log.i(TAG, String.format("%s -> %s (%s)", playerState.name(), playerState.name(), currentPlaying)); + Log.i(TAG, String.format("%s -> %s (%s)", this.playerState.name(), playerState.name(), currentPlaying)); this.playerState = playerState; @@ -286,7 +286,14 @@ public class Player updateRemoteControl(); } - onPlayerStateChanged.accept(playerState, currentPlaying); + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onPlayerStateChanged.accept(playerState, currentPlaying); + } + }; + mainHandler.post(myRunnable); if (playerState == STARTED && positionCache == null) { @@ -301,11 +308,20 @@ public class Player } } - public synchronized void setCurrentPlaying(DownloadFile currentPlaying) + public synchronized void setCurrentPlaying(final DownloadFile currentPlaying) { + Log.v(TAG, String.format("setCurrentPlaying %s", currentPlaying)); this.currentPlaying = currentPlaying; updateRemoteControl(); - onCurrentPlayingChanged.accept(currentPlaying); + + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onCurrentPlayingChanged.accept(currentPlaying); + } + }; + mainHandler.post(myRunnable); } public synchronized void setNextPlaying(DownloadFile nextToPlay) @@ -362,7 +378,6 @@ public class Player nextPlayingTask = null; } - updateRemoteControl(); setCurrentPlaying(fileToPlay); bufferAndPlay(); } @@ -375,7 +390,15 @@ public class Player setCurrentPlaying(nextPlaying); setPlayerState(PlayerState.STARTED); setupHandlers(currentPlaying, false); - onNextSongRequested.run(); + + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onNextSongRequested.run(); + } + }; + mainHandler.post(myRunnable); // Proxy should not be being used here since the next player was already setup to play if (proxy != null) @@ -409,7 +432,7 @@ public class Player } } - public void updateRemoteControl() + private void updateRemoteControl() { if (!Util.isLockScreenEnabled(context)) { @@ -488,7 +511,7 @@ public class Player } } - public void setUpRemoteControlClient() + private void setUpRemoteControlClient() { if (!Util.isLockScreenEnabled(context)) return; @@ -705,7 +728,14 @@ public class Player } } - onPrepared.run(); + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onPrepared.run(); + } + }; + mainHandler.post(myRunnable); } }); @@ -838,7 +868,14 @@ public class Player } else { - onSongCompleted.accept(currentPlaying); + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onSongCompleted.accept(currentPlaying); + } + }; + mainHandler.post(myRunnable); } return; From b83631107cd0fdd9f2ace9d44da70bbfa9a71912 Mon Sep 17 00:00:00 2001 From: Nite Date: Thu, 25 Jun 2020 14:33:44 +0200 Subject: [PATCH 06/25] Major cleanup, minor fixes --- ultrasonic/lint-baseline.xml | 4 +- .../ultrasonic/activity/BookmarkActivity.java | 18 +- .../ultrasonic/activity/DownloadActivity.java | 158 +++++++++--------- .../activity/EqualizerActivity.java | 7 +- .../ultrasonic/activity/MainActivity.java | 15 +- .../ultrasonic/activity/SearchActivity.java | 18 +- .../activity/SelectAlbumActivity.java | 14 +- .../activity/SubsonicTabActivity.java | 60 +++---- .../ultrasonic/fragment/SettingsFragment.java | 7 +- .../provider/UltraSonicAppWidgetProvider.java | 4 +- .../receiver/A2dpIntentReceiver.java | 16 +- .../receiver/MediaButtonIntentReceiver.java | 4 +- .../moire/ultrasonic/service/Downloader.java | 78 ++++----- ...oxService.java => JukeboxMediaPlayer.java} | 24 +-- .../{Player.java => LocalMediaPlayer.java} | 122 +++++++------- ...ervice.java => MediaPlayerController.java} | 12 +- ...pl.java => MediaPlayerControllerImpl.java} | 152 ++++++----------- ....java => MediaPlayerLifecycleSupport.java} | 114 +++++++------ .../service/MediaPlayerService.java | 115 +++++++------ .../java/org/moire/ultrasonic/util/Util.java | 24 ++- .../ultrasonic/util/VideoPlayerType.java | 2 +- .../org/moire/ultrasonic/view/SongView.java | 8 +- .../moire/ultrasonic/view/VisualizerView.java | 9 +- .../moire/ultrasonic/di/MusicServiceModule.kt | 9 +- .../src/main/res/values-pt-rBR/strings.xml | 2 +- ultrasonic/src/main/res/values-pt/strings.xml | 2 +- ultrasonic/src/main/res/values/strings.xml | 2 +- 27 files changed, 453 insertions(+), 547 deletions(-) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{JukeboxService.java => JukeboxMediaPlayer.java} (93%) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{Player.java => LocalMediaPlayer.java} (94%) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{DownloadService.java => MediaPlayerController.java} (92%) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{DownloadServiceImpl.java => MediaPlayerControllerImpl.java} (78%) rename ultrasonic/src/main/java/org/moire/ultrasonic/service/{DownloadServiceLifecycleSupport.java => MediaPlayerLifecycleSupport.java} (72%) diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index fc439fa2..9a3a6166 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -960,7 +960,7 @@ songs) { - if (getDownloadService() == null) + if (getMediaPlayerController() == null) { return; } @@ -356,7 +356,7 @@ public class BookmarkActivity extends SubsonicTabActivity public void run() { warnIfNetworkOrStorageUnavailable(); - getDownloadService().downloadBackground(songs, save); + getMediaPlayerController().downloadBackground(songs, save); if (save) { @@ -382,19 +382,19 @@ public class BookmarkActivity extends SubsonicTabActivity songs = getSelectedSongs(albumListView); } - if (getDownloadService() != null) + if (getMediaPlayerController() != null) { - getDownloadService().delete(songs); + getMediaPlayerController().delete(songs); } } private void unpin() { - if (getDownloadService() != null) + if (getMediaPlayerController() != null) { List songs = getSelectedSongs(albumListView); Util.toast(BookmarkActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size())); - getDownloadService().unpin(songs); + getMediaPlayerController().unpin(songs); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java index 2cc55a02..f03c005d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java @@ -60,7 +60,7 @@ import org.moire.ultrasonic.domain.RepeatMode; import org.moire.ultrasonic.featureflags.Feature; import org.moire.ultrasonic.featureflags.FeatureStorage; import org.moire.ultrasonic.service.DownloadFile; -import org.moire.ultrasonic.service.DownloadService; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.util.Constants; @@ -257,7 +257,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Void doInBackground() throws Throwable { - getDownloadService().previous(); + getMediaPlayerController().previous(); return null; } @@ -295,7 +295,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi { if (downloader.getValue().getCurrentPlayingIndex() < downloader.getValue().downloadList.size() - 1) { - getDownloadService().next(); + getMediaPlayerController().next(); return true; } else @@ -337,7 +337,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Void doInBackground() throws Throwable { - getDownloadService().pause(); + getMediaPlayerController().pause(); return null; } @@ -361,7 +361,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Void doInBackground() throws Throwable { - getDownloadService().reset(); + getMediaPlayerController().reset(); return null; } @@ -406,7 +406,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override public void onClick(final View view) { - getDownloadService().shuffle(); + getMediaPlayerController().shuffle(); Util.toast(DownloadActivity.this, R.string.download_menu_shuffle_notification); } }); @@ -416,9 +416,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override public void onClick(final View view) { - final RepeatMode repeatMode = getDownloadService().getRepeatMode().next(); + final RepeatMode repeatMode = getMediaPlayerController().getRepeatMode().next(); - getDownloadService().setRepeatMode(repeatMode); + getMediaPlayerController().setRepeatMode(repeatMode); onDownloadListChanged(); switch (repeatMode) @@ -448,7 +448,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Void doInBackground() throws Throwable { - getDownloadService().seekTo(getProgressBar().getProgress()); + getMediaPlayerController().seekTo(getProgressBar().getProgress()); return null; } @@ -483,7 +483,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Void doInBackground() throws Throwable { - getDownloadService().play(position); + getMediaPlayerController().play(position); return null; } @@ -499,15 +499,15 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi registerForContextMenu(playlistView); - final DownloadService downloadService = getDownloadService(); - if (downloadService != null && getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, false)) + final MediaPlayerController mediaPlayerController = getMediaPlayerController(); + if (mediaPlayerController != null && getIntent().getBooleanExtra(Constants.INTENT_EXTRA_NAME_SHUFFLE, false)) { warnIfNetworkOrStorageUnavailable(); - downloadService.setShufflePlayEnabled(true); + mediaPlayerController.setShufflePlayEnabled(true); } - visualizerAvailable = (downloadService != null) && (downloadService.getVisualizerController() != null); - equalizerAvailable = (downloadService != null) && (downloadService.getEqualizerController() != null); + visualizerAvailable = (mediaPlayerController != null) && (mediaPlayerController.getVisualizerController() != null); + equalizerAvailable = (mediaPlayerController != null) && (mediaPlayerController.getEqualizerController() != null); new Thread(new Runnable() { @@ -516,8 +516,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi { try { - DownloadService downloadService = getDownloadService(); - jukeboxAvailable = (downloadService != null) && (downloadService.isJukeboxAvailable()); + MediaPlayerController mediaPlayerController = getMediaPlayerController(); + jukeboxAvailable = (mediaPlayerController != null) && (mediaPlayerController.isJukeboxAvailable()); } catch (Exception e) { @@ -549,7 +549,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi public boolean onTouch(final View view, final MotionEvent motionEvent) { visualizerView.setActive(!visualizerView.isActive()); - getDownloadService().setShowVisualization(visualizerView.isActive()); + getMediaPlayerController().setShowVisualization(visualizerView.isActive()); return true; } }); @@ -565,9 +565,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi { super.onResume(); - final DownloadService downloadService = getDownloadService(); + final MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (downloadService == null || player.getValue().currentPlaying == null) + if (mediaPlayerController == null || localMediaPlayer.getValue().currentPlaying == null) { playlistFlipper.setDisplayedChild(1); } @@ -592,7 +592,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi executorService = Executors.newSingleThreadScheduledExecutor(); executorService.scheduleWithFixedDelay(runnable, 0L, 250L, TimeUnit.MILLISECONDS); - if (downloadService != null && downloadService.getKeepScreenOn()) + if (mediaPlayerController != null && mediaPlayerController.getKeepScreenOn()) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } @@ -603,7 +603,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (visualizerView != null) { - visualizerView.setActive(downloadService != null && downloadService.getShowVisualization()); + visualizerView.setActive(mediaPlayerController != null && mediaPlayerController.getShowVisualization()); } invalidateOptionsMenu(); @@ -612,7 +612,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi // Scroll to current playing/downloading. private void scrollToCurrent() { - if (getDownloadService() == null) + if (getMediaPlayerController() == null) { return; } @@ -706,7 +706,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi { if (id == DIALOG_SAVE_PLAYLIST) { - final String playlistName = (getDownloadService() != null) ? getDownloadService().getSuggestedPlaylistName() : null; + final String playlistName = (getMediaPlayerController() != null) ? getMediaPlayerController().getSuggestedPlaylistName() : null; if (playlistName != null) { playlistNameView.setText(playlistName); @@ -778,11 +778,11 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi visualizerMenuItem.setVisible(visualizerAvailable); } - final DownloadService downloadService = getDownloadService(); + final MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (downloadService != null) + if (mediaPlayerController != null) { - DownloadFile downloadFile = player.getValue().currentPlaying; + DownloadFile downloadFile = localMediaPlayer.getValue().currentPlaying; if (downloadFile != null) { @@ -807,7 +807,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi } - if (downloadService.getKeepScreenOn()) + if (mediaPlayerController.getKeepScreenOn()) { if (screenOption != null) { @@ -827,7 +827,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi jukeboxOption.setEnabled(jukeboxAvailable); jukeboxOption.setVisible(jukeboxAvailable); - if (downloadService.isJukeboxEnabled()) + if (mediaPlayerController.isJukeboxEnabled()) { jukeboxOption.setTitle(R.string.download_menu_jukebox_off); } @@ -967,23 +967,23 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi startActivityForResultWithoutTransition(this, intent); return true; case R.id.menu_remove: - getDownloadService().remove(song); + getMediaPlayerController().remove(song); onDownloadListChanged(); return true; case R.id.menu_item_screen_on_off: - if (getDownloadService().getKeepScreenOn()) + if (getMediaPlayerController().getKeepScreenOn()) { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - getDownloadService().setKeepScreenOn(false); + getMediaPlayerController().setKeepScreenOn(false); } else { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - getDownloadService().setKeepScreenOn(true); + getMediaPlayerController().setKeepScreenOn(true); } return true; case R.id.menu_shuffle: - getDownloadService().shuffle(); + getMediaPlayerController().shuffle(); Util.toast(this, R.string.download_menu_shuffle_notification); return true; case R.id.menu_item_equalizer: @@ -1002,20 +1002,20 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi visualizerViewLayout.setVisibility(View.VISIBLE); } - getDownloadService().setShowVisualization(visualizerView.isActive()); + getMediaPlayerController().setShowVisualization(visualizerView.isActive()); Util.toast(DownloadActivity.this, active ? R.string.download_visualizer_on : R.string.download_visualizer_off); return true; case R.id.menu_item_jukebox: - final boolean jukeboxEnabled = !getDownloadService().isJukeboxEnabled(); - getDownloadService().setJukeboxEnabled(jukeboxEnabled); + final boolean jukeboxEnabled = !getMediaPlayerController().isJukeboxEnabled(); + getMediaPlayerController().setJukeboxEnabled(jukeboxEnabled); Util.toast(DownloadActivity.this, jukeboxEnabled ? R.string.download_jukebox_on : R.string.download_jukebox_off, false); return true; case R.id.menu_item_toggle_list: toggleFullScreenAlbumArt(); return true; case R.id.menu_item_clear_playlist: - getDownloadService().setShufflePlayEnabled(false); - getDownloadService().clear(); + getMediaPlayerController().setShufflePlayEnabled(false); + getMediaPlayerController().clear(); onDownloadListChanged(); return true; case R.id.menu_item_save_playlist: @@ -1077,7 +1077,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi } final String songId = currentSong.getId(); - final int playerPosition = getDownloadService().getPlayerPosition(); + final int playerPosition = getMediaPlayerController().getPlayerPosition(); currentSong.setBookmarkPosition(playerPosition); @@ -1137,10 +1137,10 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return true; case R.id.menu_item_share: - DownloadService downloadService = getDownloadService(); + MediaPlayerController mediaPlayerController = getMediaPlayerController(); List entries = new ArrayList(); - if (downloadService != null) + if (mediaPlayerController != null) { List downloadServiceSongs = downloader.getValue().downloadList; @@ -1170,7 +1170,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi private void update() { - if (getDownloadService() == null) + if (getMediaPlayerController() == null) { return; } @@ -1180,7 +1180,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi onDownloadListChanged(); } - if (currentPlaying != player.getValue().currentPlaying) + if (currentPlaying != localMediaPlayer.getValue().currentPlaying) { onCurrentChanged(); } @@ -1192,7 +1192,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi private void savePlaylistInBackground(final String playlistName) { Util.toast(DownloadActivity.this, getResources().getString(R.string.download_playlist_saving, playlistName)); - getDownloadService().setSuggestedPlaylistName(playlistName); + getMediaPlayerController().setSuggestedPlaylistName(playlistName); new SilentBackgroundTask(this) { @Override @@ -1243,7 +1243,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi private void start() { - final DownloadService service = getDownloadService(); + final MediaPlayerController service = getMediaPlayerController(); final PlayerState state = service.getPlayerState(); if (state == PAUSED || state == COMPLETED || state == STOPPED) @@ -1269,8 +1269,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi private void onDownloadListChanged() { - final DownloadService downloadService = getDownloadService(); - if (downloadService == null) + final MediaPlayerController mediaPlayerController = getMediaPlayerController(); + if (mediaPlayerController == null) { return; } @@ -1306,18 +1306,18 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi public void remove(int which) { DownloadFile item = adapter.getItem(which); - DownloadService downloadService = getDownloadService(); + MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (item == null || downloadService == null) + if (item == null || mediaPlayerController == null) { return; } - DownloadFile currentPlaying = player.getValue().currentPlaying; + DownloadFile currentPlaying = localMediaPlayer.getValue().currentPlaying; if (currentPlaying == item) { - getDownloadService().next(); + getMediaPlayerController().next(); } adapter.remove(item); @@ -1335,7 +1335,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE); currentRevision = downloader.getValue().getDownloadListUpdateRevision(); - switch (downloadService.getRepeatMode()) + switch (mediaPlayerController.getRepeatMode()) { case OFF: repeatButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_repeat_off)); @@ -1353,14 +1353,14 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi private void onCurrentChanged() { - DownloadService downloadService = getDownloadService(); + MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (downloadService == null) + if (mediaPlayerController == null) { return; } - currentPlaying = player.getValue().currentPlaying; + currentPlaying = localMediaPlayer.getValue().currentPlaying; scrollToCurrent(); @@ -1398,16 +1398,16 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi private void onSliderProgressChanged() { - DownloadService downloadService = getDownloadService(); + MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (downloadService == null || onProgressChangedTask != null) + if (mediaPlayerController == null || onProgressChangedTask != null) { return; } onProgressChangedTask = new SilentBackgroundTask(this) { - DownloadService downloadService; + MediaPlayerController mediaPlayerController; boolean isJukeboxEnabled; int millisPlayed; Integer duration; @@ -1416,11 +1416,11 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Void doInBackground() throws Throwable { - downloadService = getDownloadService(); - isJukeboxEnabled = downloadService.isJukeboxEnabled(); - millisPlayed = Math.max(0, downloadService.getPlayerPosition()); - duration = downloadService.getPlayerDuration(); - playerState = getDownloadService().getPlayerState(); + this.mediaPlayerController = getMediaPlayerController(); + isJukeboxEnabled = this.mediaPlayerController.isJukeboxEnabled(); + millisPlayed = Math.max(0, this.mediaPlayerController.getPlayerPosition()); + duration = this.mediaPlayerController.getPlayerDuration(); + playerState = getMediaPlayerController().getPlayerState(); return null; } @@ -1457,9 +1457,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi setActionBarSubtitle(R.string.download_playerstate_buffering); break; case STARTED: - final DownloadService downloadService = getDownloadService(); + final MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (downloadService != null && downloadService.isShufflePlayEnabled()) + if (mediaPlayerController != null && mediaPlayerController.isShufflePlayEnabled()) { setActionBarSubtitle(R.string.download_playerstate_playing_shuffle); } @@ -1503,7 +1503,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi break; } - // TODO: It would be a lot nicer if DownloadService would send an event when this is necessary instead of updating every time + // TODO: It would be a lot nicer if MediaPlayerController would send an event when this is necessary instead of updating every time displaySongRating(); onProgressChangedTask = null; @@ -1514,8 +1514,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi private void changeProgress(final int ms) { - final DownloadService downloadService = getDownloadService(); - if (downloadService == null) + final MediaPlayerController mediaPlayerController = getMediaPlayerController(); + if (mediaPlayerController == null) { return; } @@ -1529,12 +1529,12 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Void doInBackground() throws Throwable { - msPlayed = Math.max(0, downloadService.getPlayerPosition()); - duration = downloadService.getPlayerDuration(); + msPlayed = Math.max(0, mediaPlayerController.getPlayerPosition()); + duration = mediaPlayerController.getPlayerDuration(); final int msTotal = duration; seekTo = msPlayed + ms > msTotal ? msTotal : msPlayed + ms; - downloadService.seekTo(seekTo); + mediaPlayerController.seekTo(seekTo); return null; } @@ -1562,9 +1562,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { - final DownloadService downloadService = getDownloadService(); + final MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (downloadService == null || e1 == null || e2 == null) + if (mediaPlayerController == null || e1 == null || e2 == null) { return false; } @@ -1582,7 +1582,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi warnIfNetworkOrStorageUnavailable(); if (downloader.getValue().getCurrentPlayingIndex() < downloader.getValue().downloadList.size() - 1) { - downloadService.next(); + mediaPlayerController.next(); onCurrentChanged(); onSliderProgressChanged(); } @@ -1593,7 +1593,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (e2X - e1X > swipeDistance && absX > swipeVelocity) { warnIfNetworkOrStorageUnavailable(); - downloadService.previous(); + mediaPlayerController.previous(); onCurrentChanged(); onSliderProgressChanged(); return true; @@ -1603,7 +1603,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (e2Y - e1Y > swipeDistance && absY > swipeVelocity) { warnIfNetworkOrStorageUnavailable(); - downloadService.seekTo(downloadService.getPlayerPosition() + 30000); + mediaPlayerController.seekTo(mediaPlayerController.getPlayerPosition() + 30000); onSliderProgressChanged(); return true; } @@ -1612,7 +1612,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (e1Y - e2Y > swipeDistance && absY > swipeVelocity) { warnIfNetworkOrStorageUnavailable(); - downloadService.seekTo(downloadService.getPlayerPosition() - 8000); + mediaPlayerController.seekTo(mediaPlayerController.getPlayerPosition() - 8000); onSliderProgressChanged(); return true; } @@ -1663,6 +1663,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return; displaySongRating(); - getDownloadService().setSongRating(rating); + getMediaPlayerController().setSongRating(rating); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java index 329bdf79..07b48a96 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/EqualizerActivity.java @@ -32,8 +32,7 @@ import android.widget.TextView; import org.moire.ultrasonic.R; import org.moire.ultrasonic.audiofx.EqualizerController; -import org.moire.ultrasonic.service.DownloadService; -import org.moire.ultrasonic.service.DownloadServiceImpl; +import org.moire.ultrasonic.service.MediaPlayerController; import java.util.HashMap; import java.util.Map; @@ -56,7 +55,7 @@ public class EqualizerActivity extends ResultActivity private EqualizerController equalizerController; private Equalizer equalizer; - private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); @Override public void onCreate(Bundle bundle) @@ -129,7 +128,7 @@ public class EqualizerActivity extends ResultActivity private void setup() { - equalizerController = downloadServiceImpl.getValue().getEqualizerController(); + equalizerController = mediaPlayerControllerLazy.getValue().getEqualizerController(); equalizer = equalizerController.getEqualizer(); initEqualizer(); 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 1b053e19..f9052ffd 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java @@ -34,11 +34,8 @@ import android.widget.ListView; 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.DownloadServiceLifecycleSupport; -import org.moire.ultrasonic.service.ExternalStorageMonitor; -import org.moire.ultrasonic.service.MediaPlayerService; +import org.moire.ultrasonic.service.MediaPlayerController; +import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.util.Constants; @@ -73,7 +70,7 @@ public class MainActivity extends SubsonicTabActivity private static boolean infoDialogDisplayed; private static boolean shouldUseId3; - private Lazy lifecycleSupport = inject(DownloadServiceLifecycleSupport.class); + private Lazy lifecycleSupport = inject(MediaPlayerLifecycleSupport.class); /** * Called when the activity is first created. @@ -87,9 +84,9 @@ public class MainActivity extends SubsonicTabActivity { setResult(Constants.RESULT_CLOSE_ALL); - if (getDownloadService() != null) + if (getMediaPlayerController() != null) { - getDownloadService().stopJukeboxService(); + getMediaPlayerController().stopJukeboxService(); } if (getImageLoader() != null) @@ -464,7 +461,7 @@ public class MainActivity extends SubsonicTabActivity private void setActiveServer(final int instance) { - final DownloadService service = getDownloadService(); + final MediaPlayerController service = getMediaPlayerController(); if (Util.getActiveServer(this) != instance) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java index fb0b542b..113449eb 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java @@ -37,7 +37,7 @@ import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.domain.SearchCriteria; import org.moire.ultrasonic.domain.SearchResult; -import org.moire.ultrasonic.service.DownloadService; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.util.BackgroundTask; @@ -322,7 +322,7 @@ public class SearchActivity extends SubsonicTabActivity { songs.add(entry); Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size())); - getDownloadService().unpin(songs); + getMediaPlayerController().unpin(songs); } break; case R.id.menu_item_share: @@ -341,7 +341,7 @@ public class SearchActivity extends SubsonicTabActivity private void downloadBackground(final boolean save, final List songs) { - if (getDownloadService() == null) + if (getMediaPlayerController() == null) { return; } @@ -352,7 +352,7 @@ public class SearchActivity extends SubsonicTabActivity public void run() { warnIfNetworkOrStorageUnavailable(); - getDownloadService().downloadBackground(songs, save); + getMediaPlayerController().downloadBackground(songs, save); } }; @@ -508,19 +508,19 @@ public class SearchActivity extends SubsonicTabActivity private void onSongSelected(MusicDirectory.Entry song, boolean save, boolean append, boolean autoplay, boolean playNext) { - DownloadService downloadService = getDownloadService(); - if (downloadService != null) + MediaPlayerController mediaPlayerController = getMediaPlayerController(); + if (mediaPlayerController != null) { if (!append && !playNext) { - downloadService.clear(); + mediaPlayerController.clear(); } - downloadService.download(Collections.singletonList(song), save, false, playNext, false, false); + mediaPlayerController.download(Collections.singletonList(song), save, false, playNext, false, false); if (autoplay) { - downloadService.play(downloader.getValue().downloadList.size() - 1); + mediaPlayerController.play(downloader.getValue().downloadList.size() - 1); } Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1)); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java index 76ed4e51..9b13b225 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java @@ -1010,7 +1010,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity private void enableButtons() { - if (getDownloadService() == null) + if (getMediaPlayerController() == null) { return; } @@ -1061,7 +1061,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity private void downloadBackground(final boolean save, final List songs) { - if (getDownloadService() == null) + if (getMediaPlayerController() == null) { return; } @@ -1072,7 +1072,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity public void run() { warnIfNetworkOrStorageUnavailable(); - getDownloadService().downloadBackground(songs, save); + getMediaPlayerController().downloadBackground(songs, save); if (save) { @@ -1098,19 +1098,19 @@ public class SelectAlbumActivity extends SubsonicTabActivity songs = getSelectedSongs(albumListView); } - if (getDownloadService() != null) + if (getMediaPlayerController() != null) { - getDownloadService().delete(songs); + getMediaPlayerController().delete(songs); } } private void unpin() { - if (getDownloadService() != null) + if (getMediaPlayerController() != null) { List songs = getSelectedSongs(albumListView); Util.toast(SelectAlbumActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_unpinned, songs.size(), songs.size())); - getDownloadService().unpin(songs); + getMediaPlayerController().unpin(songs); } } 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 3084b4a6..3a6b5279 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -76,10 +76,10 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen private static final String STATE_ACTIVE_POSITION = "org.moire.ultrasonic.activePosition"; private static final int DIALOG_ASK_FOR_SHARE_DETAILS = 102; - private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); - private Lazy lifecycleSupport = inject(DownloadServiceLifecycleSupport.class); + private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); + private Lazy lifecycleSupport = inject(MediaPlayerLifecycleSupport.class); protected Lazy downloader = inject(Downloader.class); - protected Lazy player = inject(Player.class); + protected Lazy localMediaPlayer = inject(LocalMediaPlayer.class); public MenuDrawer menuDrawer; @@ -211,11 +211,11 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen boolean isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN; boolean isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP; boolean isVolumeAdjust = isVolumeDown || isVolumeUp; - boolean isJukebox = getDownloadService() != null && getDownloadService().isJukeboxEnabled(); + boolean isJukebox = getMediaPlayerController() != null && getMediaPlayerController().isJukeboxEnabled(); if (isVolumeAdjust && isJukebox) { - getDownloadService().adjustJukeboxVolume(isVolumeUp); + getMediaPlayerController().adjustJukeboxVolume(isVolumeUp); return true; } @@ -265,16 +265,16 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen if (nowPlayingView != null) { - PlayerState playerState = downloadServiceImpl.getValue().getPlayerState(); + PlayerState playerState = mediaPlayerControllerLazy.getValue().getPlayerState(); if (playerState.equals(PlayerState.PAUSED) || playerState.equals(PlayerState.STARTED)) { - DownloadFile file = player.getValue().currentPlaying; + DownloadFile file = localMediaPlayer.getValue().currentPlaying; if (file != null) { final Entry song = file.getSong(); - showNowPlaying(SubsonicTabActivity.this, downloadServiceImpl.getValue(), song, playerState); + showNowPlaying(SubsonicTabActivity.this, mediaPlayerControllerLazy.getValue(), song, playerState); } } else @@ -309,9 +309,9 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen } } - private void showNowPlaying(final Context context, final DownloadService downloadService, final Entry song, final PlayerState playerState) + private void showNowPlaying(final Context context, final MediaPlayerController mediaPlayerController, final Entry song, final PlayerState playerState) { - if (context == null || downloadService == null || song == null || playerState == null) + if (context == null || mediaPlayerController == null || song == null || playerState == null) { return; } @@ -390,7 +390,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen ImageView nowPlayingControlPlay = (ImageView) nowPlayingView.findViewById(R.id.now_playing_control_play); - SwipeDetector swipeDetector = new SwipeDetector(SubsonicTabActivity.this, downloadService); + SwipeDetector swipeDetector = new SwipeDetector(SubsonicTabActivity.this, mediaPlayerController); setOnTouchListenerOnUiThread(nowPlayingView, swipeDetector); setOnClickListenerOnUiThread(nowPlayingView, new OnClickListener() @@ -406,7 +406,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen @Override public void onClick(View view) { - downloadService.togglePlayPause(); + mediaPlayerController.togglePlayPause(); } }); @@ -765,9 +765,9 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen } } - public DownloadService getDownloadService() + public MediaPlayerController getMediaPlayerController() { - return downloadServiceImpl.getValue(); + return mediaPlayerControllerLazy.getValue(); } protected void warnIfNetworkOrStorageUnavailable() @@ -818,7 +818,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen void download(final boolean append, final boolean save, final boolean autoPlay, final boolean playNext, final boolean shuffle, final List songs) { - if (getDownloadService() == null) + if (getMediaPlayerController() == null) { return; } @@ -830,16 +830,16 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen { if (!append && !playNext) { - getDownloadService().clear(); + getMediaPlayerController().clear(); } warnIfNetworkOrStorageUnavailable(); - getDownloadService().download(songs, save, autoPlay, playNext, shuffle, false); + getMediaPlayerController().download(songs, save, autoPlay, playNext, shuffle, false); String playlistName = getIntent().getStringExtra(Constants.INTENT_EXTRA_NAME_PLAYLIST_NAME); if (playlistName != null) { - getDownloadService().setSuggestedPlaylistName(playlistName); + getMediaPlayerController().setSuggestedPlaylistName(playlistName); } if (autoPlay) @@ -994,23 +994,23 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen Collections.sort(songs, new EntryByDiscAndTrackComparator()); } - DownloadService downloadService = getDownloadService(); - if (!songs.isEmpty() && downloadService != null) + MediaPlayerController mediaPlayerController = getMediaPlayerController(); + if (!songs.isEmpty() && mediaPlayerController != null) { if (!append && !playNext && !unpin && !background) { - downloadService.clear(); + mediaPlayerController.clear(); } warnIfNetworkOrStorageUnavailable(); if (!background) { if (unpin) { - downloadService.unpin(songs); + mediaPlayerController.unpin(songs); } else { - downloadService.download(songs, save, autoplay, playNext, shuffle, false); + mediaPlayerController.download(songs, save, autoplay, playNext, shuffle, false); if (!append && Util.getShouldTransitionOnPlaybackPreference(SubsonicTabActivity.this)) { startActivityForResultWithoutTransition(SubsonicTabActivity.this, DownloadActivity.class); @@ -1021,11 +1021,11 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen { if (unpin) { - downloadService.unpin(songs); + mediaPlayerController.unpin(songs); } else { - downloadService.downloadBackground(songs, save); + mediaPlayerController.downloadBackground(songs, save); } } } @@ -1353,15 +1353,15 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen protected class SwipeDetector implements OnTouchListener { - public SwipeDetector(SubsonicTabActivity activity, final DownloadService downloadService) + public SwipeDetector(SubsonicTabActivity activity, final MediaPlayerController mediaPlayerController) { - this.downloadService = downloadService; + this.mediaPlayerController = mediaPlayerController; this.activity = activity; } private static final int MIN_DISTANCE = 30; private float downX, downY, upX, upY; - private DownloadService downloadService; + private MediaPlayerController mediaPlayerController; private SubsonicTabActivity activity; @Override @@ -1388,12 +1388,12 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen // left or right if (deltaX < 0) { - downloadService.previous(); + mediaPlayerController.previous(); return false; } if (deltaX > 0) { - downloadService.next(); + mediaPlayerController.next(); return false; } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java index 57e780ae..abe44f7d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java @@ -16,8 +16,7 @@ import org.moire.ultrasonic.activity.SubsonicTabActivity; import org.moire.ultrasonic.featureflags.Feature; import org.moire.ultrasonic.featureflags.FeatureStorage; import org.moire.ultrasonic.provider.SearchSuggestionProvider; -import org.moire.ultrasonic.service.DownloadService; -import org.moire.ultrasonic.service.DownloadServiceImpl; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.util.*; import java.io.File; @@ -66,7 +65,7 @@ public class SettingsFragment extends PreferenceFragment private SharedPreferences settings; private int activeServers; - private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -407,6 +406,6 @@ public class SettingsFragment extends PreferenceFragment } // Clear download queue. - downloadServiceImpl.getValue().clear(); + mediaPlayerControllerLazy.getValue().clear(); } } 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 e88db061..f710fe8e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java @@ -17,7 +17,7 @@ import org.moire.ultrasonic.R; import org.moire.ultrasonic.activity.DownloadActivity; import org.moire.ultrasonic.activity.MainActivity; import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.service.DownloadService; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.FileUtil; @@ -67,7 +67,7 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider } /** - * Handle a change notification coming over from {@link DownloadService} + * Handle a change notification coming over from {@link MediaPlayerController} */ public void notifyChange(Context context, MusicDirectory.Entry currentSong, boolean playing, boolean setAlbum) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java index 214436c7..0fe2a691 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java @@ -5,9 +5,9 @@ import android.content.Context; import android.content.Intent; import org.moire.ultrasonic.domain.MusicDirectory.Entry; -import org.moire.ultrasonic.service.DownloadServiceImpl; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.service.Downloader; -import org.moire.ultrasonic.service.Player; +import org.moire.ultrasonic.service.LocalMediaPlayer; import kotlin.Lazy; @@ -16,19 +16,19 @@ import static org.koin.java.standalone.KoinJavaComponent.inject; public class A2dpIntentReceiver extends BroadcastReceiver { private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse"; - private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); private Lazy downloader = inject(Downloader.class); - protected Lazy player = inject(Player.class); + protected Lazy localMediaPlayer = inject(LocalMediaPlayer.class); @Override public void onReceive(Context context, Intent intent) { - if (player.getValue().currentPlaying == null) + if (localMediaPlayer.getValue().currentPlaying == null) { return; } - Entry song = player.getValue().currentPlaying.getSong(); + Entry song = localMediaPlayer.getValue().currentPlaying.getSong(); if (song == null) { @@ -38,7 +38,7 @@ public class A2dpIntentReceiver extends BroadcastReceiver Intent avrcpIntent = new Intent(PLAYSTATUS_RESPONSE); Integer duration = song.getDuration(); - int playerPosition = downloadServiceImpl.getValue().getPlayerPosition(); + int playerPosition = mediaPlayerControllerLazy.getValue().getPlayerPosition(); int listSize = downloader.getValue().getDownloads().size(); if (duration != null) @@ -49,7 +49,7 @@ public class A2dpIntentReceiver extends BroadcastReceiver avrcpIntent.putExtra("position", (long) playerPosition); avrcpIntent.putExtra("ListSize", (long) listSize); - switch (downloadServiceImpl.getValue().getPlayerState()) + switch (mediaPlayerControllerLazy.getValue().getPlayerState()) { case STARTED: avrcpIntent.putExtra("playing", true); 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 2315dc3e..ee636c40 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java @@ -25,7 +25,7 @@ import android.os.Bundle; import android.os.Parcelable; import android.util.Log; -import org.moire.ultrasonic.service.DownloadServiceLifecycleSupport; +import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport; import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.Util; @@ -39,7 +39,7 @@ import static org.koin.java.standalone.KoinJavaComponent.inject; public class MediaButtonIntentReceiver extends BroadcastReceiver { private static final String TAG = MediaButtonIntentReceiver.class.getSimpleName(); - private Lazy lifecycleSupport = inject(DownloadServiceLifecycleSupport.class); + private Lazy lifecycleSupport = inject(MediaPlayerLifecycleSupport.class); @Override public void onReceive(Context context, Intent intent) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java index f5f18df4..dd22283e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java @@ -26,29 +26,31 @@ public class Downloader { private static final String TAG = Downloader.class.getSimpleName(); - private final ShufflePlayBuffer shufflePlayBuffer; - private final ExternalStorageMonitor externalStorageMonitor; - private final Player player; - public Lazy jukeboxService = inject(JukeboxService.class); - public final List downloadList = new ArrayList<>(); public final List backgroundDownloadList = new ArrayList<>(); + public DownloadFile currentDownloading; + + private final ShufflePlayBuffer shufflePlayBuffer; + private final ExternalStorageMonitor externalStorageMonitor; + private final LocalMediaPlayer localMediaPlayer; + private final Context context; + + // TODO: This is a circular reference, try to remove + private Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); + private final List cleanupCandidates = new ArrayList<>(); private final LRUCache downloadFileCache = new LRUCache<>(100); - - public DownloadFile currentDownloading; - public static long revision; - private ScheduledExecutorService executorService; - private Context context; + private long revision; + public Downloader(Context context, ShufflePlayBuffer shufflePlayBuffer, ExternalStorageMonitor externalStorageMonitor, - Player player) + LocalMediaPlayer localMediaPlayer) { this.context = context; this.shufflePlayBuffer = shufflePlayBuffer; this.externalStorageMonitor = externalStorageMonitor; - this.player = player; + this.localMediaPlayer = localMediaPlayer; } public void onCreate() @@ -78,6 +80,8 @@ public class Downloader public void onDestroy() { executorService.shutdown(); + clear(); + clearBackground(); Log.i(TAG, "Downloader destroyed"); } @@ -93,7 +97,7 @@ public class Downloader checkShufflePlay(context); } - if (jukeboxService.getValue().isEnabled() || !Util.isNetworkConnected(context)) + if (jukeboxMediaPlayer.getValue().isEnabled() || !Util.isNetworkConnected(context)) { return; } @@ -104,7 +108,7 @@ public class Downloader } // Need to download current playing? - if (player.currentPlaying != null && player.currentPlaying != currentDownloading && !player.currentPlaying.isWorkDone()) + if (localMediaPlayer.currentPlaying != null && localMediaPlayer.currentPlaying != currentDownloading && !localMediaPlayer.currentPlaying.isWorkDone()) { // Cancel current download, if necessary. if (currentDownloading != null) @@ -112,7 +116,7 @@ public class Downloader currentDownloading.cancelDownload(); } - currentDownloading = player.currentPlaying; + currentDownloading = localMediaPlayer.currentPlaying; currentDownloading.download(); cleanupCandidates.add(currentDownloading); @@ -138,7 +142,7 @@ public class Downloader if (n != 0) { - int start = player.currentPlaying == null ? 0 : getCurrentPlayingIndex(); + int start = localMediaPlayer.currentPlaying == null ? 0 : getCurrentPlayingIndex(); if (start == -1) start = 0; int i = start; @@ -154,12 +158,12 @@ public class Downloader cleanupCandidates.add(currentDownloading); if (i == (start + 1)) { - player.setNextPlayerState(DOWNLOADING); + localMediaPlayer.setNextPlayerState(DOWNLOADING); } break; } } - else if (player.currentPlaying != downloadFile) + else if (localMediaPlayer.currentPlaying != downloadFile) { preloaded++; } @@ -201,7 +205,7 @@ public class Downloader public synchronized int getCurrentPlayingIndex() { - return downloadList.indexOf(player.currentPlaying); + return downloadList.indexOf(localMediaPlayer.currentPlaying); } public long getDownloadListDuration() @@ -237,11 +241,6 @@ public class Downloader return temp; } - public List getBackgroundDownloads() - { - return backgroundDownloadList; - } - public long getDownloadListUpdateRevision() { return revision; @@ -258,7 +257,7 @@ public class Downloader } } - public synchronized void clearBackground() + private void clearBackground() { if (currentDownloading != null && backgroundDownloadList.contains(currentDownloading)) { @@ -337,25 +336,14 @@ public class Downloader public synchronized void shuffle() { Collections.shuffle(downloadList); - if (player.currentPlaying != null) + if (localMediaPlayer.currentPlaying != null) { downloadList.remove(getCurrentPlayingIndex()); - downloadList.add(0, player.currentPlaying); + downloadList.add(0, localMediaPlayer.currentPlaying); } revision++; } - public synchronized void setFirstPlaying() - { - if (player.currentPlaying == null && downloadList.size() > 0) - { - player.currentPlaying = downloadList.get(0); - player.currentPlaying.setPlaying(true); - } - - checkDownloads(); - } - public synchronized DownloadFile getDownloadFileForSong(MusicDirectory.Entry song) { for (DownloadFile downloadFile : downloadList) @@ -388,7 +376,7 @@ public class Downloader while (iterator.hasNext()) { DownloadFile downloadFile = iterator.next(); - if (downloadFile != player.currentPlaying && downloadFile != currentDownloading) + if (downloadFile != localMediaPlayer.currentPlaying && downloadFile != currentDownloading) { if (downloadFile.cleanup()) { @@ -418,7 +406,7 @@ public class Downloader } } - int currIndex = player.currentPlaying == null ? 0 : getCurrentPlayingIndex(); + int currIndex = localMediaPlayer.currentPlaying == null ? 0 : getCurrentPlayingIndex(); // Only shift playlist if playing song #5 or later. if (currIndex > 4) @@ -435,19 +423,19 @@ public class Downloader if (revisionBefore != revision) { - jukeboxService.getValue().updatePlaylist(); + jukeboxMediaPlayer.getValue().updatePlaylist(); } if (wasEmpty && !downloadList.isEmpty()) { - if (jukeboxService.getValue().isEnabled()) + if (jukeboxMediaPlayer.getValue().isEnabled()) { - jukeboxService.getValue().skip(0, 0); - player.setPlayerState(STARTED); + jukeboxMediaPlayer.getValue().skip(0, 0); + localMediaPlayer.setPlayerState(STARTED); } else { - player.play(downloadList.get(0)); + localMediaPlayer.play(downloadList.get(0)); } } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java similarity index 93% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java index 449e6a43..b79a572f 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java @@ -27,6 +27,7 @@ import android.view.View; import android.widget.ProgressBar; import android.widget.Toast; +import org.jetbrains.annotations.NotNull; import org.moire.ultrasonic.R; import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException; import org.moire.ultrasonic.domain.JukeboxStatus; @@ -54,9 +55,9 @@ import static org.koin.java.standalone.KoinJavaComponent.inject; * @author Sindre Mehus * @version $Id$ */ -public class JukeboxService +public class JukeboxMediaPlayer { - private static final String TAG = JukeboxService.class.getSimpleName(); + private static final String TAG = JukeboxMediaPlayer.class.getSimpleName(); private static final long STATUS_UPDATE_INTERVAL_SECONDS = 5L; private final TaskQueue tasks = new TaskQueue(); @@ -72,7 +73,7 @@ public class JukeboxService private Context context; // TODO: These create circular references, try to refactor - private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy mediaPlayerControllerLazy = inject(MediaPlayerControllerImpl.class); private final Downloader downloader; // TODO: Report warning if queue fills up. @@ -81,7 +82,7 @@ public class JukeboxService // TODO: Persist RC state? // TODO: Minimize status updates. - public JukeboxService(Context context, Downloader downloader) + public JukeboxMediaPlayer(Context context, Downloader downloader) { this.context = context; this.downloader = downloader; @@ -187,7 +188,7 @@ public class JukeboxService if (index != null && index != -1 && index != downloader.getCurrentPlayingIndex()) { - downloadServiceImpl.getValue().setCurrentPlaying(index); + mediaPlayerControllerLazy.getValue().setCurrentPlaying(index); } } @@ -224,7 +225,7 @@ public class JukeboxService } }); - downloadServiceImpl.getValue().setJukeboxEnabled(false); + mediaPlayerControllerLazy.getValue().setJukeboxEnabled(false); } public void updatePlaylist() @@ -258,7 +259,7 @@ public class JukeboxService } tasks.add(new Skip(index, offsetSeconds)); - downloadServiceImpl.getValue().setPlayerState(PlayerState.STARTED); + mediaPlayerControllerLazy.getValue().setPlayerState(PlayerState.STARTED); } public void stop() @@ -290,10 +291,8 @@ public class JukeboxService tasks.remove(SetGain.class); tasks.add(new SetGain(gain)); - if (volumeToast == null) - { - volumeToast = new VolumeToast(context); - } + if (volumeToast == null) volumeToast = new VolumeToast(context); + volumeToast.setVolume(gain); } @@ -377,10 +376,11 @@ public class JukeboxService } } - private abstract class JukeboxTask + private abstract static class JukeboxTask { abstract JukeboxStatus execute() throws Exception; + @NotNull @Override public String toString() { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java similarity index 94% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java index def1bb07..02659e46 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Player.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java @@ -18,6 +18,7 @@ import android.os.PowerManager; import android.util.Log; import android.widget.SeekBar; +import org.jetbrains.annotations.NotNull; import org.moire.ultrasonic.activity.DownloadActivity; import org.moire.ultrasonic.audiofx.EqualizerController; import org.moire.ultrasonic.audiofx.VisualizerController; @@ -32,6 +33,7 @@ import org.moire.ultrasonic.util.Util; import java.io.File; import java.net.URLEncoder; +import java.util.Locale; import static org.moire.ultrasonic.domain.PlayerState.COMPLETED; import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING; @@ -41,37 +43,9 @@ import static org.moire.ultrasonic.domain.PlayerState.PREPARED; import static org.moire.ultrasonic.domain.PlayerState.PREPARING; import static org.moire.ultrasonic.domain.PlayerState.STARTED; -public class Player +public class LocalMediaPlayer { - private static final String TAG = Player.class.getSimpleName(); - private final Context context; - - private PowerManager.WakeLock wakeLock; - public DownloadFile currentPlaying; - public DownloadFile nextPlaying; - private static boolean nextSetup; - private static CancellableTask nextPlayingTask; - private MediaPlayer mediaPlayer; - - private MediaPlayer nextMediaPlayer; - private Looper mediaPlayerLooper; - private Handler mediaPlayerHandler; - public static int cachedPosition; - private StreamProxy proxy; - - public PlayerState playerState = IDLE; - public PlayerState nextPlayerState = IDLE; - - private AudioManager audioManager; - public RemoteControlClient remoteControlClient; - - public static boolean equalizerAvailable; - public static boolean visualizerAvailable; - private EqualizerController equalizerController; - private VisualizerController visualizerController; - private CancellableTask bufferTask; - private PositionCache positionCache; - private int secondaryProgress = -1; + private static final String TAG = LocalMediaPlayer.class.getSimpleName(); public Consumer onCurrentPlayingChanged; public Consumer onSongCompleted; @@ -79,6 +53,35 @@ public class Player public Runnable onPrepared; public Runnable onNextSongRequested; + public static boolean equalizerAvailable; + public static boolean visualizerAvailable; + + public PlayerState playerState = IDLE; + public DownloadFile currentPlaying; + public DownloadFile nextPlaying; + private PlayerState nextPlayerState = IDLE; + private boolean nextSetup; + private CancellableTask nextPlayingTask; + private PowerManager.WakeLock wakeLock; + + private MediaPlayer mediaPlayer; + private MediaPlayer nextMediaPlayer; + private Looper mediaPlayerLooper; + private Handler mediaPlayerHandler; + private int cachedPosition; + private StreamProxy proxy; + + private AudioManager audioManager; + private RemoteControlClient remoteControlClient; + + private EqualizerController equalizerController; + private VisualizerController visualizerController; + private CancellableTask bufferTask; + private PositionCache positionCache; + private int secondaryProgress = -1; + + private final Context context; + static { try @@ -105,7 +108,7 @@ public class Player } } - public Player(Context context) + public LocalMediaPlayer(Context context) { this.context = context; } @@ -134,7 +137,7 @@ public class Player @Override public boolean onError(MediaPlayer mediaPlayer, int what, int more) { - handleError(new Exception(String.format("MediaPlayer error: %d (%d)", what, more))); + handleError(new Exception(String.format(Locale.getDefault(), "MediaPlayer error: %d (%d)", what, more))); return false; } }); @@ -187,7 +190,7 @@ public class Player } } - Log.i(TAG, "Player created"); + Log.i(TAG, "LocalMediaPlayer created"); } public void onDestroy() @@ -237,7 +240,7 @@ public class Player { } - Log.i(TAG, "Player destroyed"); + Log.i(TAG, "LocalMediaPlayer destroyed"); } public EqualizerController getEqualizerController() @@ -452,28 +455,18 @@ public class Player 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 (playerState == 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); + } + } else { + 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); + } } if (currentPlaying != null) @@ -486,7 +479,7 @@ public class Player String album = currentSong.getAlbum(); String title = currentSong.getTitle(); Integer currentSongDuration = currentSong.getDuration(); - Long duration = 0L; + long duration = 0L; if (currentSongDuration != null) duration = (long) currentSongDuration * 1000; @@ -653,7 +646,8 @@ public class Player proxy.start(); } - dataSource = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), URLEncoder.encode(dataSource, Constants.UTF_8)); + dataSource = String.format(Locale.getDefault(), "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) @@ -708,7 +702,7 @@ public class Player DownloadActivity.getProgressBar().setSecondaryProgress(100 * progressBar.getMax()); } - synchronized (Player.this) + synchronized (LocalMediaPlayer.this) { if (position != 0) { @@ -856,15 +850,11 @@ public class Player if (Util.getGaplessPlaybackPreference(context) && nextPlaying != null && nextPlayerState == PlayerState.PREPARED) { - if (!nextSetup) - { - playNext(); - } - else + if (nextSetup) { nextSetup = false; - playNext(); } + playNext(); } else { @@ -978,6 +968,7 @@ public class Player return completeFileAvailable || size >= expectedFileSize; } + @NotNull @Override public String toString() { @@ -1041,6 +1032,7 @@ public class Player return completeFileAvailable && (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED); } + @NotNull @Override public String toString() { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java similarity index 92% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadService.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java index b11eaaf6..19f15d80 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java @@ -30,7 +30,7 @@ import java.util.List; * @author Sindre Mehus * @version $Id$ */ -public interface DownloadService +public interface MediaPlayerController { void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, boolean newPlaylist); @@ -53,10 +53,6 @@ public interface DownloadService boolean getShowVisualization(); - boolean getEqualizerAvailable(); - - boolean getVisualizerAvailable(); - void setShowVisualization(boolean showVisualization); void clear(); @@ -103,8 +99,6 @@ public interface DownloadService boolean isJukeboxAvailable(); - boolean isSharingAvailable(); - void setJukeboxEnabled(boolean b); void adjustJukeboxVolume(boolean up); @@ -113,14 +107,10 @@ public interface DownloadService void setVolume(float volume); - void swap(boolean mainList, int from, int to); - void restore(List songs, int currentPlayingIndex, int currentPlayingPosition, boolean autoPlay, boolean newPlaylist); void stopJukeboxService(); - void startJukeboxService(); - void updateNotification(); void setSongRating(final int rating); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java similarity index 78% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java index 8b7b2795..cd42902f 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java @@ -20,7 +20,6 @@ package org.moire.ultrasonic.service; import android.content.Context; import android.content.Intent; -import android.os.Handler; import android.util.Log; import org.koin.java.standalone.KoinJavaComponent; @@ -47,9 +46,9 @@ import static org.koin.java.standalone.KoinJavaComponent.inject; * @author Sindre Mehus, Joshua Bahnsen * @version $Id$ */ -public class DownloadServiceImpl implements DownloadService +public class MediaPlayerControllerImpl implements MediaPlayerController { - private static final String TAG = DownloadServiceImpl.class.getSimpleName(); + private static final String TAG = MediaPlayerControllerImpl.class.getSimpleName(); private String suggestedPlaylistName; private boolean keepScreenOn; @@ -58,20 +57,20 @@ public class DownloadServiceImpl implements DownloadService private boolean autoPlayStart; private Context context; - public Lazy jukeboxService = inject(JukeboxService.class); + private Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); private Lazy externalStorageMonitor = inject(ExternalStorageMonitor.class); private final Downloader downloader; private final ShufflePlayBuffer shufflePlayBuffer; - private final Player player; + private final LocalMediaPlayer localMediaPlayer; - public DownloadServiceImpl(Context context, Downloader downloader, ShufflePlayBuffer shufflePlayBuffer, - Player player) + public MediaPlayerControllerImpl(Context context, Downloader downloader, ShufflePlayBuffer shufflePlayBuffer, + LocalMediaPlayer localMediaPlayer) { this.context = context; this.downloader = downloader; this.shufflePlayBuffer = shufflePlayBuffer; - this.player = player; + this.localMediaPlayer = localMediaPlayer; externalStorageMonitor.getValue().onCreate(new Runnable() { @Override @@ -83,14 +82,14 @@ public class DownloadServiceImpl implements DownloadService int instance = Util.getActiveServer(context); setJukeboxEnabled(Util.getJukeboxEnabled(context, instance)); - Log.i(TAG, "DownloadServiceImpl created"); + Log.i(TAG, "MediaPlayerControllerImpl created"); } public void onDestroy() { externalStorageMonitor.getValue().onDestroy(); context.stopService(new Intent(context, MediaPlayerService.class)); - Log.i(TAG, "DownloadServiceImpl destroyed"); + Log.i(TAG, "MediaPlayerControllerImpl destroyed"); } private void executeOnStartedMediaPlayerService(final Consumer taskToExecute) @@ -120,20 +119,20 @@ public class DownloadServiceImpl implements DownloadService } }); - if (player.currentPlaying != null) + if (localMediaPlayer.currentPlaying != null) { - if (autoPlay && jukeboxService.getValue().isEnabled()) + if (autoPlay && jukeboxMediaPlayer.getValue().isEnabled()) { - jukeboxService.getValue().skip(downloader.getCurrentPlayingIndex(), currentPlayingPosition / 1000); + jukeboxMediaPlayer.getValue().skip(downloader.getCurrentPlayingIndex(), currentPlayingPosition / 1000); } else { - if (player.currentPlaying.isCompleteFileAvailable()) + if (localMediaPlayer.currentPlaying.isCompleteFileAvailable()) { executeOnStartedMediaPlayerService(new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { - player.doPlay(player.currentPlaying, currentPlayingPosition, autoPlay); + localMediaPlayer.doPlay(localMediaPlayer.currentPlaying, currentPlayingPosition, autoPlay); } }); } @@ -167,7 +166,7 @@ public class DownloadServiceImpl implements DownloadService @Override public synchronized void togglePlayPause() { - if (player.playerState == PlayerState.IDLE) autoPlayStart = true; + if (localMediaPlayer.playerState == PlayerState.IDLE) autoPlayStart = true; executeOnStartedMediaPlayerService(new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { @@ -224,7 +223,7 @@ public class DownloadServiceImpl implements DownloadService public synchronized void download(List songs, boolean save, boolean autoPlay, boolean playNext, boolean shuffle, boolean newPlaylist) { downloader.download(songs, save, autoPlay, playNext, newPlaylist); - jukeboxService.getValue().updatePlaylist(); + jukeboxMediaPlayer.getValue().updatePlaylist(); if (shuffle) shuffle(); @@ -240,7 +239,13 @@ public class DownloadServiceImpl implements DownloadService } else { - downloader.setFirstPlaying(); + if (localMediaPlayer.currentPlaying == null && downloader.downloadList.size() > 0) + { + localMediaPlayer.currentPlaying = downloader.downloadList.get(0); + localMediaPlayer.currentPlaying.setPlaying(true); + } + + downloader.checkDownloads(); } downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); @@ -256,7 +261,7 @@ public class DownloadServiceImpl implements DownloadService public synchronized void setCurrentPlaying(DownloadFile currentPlaying) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) player.setCurrentPlaying(currentPlaying); + if (mediaPlayerService != null) localMediaPlayer.setCurrentPlaying(currentPlaying); } public synchronized void setCurrentPlaying(int index) @@ -268,19 +273,13 @@ public class DownloadServiceImpl implements DownloadService public synchronized void setPlayerState(PlayerState state) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) player.setPlayerState(state); + if (mediaPlayerService != null) localMediaPlayer.setPlayerState(state); } @Override public void stopJukeboxService() { - jukeboxService.getValue().stopJukeboxService(); - } - - @Override - public void startJukeboxService() - { - jukeboxService.getValue().startJukeboxService(); + jukeboxMediaPlayer.getValue().stopJukeboxService(); } @Override @@ -306,7 +305,7 @@ public class DownloadServiceImpl implements DownloadService downloader.shuffle(); downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); - jukeboxService.getValue().updatePlaylist(); + jukeboxMediaPlayer.getValue().updatePlaylist(); MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); @@ -361,7 +360,7 @@ public class DownloadServiceImpl implements DownloadService MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService != null) mediaPlayerService.clear(serialize); - jukeboxService.getValue().updatePlaylist(); + jukeboxMediaPlayer.getValue().updatePlaylist(); } @Override @@ -380,13 +379,13 @@ public class DownloadServiceImpl implements DownloadService } downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); - jukeboxService.getValue().updatePlaylist(); + jukeboxMediaPlayer.getValue().updatePlaylist(); } @Override public synchronized void remove(DownloadFile downloadFile) { - if (downloadFile == player.currentPlaying) + if (downloadFile == localMediaPlayer.currentPlaying) { reset(); setCurrentPlaying(null); @@ -395,9 +394,9 @@ public class DownloadServiceImpl implements DownloadService downloader.removeDownloadFile(downloadFile); downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); - jukeboxService.getValue().updatePlaylist(); + jukeboxMediaPlayer.getValue().updatePlaylist(); - if (downloadFile == player.nextPlaying) + if (downloadFile == localMediaPlayer.nextPlaying) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); @@ -456,7 +455,7 @@ public class DownloadServiceImpl implements DownloadService public synchronized void reset() { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) player.reset(); + if (mediaPlayerService != null) localMediaPlayer.reset(); } @Override @@ -470,9 +469,9 @@ public class DownloadServiceImpl implements DownloadService @Override public synchronized int getPlayerDuration() { - if (player.currentPlaying != null) + if (localMediaPlayer.currentPlaying != null) { - Integer duration = player.currentPlaying.getSong().getDuration(); + Integer duration = localMediaPlayer.currentPlaying.getSong().getDuration(); if (duration != null) { return duration * 1000; @@ -485,7 +484,7 @@ public class DownloadServiceImpl implements DownloadService } @Override - public PlayerState getPlayerState() { return player.playerState; } + public PlayerState getPlayerState() { return localMediaPlayer.playerState; } @Override public void setSuggestedPlaylistName(String name) @@ -499,21 +498,12 @@ public class DownloadServiceImpl implements DownloadService return suggestedPlaylistName; } - @Override - public boolean getEqualizerAvailable() { return player.equalizerAvailable; } - - @Override - public boolean getVisualizerAvailable() - { - return player.visualizerAvailable; - } - @Override public EqualizerController getEqualizerController() { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService == null) return null; - return player.getEqualizerController(); + return localMediaPlayer.getEqualizerController(); } @Override @@ -521,13 +511,13 @@ public class DownloadServiceImpl implements DownloadService { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); if (mediaPlayerService == null) return null; - return player.getVisualizerController(); + return localMediaPlayer.getVisualizerController(); } @Override public boolean isJukeboxEnabled() { - return jukeboxService.getValue().isEnabled(); + return jukeboxMediaPlayer.getValue().isEnabled(); } @Override @@ -547,32 +537,15 @@ public class DownloadServiceImpl implements DownloadService return false; } - @Override - public boolean isSharingAvailable() - { - try - { - String username = Util.getUserName(context, Util.getActiveServer(context)); - UserInfo user = MusicServiceFactory.getMusicService(context).getUser(username, context, null); - return user.getShareRole(); - } - catch (Exception e) - { - Log.w(TAG, "Error getting user information", e); - } - - return false; - } - @Override public void setJukeboxEnabled(boolean jukeboxEnabled) { - jukeboxService.getValue().setEnabled(jukeboxEnabled); + jukeboxMediaPlayer.getValue().setEnabled(jukeboxEnabled); setPlayerState(PlayerState.IDLE); if (jukeboxEnabled) { - jukeboxService.getValue().startJukeboxService(); + jukeboxMediaPlayer.getValue().startJukeboxService(); reset(); @@ -584,59 +557,28 @@ public class DownloadServiceImpl implements DownloadService } else { - jukeboxService.getValue().stopJukeboxService(); + jukeboxMediaPlayer.getValue().stopJukeboxService(); } } @Override public void adjustJukeboxVolume(boolean up) { - jukeboxService.getValue().adjustVolume(up); + jukeboxMediaPlayer.getValue().adjustVolume(up); } @Override public void setVolume(float volume) { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) player.setVolume(volume); - } - - @Override - public synchronized void swap(boolean mainList, int from, int to) - { - List list = mainList ? downloader.downloadList : downloader.backgroundDownloadList; - int max = list.size(); - - if (to >= max) - { - to = max - 1; - } - else if (to < 0) - { - to = 0; - } - - int currentPlayingIndex = downloader.getCurrentPlayingIndex(); - DownloadFile movedSong = list.remove(from); - list.add(to, movedSong); - - if (jukeboxService.getValue().isEnabled() && mainList) - { - jukeboxService.getValue().updatePlaylist(); - } - else if (mainList && (movedSong == player.nextPlaying || (currentPlayingIndex + 1) == to)) - { - // Moving next playing or moving a song to be next playing - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); - } + if (mediaPlayerService != null) localMediaPlayer.setVolume(volume); } @Override public void updateNotification() { MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.updateNotification(player.playerState, player.currentPlaying); + if (mediaPlayerService != null) mediaPlayerService.updateNotification(localMediaPlayer.playerState, localMediaPlayer.currentPlaying); } public void setSongRating(final int rating) @@ -644,10 +586,10 @@ public class DownloadServiceImpl implements DownloadService if (!KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING)) return; - if (player.currentPlaying == null) + if (localMediaPlayer.currentPlaying == null) return; - final Entry song = player.currentPlaying.getSong(); + final Entry song = localMediaPlayer.currentPlaying.getSong(); song.setUserRating(rating); new Thread(new Runnable() diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java similarity index 72% rename from ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java rename to ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index a63829a9..15663aa6 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadServiceLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -18,7 +18,6 @@ */ package org.moire.ultrasonic.service; -import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -43,20 +42,20 @@ import static org.koin.java.standalone.KoinJavaComponent.inject; /** * @author Sindre Mehus */ -public class DownloadServiceLifecycleSupport +public class MediaPlayerLifecycleSupport { - private static final String TAG = DownloadServiceLifecycleSupport.class.getSimpleName(); + private static final String TAG = MediaPlayerLifecycleSupport.class.getSimpleName(); private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); - private final DownloadServiceImpl downloadService; // From DI + private final MediaPlayerControllerImpl mediaPlayerController; // From DI private final Downloader downloader; // From DI - - private BroadcastReceiver headsetEventReceiver; private Context context; - public DownloadServiceLifecycleSupport(Context context, final DownloadServiceImpl downloadService, final Downloader downloader) + private BroadcastReceiver headsetEventReceiver; + + public MediaPlayerLifecycleSupport(Context context, final MediaPlayerControllerImpl mediaPlayerController, final Downloader downloader) { - this.downloadService = downloadService; + this.mediaPlayerController = mediaPlayerController; this.context = context; this.downloader = downloader; @@ -80,11 +79,11 @@ public class DownloadServiceLifecycleSupport @Override public void accept(State state) { // TODO: here the autoPlay = false creates problems when Ultrasonic is started by a Play MediaButton as the player won't start this way. - downloadService.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, false, false); + mediaPlayerController.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, false, false); // Work-around: Serialize again, as the restore() method creates a serialization without current playing info. downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, - downloader.getCurrentPlayingIndex(), downloadService.getPlayerPosition()); + downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition()); } }); @@ -94,13 +93,26 @@ public class DownloadServiceLifecycleSupport public void onDestroy() { - downloadService.clear(false); + mediaPlayerController.clear(false); context.unregisterReceiver(headsetEventReceiver); context.unregisterReceiver(intentReceiver); - downloadService.onDestroy(); + mediaPlayerController.onDestroy(); Log.i(TAG, "LifecycleSupport destroyed"); } + public void receiveIntent(Intent intent) + { + Log.i(TAG, "Received intent"); + if (intent != null && intent.getExtras() != null) + { + KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT); + if (event != null) + { + handleKeyEvent(event); + } + } + } + private void registerHeadsetReceiver() { // Pause when headset is unplugged. final SharedPreferences sp = Util.getPreferences(context); @@ -119,38 +131,32 @@ public class DownloadServiceLifecycleSupport Log.i(TAG, String.format("Headset event for: %s", extras.get("name"))); final int state = extras.getInt("state"); if (state == 0) { - if (!downloadService.isJukeboxEnabled()) { - downloadService.pause(); + if (!mediaPlayerController.isJukeboxEnabled()) { + mediaPlayerController.pause(); } } else if (state == 1) { - if (!downloadService.isJukeboxEnabled() && + if (!mediaPlayerController.isJukeboxEnabled() && sp.getBoolean(spKey, false) && - downloadService.getPlayerState() == PlayerState.PAUSED) { - downloadService.start(); + mediaPlayerController.getPlayerState() == PlayerState.PAUSED) { + mediaPlayerController.start(); } } } }; - @SuppressLint("InlinedApi") - IntentFilter headsetIntentFilter = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) ? - new IntentFilter(AudioManager.ACTION_HEADSET_PLUG) : - new IntentFilter(Intent.ACTION_HEADSET_PLUG); + + + IntentFilter headsetIntentFilter; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + { + headsetIntentFilter = new IntentFilter(AudioManager.ACTION_HEADSET_PLUG); + } + else + { + headsetIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); + } context.registerReceiver(headsetEventReceiver, headsetIntentFilter); } - public void receiveIntent(Intent intent) - { - Log.i(TAG, "Received intent"); - if (intent != null && intent.getExtras() != null) - { - KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT); - if (event != null) - { - handleKeyEvent(event); - } - } - } - private void handleKeyEvent(KeyEvent event) { if (event.getAction() != KeyEvent.ACTION_DOWN || event.getRepeatCount() > 0) @@ -162,47 +168,47 @@ public class DownloadServiceLifecycleSupport { case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_HEADSETHOOK: - downloadService.togglePlayPause(); + mediaPlayerController.togglePlayPause(); break; case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - downloadService.previous(); + mediaPlayerController.previous(); break; case KeyEvent.KEYCODE_MEDIA_NEXT: if (downloader.getCurrentPlayingIndex() < downloader.downloadList.size() - 1) { - downloadService.next(); + mediaPlayerController.next(); } break; case KeyEvent.KEYCODE_MEDIA_STOP: - downloadService.stop(); + mediaPlayerController.stop(); break; case KeyEvent.KEYCODE_MEDIA_PLAY: - if (downloadService.getPlayerState() == PlayerState.IDLE) + if (mediaPlayerController.getPlayerState() == PlayerState.IDLE) { - downloadService.play(); + mediaPlayerController.play(); } - else if (downloadService.getPlayerState() != PlayerState.STARTED) + else if (mediaPlayerController.getPlayerState() != PlayerState.STARTED) { - downloadService.start(); + mediaPlayerController.start(); } break; case KeyEvent.KEYCODE_MEDIA_PAUSE: - downloadService.pause(); + mediaPlayerController.pause(); break; case KeyEvent.KEYCODE_1: - downloadService.setSongRating(1); + mediaPlayerController.setSongRating(1); break; case KeyEvent.KEYCODE_2: - downloadService.setSongRating(2); + mediaPlayerController.setSongRating(2); break; case KeyEvent.KEYCODE_3: - downloadService.setSongRating(3); + mediaPlayerController.setSongRating(3); break; case KeyEvent.KEYCODE_4: - downloadService.setSongRating(4); + mediaPlayerController.setSongRating(4); break; case KeyEvent.KEYCODE_5: - downloadService.setSongRating(5); + mediaPlayerController.setSongRating(5); break; default: break; @@ -224,20 +230,20 @@ public class DownloadServiceLifecycleSupport switch(action) { case Constants.CMD_PLAY: - downloadService.play(); + mediaPlayerController.play(); break; case Constants.CMD_NEXT: - downloadService.next(); + mediaPlayerController.next(); break; case Constants.CMD_PREVIOUS: - downloadService.previous(); + mediaPlayerController.previous(); break; case Constants.CMD_TOGGLEPAUSE: - downloadService.togglePlayPause(); + mediaPlayerController.togglePlayPause(); break; case Constants.CMD_STOP: - downloadService.pause(); - downloadService.seekTo(0); + mediaPlayerController.pause(); + mediaPlayerController.seekTo(0); break; case Constants.CMD_PROCESS_KEYCODE: receiveIntent(intent); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index 3e2b15f2..c57197e0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -59,12 +59,12 @@ public class MediaPlayerService extends Service private final IBinder binder = new SimpleServiceBinder<>(this); private final Scrobbler scrobbler = new Scrobbler(); - public Lazy jukeboxService = inject(JukeboxService.class); + public Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); private Lazy shufflePlayBufferLazy = inject(ShufflePlayBuffer.class); private Lazy downloaderLazy = inject(Downloader.class); - private Lazy playerLazy = inject(Player.class); - private Player player; + private Lazy localMediaPlayerLazy = inject(LocalMediaPlayer.class); + private LocalMediaPlayer localMediaPlayer; private Downloader downloader; private ShufflePlayBuffer shufflePlayBuffer; @@ -112,24 +112,24 @@ public class MediaPlayerService extends Service super.onCreate(); downloader = downloaderLazy.getValue(); - player = playerLazy.getValue(); + localMediaPlayer = localMediaPlayerLazy.getValue(); shufflePlayBuffer = shufflePlayBufferLazy.getValue(); downloader.onCreate(); shufflePlayBuffer.onCreate(); - player.onCreate(); + localMediaPlayer.onCreate(); setupOnCurrentPlayingChangedHandler(); setupOnPlayerStateChangedHandler(); setupOnSongCompletedHandler(); - player.onPrepared = new Runnable() { + localMediaPlayer.onPrepared = new Runnable() { @Override public void run() { downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } }; - player.onNextSongRequested = new Runnable() { + localMediaPlayer.onNextSongRequested = new Runnable() { @Override public void run() { setNextPlaying(); @@ -171,7 +171,7 @@ public class MediaPlayerService extends Service try { - player.onDestroy(); + localMediaPlayer.onDestroy(); shufflePlayBuffer.onDestroy(); downloader.onDestroy(); } @@ -184,37 +184,37 @@ public class MediaPlayerService extends Service public synchronized void seekTo(int position) { - if (jukeboxService.getValue().isEnabled()) + if (jukeboxMediaPlayer.getValue().isEnabled()) { - jukeboxService.getValue().skip(downloader.getCurrentPlayingIndex(), position / 1000); + jukeboxMediaPlayer.getValue().skip(downloader.getCurrentPlayingIndex(), position / 1000); } else { - player.seekTo(position); + localMediaPlayer.seekTo(position); } } public synchronized int getPlayerPosition() { - if (player.playerState == IDLE || player.playerState == DOWNLOADING || player.playerState == PREPARING) + if (localMediaPlayer.playerState == IDLE || localMediaPlayer.playerState == DOWNLOADING || localMediaPlayer.playerState == PREPARING) { return 0; } - return jukeboxService.getValue().isEnabled() ? jukeboxService.getValue().getPositionSeconds() * 1000 : - player.getPlayerPosition(); + return jukeboxMediaPlayer.getValue().isEnabled() ? jukeboxMediaPlayer.getValue().getPositionSeconds() * 1000 : + localMediaPlayer.getPlayerPosition(); } public synchronized int getPlayerDuration() { - return player.getPlayerDuration(); + return localMediaPlayer.getPlayerDuration(); } public synchronized void setCurrentPlaying(int currentPlayingIndex) { try { - player.setCurrentPlaying(downloader.downloadList.get(currentPlayingIndex)); + localMediaPlayer.setCurrentPlaying(downloader.downloadList.get(currentPlayingIndex)); } catch (IndexOutOfBoundsException x) { @@ -224,7 +224,7 @@ public class MediaPlayerService extends Service public void setupOnCurrentPlayingChangedHandler() { - player.onCurrentPlayingChanged = new Consumer() { + localMediaPlayer.onCurrentPlayingChanged = new Consumer() { @Override public void accept(DownloadFile currentPlaying) { if (currentPlaying != null) @@ -241,7 +241,7 @@ public class MediaPlayerService extends Service } // Update widget - PlayerState playerState = player.playerState; + PlayerState playerState = localMediaPlayer.playerState; MusicDirectory.Entry song = currentPlaying == null? null : currentPlaying.getSong(); UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true); @@ -253,7 +253,7 @@ public class MediaPlayerService extends Service if (currentPlaying != null) { if (tabInstance != null) { - updateNotification(player.playerState, currentPlaying); + updateNotification(localMediaPlayer.playerState, currentPlaying); tabInstance.showNowPlaying(); } } @@ -263,7 +263,7 @@ public class MediaPlayerService extends Service { tabInstance.hideNowPlaying(); stopForeground(true); - player.clearRemoteControl(); + localMediaPlayer.clearRemoteControl(); isInForeground = false; stopSelf(); } @@ -278,7 +278,7 @@ public class MediaPlayerService extends Service if (!gaplessPlayback) { - player.setNextPlaying(null); + localMediaPlayer.setNextPlaying(null); return; } @@ -295,35 +295,34 @@ public class MediaPlayerService extends Service index = (index + 1) % downloader.downloadList.size(); break; case SINGLE: - break; default: break; } } - player.clearNextPlaying(); + localMediaPlayer.clearNextPlaying(); if (index < downloader.downloadList.size() && index != -1) { - player.setNextPlaying(downloader.downloadList.get(index)); + localMediaPlayer.setNextPlaying(downloader.downloadList.get(index)); } else { - player.setNextPlaying(null); + localMediaPlayer.setNextPlaying(null); } } public synchronized void togglePlayPause() { - if (player.playerState == PAUSED || player.playerState == COMPLETED || player.playerState == STOPPED) + if (localMediaPlayer.playerState == PAUSED || localMediaPlayer.playerState == COMPLETED || localMediaPlayer.playerState == STOPPED) { start(); } - else if (player.playerState == IDLE) + else if (localMediaPlayer.playerState == IDLE) { play(); } - else if (player.playerState == STARTED) + else if (localMediaPlayer.playerState == STARTED) { pause(); } @@ -361,14 +360,14 @@ public class MediaPlayerService extends Service { if (start) { - if (jukeboxService.getValue().isEnabled()) + if (jukeboxMediaPlayer.getValue().isEnabled()) { - jukeboxService.getValue().skip(index, 0); - player.setPlayerState(STARTED); + jukeboxMediaPlayer.getValue().skip(index, 0); + localMediaPlayer.setPlayerState(STARTED); } else { - player.play(downloader.downloadList.get(index)); + localMediaPlayer.play(downloader.downloadList.get(index)); } } @@ -379,60 +378,60 @@ public class MediaPlayerService extends Service private synchronized void resetPlayback() { - player.reset(); - player.setCurrentPlaying(null); + localMediaPlayer.reset(); + localMediaPlayer.setCurrentPlaying(null); downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } public synchronized void pause() { - if (player.playerState == STARTED) + if (localMediaPlayer.playerState == STARTED) { - if (jukeboxService.getValue().isEnabled()) + if (jukeboxMediaPlayer.getValue().isEnabled()) { - jukeboxService.getValue().stop(); + jukeboxMediaPlayer.getValue().stop(); } else { - player.pause(); + localMediaPlayer.pause(); } - player.setPlayerState(PAUSED); + localMediaPlayer.setPlayerState(PAUSED); } } public synchronized void stop() { - if (player.playerState == STARTED) + if (localMediaPlayer.playerState == STARTED) { - if (jukeboxService.getValue().isEnabled()) + if (jukeboxMediaPlayer.getValue().isEnabled()) { - jukeboxService.getValue().stop(); + jukeboxMediaPlayer.getValue().stop(); } else { - player.pause(); + localMediaPlayer.pause(); } } - player.setPlayerState(STOPPED); + localMediaPlayer.setPlayerState(STOPPED); } public synchronized void start() { - if (jukeboxService.getValue().isEnabled()) + if (jukeboxMediaPlayer.getValue().isEnabled()) { - jukeboxService.getValue().start(); + jukeboxMediaPlayer.getValue().start(); } else { - player.start(); + localMediaPlayer.start(); } - player.setPlayerState(STARTED); + localMediaPlayer.setPlayerState(STARTED); } public void setupOnPlayerStateChangedHandler() { - player.onPlayerStateChanged = new BiConsumer() { + localMediaPlayer.onPlayerStateChanged = new BiConsumer() { @Override public void accept(PlayerState playerState, DownloadFile currentPlaying) { if (playerState == PAUSED) @@ -442,13 +441,13 @@ public class MediaPlayerService extends Service boolean showWhenPaused = (playerState != PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(MediaPlayerService.this)); boolean show = playerState == PlayerState.STARTED || showWhenPaused; + MusicDirectory.Entry song = currentPlaying == null? null : currentPlaying.getSong(); Util.broadcastPlaybackStatusChange(MediaPlayerService.this, playerState); - Util.broadcastA2dpPlayStatusChange(MediaPlayerService.this, playerState, currentPlaying.getSong(), + Util.broadcastA2dpPlayStatusChange(MediaPlayerService.this, playerState, song, downloader.downloadList.size() + downloader.backgroundDownloadList.size(), downloader.downloadList.indexOf(currentPlaying) + 1, getPlayerPosition()); - MusicDirectory.Entry song = currentPlaying.getSong(); // Update widget UltraSonicAppWidgetProvider4x1.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); UltraSonicAppWidgetProvider4x2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true); @@ -460,7 +459,7 @@ public class MediaPlayerService extends Service { if (tabInstance != null) { - // Only update notification is player state is one that will change the icon + // Only update notification is localMediaPlayer state is one that will change the icon if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) { updateNotification(playerState, currentPlaying); @@ -473,7 +472,7 @@ public class MediaPlayerService extends Service if (tabInstance != null) { stopForeground(true); - player.clearRemoteControl(); + localMediaPlayer.clearRemoteControl(); isInForeground = false; tabInstance.hideNowPlaying(); stopSelf(); @@ -494,7 +493,7 @@ public class MediaPlayerService extends Service private void setupOnSongCompletedHandler() { - player.onSongCompleted = new Consumer() { + localMediaPlayer.onSongCompleted = new Consumer() { @Override public void accept(DownloadFile currentPlaying) { int index = downloader.getCurrentPlayingIndex(); @@ -527,7 +526,7 @@ public class MediaPlayerService extends Service if (Util.getShouldClearPlaylist(MediaPlayerService.this)) { clear(true); - jukeboxService.getValue().updatePlaylist(); + jukeboxMediaPlayer.getValue().updatePlaylist(); } resetPlayback(); @@ -552,9 +551,9 @@ public class MediaPlayerService extends Service public synchronized void clear(boolean serialize) { - player.reset(); + localMediaPlayer.reset(); downloader.clear(); - player.setCurrentPlaying(null); + localMediaPlayer.setCurrentPlaying(null); setNextPlaying(); @@ -567,7 +566,7 @@ public class MediaPlayerService extends Service public void updateNotification(PlayerState playerState, DownloadFile currentPlaying) { if (Util.isNotificationEnabled(this)) { - if (isInForeground == true) { + if (isInForeground) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying)); 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 1bc31fe3..a64e2a6b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -54,11 +54,7 @@ import org.moire.ultrasonic.domain.*; import org.moire.ultrasonic.domain.MusicDirectory.Entry; 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.DownloadServiceLifecycleSupport; -import org.moire.ultrasonic.service.Downloader; -import org.moire.ultrasonic.service.MediaPlayerService; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.service.MusicServiceFactory; import java.io.*; @@ -1183,22 +1179,22 @@ public class Util extends DownloadActivity @Override public void onAudioFocusChange(int focusChange) { - DownloadService downloadService = (DownloadService) context; - if ((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !downloadService.isJukeboxEnabled()) + MediaPlayerController mediaPlayerController = (MediaPlayerController) context; + if ((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !mediaPlayerController.isJukeboxEnabled()) { - if (downloadService.getPlayerState() == PlayerState.STARTED) + if (mediaPlayerController.getPlayerState() == PlayerState.STARTED) { SharedPreferences preferences = getPreferences(context); int lossPref = Integer.parseInt(preferences.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1")); if (lossPref == 2 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) { lowerFocus = true; - downloadService.setVolume(0.1f); + mediaPlayerController.setVolume(0.1f); } else if (lossPref == 0 || (lossPref == 1)) { pauseFocus = true; - downloadService.pause(); + mediaPlayerController.pause(); } } } @@ -1207,18 +1203,18 @@ public class Util extends DownloadActivity if (pauseFocus) { pauseFocus = false; - downloadService.start(); + mediaPlayerController.start(); } else if (lowerFocus) { lowerFocus = false; - downloadService.setVolume(1.0f); + mediaPlayerController.setVolume(1.0f); } } - else if (focusChange == AudioManager.AUDIOFOCUS_LOSS && !downloadService.isJukeboxEnabled()) + else if (focusChange == AudioManager.AUDIOFOCUS_LOSS && !mediaPlayerController.isJukeboxEnabled()) { hasFocus = false; - downloadService.pause(); + mediaPlayerController.pause(); audioManager.abandonAudioFocus(this); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java index a03036a4..c000f3f8 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java @@ -41,7 +41,7 @@ public enum VideoPlayerType public void playVideo(final Activity activity, MusicDirectory.Entry entry) throws Exception { - // Check if MX Player is installed. + // Check if MX LocalMediaPlayer is installed. boolean installedAd = Util.isPackageInstalled(activity, PACKAGE_NAME_MX_AD); boolean installedPro = Util.isPackageInstalled(activity, PACKAGE_NAME_MX_PRO); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java index 5c617dbc..973bf91e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java @@ -37,12 +37,10 @@ import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.featureflags.Feature; import org.moire.ultrasonic.featureflags.FeatureStorage; import org.moire.ultrasonic.service.DownloadFile; -import org.moire.ultrasonic.service.DownloadService; -import org.moire.ultrasonic.service.DownloadServiceImpl; import org.moire.ultrasonic.service.Downloader; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.service.Player; +import org.moire.ultrasonic.service.LocalMediaPlayer; import org.moire.ultrasonic.util.Util; import org.moire.ultrasonic.util.VideoPlayerType; @@ -85,7 +83,7 @@ public class SongView extends UpdateView implements Checkable private boolean useFiveStarRating; private Lazy downloader = inject(Downloader.class); - protected Lazy player = inject(Player.class); + protected Lazy localMediaPlayer = inject(LocalMediaPlayer.class); public SongView(Context context) { @@ -413,7 +411,7 @@ public class SongView extends UpdateView implements Checkable viewHolder.fiveStar4.setImageDrawable(rating > 3 ? starDrawable : starHollowDrawable); viewHolder.fiveStar5.setImageDrawable(rating > 4 ? starDrawable : starHollowDrawable); - boolean playing = player.getValue().currentPlaying == downloadFile; + boolean playing = localMediaPlayer.getValue().currentPlaying == downloadFile; if (playing) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java index 92332344..d08e062f 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/VisualizerView.java @@ -27,8 +27,7 @@ import android.view.View; import org.moire.ultrasonic.audiofx.VisualizerController; import org.moire.ultrasonic.domain.PlayerState; -import org.moire.ultrasonic.service.DownloadService; -import org.moire.ultrasonic.service.DownloadServiceImpl; +import org.moire.ultrasonic.service.MediaPlayerController; import kotlin.Lazy; @@ -51,7 +50,7 @@ public class VisualizerView extends View private float[] points; private boolean active; - private Lazy downloadServiceImpl = inject(DownloadServiceImpl.class); + private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); public VisualizerView(Context context) { @@ -104,7 +103,7 @@ public class VisualizerView extends View private Visualizer getVizualizer() { - VisualizerController visualizerController = downloadServiceImpl.getValue().getVisualizerController(); + VisualizerController visualizerController = mediaPlayerControllerLazy.getValue().getVisualizerController(); return visualizerController == null ? null : visualizerController.getVisualizer(); } @@ -124,7 +123,7 @@ public class VisualizerView extends View return; } - if (downloadServiceImpl.getValue().getPlayerState() != PlayerState.STARTED) + if (mediaPlayerControllerLazy.getValue().getPlayerState() != PlayerState.STARTED) { return; } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 7072738d..64ae896e 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -113,12 +113,13 @@ val musicServiceModule = module(MUSIC_SERVICE_CONTEXT) { single { SubsonicImageLoader(getProperty(DiProperties.APP_CONTEXT), get()) } - single { DownloadServiceImpl(androidContext(), get(), get(), get()) } - single { JukeboxService(androidContext(), get()) } - single { DownloadServiceLifecycleSupport(androidContext(), get(), get()) } + single { MediaPlayerControllerImpl(androidContext(), get(), get(), get()) } + single { MediaPlayerControllerImpl(androidContext(), get(), get(), get()) } + single { JukeboxMediaPlayer(androidContext(), get()) } + single { MediaPlayerLifecycleSupport(androidContext(), get(), get()) } single { DownloadQueueSerializer(androidContext()) } single { ExternalStorageMonitor(androidContext()) } single { ShufflePlayBuffer(androidContext()) } single { Downloader(androidContext(), get(), get(), get()) } - single { Player(androidContext()) } + single { LocalMediaPlayer(androidContext()) } } diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index f188ccc9..3b25b2ff 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -334,7 +334,7 @@ 0.00 MB -:-- 0:00 - O player MX não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo. + O localMediaPlayer MX não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo. Baixar Player MX Toque para selecionar a música Cartão SD indisponível diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index b5b89e5f..996d9fec 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -334,7 +334,7 @@ 0.00 MB —:—— 0:00 - O player MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo. + O localMediaPlayer MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo. Descarregar Player MX Toque para selecionar a música Cartão SD indisponível diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 4a2b453e..64fa495e 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -314,7 +314,7 @@ Browse Using ID3 Tags Use ID3 tag methods instead of file system based methods Video - Video player + Video localMediaPlayer View Refresh .5 seconds 1 second From 0bef3ae417da4adbc77e882a1e9db28d014640b4 Mon Sep 17 00:00:00 2001 From: Nite Date: Fri, 26 Jun 2020 13:31:31 +0200 Subject: [PATCH 07/25] Fixed concurrency problems --- .../service/MediaPlayerControllerImpl.java | 31 ++----- .../service/MediaPlayerService.java | 85 ++++++++++++------- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java index cd42902f..15813a2e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java @@ -92,19 +92,6 @@ public class MediaPlayerControllerImpl implements MediaPlayerController Log.i(TAG, "MediaPlayerControllerImpl destroyed"); } - private void executeOnStartedMediaPlayerService(final Consumer taskToExecute) - { - Thread t = new Thread() - { - public void run() - { - MediaPlayerService instance = MediaPlayerService.getInstance(context); - taskToExecute.accept(instance); - } - }; - t.start(); - } - @Override public synchronized void restore(List songs, final int currentPlayingIndex, final int currentPlayingPosition, final boolean autoPlay, boolean newPlaylist) { @@ -112,7 +99,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController if (currentPlayingIndex != -1) { - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.play(currentPlayingIndex, autoPlayStart); @@ -129,7 +116,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController { if (localMediaPlayer.currentPlaying.isCompleteFileAvailable()) { - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { localMediaPlayer.doPlay(localMediaPlayer.currentPlaying, currentPlayingPosition, autoPlay); @@ -145,7 +132,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController @Override public synchronized void play(final int index) { - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context,new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.play(index, true); @@ -155,7 +142,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController public synchronized void play() { - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.play(); @@ -167,7 +154,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController public synchronized void togglePlayPause() { if (localMediaPlayer.playerState == PlayerState.IDLE) autoPlayStart = true; - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context,new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.togglePlayPause(); @@ -178,7 +165,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController @Override public synchronized void seekTo(final int position) { - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.seekTo(position); @@ -189,7 +176,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController @Override public synchronized void pause() { - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.pause(); @@ -200,7 +187,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController @Override public synchronized void start() { - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.start(); @@ -211,7 +198,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController @Override public synchronized void stop() { - executeOnStartedMediaPlayerService(new Consumer() { + MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.stop(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index c57197e0..a1622a31 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -55,6 +55,7 @@ public class MediaPlayerService extends Service private static final int NOTIFICATION_ID = 3033; private static MediaPlayerService instance = null; + private static final Object instanceLock = new Object(); private final IBinder binder = new SimpleServiceBinder<>(this); private final Scrobbler scrobbler = new Scrobbler(); @@ -75,28 +76,42 @@ public class MediaPlayerService extends Service public static MediaPlayerService getInstance(Context context) { - for (int i = 0; i < 5; i++) - { - if (instance != null) return instance; + synchronized (instanceLock) { + 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)); + 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); } - Util.sleepQuietly(50L); + return instance; } - - return instance; } public static MediaPlayerService getRunningInstance() { - return instance; + synchronized (instanceLock) + { + return instance; + } + } + + public static void executeOnStartedMediaPlayerService(final Context context, final Consumer taskToExecute) + { + Thread t = new Thread() + { + public void run() + { + MediaPlayerService instance = getInstance(context); + taskToExecute.accept(instance); + } + }; + t.start(); } @Nullable @@ -169,19 +184,25 @@ public class MediaPlayerService extends Service instance = null; - try - { + try { localMediaPlayer.onDestroy(); shufflePlayBuffer.onDestroy(); downloader.onDestroy(); - } - catch (Throwable ignored) - { + } catch (Throwable ignored) { } Log.i(TAG, "MediaPlayerService stopped"); } + private void stopIfIdle() + { + synchronized (instanceLock) + { + // currentPlaying could be changed from another thread in the meantime, so check again before stopping for good + if (localMediaPlayer.currentPlaying == null) stopSelf(); + } + } + public synchronized void seekTo(int position) { if (jukeboxMediaPlayer.getValue().isEnabled()) @@ -252,8 +273,8 @@ public class MediaPlayerService extends Service if (currentPlaying != null) { + updateNotification(localMediaPlayer.playerState, currentPlaying); if (tabInstance != null) { - updateNotification(localMediaPlayer.playerState, currentPlaying); tabInstance.showNowPlaying(); } } @@ -262,11 +283,11 @@ public class MediaPlayerService extends Service if (tabInstance != null) { tabInstance.hideNowPlaying(); - stopForeground(true); - localMediaPlayer.clearRemoteControl(); - isInForeground = false; - stopSelf(); } + stopForeground(true); + localMediaPlayer.clearRemoteControl(); + isInForeground = false; + stopIfIdle(); } } }; @@ -457,12 +478,12 @@ public class MediaPlayerService extends Service if (show) { - if (tabInstance != null) + // Only update notification if localMediaPlayer state is one that will change the icon + if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) { - // Only update notification is localMediaPlayer state is one that will change the icon - if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) + updateNotification(playerState, currentPlaying); + if (tabInstance != null) { - updateNotification(playerState, currentPlaying); tabInstance.showNowPlaying(); } } @@ -471,12 +492,12 @@ public class MediaPlayerService extends Service { if (tabInstance != null) { - stopForeground(true); - localMediaPlayer.clearRemoteControl(); - isInForeground = false; tabInstance.hideNowPlaying(); - stopSelf(); } + stopForeground(true); + localMediaPlayer.clearRemoteControl(); + isInForeground = false; + stopIfIdle(); } if (playerState == STARTED) From bbe9f39300e99da0a26642d87a738fca5d469d84 Mon Sep 17 00:00:00 2001 From: Nite Date: Fri, 26 Jun 2020 15:18:14 +0200 Subject: [PATCH 08/25] Moved externally visible functions to interface, added comments --- .../ultrasonic/activity/BookmarkActivity.java | 6 +- .../ultrasonic/activity/DownloadActivity.java | 39 ++++++----- .../ultrasonic/activity/SearchActivity.java | 2 +- .../activity/SelectAlbumActivity.java | 6 +- .../activity/SubsonicTabActivity.java | 5 +- .../receiver/A2dpIntentReceiver.java | 10 +-- .../moire/ultrasonic/service/BiConsumer.java | 5 ++ .../moire/ultrasonic/service/Consumer.java | 4 ++ .../ultrasonic/service/DownloadFile.java | 7 +- .../service/DownloadQueueSerializer.java | 5 ++ .../moire/ultrasonic/service/Downloader.java | 6 +- .../service/ExternalStorageMonitor.java | 3 + .../ultrasonic/service/LocalMediaPlayer.java | 4 ++ .../service/MediaPlayerController.java | 20 +++++- .../service/MediaPlayerControllerImpl.java | 69 ++++++++++++++++--- .../service/MediaPlayerLifecycleSupport.java | 16 ++--- .../service/MediaPlayerService.java | 16 +++-- .../org/moire/ultrasonic/service/State.java | 3 + .../moire/ultrasonic/service/Supplier.java | 4 ++ .../moire/ultrasonic/util/CacheCleaner.java | 4 ++ .../org/moire/ultrasonic/view/SongView.java | 12 ++-- .../moire/ultrasonic/di/MusicServiceModule.kt | 8 ++- 22 files changed, 180 insertions(+), 74 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java index 038fa16f..cae4632d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/BookmarkActivity.java @@ -34,6 +34,7 @@ import org.moire.ultrasonic.R; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.service.DownloadFile; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.util.Constants; @@ -296,7 +297,8 @@ public class BookmarkActivity extends SubsonicTabActivity private void enableButtons() { - if (getMediaPlayerController() == null) + MediaPlayerController mediaPlayerController = getMediaPlayerController(); + if (mediaPlayerController == null) { return; } @@ -310,7 +312,7 @@ public class BookmarkActivity extends SubsonicTabActivity for (MusicDirectory.Entry song : selection) { - DownloadFile downloadFile = downloader.getValue().getDownloadFileForSong(song); + DownloadFile downloadFile = mediaPlayerController.getDownloadFileForSong(song); if (downloadFile.isWorkDone()) { deleteEnabled = true; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java index f03c005d..80e8e291 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java @@ -293,7 +293,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi @Override protected Boolean doInBackground() throws Throwable { - if (downloader.getValue().getCurrentPlayingIndex() < downloader.getValue().downloadList.size() - 1) + if (getMediaPlayerController().getCurrentPlayingNumberOnPlaylist() < getMediaPlayerController().getPlaylistSize() - 1) { getMediaPlayerController().next(); return true; @@ -567,7 +567,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi final MediaPlayerController mediaPlayerController = getMediaPlayerController(); - if (mediaPlayerController == null || localMediaPlayer.getValue().currentPlaying == null) + if (mediaPlayerController == null || mediaPlayerController.getCurrentPlaying() == null) { playlistFlipper.setDisplayedChild(1); } @@ -632,7 +632,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi } } - final DownloadFile currentDownloading = downloader.getValue().currentDownloading; + final DownloadFile currentDownloading = getMediaPlayerController().getCurrentDownloading(); for (int i = 0; i < count; i++) { if (currentDownloading == playlistView.getItemAtPosition(i)) @@ -782,7 +782,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (mediaPlayerController != null) { - DownloadFile downloadFile = localMediaPlayer.getValue().currentPlaying; + DownloadFile downloadFile = mediaPlayerController.getCurrentPlaying(); if (downloadFile != null) { @@ -1019,7 +1019,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi onDownloadListChanged(); return true; case R.id.menu_item_save_playlist: - if (!downloader.getValue().downloadList.isEmpty()) + if (getMediaPlayerController().getPlaylistSize() > 0) { showDialog(DIALOG_SAVE_PLAYLIST); } @@ -1142,7 +1142,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (mediaPlayerController != null) { - List downloadServiceSongs = downloader.getValue().downloadList; + List downloadServiceSongs = mediaPlayerController.getPlayList(); if (downloadServiceSongs != null) { @@ -1170,17 +1170,18 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi private void update() { - if (getMediaPlayerController() == null) + MediaPlayerController mediaPlayerController = getMediaPlayerController(); + if (mediaPlayerController == null) { return; } - if (currentRevision != downloader.getValue().getDownloadListUpdateRevision()) + if (currentRevision != mediaPlayerController.getPlayListUpdateRevision()) { onDownloadListChanged(); } - if (currentPlaying != localMediaPlayer.getValue().currentPlaying) + if (currentPlaying != mediaPlayerController.getCurrentPlaying()) { onCurrentChanged(); } @@ -1199,7 +1200,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi protected Void doInBackground() throws Throwable { final List entries = new LinkedList(); - for (final DownloadFile downloadFile : downloader.getValue().downloadList) + for (final DownloadFile downloadFile : getMediaPlayerController().getPlayList()) { entries.add(downloadFile.getSong()); } @@ -1254,7 +1255,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi { warnIfNetworkOrStorageUnavailable(); - final int current = downloader.getValue().getCurrentPlayingIndex(); + final int current = getMediaPlayerController().getCurrentPlayingNumberOnPlaylist(); if (current == -1) { @@ -1275,7 +1276,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return; } - final List list = downloader.getValue().downloadList; + final List list = mediaPlayerController.getPlayList(); emptyTextView.setText(R.string.download_empty); final SongListAdapter adapter = new SongListAdapter(this, list); @@ -1313,7 +1314,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return; } - DownloadFile currentPlaying = localMediaPlayer.getValue().currentPlaying; + DownloadFile currentPlaying = mediaPlayerController.getCurrentPlaying(); if (currentPlaying == item) { @@ -1333,7 +1334,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi }); emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE); - currentRevision = downloader.getValue().getDownloadListUpdateRevision(); + currentRevision = mediaPlayerController.getPlayListUpdateRevision(); switch (mediaPlayerController.getRepeatMode()) { @@ -1360,13 +1361,13 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi return; } - currentPlaying = localMediaPlayer.getValue().currentPlaying; + currentPlaying = mediaPlayerController.getCurrentPlaying(); scrollToCurrent(); - long totalDuration = downloader.getValue().getDownloadListDuration(); - long totalSongs = downloader.getValue().downloadList.size(); - int currentSongIndex = downloader.getValue().getCurrentPlayingIndex() + 1; + long totalDuration = mediaPlayerController.getPlayListDuration(); + long totalSongs = mediaPlayerController.getPlaylistSize(); + int currentSongIndex = mediaPlayerController.getCurrentPlayingNumberOnPlaylist() + 1; String duration = Util.formatTotalDuration(totalDuration); @@ -1580,7 +1581,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (e1X - e2X > swipeDistance && absX > swipeVelocity) { warnIfNetworkOrStorageUnavailable(); - if (downloader.getValue().getCurrentPlayingIndex() < downloader.getValue().downloadList.size() - 1) + if (mediaPlayerController.getCurrentPlayingNumberOnPlaylist() < mediaPlayerController.getPlaylistSize() - 1) { mediaPlayerController.next(); onCurrentChanged(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java index 113449eb..88b7a70a 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SearchActivity.java @@ -520,7 +520,7 @@ public class SearchActivity extends SubsonicTabActivity if (autoplay) { - mediaPlayerController.play(downloader.getValue().downloadList.size() - 1); + mediaPlayerController.play(mediaPlayerController.getPlaylistSize() - 1); } Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1)); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java index 9b13b225..a96159a2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SelectAlbumActivity.java @@ -40,6 +40,7 @@ import org.moire.ultrasonic.R; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.domain.Share; import org.moire.ultrasonic.service.DownloadFile; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.util.AlbumHeader; @@ -1010,7 +1011,8 @@ public class SelectAlbumActivity extends SubsonicTabActivity private void enableButtons() { - if (getMediaPlayerController() == null) + MediaPlayerController mediaPlayerController = getMediaPlayerController(); + if (mediaPlayerController == null) { return; } @@ -1024,7 +1026,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity for (MusicDirectory.Entry song : selection) { - DownloadFile downloadFile = downloader.getValue().getDownloadFileForSong(song); + DownloadFile downloadFile = mediaPlayerController.getDownloadFileForSong(song); if (downloadFile.isWorkDone()) { deleteEnabled = true; 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 3a6b5279..06d10d70 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -78,9 +78,6 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); private Lazy lifecycleSupport = inject(MediaPlayerLifecycleSupport.class); - protected Lazy downloader = inject(Downloader.class); - protected Lazy localMediaPlayer = inject(LocalMediaPlayer.class); - public MenuDrawer menuDrawer; private int activePosition = 1; @@ -269,7 +266,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen if (playerState.equals(PlayerState.PAUSED) || playerState.equals(PlayerState.STARTED)) { - DownloadFile file = localMediaPlayer.getValue().currentPlaying; + DownloadFile file = mediaPlayerControllerLazy.getValue().getCurrentPlaying(); if (file != null) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java index 0fe2a691..9bc2ad66 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/A2dpIntentReceiver.java @@ -6,8 +6,6 @@ import android.content.Intent; import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.service.MediaPlayerController; -import org.moire.ultrasonic.service.Downloader; -import org.moire.ultrasonic.service.LocalMediaPlayer; import kotlin.Lazy; @@ -17,18 +15,16 @@ public class A2dpIntentReceiver extends BroadcastReceiver { private static final String PLAYSTATUS_RESPONSE = "com.android.music.playstatusresponse"; private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); - private Lazy downloader = inject(Downloader.class); - protected Lazy localMediaPlayer = inject(LocalMediaPlayer.class); @Override public void onReceive(Context context, Intent intent) { - if (localMediaPlayer.getValue().currentPlaying == null) + if (mediaPlayerControllerLazy.getValue().getCurrentPlaying() == null) { return; } - Entry song = localMediaPlayer.getValue().currentPlaying.getSong(); + Entry song = mediaPlayerControllerLazy.getValue().getCurrentPlaying().getSong(); if (song == null) { @@ -39,7 +35,7 @@ public class A2dpIntentReceiver extends BroadcastReceiver Integer duration = song.getDuration(); int playerPosition = mediaPlayerControllerLazy.getValue().getPlayerPosition(); - int listSize = downloader.getValue().getDownloads().size(); + int listSize = mediaPlayerControllerLazy.getValue().getPlaylistSize(); if (duration != null) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/BiConsumer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/BiConsumer.java index 537ef5f4..8909762e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/BiConsumer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/BiConsumer.java @@ -1,5 +1,10 @@ package org.moire.ultrasonic.service; +/** + * Abstract class for consumers with two parameters + * @param The type of the first object to consume + * @param The type of the second object to consume + */ public abstract class BiConsumer { public abstract void accept(T t, U u); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java index 4fd5c473..d2b09de7 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java @@ -1,5 +1,9 @@ package org.moire.ultrasonic.service; +/** + * Abstract class for consumers with one parameter + * @param The type of the object to consume + */ public abstract class Consumer { public abstract void accept(T t); 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 2d0c7b90..4bfa4277 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java @@ -24,6 +24,7 @@ import android.os.PowerManager; import android.text.TextUtils; import android.util.Log; +import org.jetbrains.annotations.NotNull; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.util.CacheCleaner; import org.moire.ultrasonic.util.CancellableTask; @@ -51,7 +52,6 @@ import static org.koin.java.standalone.KoinJavaComponent.inject; */ public class DownloadFile { - private static final String TAG = DownloadFile.class.getSimpleName(); private final Context context; private final MusicDirectory.Entry song; @@ -70,7 +70,6 @@ public class DownloadFile private Lazy downloader = inject(Downloader.class); - public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) { super(); @@ -287,6 +286,7 @@ public class DownloadFile this.isPlaying = isPlaying; } + @NotNull @Override public String toString() { @@ -309,7 +309,7 @@ public class DownloadFile { PowerManager pm = (PowerManager) context.getSystemService(POWER_SERVICE); wakeLock = pm.newWakeLock(SCREEN_DIM_WAKE_LOCK | ON_AFTER_RELEASE, toString()); - wakeLock.acquire(); + wakeLock.acquire(10*60*1000L /*10 minutes*/); Log.i(TAG, String.format("Acquired wake lock %s", wakeLock)); } @@ -450,6 +450,7 @@ public class DownloadFile } } + @NotNull @Override public String toString() { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadQueueSerializer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadQueueSerializer.java index 570ab067..3fe70765 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadQueueSerializer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadQueueSerializer.java @@ -11,6 +11,11 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +/** + * This class is responsible for the serialization / deserialization + * of the DownloadQueue (playlist) to the filesystem. + * It also serializes the player state e.g. current playing number and play position. + */ public class DownloadQueueSerializer { private static final String TAG = DownloadQueueSerializer.class.getSimpleName(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java index dd22283e..bc7449fa 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java @@ -22,6 +22,10 @@ import static org.koin.java.standalone.KoinJavaComponent.inject; import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING; import static org.moire.ultrasonic.domain.PlayerState.STARTED; +/** + * This class is responsible for maintaining the playlist and downloading + * its items from the network to the filesystem. + */ public class Downloader { private static final String TAG = Downloader.class.getSimpleName(); @@ -85,7 +89,7 @@ public class Downloader Log.i(TAG, "Downloader destroyed"); } - protected synchronized void checkDownloads() + public synchronized void checkDownloads() { if (!Util.isExternalStoragePresent() || !externalStorageMonitor.isExternalStorageAvailable()) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java index 489eb39b..390eb56b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java @@ -6,6 +6,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.util.Log; +/** + * Monitors the state of the mobile's external storage + */ public class ExternalStorageMonitor { private static final String TAG = ExternalStorageMonitor.class.getSimpleName(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java index 02659e46..c00b3d2e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java @@ -43,6 +43,9 @@ import static org.moire.ultrasonic.domain.PlayerState.PREPARED; import static org.moire.ultrasonic.domain.PlayerState.PREPARING; import static org.moire.ultrasonic.domain.PlayerState.STARTED; +/** + * Represents a Media Player which uses the mobile's resources for playback + */ public class LocalMediaPlayer { private static final String TAG = LocalMediaPlayer.class.getSimpleName(); @@ -59,6 +62,7 @@ public class LocalMediaPlayer public PlayerState playerState = IDLE; public DownloadFile currentPlaying; public DownloadFile nextPlaying; + private PlayerState nextPlayerState = IDLE; private boolean nextSetup; private CancellableTask nextPlayingTask; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java index 19f15d80..ad6eb2e0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java @@ -27,12 +27,14 @@ import org.moire.ultrasonic.domain.RepeatMode; import java.util.List; /** + * This interface contains all functions which are necessary for the Application UI + * to control the Media Player implementation. + * * @author Sindre Mehus * @version $Id$ */ public interface MediaPlayerController { - void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, boolean newPlaylist); void downloadBackground(List songs, boolean save); @@ -114,4 +116,20 @@ public interface MediaPlayerController void updateNotification(); void setSongRating(final int rating); + + DownloadFile getCurrentPlaying(); + + int getPlaylistSize(); + + int getCurrentPlayingNumberOnPlaylist(); + + DownloadFile getCurrentDownloading(); + + List getPlayList(); + + long getPlayListUpdateRevision(); + + long getPlayListDuration(); + + DownloadFile getDownloadFileForSong(Entry song); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java index 15813a2e..4b71ac4b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java @@ -43,6 +43,10 @@ import kotlin.Lazy; import static org.koin.java.standalone.KoinJavaComponent.inject; /** + * The implementation of the Media Player Controller. + * This class contains everything that is necessary for the Application UI + * to control the Media Player implementation. + * * @author Sindre Mehus, Joshua Bahnsen * @version $Id$ */ @@ -58,21 +62,24 @@ public class MediaPlayerControllerImpl implements MediaPlayerController private Context context; private Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); - private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); - private Lazy externalStorageMonitor = inject(ExternalStorageMonitor.class); + private final DownloadQueueSerializer downloadQueueSerializer; + private final ExternalStorageMonitor externalStorageMonitor; private final Downloader downloader; private final ShufflePlayBuffer shufflePlayBuffer; private final LocalMediaPlayer localMediaPlayer; - public MediaPlayerControllerImpl(Context context, Downloader downloader, ShufflePlayBuffer shufflePlayBuffer, - LocalMediaPlayer localMediaPlayer) + public MediaPlayerControllerImpl(Context context, DownloadQueueSerializer downloadQueueSerializer, + ExternalStorageMonitor externalStorageMonitor, Downloader downloader, + ShufflePlayBuffer shufflePlayBuffer, LocalMediaPlayer localMediaPlayer) { this.context = context; + this.downloadQueueSerializer = downloadQueueSerializer; + this.externalStorageMonitor = externalStorageMonitor; this.downloader = downloader; this.shufflePlayBuffer = shufflePlayBuffer; this.localMediaPlayer = localMediaPlayer; - externalStorageMonitor.getValue().onCreate(new Runnable() { + this.externalStorageMonitor.onCreate(new Runnable() { @Override public void run() { reset(); @@ -87,7 +94,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController public void onDestroy() { - externalStorageMonitor.getValue().onDestroy(); + externalStorageMonitor.onDestroy(); context.stopService(new Intent(context, MediaPlayerService.class)); Log.i(TAG, "MediaPlayerControllerImpl destroyed"); } @@ -235,14 +242,14 @@ public class MediaPlayerControllerImpl implements MediaPlayerController downloader.checkDownloads(); } - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } @Override public synchronized void downloadBackground(List songs, boolean save) { downloader.downloadBackground(songs, save); - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } public synchronized void setCurrentPlaying(DownloadFile currentPlaying) @@ -291,7 +298,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController { downloader.shuffle(); - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); jukeboxMediaPlayer.getValue().updatePlaylist(); MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); @@ -365,7 +372,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController } } - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); jukeboxMediaPlayer.getValue().updatePlaylist(); } @@ -380,7 +387,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController downloader.removeDownloadFile(downloadFile); - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); jukeboxMediaPlayer.getValue().updatePlaylist(); if (downloadFile == localMediaPlayer.nextPlaying) @@ -597,4 +604,44 @@ public class MediaPlayerControllerImpl implements MediaPlayerController updateNotification(); } + + @Override + public DownloadFile getCurrentPlaying() { + return localMediaPlayer.currentPlaying; + } + + @Override + public int getPlaylistSize() { + return downloader.downloadList.size(); + } + + @Override + public int getCurrentPlayingNumberOnPlaylist() { + return downloader.getCurrentPlayingIndex(); + } + + @Override + public DownloadFile getCurrentDownloading() { + return downloader.currentDownloading; + } + + @Override + public List getPlayList() { + return downloader.downloadList; + } + + @Override + public long getPlayListUpdateRevision() { + return downloader.getDownloadListUpdateRevision(); + } + + @Override + public long getPlayListDuration() { + return downloader.getDownloadListDuration(); + } + + @Override + public DownloadFile getDownloadFileForSong(Entry song) { + return downloader.getDownloadFileForSong(song); + } } \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index 15663aa6..f34c06b3 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -35,26 +35,26 @@ import org.moire.ultrasonic.util.CacheCleaner; import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.Util; -import kotlin.Lazy; - -import static org.koin.java.standalone.KoinJavaComponent.inject; - /** + * This class is responsible for handling received events for the Media Player implementation + * * @author Sindre Mehus */ public class MediaPlayerLifecycleSupport { private static final String TAG = MediaPlayerLifecycleSupport.class.getSimpleName(); - private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); + private DownloadQueueSerializer downloadQueueSerializer; // From DI private final MediaPlayerControllerImpl mediaPlayerController; // From DI private final Downloader downloader; // From DI private Context context; private BroadcastReceiver headsetEventReceiver; - public MediaPlayerLifecycleSupport(Context context, final MediaPlayerControllerImpl mediaPlayerController, final Downloader downloader) + public MediaPlayerLifecycleSupport(Context context, DownloadQueueSerializer downloadQueueSerializer, + final MediaPlayerControllerImpl mediaPlayerController, final Downloader downloader) { + this.downloadQueueSerializer = downloadQueueSerializer; this.mediaPlayerController = mediaPlayerController; this.context = context; this.downloader = downloader; @@ -75,14 +75,14 @@ public class MediaPlayerLifecycleSupport commandFilter.addAction(Constants.CMD_PROCESS_KEYCODE); context.registerReceiver(intentReceiver, commandFilter); - downloadQueueSerializer.getValue().deserializeDownloadQueue(new Consumer() { + this.downloadQueueSerializer.deserializeDownloadQueue(new Consumer() { @Override public void accept(State state) { // TODO: here the autoPlay = false creates problems when Ultrasonic is started by a Play MediaButton as the player won't start this way. mediaPlayerController.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, false, false); // Work-around: Serialize again, as the restore() method creates a serialization without current playing info. - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, + MediaPlayerLifecycleSupport.this.downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition()); } }); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index a1622a31..c5c26d67 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -47,6 +47,10 @@ import static org.moire.ultrasonic.domain.PlayerState.PREPARING; import static org.moire.ultrasonic.domain.PlayerState.STARTED; import static org.moire.ultrasonic.domain.PlayerState.STOPPED; +/** + * Android Foreground Service for playing music + * while the rest of the Ultrasonic App is in the background. + */ public class MediaPlayerService extends Service { private static final String TAG = MediaPlayerService.class.getSimpleName(); @@ -61,13 +65,14 @@ public class MediaPlayerService extends Service private final Scrobbler scrobbler = new Scrobbler(); public Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); - private Lazy downloadQueueSerializer = inject(DownloadQueueSerializer.class); + private Lazy downloadQueueSerializerLazy = inject(DownloadQueueSerializer.class); private Lazy shufflePlayBufferLazy = inject(ShufflePlayBuffer.class); private Lazy downloaderLazy = inject(Downloader.class); private Lazy localMediaPlayerLazy = inject(LocalMediaPlayer.class); private LocalMediaPlayer localMediaPlayer; private Downloader downloader; private ShufflePlayBuffer shufflePlayBuffer; + private DownloadQueueSerializer downloadQueueSerializer; private boolean isInForeground = false; private NotificationCompat.Builder notificationBuilder; @@ -129,6 +134,7 @@ public class MediaPlayerService extends Service downloader = downloaderLazy.getValue(); localMediaPlayer = localMediaPlayerLazy.getValue(); shufflePlayBuffer = shufflePlayBufferLazy.getValue(); + downloadQueueSerializer = downloadQueueSerializerLazy.getValue(); downloader.onCreate(); shufflePlayBuffer.onCreate(); @@ -140,7 +146,7 @@ public class MediaPlayerService extends Service localMediaPlayer.onPrepared = new Runnable() { @Override public void run() { - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } }; @@ -401,7 +407,7 @@ public class MediaPlayerService extends Service { localMediaPlayer.reset(); localMediaPlayer.setCurrentPlaying(null); - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } @@ -457,7 +463,7 @@ public class MediaPlayerService extends Service public void accept(PlayerState playerState, DownloadFile currentPlaying) { if (playerState == PAUSED) { - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } boolean showWhenPaused = (playerState != PlayerState.STOPPED && Util.isNotificationAlwaysEnabled(MediaPlayerService.this)); @@ -579,7 +585,7 @@ public class MediaPlayerService extends Service setNextPlaying(); if (serialize) { - downloadQueueSerializer.getValue().serializeDownloadQueue(downloader.downloadList, + downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/State.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/State.java index b0c19e90..60c282c9 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/State.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/State.java @@ -6,6 +6,9 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; +/** + * Represents the state of the Media Player implementation + */ public class State implements Serializable { public static final long serialVersionUID = -6346438781062572270L; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Supplier.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Supplier.java index 9a4a072c..67ebea09 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Supplier.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Supplier.java @@ -1,5 +1,9 @@ package org.moire.ultrasonic.service; +/** + * Abstract class for supplying items to a consumer + * @param The type of the item supplied + */ public abstract class Supplier { public abstract T get(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java index cc6a83a1..830eb882 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java @@ -27,6 +27,10 @@ import static org.koin.java.standalone.KoinJavaComponent.inject; * @author Sindre Mehus * @version $Id$ */ + +/** + * Responsible for cleaning up files from the offline download cache on the filesystem + */ public class CacheCleaner { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java index 973bf91e..b12b438a 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/SongView.java @@ -37,10 +37,9 @@ import org.moire.ultrasonic.domain.MusicDirectory.Entry; import org.moire.ultrasonic.featureflags.Feature; import org.moire.ultrasonic.featureflags.FeatureStorage; import org.moire.ultrasonic.service.DownloadFile; -import org.moire.ultrasonic.service.Downloader; +import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicServiceFactory; -import org.moire.ultrasonic.service.LocalMediaPlayer; import org.moire.ultrasonic.util.Util; import org.moire.ultrasonic.util.VideoPlayerType; @@ -82,8 +81,7 @@ public class SongView extends UpdateView implements Checkable private boolean maximized = false; private boolean useFiveStarRating; - private Lazy downloader = inject(Downloader.class); - protected Lazy localMediaPlayer = inject(LocalMediaPlayer.class); + private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); public SongView(Context context) { @@ -170,7 +168,7 @@ public class SongView extends UpdateView implements Checkable this.song = song; - this.downloadFile = downloader.getValue().getDownloadFileForSong(song); + this.downloadFile = mediaPlayerControllerLazy.getValue().getDownloadFileForSong(song); StringBuilder artist = new StringBuilder(60); @@ -321,7 +319,7 @@ public class SongView extends UpdateView implements Checkable { updateBackground(); - downloadFile = downloader.getValue().getDownloadFileForSong(this.song); + downloadFile = mediaPlayerControllerLazy.getValue().getDownloadFileForSong(this.song); File partialFile = downloadFile.getPartialFile(); if (downloadFile.isWorkDone()) @@ -411,7 +409,7 @@ public class SongView extends UpdateView implements Checkable viewHolder.fiveStar4.setImageDrawable(rating > 3 ? starDrawable : starHollowDrawable); viewHolder.fiveStar5.setImageDrawable(rating > 4 ? starDrawable : starHollowDrawable); - boolean playing = localMediaPlayer.getValue().currentPlaying == downloadFile; + boolean playing = mediaPlayerControllerLazy.getValue().getCurrentPlaying() == downloadFile; if (playing) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 64ae896e..c13d5ad3 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -113,13 +113,15 @@ val musicServiceModule = module(MUSIC_SERVICE_CONTEXT) { single { SubsonicImageLoader(getProperty(DiProperties.APP_CONTEXT), get()) } - single { MediaPlayerControllerImpl(androidContext(), get(), get(), get()) } - single { MediaPlayerControllerImpl(androidContext(), get(), get(), get()) } + single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } single { JukeboxMediaPlayer(androidContext(), get()) } - single { MediaPlayerLifecycleSupport(androidContext(), get(), get()) } + single { MediaPlayerLifecycleSupport(androidContext(), get(), get(), get()) } single { DownloadQueueSerializer(androidContext()) } single { ExternalStorageMonitor(androidContext()) } single { ShufflePlayBuffer(androidContext()) } single { Downloader(androidContext(), get(), get(), get()) } single { LocalMediaPlayer(androidContext()) } + + // TODO: Ideally this can be cleaned up when all circular references are removed. + single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } } From bd77c2a8517b02baef56c27be2a39524b7e7f800 Mon Sep 17 00:00:00 2001 From: Nite Date: Fri, 26 Jun 2020 16:33:27 +0200 Subject: [PATCH 09/25] Added null checks, moved MediaPlayer to its own Koin module --- .../moire/ultrasonic/service/Downloader.java | 4 +- .../ultrasonic/service/LocalMediaPlayer.java | 93 +++++++++++-------- .../moire/ultrasonic/util/StreamProxy.java | 2 +- .../kotlin/org/moire/ultrasonic/app/UApp.kt | 10 +- .../moire/ultrasonic/di/MediaPlayerModule.kt | 20 ++++ .../moire/ultrasonic/di/MusicServiceModule.kt | 12 --- 6 files changed, 80 insertions(+), 61 deletions(-) create mode 100644 ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java index bc7449fa..1da669c8 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java @@ -47,7 +47,6 @@ public class Downloader private ScheduledExecutorService executorService; private long revision; - public Downloader(Context context, ShufflePlayBuffer shufflePlayBuffer, ExternalStorageMonitor externalStorageMonitor, LocalMediaPlayer localMediaPlayer) { @@ -150,6 +149,7 @@ public class Downloader if (start == -1) start = 0; int i = start; + // Check all DownloadFiles on the playlist do { DownloadFile downloadFile = downloadList.get(i); @@ -162,6 +162,7 @@ public class Downloader cleanupCandidates.add(currentDownloading); if (i == (start + 1)) { + // The next file on the playlist is currently downloading localMediaPlayer.setNextPlayerState(DOWNLOADING); } break; @@ -176,6 +177,7 @@ public class Downloader } while (i != start); } + // If the downloadList contains no work, check the backgroundDownloadList if ((preloaded + 1 == n || preloaded >= Util.getPreloadCount(context) || downloadList.isEmpty()) && !backgroundDownloadList.isEmpty()) { for (int i = 0; i < backgroundDownloadList.size(); i++) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java index c00b3d2e..e8acdddc 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java @@ -293,14 +293,17 @@ public class LocalMediaPlayer updateRemoteControl(); } - Handler mainHandler = new Handler(context.getMainLooper()); - Runnable myRunnable = new Runnable() { - @Override - public void run() { - onPlayerStateChanged.accept(playerState, currentPlaying); - } - }; - mainHandler.post(myRunnable); + if (onPlayerStateChanged != null) + { + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onPlayerStateChanged.accept(playerState, currentPlaying); + } + }; + mainHandler.post(myRunnable); + } if (playerState == STARTED && positionCache == null) { @@ -321,14 +324,17 @@ public class LocalMediaPlayer this.currentPlaying = currentPlaying; updateRemoteControl(); - Handler mainHandler = new Handler(context.getMainLooper()); - Runnable myRunnable = new Runnable() { - @Override - public void run() { - onCurrentPlayingChanged.accept(currentPlaying); - } - }; - mainHandler.post(myRunnable); + if (onCurrentPlayingChanged != null) + { + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onCurrentPlayingChanged.accept(currentPlaying); + } + }; + mainHandler.post(myRunnable); + } } public synchronized void setNextPlaying(DownloadFile nextToPlay) @@ -398,14 +404,16 @@ public class LocalMediaPlayer setPlayerState(PlayerState.STARTED); setupHandlers(currentPlaying, false); - Handler mainHandler = new Handler(context.getMainLooper()); - Runnable myRunnable = new Runnable() { - @Override - public void run() { - onNextSongRequested.run(); - } - }; - mainHandler.post(myRunnable); + if (onNextSongRequested != null) { + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onNextSongRequested.run(); + } + }; + mainHandler.post(myRunnable); + } // Proxy should not be being used here since the next player was already setup to play if (proxy != null) @@ -726,14 +734,16 @@ public class LocalMediaPlayer } } - Handler mainHandler = new Handler(context.getMainLooper()); - Runnable myRunnable = new Runnable() { - @Override - public void run() { - onPrepared.run(); - } - }; - mainHandler.post(myRunnable); + if (onPrepared != null) { + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onPrepared.run(); + } + }; + mainHandler.post(myRunnable); + } } }); @@ -862,14 +872,17 @@ public class LocalMediaPlayer } else { - Handler mainHandler = new Handler(context.getMainLooper()); - Runnable myRunnable = new Runnable() { - @Override - public void run() { - onSongCompleted.accept(currentPlaying); - } - }; - mainHandler.post(myRunnable); + if (onSongCompleted != null) + { + Handler mainHandler = new Handler(context.getMainLooper()); + Runnable myRunnable = new Runnable() { + @Override + public void run() { + onSongCompleted.accept(currentPlaying); + } + }; + mainHandler.post(myRunnable); + } } return; 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 34df0285..943b4993 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/StreamProxy.java @@ -170,7 +170,7 @@ public class StreamProxy implements Runnable public void run() { Log.i(TAG, "Streaming song in background"); - DownloadFile downloadFile = currentPlaying.get(); + DownloadFile downloadFile = currentPlaying == null? null : 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/kotlin/org/moire/ultrasonic/app/UApp.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt index 8e1f9fa3..8e08d51d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt @@ -2,12 +2,7 @@ package org.moire.ultrasonic.app import androidx.multidex.MultiDexApplication import org.koin.android.ext.android.startKoin -import org.moire.ultrasonic.di.DiProperties -import org.moire.ultrasonic.di.appPermanentStorage -import org.moire.ultrasonic.di.baseNetworkModule -import org.moire.ultrasonic.di.directoriesModule -import org.moire.ultrasonic.di.featureFlagsModule -import org.moire.ultrasonic.di.musicServiceModule +import org.moire.ultrasonic.di.* class UApp : MultiDexApplication() { override fun onCreate() { @@ -20,7 +15,8 @@ class UApp : MultiDexApplication() { appPermanentStorage, baseNetworkModule, featureFlagsModule, - musicServiceModule + musicServiceModule, + mediaPlayerModule ), extraProperties = mapOf( DiProperties.APP_CONTEXT to applicationContext diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt new file mode 100644 index 00000000..f0f529f3 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt @@ -0,0 +1,20 @@ +package org.moire.ultrasonic.di + +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module.module +import org.moire.ultrasonic.service.* +import org.moire.ultrasonic.util.ShufflePlayBuffer + +val mediaPlayerModule = module { + single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } + single { JukeboxMediaPlayer(androidContext(), get()) } + single { MediaPlayerLifecycleSupport(androidContext(), get(), get(), get()) } + single { DownloadQueueSerializer(androidContext()) } + single { ExternalStorageMonitor(androidContext()) } + single { ShufflePlayBuffer(androidContext()) } + single { Downloader(androidContext(), get(), get(), get()) } + single { LocalMediaPlayer(androidContext()) } + + // TODO: Ideally this can be cleaned up when all circular references are removed. + single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } +} \ No newline at end of file diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index c13d5ad3..8fb7c282 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -112,16 +112,4 @@ val musicServiceModule = module(MUSIC_SERVICE_CONTEXT) { } single { SubsonicImageLoader(getProperty(DiProperties.APP_CONTEXT), get()) } - - single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } - single { JukeboxMediaPlayer(androidContext(), get()) } - single { MediaPlayerLifecycleSupport(androidContext(), get(), get(), get()) } - single { DownloadQueueSerializer(androidContext()) } - single { ExternalStorageMonitor(androidContext()) } - single { ShufflePlayBuffer(androidContext()) } - single { Downloader(androidContext(), get(), get(), get()) } - single { LocalMediaPlayer(androidContext()) } - - // TODO: Ideally this can be cleaned up when all circular references are removed. - single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } } From fdc1748f8a18a4c105c5b5b7fdc95129399e8a1d Mon Sep 17 00:00:00 2001 From: Nite Date: Mon, 29 Jun 2020 14:44:19 +0200 Subject: [PATCH 10/25] Fixed foreground service stop and start Fixed play on restore --- .../ultrasonic/service/LocalMediaPlayer.java | 15 ++++----- .../service/MediaPlayerControllerImpl.java | 32 +++++++++---------- .../service/MediaPlayerService.java | 8 +++-- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java index e8acdddc..2abbd53e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java @@ -119,21 +119,20 @@ public class LocalMediaPlayer public void onCreate() { + if (mediaPlayer != null) + { + mediaPlayer.release(); + } + + mediaPlayer = new MediaPlayer(); + new Thread(new Runnable() { @Override public void run() { Thread.currentThread().setName("MediaPlayerThread"); - Looper.prepare(); - - if (mediaPlayer != null) - { - mediaPlayer.release(); - } - - mediaPlayer = new MediaPlayer(); mediaPlayer.setWakeMode(context, PowerManager.PARTIAL_WAKE_LOCK); mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java index 4b71ac4b..fe840b75 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java @@ -96,6 +96,9 @@ public class MediaPlayerControllerImpl implements MediaPlayerController { externalStorageMonitor.onDestroy(); context.stopService(new Intent(context, MediaPlayerService.class)); + shufflePlayBuffer.onDestroy(); + downloader.onDestroy(); + Log.i(TAG, "MediaPlayerControllerImpl destroyed"); } @@ -110,29 +113,24 @@ public class MediaPlayerControllerImpl implements MediaPlayerController @Override public void accept(MediaPlayerService mediaPlayerService) { mediaPlayerService.play(currentPlayingIndex, autoPlayStart); - } - }); - if (localMediaPlayer.currentPlaying != null) - { - if (autoPlay && jukeboxMediaPlayer.getValue().isEnabled()) - { - jukeboxMediaPlayer.getValue().skip(downloader.getCurrentPlayingIndex(), currentPlayingPosition / 1000); - } - else - { - if (localMediaPlayer.currentPlaying.isCompleteFileAvailable()) + if (localMediaPlayer.currentPlaying != null) { - MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { - @Override - public void accept(MediaPlayerService mediaPlayerService) { + if (autoPlay && jukeboxMediaPlayer.getValue().isEnabled()) + { + jukeboxMediaPlayer.getValue().skip(downloader.getCurrentPlayingIndex(), currentPlayingPosition / 1000); + } + else + { + if (localMediaPlayer.currentPlaying.isCompleteFileAvailable()) + { localMediaPlayer.doPlay(localMediaPlayer.currentPlaying, currentPlayingPosition, autoPlay); } - }); + } } + autoPlayStart = false; } - } - autoPlayStart = false; + }); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index c5c26d67..63397e2b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -191,9 +191,9 @@ public class MediaPlayerService extends Service instance = null; try { + downloadQueueSerializer.serializeDownloadQueueNow(downloader.downloadList, + downloader.getCurrentPlayingIndex(), getPlayerPosition()); localMediaPlayer.onDestroy(); - shufflePlayBuffer.onDestroy(); - downloader.onDestroy(); } catch (Throwable ignored) { } @@ -205,7 +205,7 @@ public class MediaPlayerService extends Service synchronized (instanceLock) { // currentPlaying could be changed from another thread in the meantime, so check again before stopping for good - if (localMediaPlayer.currentPlaying == null) stopSelf(); + if (localMediaPlayer.currentPlaying == null || localMediaPlayer.playerState == STOPPED) stopSelf(); } } @@ -385,6 +385,8 @@ public class MediaPlayerService extends Service } else { + setCurrentPlaying(index); + if (start) { if (jukeboxMediaPlayer.getValue().isEnabled()) From 7dad738fcecf78ac767466acd6f9133446d7f2c4 Mon Sep 17 00:00:00 2001 From: Nite Date: Mon, 29 Jun 2020 16:17:22 +0200 Subject: [PATCH 11/25] Fixed pause on bluetooth disconnect, minor fixes --- .../java/org/moire/ultrasonic/service/Downloader.java | 9 +++++++-- .../org/moire/ultrasonic/service/LocalMediaPlayer.java | 2 +- .../ultrasonic/service/MediaPlayerControllerImpl.java | 1 - .../ultrasonic/service/MediaPlayerLifecycleSupport.java | 3 +++ .../org/moire/ultrasonic/service/MediaPlayerService.java | 2 ++ .../org/moire/ultrasonic/util/ShufflePlayBuffer.java | 2 ++ 6 files changed, 15 insertions(+), 4 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java index 1da669c8..285870c3 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java @@ -77,17 +77,22 @@ public class Downloader executorService = Executors.newSingleThreadScheduledExecutor(); executorService.scheduleWithFixedDelay(downloadChecker, 5, 5, TimeUnit.SECONDS); Log.i(TAG, "Downloader created"); - } public void onDestroy() { - executorService.shutdown(); + stop(); clear(); clearBackground(); Log.i(TAG, "Downloader destroyed"); } + public void stop() + { + executorService.shutdown(); + Log.i(TAG, "Downloader stopped"); + } + public synchronized void checkDownloads() { if (!Util.isExternalStoragePresent() || !externalStorageMonitor.isExternalStorageAvailable()) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java index 2abbd53e..10ce6ad0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java @@ -1081,7 +1081,7 @@ public class LocalMediaPlayer cachedPosition = mediaPlayer.getCurrentPosition(); } - Util.sleepQuietly(25L); + Util.sleepQuietly(50L); } catch (Exception e) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java index fe840b75..f0e70c4c 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java @@ -96,7 +96,6 @@ public class MediaPlayerControllerImpl implements MediaPlayerController { externalStorageMonitor.onDestroy(); context.stopService(new Intent(context, MediaPlayerService.class)); - shufflePlayBuffer.onDestroy(); downloader.onDestroy(); Log.i(TAG, "MediaPlayerControllerImpl destroyed"); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index f34c06b3..a616b6a6 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -245,6 +245,9 @@ public class MediaPlayerLifecycleSupport mediaPlayerController.pause(); mediaPlayerController.seekTo(0); break; + case Constants.CMD_PAUSE: + mediaPlayerController.pause(); + break; case Constants.CMD_PROCESS_KEYCODE: receiveIntent(intent); break; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index 63397e2b..04393095 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -194,6 +194,8 @@ public class MediaPlayerService extends Service downloadQueueSerializer.serializeDownloadQueueNow(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); localMediaPlayer.onDestroy(); + downloader.stop(); + shufflePlayBuffer.onDestroy(); } catch (Throwable ignored) { } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java index 07aef6b7..9c84d6a0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java @@ -65,11 +65,13 @@ public class ShufflePlayBuffer } }; executorService.scheduleWithFixedDelay(runnable, 1, 10, TimeUnit.SECONDS); + Log.i(TAG, "ShufflePlayBuffer created"); } public void onDestroy() { executorService.shutdown(); + Log.i(TAG, "ShufflePlayBuffer destroyed"); } public List get(int size) From decca7103fc0054db43edc2cb60e61ba55a00218 Mon Sep 17 00:00:00 2001 From: Nite Date: Tue, 30 Jun 2020 15:16:45 +0200 Subject: [PATCH 12/25] Fixed exit and restart --- .../ultrasonic/activity/SubsonicTabActivity.java | 2 +- .../service/MediaPlayerControllerImpl.java | 10 ++++++++++ .../service/MediaPlayerLifecycleSupport.java | 13 +++++++++++++ .../ultrasonic/service/MediaPlayerService.java | 2 -- 4 files changed, 24 insertions(+), 3 deletions(-) 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 06d10d70..3e534fce 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -160,7 +160,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen Util.registerMediaButtonEventReceiver(this); // Lifecycle support's constructor registers some event receivers so it should be created early - lifecycleSupport.getValue(); + lifecycleSupport.getValue().onCreate(); // Make sure to update theme if (theme != null && !theme.equals(Util.getTheme(this))) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java index f0e70c4c..f47adad9 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java @@ -54,6 +54,7 @@ public class MediaPlayerControllerImpl implements MediaPlayerController { private static final String TAG = MediaPlayerControllerImpl.class.getSimpleName(); + private boolean created = false; private String suggestedPlaylistName; private boolean keepScreenOn; @@ -79,6 +80,12 @@ public class MediaPlayerControllerImpl implements MediaPlayerController this.shufflePlayBuffer = shufflePlayBuffer; this.localMediaPlayer = localMediaPlayer; + Log.i(TAG, "MediaPlayerControllerImpl constructed"); + } + + public void onCreate() + { + if (created) return; this.externalStorageMonitor.onCreate(new Runnable() { @Override public void run() { @@ -88,15 +95,18 @@ public class MediaPlayerControllerImpl implements MediaPlayerController int instance = Util.getActiveServer(context); setJukeboxEnabled(Util.getJukeboxEnabled(context, instance)); + created = true; Log.i(TAG, "MediaPlayerControllerImpl created"); } public void onDestroy() { + if (!created) return; externalStorageMonitor.onDestroy(); context.stopService(new Intent(context, MediaPlayerService.class)); downloader.onDestroy(); + created = false; Log.i(TAG, "MediaPlayerControllerImpl destroyed"); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index a616b6a6..77ccf2bc 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -44,6 +44,7 @@ public class MediaPlayerLifecycleSupport { private static final String TAG = MediaPlayerLifecycleSupport.class.getSimpleName(); + private boolean created = false; private DownloadQueueSerializer downloadQueueSerializer; // From DI private final MediaPlayerControllerImpl mediaPlayerController; // From DI private final Downloader downloader; // From DI @@ -59,6 +60,12 @@ public class MediaPlayerLifecycleSupport this.context = context; this.downloader = downloader; + Log.i(TAG, "LifecycleSupport constructed"); + } + + public void onCreate() + { + if (created) return; registerHeadsetReceiver(); // React to media buttons. @@ -75,6 +82,7 @@ public class MediaPlayerLifecycleSupport commandFilter.addAction(Constants.CMD_PROCESS_KEYCODE); context.registerReceiver(intentReceiver, commandFilter); + mediaPlayerController.onCreate(); this.downloadQueueSerializer.deserializeDownloadQueue(new Consumer() { @Override public void accept(State state) { @@ -88,15 +96,20 @@ public class MediaPlayerLifecycleSupport }); new CacheCleaner(context).clean(); + created = true; Log.i(TAG, "LifecycleSupport created"); } public void onDestroy() { + if (!created) return; + downloadQueueSerializer.serializeDownloadQueueNow(downloader.downloadList, + downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition()); mediaPlayerController.clear(false); context.unregisterReceiver(headsetEventReceiver); context.unregisterReceiver(intentReceiver); mediaPlayerController.onDestroy(); + created = false; Log.i(TAG, "LifecycleSupport destroyed"); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index 04393095..5cd9e008 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -191,8 +191,6 @@ public class MediaPlayerService extends Service instance = null; try { - downloadQueueSerializer.serializeDownloadQueueNow(downloader.downloadList, - downloader.getCurrentPlayingIndex(), getPlayerPosition()); localMediaPlayer.onDestroy(); downloader.stop(); shufflePlayBuffer.onDestroy(); From 7f5ff002372946c25c9ee0f8e426e14f511f9304 Mon Sep 17 00:00:00 2001 From: Nite Date: Tue, 30 Jun 2020 16:19:50 +0200 Subject: [PATCH 13/25] Fixed Media Key processing --- .../service/MediaPlayerLifecycleSupport.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index 77ccf2bc..96bc5849 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -64,6 +64,11 @@ public class MediaPlayerLifecycleSupport } public void onCreate() + { + onCreate(false); + } + + private void onCreate(final boolean autoPlay) { if (created) return; registerHeadsetReceiver(); @@ -86,8 +91,7 @@ public class MediaPlayerLifecycleSupport this.downloadQueueSerializer.deserializeDownloadQueue(new Consumer() { @Override public void accept(State state) { - // TODO: here the autoPlay = false creates problems when Ultrasonic is started by a Play MediaButton as the player won't start this way. - mediaPlayerController.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, false, false); + mediaPlayerController.restore(state.songs, state.currentPlayingIndex, state.currentPlayingPosition, autoPlay, false); // Work-around: Serialize again, as the restore() method creates a serialization without current playing info. MediaPlayerLifecycleSupport.this.downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, @@ -177,7 +181,16 @@ public class MediaPlayerLifecycleSupport return; } - switch (event.getKeyCode()) + int keyCode = event.getKeyCode(); + boolean autoStart = (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || + keyCode == KeyEvent.KEYCODE_MEDIA_PLAY || + keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS || + keyCode == KeyEvent.KEYCODE_MEDIA_NEXT); + + // We can receive intents (e.g. MediaButton) when everything is stopped, so we need to start + if (!created) onCreate(autoStart); + + switch (keyCode) { case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: case KeyEvent.KEYCODE_HEADSETHOOK: From 2ff0151e8c31611b1e174ec3613551d18e5e50bd Mon Sep 17 00:00:00 2001 From: Nite Date: Tue, 30 Jun 2020 16:47:29 +0200 Subject: [PATCH 14/25] Fixed widget operation when Ultrasonic is stopped --- .../provider/UltraSonicAppWidgetProvider.java | 10 ++-- .../receiver/MediaButtonIntentReceiver.java | 53 ++++++++++--------- 2 files changed, 35 insertions(+), 28 deletions(-) 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 f710fe8e..849ded07 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/provider/UltraSonicAppWidgetProvider.java @@ -17,6 +17,7 @@ import org.moire.ultrasonic.R; import org.moire.ultrasonic.activity.DownloadActivity; import org.moire.ultrasonic.activity.MainActivity; import org.moire.ultrasonic.domain.MusicDirectory; +import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver; import org.moire.ultrasonic.service.MediaPlayerController; import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.FileUtil; @@ -199,19 +200,22 @@ public class UltraSonicAppWidgetProvider extends AppWidgetProvider // Emulate media button clicks. intent = new Intent(Constants.CMD_PROCESS_KEYCODE); - intent.setPackage(context.getPackageName()); + //intent.setPackage(context.getPackageName()); + intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class)); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); pendingIntent = PendingIntent.getBroadcast(context, 11, intent, 0); views.setOnClickPendingIntent(R.id.control_play, pendingIntent); intent = new Intent(Constants.CMD_PROCESS_KEYCODE); - intent.setPackage(context.getPackageName()); + //intent.setPackage(context.getPackageName()); + intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class)); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_NEXT)); pendingIntent = PendingIntent.getBroadcast(context, 12, intent, 0); views.setOnClickPendingIntent(R.id.control_next, pendingIntent); intent = new Intent(Constants.CMD_PROCESS_KEYCODE); - intent.setPackage(context.getPackageName()); + //intent.setPackage(context.getPackageName()); + intent.setComponent(new ComponentName(context, MediaButtonIntentReceiver.class)); intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); pendingIntent = PendingIntent.getBroadcast(context, 13, 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 ee636c40..51b9db57 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/MediaButtonIntentReceiver.java @@ -44,37 +44,40 @@ public class MediaButtonIntentReceiver extends BroadcastReceiver @Override public void onReceive(Context context, Intent intent) { - if (Util.getMediaButtonsPreference(context)) + String intentAction = intent.getAction(); + + // If media button are turned off and we received a media button, exit + if (!Util.getMediaButtonsPreference(context) && + Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) return; + + // Only process media buttons and CMD_PROCESS_KEYCODE, which is received from the widgets + if (!Intent.ACTION_MEDIA_BUTTON.equals(intentAction) && + !Constants.CMD_PROCESS_KEYCODE.equals(intentAction)) return; + + Bundle extras = intent.getExtras(); + + if (extras == null) { - String intentAction = intent.getAction(); + return; + } - if (!Intent.ACTION_MEDIA_BUTTON.equals(intentAction)) return; + Parcelable event = (Parcelable) extras.get(Intent.EXTRA_KEY_EVENT); + Log.i(TAG, "Got MEDIA_BUTTON key event: " + event); - Bundle extras = intent.getExtras(); + try + { + Intent serviceIntent = new Intent(Constants.CMD_PROCESS_KEYCODE); + serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); + lifecycleSupport.getValue().receiveIntent(serviceIntent); - if (extras == null) + if (isOrderedBroadcast()) { - return; - } - - Parcelable event = (Parcelable) extras.get(Intent.EXTRA_KEY_EVENT); - Log.i(TAG, "Got MEDIA_BUTTON key event: " + event); - - try - { - Intent serviceIntent = new Intent(Constants.CMD_PROCESS_KEYCODE); - serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); - lifecycleSupport.getValue().receiveIntent(serviceIntent); - - if (isOrderedBroadcast()) - { - abortBroadcast(); - } - } - catch (Exception x) - { - // Ignored. + abortBroadcast(); } } + catch (Exception x) + { + // Ignored. + } } } From 5b5c8ea8b7734fcc3745724c6e50c42f086a52ec Mon Sep 17 00:00:00 2001 From: Nite Date: Tue, 30 Jun 2020 17:09:13 +0200 Subject: [PATCH 15/25] Fixed CI things --- .../kotlin/org/moire/ultrasonic/app/UApp.kt | 8 +++++++- .../moire/ultrasonic/di/MediaPlayerModule.kt | 18 ++++++++++++++---- .../moire/ultrasonic/di/MusicServiceModule.kt | 7 ++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt index 8e08d51d..fe97c592 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/app/UApp.kt @@ -2,7 +2,13 @@ package org.moire.ultrasonic.app import androidx.multidex.MultiDexApplication import org.koin.android.ext.android.startKoin -import org.moire.ultrasonic.di.* +import org.moire.ultrasonic.di.DiProperties +import org.moire.ultrasonic.di.appPermanentStorage +import org.moire.ultrasonic.di.baseNetworkModule +import org.moire.ultrasonic.di.directoriesModule +import org.moire.ultrasonic.di.featureFlagsModule +import org.moire.ultrasonic.di.mediaPlayerModule +import org.moire.ultrasonic.di.musicServiceModule class UApp : MultiDexApplication() { override fun onCreate() { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt index f0f529f3..c740f0b4 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt @@ -2,11 +2,21 @@ package org.moire.ultrasonic.di import org.koin.android.ext.koin.androidContext import org.koin.dsl.module.module -import org.moire.ultrasonic.service.* +import org.moire.ultrasonic.service.DownloadQueueSerializer +import org.moire.ultrasonic.service.Downloader +import org.moire.ultrasonic.service.ExternalStorageMonitor +import org.moire.ultrasonic.service.JukeboxMediaPlayer +import org.moire.ultrasonic.service.LocalMediaPlayer +import org.moire.ultrasonic.service.MediaPlayerController +import org.moire.ultrasonic.service.MediaPlayerControllerImpl +import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport import org.moire.ultrasonic.util.ShufflePlayBuffer val mediaPlayerModule = module { - single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } + single { + MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) + } + single { JukeboxMediaPlayer(androidContext(), get()) } single { MediaPlayerLifecycleSupport(androidContext(), get(), get(), get()) } single { DownloadQueueSerializer(androidContext()) } @@ -15,6 +25,6 @@ val mediaPlayerModule = module { single { Downloader(androidContext(), get(), get(), get()) } single { LocalMediaPlayer(androidContext()) } - // TODO: Ideally this can be cleaned up when all circular references are removed. + // TODO Ideally this can be cleaned up when all circular references are removed. single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } -} \ No newline at end of file +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt index 8fb7c282..193e34d9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MusicServiceModule.kt @@ -3,7 +3,6 @@ package org.moire.ultrasonic.di import android.content.SharedPreferences import android.util.Log -import org.koin.android.ext.koin.androidContext import kotlin.math.abs import org.koin.dsl.module.module import org.moire.ultrasonic.BuildConfig @@ -12,10 +11,12 @@ import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration import org.moire.ultrasonic.api.subsonic.di.subsonicApiModule import org.moire.ultrasonic.cache.PermanentFileStorage -import org.moire.ultrasonic.service.* +import org.moire.ultrasonic.service.CachedMusicService +import org.moire.ultrasonic.service.MusicService +import org.moire.ultrasonic.service.OfflineMusicService +import org.moire.ultrasonic.service.RESTMusicService import org.moire.ultrasonic.subsonic.loader.image.SubsonicImageLoader import org.moire.ultrasonic.util.Constants -import org.moire.ultrasonic.util.ShufflePlayBuffer internal const val MUSIC_SERVICE_CONTEXT = "CurrentMusicService" internal const val ONLINE_MUSIC_SERVICE = "OnlineMusicService" From 60250a42a93b8fc9386d3e4ab1b11d23d00a2881 Mon Sep 17 00:00:00 2001 From: Nite Date: Tue, 30 Jun 2020 22:10:15 +0200 Subject: [PATCH 16/25] Fixed wrong context --- .../java/org/moire/ultrasonic/activity/DownloadActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java index 80e8e291..4b6369a9 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/DownloadActivity.java @@ -186,8 +186,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi if (!useFiveStarRating) ratingLinearLayout.setVisibility(View.GONE); - hollowStar = Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_hollow); - fullStar = Util.getDrawableFromAttribute(SubsonicTabActivity.getInstance(), R.attr.star_full); + hollowStar = Util.getDrawableFromAttribute(this, R.attr.star_hollow); + fullStar = Util.getDrawableFromAttribute(this, R.attr.star_full); fiveStar1ImageView.setOnClickListener(new View.OnClickListener() { From 091374f922c6864797bb2764123a42d03c02d1d1 Mon Sep 17 00:00:00 2001 From: Nite Date: Thu, 2 Jul 2020 17:20:06 +0200 Subject: [PATCH 17/25] Fixed wired headset handling Fixed MediaPlayer start timeout for slow phones --- .../ultrasonic/service/MediaPlayerLifecycleSupport.java | 1 + .../org/moire/ultrasonic/service/MediaPlayerService.java | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index 96bc5849..59057b7d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -184,6 +184,7 @@ public class MediaPlayerLifecycleSupport int keyCode = event.getKeyCode(); boolean autoStart = (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY || + keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index 5cd9e008..c389f978 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -82,7 +82,7 @@ public class MediaPlayerService extends Service public static MediaPlayerService getInstance(Context context) { synchronized (instanceLock) { - for (int i = 0; i < 5; i++) { + for (int i = 0; i < 20; i++) { if (instance != null) return instance; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -113,6 +113,12 @@ public class MediaPlayerService extends Service public void run() { MediaPlayerService instance = getInstance(context); + if (instance == null) + { + Log.e(TAG, "ExecuteOnStartedMediaPlayerService failed to get a MediaPlayerService instance!"); + return; + } + taskToExecute.accept(instance); } }; From f5748d7dc097d435bb17865898b0eb2af39583dd Mon Sep 17 00:00:00 2001 From: Nite Date: Thu, 2 Jul 2020 19:01:33 +0200 Subject: [PATCH 18/25] Fixed lost lockscreen controls when exiting the app --- .../moire/ultrasonic/activity/MainActivity.java | 2 +- .../activity/SubsonicTabActivity.java | 4 ++-- .../ultrasonic/fragment/SettingsFragment.java | 4 ++-- .../receiver/BluetoothIntentReceiver.java | 2 +- .../ultrasonic/service/LocalMediaPlayer.java | 14 ++++++++------ .../service/MediaPlayerLifecycleSupport.java | 2 +- .../java/org/moire/ultrasonic/util/Util.java | 17 +++++++++++++++-- 7 files changed, 30 insertions(+), 15 deletions(-) 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 f9052ffd..66297dba 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/MainActivity.java @@ -482,7 +482,7 @@ public class MainActivity extends SubsonicTabActivity private void exit() { lifecycleSupport.getValue().onDestroy(); - Util.unregisterMediaButtonEventReceiver(this); + Util.unregisterMediaButtonEventReceiver(this, false); 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 3e534fce..6d790618 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/activity/SubsonicTabActivity.java @@ -158,7 +158,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen applyTheme(); instance = this; - Util.registerMediaButtonEventReceiver(this); + Util.registerMediaButtonEventReceiver(this, false); // Lifecycle support's constructor registers some event receivers so it should be created early lifecycleSupport.getValue().onCreate(); @@ -195,7 +195,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen @Override protected void onDestroy() { - Util.unregisterMediaButtonEventReceiver(this); + Util.unregisterMediaButtonEventReceiver(this, false); super.onDestroy(); destroyed = true; nowPlayingView = null; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java index abe44f7d..d15d6a0e 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java @@ -375,10 +375,10 @@ public class SettingsFragment extends PreferenceFragment private void setMediaButtonsEnabled(boolean enabled) { if (enabled) { lockScreenEnabled.setEnabled(true); - Util.registerMediaButtonEventReceiver(getActivity()); + Util.registerMediaButtonEventReceiver(getActivity(), false); } else { lockScreenEnabled.setEnabled(false); - Util.unregisterMediaButtonEventReceiver(getActivity()); + Util.unregisterMediaButtonEventReceiver(getActivity(), false); } } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/BluetoothIntentReceiver.java b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/BluetoothIntentReceiver.java index 4cf7bfe2..fa1c61c4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/BluetoothIntentReceiver.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/receiver/BluetoothIntentReceiver.java @@ -65,7 +65,7 @@ public class BluetoothIntentReceiver extends BroadcastReceiver if (connected) { Log.i(TAG, "Connected to Bluetooth device, requesting media button focus."); - Util.registerMediaButtonEventReceiver(context); + Util.registerMediaButtonEventReceiver(context, false); } if (disconnected) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java index 10ce6ad0..3b3f8828 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java @@ -168,7 +168,7 @@ public class LocalMediaPlayer wakeLock.setReferenceCounted(false); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - + Util.registerMediaButtonEventReceiver(context, true); setUpRemoteControlClient(); if (equalizerAvailable) @@ -202,6 +202,11 @@ public class LocalMediaPlayer try { + Intent i = new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + i.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, mediaPlayer.getAudioSessionId()); + i.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.getPackageName()); + context.sendBroadcast(i); + mediaPlayer.release(); if (nextMediaPlayer != null) { @@ -230,17 +235,14 @@ public class LocalMediaPlayer 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, context.getPackageName()); - context.sendBroadcast(i); - audioManager.unregisterRemoteControlClient(remoteControlClient); clearRemoteControl(); + Util.unregisterMediaButtonEventReceiver(context, true); wakeLock.release(); } catch (Throwable ignored) { + Log.w(TAG, "LocalMediaPlayer onDestroy exception: ", ignored); } Log.i(TAG, "LocalMediaPlayer destroyed"); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index 59057b7d..95bf2a59 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -74,7 +74,7 @@ public class MediaPlayerLifecycleSupport registerHeadsetReceiver(); // React to media buttons. - Util.registerMediaButtonEventReceiver(context); + Util.registerMediaButtonEventReceiver(context, true); // Register the handler for outside intents. IntentFilter commandFilter = new IntentFilter(); 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 a64e2a6b..4de5fa45 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -96,6 +96,9 @@ public class Util extends DownloadActivity private static boolean pauseFocus; private static boolean lowerFocus; + private static boolean mediaButtonsRegisteredForUI; + private static boolean mediaButtonsRegisteredForService; + private static final Map SERVER_REST_VERSIONS = new ConcurrentHashMap(); // Used by hexEncode() @@ -890,19 +893,29 @@ public class Util extends DownloadActivity return Bitmap.createScaledBitmap(bitmap, size, getScaledHeight(bitmap, size), true); } - public static void registerMediaButtonEventReceiver(Context context) + public static void registerMediaButtonEventReceiver(Context context, boolean isService) { if (getMediaButtonsPreference(context)) { + if (isService) mediaButtonsRegisteredForService = true; + else mediaButtonsRegisteredForUI = true; + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager.registerMediaButtonEventReceiver(new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName())); } } - public static void unregisterMediaButtonEventReceiver(Context context) + public static void unregisterMediaButtonEventReceiver(Context context, boolean isService) { + if (isService) mediaButtonsRegisteredForService = false; + else mediaButtonsRegisteredForUI = false; + + // Do not unregister while there is an active part of the app which needs the control + if (mediaButtonsRegisteredForService || mediaButtonsRegisteredForUI) return; + AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager.unregisterMediaButtonEventReceiver(new ComponentName(context.getPackageName(), MediaButtonIntentReceiver.class.getName())); + Log.i(TAG, "MediaButtonEventReceiver unregistered."); } public static MusicDirectory getSongsFromSearchResult(SearchResult searchResult) From aae40c076eb80116cd37c2d174843dbd3b9d36c4 Mon Sep 17 00:00:00 2001 From: Nite Date: Sat, 4 Jul 2020 09:20:27 +0200 Subject: [PATCH 19/25] Fixed Api bug in DragSortListView --- .../main/java/com/mobeta/android/dslv/DragSortListView.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/library/src/main/java/com/mobeta/android/dslv/DragSortListView.java b/core/library/src/main/java/com/mobeta/android/dslv/DragSortListView.java index 4f8ec744..b7e09722 100644 --- a/core/library/src/main/java/com/mobeta/android/dslv/DragSortListView.java +++ b/core/library/src/main/java/com/mobeta/android/dslv/DragSortListView.java @@ -2933,7 +2933,9 @@ public class DragSortListView extends ListView { // always do scroll mBlockLayoutRequests = true; - setSelectionFromTop(movePos, top - padTop); + // This cast is a workaround of an API bug, see https://issuetracker.google.com/issues/37045361 + ((ListView)DragSortListView.this).setSelectionFromTop(movePos, top - padTop); + DragSortListView.this.layoutChildren(); invalidate(); From 3d61dde83f67bbd97b10ad7afe6dc84e13b0d10b Mon Sep 17 00:00:00 2001 From: Nite Date: Tue, 7 Jul 2020 18:50:26 +0200 Subject: [PATCH 20/25] Fixed audio focus handling --- .../ultrasonic/service/AudioFocusHandler.java | 90 +++++++++++++++++++ .../ultrasonic/service/LocalMediaPlayer.java | 10 ++- .../java/org/moire/ultrasonic/util/Util.java | 58 ------------ .../moire/ultrasonic/di/MediaPlayerModule.kt | 4 +- 4 files changed, 99 insertions(+), 63 deletions(-) create mode 100644 ultrasonic/src/main/java/org/moire/ultrasonic/service/AudioFocusHandler.java diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/AudioFocusHandler.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/AudioFocusHandler.java new file mode 100644 index 00000000..282b1061 --- /dev/null +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/AudioFocusHandler.java @@ -0,0 +1,90 @@ +package org.moire.ultrasonic.service; + +import android.content.Context; +import android.content.SharedPreferences; +import android.media.AudioManager; +import android.util.Log; + +import org.moire.ultrasonic.domain.PlayerState; +import org.moire.ultrasonic.util.Constants; +import org.moire.ultrasonic.util.Util; + +import kotlin.Lazy; + +import static org.koin.java.standalone.KoinJavaComponent.inject; + +public class AudioFocusHandler +{ + private static final String TAG = AudioFocusHandler.class.getSimpleName(); + + private static boolean hasFocus; + private static boolean pauseFocus; + private static boolean lowerFocus; + + // TODO: This is a circular reference, try to remove it + private Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); + private Context context; + + public AudioFocusHandler(Context context) + { + this.context = context; + } + + public void requestAudioFocus() + { + if (!hasFocus) + { + final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + hasFocus = true; + audioManager.requestAudioFocus(new AudioManager.OnAudioFocusChangeListener() + { + @Override + public void onAudioFocusChange(int focusChange) + { + MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue(); + if ((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !mediaPlayerController.isJukeboxEnabled()) + { + Log.v(TAG, "Lost Audio Focus"); + if (mediaPlayerController.getPlayerState() == PlayerState.STARTED) + { + SharedPreferences preferences = Util.getPreferences(context); + int lossPref = Integer.parseInt(preferences.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1")); + if (lossPref == 2 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) + { + lowerFocus = true; + mediaPlayerController.setVolume(0.1f); + } + else if (lossPref == 0 || (lossPref == 1)) + { + pauseFocus = true; + mediaPlayerController.pause(); + } + } + } + else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) + { + Log.v(TAG, "Regained Audio Focus"); + if (pauseFocus) + { + pauseFocus = false; + mediaPlayerController.start(); + } + else if (lowerFocus) + { + lowerFocus = false; + mediaPlayerController.setVolume(1.0f); + } + } + else if (focusChange == AudioManager.AUDIOFOCUS_LOSS && !mediaPlayerController.isJukeboxEnabled()) + { + hasFocus = false; + mediaPlayerController.pause(); + audioManager.abandonAudioFocus(this); + Log.v(TAG, "Abandoned Audio Focus"); + } + } + }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); + Log.v(TAG, "Got Audio Focus"); + } + } +} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java index 3b3f8828..d10cf796 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/LocalMediaPlayer.java @@ -84,6 +84,7 @@ public class LocalMediaPlayer private PositionCache positionCache; private int secondaryProgress = -1; + private final AudioFocusHandler audioFocusHandler; private final Context context; static @@ -112,8 +113,9 @@ public class LocalMediaPlayer } } - public LocalMediaPlayer(Context context) + public LocalMediaPlayer(AudioFocusHandler audioFocusHandler, Context context) { + this.audioFocusHandler = audioFocusHandler; this.context = context; } @@ -240,9 +242,9 @@ public class LocalMediaPlayer Util.unregisterMediaButtonEventReceiver(context, true); wakeLock.release(); } - catch (Throwable ignored) + catch (Throwable exception) { - Log.w(TAG, "LocalMediaPlayer onDestroy exception: ", ignored); + Log.w(TAG, "LocalMediaPlayer onDestroy exception: ", exception); } Log.i(TAG, "LocalMediaPlayer destroyed"); @@ -286,7 +288,7 @@ public class LocalMediaPlayer if (playerState == PlayerState.STARTED) { - Util.requestAudioFocus(context); + audioFocusHandler.requestAudioFocus(); } if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) 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 4de5fa45..8b4108f2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -92,10 +92,6 @@ public class Util extends DownloadActivity public static final String CM_AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged"; public static final String CM_AVRCP_METADATA_CHANGED = "com.android.music.metachanged"; - private static boolean hasFocus; - private static boolean pauseFocus; - private static boolean lowerFocus; - private static boolean mediaButtonsRegisteredForUI; private static boolean mediaButtonsRegisteredForService; @@ -1181,60 +1177,6 @@ public class Util extends DownloadActivity return size; } - public static void requestAudioFocus(final Context context) - { - if (!hasFocus) - { - final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - hasFocus = true; - audioManager.requestAudioFocus(new OnAudioFocusChangeListener() - { - @Override - public void onAudioFocusChange(int focusChange) - { - MediaPlayerController mediaPlayerController = (MediaPlayerController) context; - if ((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !mediaPlayerController.isJukeboxEnabled()) - { - if (mediaPlayerController.getPlayerState() == PlayerState.STARTED) - { - SharedPreferences preferences = getPreferences(context); - int lossPref = Integer.parseInt(preferences.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1")); - if (lossPref == 2 || (lossPref == 1 && focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)) - { - lowerFocus = true; - mediaPlayerController.setVolume(0.1f); - } - else if (lossPref == 0 || (lossPref == 1)) - { - pauseFocus = true; - mediaPlayerController.pause(); - } - } - } - else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) - { - if (pauseFocus) - { - pauseFocus = false; - mediaPlayerController.start(); - } - else if (lowerFocus) - { - lowerFocus = false; - mediaPlayerController.setVolume(1.0f); - } - } - else if (focusChange == AudioManager.AUDIOFOCUS_LOSS && !mediaPlayerController.isJukeboxEnabled()) - { - hasFocus = false; - mediaPlayerController.pause(); - audioManager.abandonAudioFocus(this); - } - } - }, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); - } - } - public static int getMinDisplayMetric(Context context) { DisplayMetrics metrics = context.getResources().getDisplayMetrics(); diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt index c740f0b4..96489a63 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt @@ -10,6 +10,7 @@ import org.moire.ultrasonic.service.LocalMediaPlayer import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerControllerImpl import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport +import org.moire.ultrasonic.service.AudioFocusHandler import org.moire.ultrasonic.util.ShufflePlayBuffer val mediaPlayerModule = module { @@ -23,7 +24,8 @@ val mediaPlayerModule = module { single { ExternalStorageMonitor(androidContext()) } single { ShufflePlayBuffer(androidContext()) } single { Downloader(androidContext(), get(), get(), get()) } - single { LocalMediaPlayer(androidContext()) } + single { LocalMediaPlayer(get(), androidContext()) } + single { AudioFocusHandler(get()) } // TODO Ideally this can be cleaned up when all circular references are removed. single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } From 4b0809f9057079fb7f4e478199bf96211a4d4b40 Mon Sep 17 00:00:00 2001 From: Nite Date: Sat, 11 Jul 2020 11:15:16 +0200 Subject: [PATCH 21/25] Fixed unnecessary starts --- .../service/MediaPlayerControllerImpl.java | 44 +++++++------------ .../service/MediaPlayerLifecycleSupport.java | 1 + 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java index f47adad9..bcc7e4bb 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java @@ -176,28 +176,6 @@ public class MediaPlayerControllerImpl implements MediaPlayerController }); } - @Override - public synchronized void seekTo(final int position) - { - MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { - @Override - public void accept(MediaPlayerService mediaPlayerService) { - mediaPlayerService.seekTo(position); - } - }); - } - - @Override - public synchronized void pause() - { - MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { - @Override - public void accept(MediaPlayerService mediaPlayerService) { - mediaPlayerService.pause(); - } - }); - } - @Override public synchronized void start() { @@ -209,15 +187,25 @@ public class MediaPlayerControllerImpl implements MediaPlayerController }); } + @Override + public synchronized void seekTo(final int position) + { + MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); + if (mediaPlayerService != null) mediaPlayerService.seekTo(position); + } + + @Override + public synchronized void pause() + { + MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); + if (mediaPlayerService != null) mediaPlayerService.pause(); + } + @Override public synchronized void stop() { - MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer() { - @Override - public void accept(MediaPlayerService mediaPlayerService) { - mediaPlayerService.stop(); - } - }); + MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); + if (mediaPlayerService != null) mediaPlayerService.stop(); } @Override diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index 95bf2a59..f8c2611d 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -269,6 +269,7 @@ public class MediaPlayerLifecycleSupport mediaPlayerController.togglePlayPause(); break; case Constants.CMD_STOP: + // TODO: There is a stop() function, shouldn't we use that? mediaPlayerController.pause(); mediaPlayerController.seekTo(0); break; From 07553464e8369c4cadee50d894596322c258a128 Mon Sep 17 00:00:00 2001 From: Nite Date: Mon, 13 Jul 2020 16:37:13 +0200 Subject: [PATCH 22/25] Fixed ktlintCheck --- .../main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt index 96489a63..8553bc2c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt @@ -2,6 +2,7 @@ package org.moire.ultrasonic.di import org.koin.android.ext.koin.androidContext import org.koin.dsl.module.module +import org.moire.ultrasonic.service.AudioFocusHandler import org.moire.ultrasonic.service.DownloadQueueSerializer import org.moire.ultrasonic.service.Downloader import org.moire.ultrasonic.service.ExternalStorageMonitor @@ -10,7 +11,6 @@ import org.moire.ultrasonic.service.LocalMediaPlayer import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerControllerImpl import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport -import org.moire.ultrasonic.service.AudioFocusHandler import org.moire.ultrasonic.util.ShufflePlayBuffer val mediaPlayerModule = module { From 5f3861b1ac31d51faf18c294cdba32ffcdecaeae Mon Sep 17 00:00:00 2001 From: Nite Date: Thu, 16 Jul 2020 14:25:58 +0200 Subject: [PATCH 23/25] Improved cold start time from Doze --- .../service/MediaPlayerControllerImpl.java | 5 + .../service/MediaPlayerLifecycleSupport.java | 118 ++++++++++-------- 2 files changed, 70 insertions(+), 53 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java index bcc7e4bb..b7e9f034 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java @@ -143,6 +143,11 @@ public class MediaPlayerControllerImpl implements MediaPlayerController } } + public synchronized void preload() + { + MediaPlayerService.getInstance(context); + } + @Override public synchronized void play(final int index) { diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index f8c2611d..7a53cbe0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -65,12 +65,17 @@ public class MediaPlayerLifecycleSupport public void onCreate() { - onCreate(false); + onCreate(false, null); } - private void onCreate(final boolean autoPlay) + private void onCreate(final boolean autoPlay, final Runnable afterCreated) { - if (created) return; + if (created) + { + if (afterCreated != null) afterCreated.run(); + return; + } + registerHeadsetReceiver(); // React to media buttons. @@ -88,6 +93,8 @@ public class MediaPlayerLifecycleSupport context.registerReceiver(intentReceiver, commandFilter); mediaPlayerController.onCreate(); + if (autoPlay) mediaPlayerController.preload(); + this.downloadQueueSerializer.deserializeDownloadQueue(new Consumer() { @Override public void accept(State state) { @@ -96,6 +103,8 @@ public class MediaPlayerLifecycleSupport // Work-around: Serialize again, as the restore() method creates a serialization without current playing info. MediaPlayerLifecycleSupport.this.downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition()); + + if (afterCreated != null) afterCreated.run(); } }); @@ -181,7 +190,7 @@ public class MediaPlayerLifecycleSupport return; } - int keyCode = event.getKeyCode(); + final int keyCode = event.getKeyCode(); boolean autoStart = (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY || keyCode == KeyEvent.KEYCODE_HEADSETHOOK || @@ -189,57 +198,60 @@ public class MediaPlayerLifecycleSupport keyCode == KeyEvent.KEYCODE_MEDIA_NEXT); // We can receive intents (e.g. MediaButton) when everything is stopped, so we need to start - if (!created) onCreate(autoStart); - - switch (keyCode) - { - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_HEADSETHOOK: - mediaPlayerController.togglePlayPause(); - break; - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - mediaPlayerController.previous(); - break; - case KeyEvent.KEYCODE_MEDIA_NEXT: - if (downloader.getCurrentPlayingIndex() < downloader.downloadList.size() - 1) + onCreate(autoStart, new Runnable() { + @Override + public void run() { + switch (keyCode) { - mediaPlayerController.next(); + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_HEADSETHOOK: + mediaPlayerController.togglePlayPause(); + break; + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + mediaPlayerController.previous(); + break; + case KeyEvent.KEYCODE_MEDIA_NEXT: + if (downloader.getCurrentPlayingIndex() < downloader.downloadList.size() - 1) + { + mediaPlayerController.next(); + } + break; + case KeyEvent.KEYCODE_MEDIA_STOP: + mediaPlayerController.stop(); + break; + case KeyEvent.KEYCODE_MEDIA_PLAY: + if (mediaPlayerController.getPlayerState() == PlayerState.IDLE) + { + mediaPlayerController.play(); + } + else if (mediaPlayerController.getPlayerState() != PlayerState.STARTED) + { + mediaPlayerController.start(); + } + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + mediaPlayerController.pause(); + break; + case KeyEvent.KEYCODE_1: + mediaPlayerController.setSongRating(1); + break; + case KeyEvent.KEYCODE_2: + mediaPlayerController.setSongRating(2); + break; + case KeyEvent.KEYCODE_3: + mediaPlayerController.setSongRating(3); + break; + case KeyEvent.KEYCODE_4: + mediaPlayerController.setSongRating(4); + break; + case KeyEvent.KEYCODE_5: + mediaPlayerController.setSongRating(5); + break; + default: + break; } - break; - case KeyEvent.KEYCODE_MEDIA_STOP: - mediaPlayerController.stop(); - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - if (mediaPlayerController.getPlayerState() == PlayerState.IDLE) - { - mediaPlayerController.play(); - } - else if (mediaPlayerController.getPlayerState() != PlayerState.STARTED) - { - mediaPlayerController.start(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - mediaPlayerController.pause(); - break; - case KeyEvent.KEYCODE_1: - mediaPlayerController.setSongRating(1); - break; - case KeyEvent.KEYCODE_2: - mediaPlayerController.setSongRating(2); - break; - case KeyEvent.KEYCODE_3: - mediaPlayerController.setSongRating(3); - break; - case KeyEvent.KEYCODE_4: - mediaPlayerController.setSongRating(4); - break; - case KeyEvent.KEYCODE_5: - mediaPlayerController.setSongRating(5); - break; - default: - break; - } + } + }); } /** From 720f43c8d95ed490b0f02f9e55f9e627070d863b Mon Sep 17 00:00:00 2001 From: Nite Date: Thu, 30 Jul 2020 11:59:22 +0200 Subject: [PATCH 24/25] Fixed unintentional replaces --- .../java/org/moire/ultrasonic/service/MediaPlayerService.java | 2 +- .../main/java/org/moire/ultrasonic/util/VideoPlayerType.java | 2 +- ultrasonic/src/main/res/values-pt-rBR/strings.xml | 2 +- ultrasonic/src/main/res/values-pt/strings.xml | 2 +- ultrasonic/src/main/res/values/strings.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java index c389f978..7c50d0e6 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerService.java @@ -492,7 +492,7 @@ public class MediaPlayerService extends Service if (show) { - // Only update notification if localMediaPlayer state is one that will change the icon + // Only update notification if player state is one that will change the icon if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) { updateNotification(playerState, currentPlaying); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java index c000f3f8..a03036a4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/VideoPlayerType.java @@ -41,7 +41,7 @@ public enum VideoPlayerType public void playVideo(final Activity activity, MusicDirectory.Entry entry) throws Exception { - // Check if MX LocalMediaPlayer is installed. + // Check if MX Player is installed. boolean installedAd = Util.isPackageInstalled(activity, PACKAGE_NAME_MX_AD); boolean installedPro = Util.isPackageInstalled(activity, PACKAGE_NAME_MX_PRO); diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index 3b25b2ff..cc7966af 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -334,7 +334,7 @@ 0.00 MB -:-- 0:00 - O localMediaPlayer MX não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo. + O Player MX não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo. Baixar Player MX Toque para selecionar a música Cartão SD indisponível diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 996d9fec..9983e536 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -334,7 +334,7 @@ 0.00 MB —:—— 0:00 - O localMediaPlayer MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo. + O Player MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo. Descarregar Player MX Toque para selecionar a música Cartão SD indisponível diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index 64fa495e..4a2b453e 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -314,7 +314,7 @@ Browse Using ID3 Tags Use ID3 tag methods instead of file system based methods Video - Video localMediaPlayer + Video player View Refresh .5 seconds 1 second From 3f3515536c64c5494ac1155d31011a727ee90508 Mon Sep 17 00:00:00 2001 From: Nite Date: Fri, 31 Jul 2020 11:22:01 +0200 Subject: [PATCH 25/25] Fixed portuguese translation of "MX Player" --- ultrasonic/src/main/res/values-pt-rBR/strings.xml | 6 +++--- ultrasonic/src/main/res/values-pt/strings.xml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index cc7966af..5c5b9611 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -334,8 +334,8 @@ 0.00 MB -:-- 0:00 - O Player MX não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo. - Baixar Player MX + O MX Player não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo. + Baixar MX Player Toque para selecionar a música Cartão SD indisponível Sem cartão SD @@ -371,7 +371,7 @@ Saudação Padrão Confira esta música que compartilhei do %s Compartilhar músicas via - Player MX + MX Player Padrão Flash Compartilhar diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 9983e536..719cc782 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -334,8 +334,8 @@ 0.00 MB —:—— 0:00 - O Player MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo. - Descarregar Player MX + O MX Player não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo. + Descarregar MX Player Toque para selecionar a música Cartão SD indisponível Sem cartão SD @@ -371,7 +371,7 @@ Saudação Padrão Confira esta música que compartilhei do %s Compartilhar músicas via - Player MX + MX Player Padrão Flash Compartilhar