Started to use Koin, refactored lifecycleSupport and Intent handling

This commit is contained in:
Nite 2020-06-22 18:35:58 +02:00
parent 13b987791e
commit 53628dde54
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
25 changed files with 651 additions and 608 deletions

View File

@ -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> 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();

View File

@ -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<DownloadServiceLifecycleSupport> 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();
}

View File

@ -126,7 +126,7 @@ public class SelectPlaylistActivity extends SubsonicTabActivity implements Adapt
List<Playlist> 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;
}

View File

@ -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> downloadServiceImpl = inject(DownloadServiceImpl.class);
private Lazy<DownloadServiceLifecycleSupport> 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()

View File

@ -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> 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();
}
}

View File

@ -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);
}
}

View File

@ -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> 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);

View File

@ -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));
}
}
}

View File

@ -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<DownloadServiceLifecycleSupport> 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.

View File

@ -0,0 +1,6 @@
package org.moire.ultrasonic.service;
public abstract class Consumer<T>
{
public abstract void accept(T t);
}

View File

@ -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);
}

View File

@ -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<DownloadFile> songs, int currentPlayingIndex, int currentPlayingPosition)
{
if (!setup.get())
{
return;
}
new SerializeTask().execute(songs, currentPlayingIndex, currentPlayingPosition);
}
public void serializeDownloadQueueNow(Iterable<DownloadFile> 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<State> afterDeserialized)
{
new DeserializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, afterDeserialized);
}
public void deserializeDownloadQueueNow(Consumer<State> 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<Object, Void, Void>
{
@Override
protected Void doInBackground(Object... params)
{
if (lock.tryLock())
{
try
{
Thread.currentThread().setName("SerializeTask");
serializeDownloadQueueNow((Iterable<DownloadFile>)params[0], (int)params[1], (int)params[2]);
}
finally
{
lock.unlock();
}
}
return null;
}
}
private class DeserializeTask extends AsyncTask<Object, Void, Void>
{
@Override
protected Void doInBackground(Object... params)
{
try
{
Thread.currentThread().setName("DeserializeTask");
lock.lock();
deserializeDownloadQueueNow((Consumer<State>)params[0]);
setup.set(true);
}
finally
{
lock.unlock();
}
return null;
}
}
}

View File

@ -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<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LRUCache<>(100);
private DownloadServiceLifecycleSupport lifecycleSupport;
private final LRUCache<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LRUCache<MusicDirectory.Entry, DownloadFile>(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> jukeboxService = inject(JukeboxService.class);
private Lazy<DownloadQueueSerializer> downloadQueueSerializer = inject(DownloadQueueSerializer.class);
private Lazy<ExternalStorageMonitor> 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<MediaPlayerService> 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<MusicDirectory.Entry> 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<MediaPlayerService>() {
@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<MediaPlayerService>() {
@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<MediaPlayerService>() {
@Override
public void accept(MediaPlayerService mediaPlayerService) {
mediaPlayerService.play(index, true);
}
});
}
public synchronized void play()
{
executeOnStartedMediaPlayerService(new Consumer<MediaPlayerService>() {
@Override
public void accept(MediaPlayerService mediaPlayerService) {
mediaPlayerService.play();
}
});
}
@Override
public synchronized void togglePlayPause()
{
if (playerState == PlayerState.IDLE) autoPlayStart = true;
executeOnStartedMediaPlayerService(new Consumer<MediaPlayerService>() {
@Override
public void accept(MediaPlayerService mediaPlayerService) {
mediaPlayerService.togglePlayPause();
}
});
}
@Override
public synchronized void seekTo(final int position)
{
executeOnStartedMediaPlayerService(new Consumer<MediaPlayerService>() {
@Override
public void accept(MediaPlayerService mediaPlayerService) {
mediaPlayerService.seekTo(position);
}
});
}
@Override
public synchronized void pause()
{
executeOnStartedMediaPlayerService(new Consumer<MediaPlayerService>() {
@Override
public void accept(MediaPlayerService mediaPlayerService) {
mediaPlayerService.pause();
}
});
}
@Override
public synchronized void start()
{
executeOnStartedMediaPlayerService(new Consumer<MediaPlayerService>() {
@Override
public void accept(MediaPlayerService mediaPlayerService) {
mediaPlayerService.start();
}
});
}
@Override
public synchronized void stop()
{
executeOnStartedMediaPlayerService(new Consumer<MediaPlayerService>() {
@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<MusicDirectory.Entry> 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<DownloadFile> getSongs()
{
return MediaPlayerService.downloadList;
}
public List<DownloadFile> getSongs() { return MediaPlayerService.downloadList; }
@Override
public long getDownloadListDuration()
@ -495,7 +588,7 @@ public class DownloadServiceImpl implements DownloadService
@Override
public synchronized List<DownloadFile> getDownloads()
{
List<DownloadFile> temp = new ArrayList<DownloadFile>();
List<DownloadFile> 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);
}
}

View File

@ -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> 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<State>() {
@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<DownloadFile> songs = new ArrayList<DownloadFile>(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<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>();
private int currentPlayingIndex;
private int currentPlayingPosition;
}
private class SerializeTask extends AsyncTask<Void, Void, Void>
{
@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<Void, Void, Void>
{
@Override
protected Void doInBackground(Void... params)
{
try
{
Thread.currentThread().setName("DeserializeTask");
lock.lock();
deserializeDownloadQueueNow();
setup.set(true);
}
finally
{
lock.unlock();
}
return null;
}
}
};
}

View File

@ -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; }
}

View File

@ -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> 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<String> ids = new ArrayList<String>();
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

View File

@ -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> jukeboxService = inject(JukeboxService.class);
private Lazy<DownloadQueueSerializer> downloadQueueSerializer = inject(DownloadQueueSerializer.class);
private Lazy<ExternalStorageMonitor> 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<DownloadFile> iterator = cleanupCandidates.iterator();

View File

@ -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

View File

@ -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<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>();
public int currentPlayingIndex;
public int currentPlayingPosition;
}

View File

@ -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> 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<File> filesToNotDelete = new HashSet<File>(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");

View File

@ -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";

View File

@ -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);
}

View File

@ -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> 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)
{

View File

@ -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> 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;
}

View File

@ -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())}
}