mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-17 04:00:39 +01:00
Merge branch 'nitehu-refactor/downloadservice' into develop
This commit is contained in:
commit
e7d4ca5331
@ -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();
|
||||
|
||||
|
@ -960,7 +960,7 @@
|
||||
|
||||
<issue
|
||||
id="StaticFieldLeak"
|
||||
message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.service.DownloadServiceLifecycleSupport.SerializeTask)"
|
||||
message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.service.MediaPlayerLifecycleSupport.SerializeTask)"
|
||||
errorLine1=" private class SerializeTask extends AsyncTask<Void, Void, Void>"
|
||||
errorLine2=" ~~~~~~~~~~~~~">
|
||||
<location
|
||||
@ -971,7 +971,7 @@
|
||||
|
||||
<issue
|
||||
id="StaticFieldLeak"
|
||||
message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.service.DownloadServiceLifecycleSupport.DeserializeTask)"
|
||||
message="This AsyncTask class should be static or leaks might occur (org.moire.ultrasonic.service.MediaPlayerLifecycleSupport.DeserializeTask)"
|
||||
errorLine1=" private class DeserializeTask extends AsyncTask<Void, Void, Void>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
|
@ -118,7 +118,7 @@
|
||||
<activity android:name=".activity.ServerSettingsActivity" />
|
||||
|
||||
<service
|
||||
android:name=".service.DownloadServiceImpl"
|
||||
android:name=".service.MediaPlayerService"
|
||||
android:label="UltraSonic Download Service"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
|
@ -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;
|
||||
@ -206,8 +207,8 @@ public class BookmarkActivity extends SubsonicTabActivity
|
||||
if (!getSelectedSongs(albumListView).isEmpty())
|
||||
{
|
||||
int position = songs.get(0).getBookmarkPosition();
|
||||
if (getDownloadService() == null) return;
|
||||
getDownloadService().restore(songs, 0, position, true, true);
|
||||
if (getMediaPlayerController() == null) return;
|
||||
getMediaPlayerController().restore(songs, 0, position, true, true);
|
||||
selectAll(false, false);
|
||||
}
|
||||
}
|
||||
@ -296,7 +297,8 @@ public class BookmarkActivity extends SubsonicTabActivity
|
||||
|
||||
private void enableButtons()
|
||||
{
|
||||
if (getDownloadService() == null)
|
||||
MediaPlayerController mediaPlayerController = getMediaPlayerController();
|
||||
if (mediaPlayerController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -310,7 +312,7 @@ public class BookmarkActivity extends SubsonicTabActivity
|
||||
|
||||
for (MusicDirectory.Entry song : selection)
|
||||
{
|
||||
DownloadFile downloadFile = getDownloadService().forSong(song);
|
||||
DownloadFile downloadFile = mediaPlayerController.getDownloadFileForSong(song);
|
||||
if (downloadFile.isWorkDone())
|
||||
{
|
||||
deleteEnabled = true;
|
||||
@ -345,7 +347,7 @@ public class BookmarkActivity extends SubsonicTabActivity
|
||||
|
||||
private void downloadBackground(final boolean save, final List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
if (getDownloadService() == null)
|
||||
if (getMediaPlayerController() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -356,7 +358,7 @@ public class BookmarkActivity extends SubsonicTabActivity
|
||||
public void run()
|
||||
{
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
getDownloadService().downloadBackground(songs, save);
|
||||
getMediaPlayerController().downloadBackground(songs, save);
|
||||
|
||||
if (save)
|
||||
{
|
||||
@ -382,19 +384,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<MusicDirectory.Entry> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
@ -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()
|
||||
{
|
||||
@ -257,7 +257,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
getDownloadService().previous();
|
||||
getMediaPlayerController().previous();
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -293,9 +293,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
@Override
|
||||
protected Boolean doInBackground() throws Throwable
|
||||
{
|
||||
if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1)
|
||||
if (getMediaPlayerController().getCurrentPlayingNumberOnPlaylist() < getMediaPlayerController().getPlaylistSize() - 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 || downloadService.getCurrentPlaying() == null)
|
||||
if (mediaPlayerController == null || mediaPlayerController.getCurrentPlaying() == 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;
|
||||
}
|
||||
@ -632,7 +632,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
}
|
||||
}
|
||||
|
||||
final DownloadFile currentDownloading = getDownloadService().getCurrentDownloading();
|
||||
final DownloadFile currentDownloading = getMediaPlayerController().getCurrentDownloading();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (currentDownloading == playlistView.getItemAtPosition(i))
|
||||
@ -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 = downloadService.getCurrentPlaying();
|
||||
DownloadFile downloadFile = mediaPlayerController.getCurrentPlaying();
|
||||
|
||||
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,24 +1002,24 @@ 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:
|
||||
if (!getDownloadService().getSongs().isEmpty())
|
||||
if (getMediaPlayerController().getPlaylistSize() > 0)
|
||||
{
|
||||
showDialog(DIALOG_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,12 +1137,12 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
|
||||
return true;
|
||||
case R.id.menu_item_share:
|
||||
DownloadService downloadService = getDownloadService();
|
||||
MediaPlayerController mediaPlayerController = getMediaPlayerController();
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
if (downloadService != null)
|
||||
if (mediaPlayerController != null)
|
||||
{
|
||||
List<DownloadFile> downloadServiceSongs = downloadService.getSongs();
|
||||
List<DownloadFile> downloadServiceSongs = mediaPlayerController.getPlayList();
|
||||
|
||||
if (downloadServiceSongs != null)
|
||||
{
|
||||
@ -1170,17 +1170,18 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
|
||||
private void update()
|
||||
{
|
||||
if (getDownloadService() == null)
|
||||
MediaPlayerController mediaPlayerController = getMediaPlayerController();
|
||||
if (mediaPlayerController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentRevision != getDownloadService().getDownloadListUpdateRevision())
|
||||
if (currentRevision != mediaPlayerController.getPlayListUpdateRevision())
|
||||
{
|
||||
onDownloadListChanged();
|
||||
}
|
||||
|
||||
if (currentPlaying != getDownloadService().getCurrentPlaying())
|
||||
if (currentPlaying != mediaPlayerController.getCurrentPlaying())
|
||||
{
|
||||
onCurrentChanged();
|
||||
}
|
||||
@ -1192,14 +1193,14 @@ 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<Void>(this)
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground() throws Throwable
|
||||
{
|
||||
final List<MusicDirectory.Entry> entries = new LinkedList<MusicDirectory.Entry>();
|
||||
for (final DownloadFile downloadFile : getDownloadService().getSongs())
|
||||
for (final DownloadFile downloadFile : getMediaPlayerController().getPlayList())
|
||||
{
|
||||
entries.add(downloadFile.getSong());
|
||||
}
|
||||
@ -1243,7 +1244,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)
|
||||
@ -1254,7 +1255,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
{
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
|
||||
final int current = service.getCurrentPlayingIndex();
|
||||
final int current = getMediaPlayerController().getCurrentPlayingNumberOnPlaylist();
|
||||
|
||||
if (current == -1)
|
||||
{
|
||||
@ -1269,13 +1270,13 @@ 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;
|
||||
}
|
||||
|
||||
final List<DownloadFile> list = downloadService.getSongs();
|
||||
final List<DownloadFile> list = mediaPlayerController.getPlayList();
|
||||
|
||||
emptyTextView.setText(R.string.download_empty);
|
||||
final SongListAdapter adapter = new SongListAdapter(this, list);
|
||||
@ -1306,18 +1307,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 = downloadService.getCurrentPlaying();
|
||||
DownloadFile currentPlaying = mediaPlayerController.getCurrentPlaying();
|
||||
|
||||
if (currentPlaying == item)
|
||||
{
|
||||
getDownloadService().next();
|
||||
getMediaPlayerController().next();
|
||||
}
|
||||
|
||||
adapter.remove(item);
|
||||
@ -1333,9 +1334,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
});
|
||||
|
||||
emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
currentRevision = downloadService.getDownloadListUpdateRevision();
|
||||
currentRevision = mediaPlayerController.getPlayListUpdateRevision();
|
||||
|
||||
switch (downloadService.getRepeatMode())
|
||||
switch (mediaPlayerController.getRepeatMode())
|
||||
{
|
||||
case OFF:
|
||||
repeatButton.setImageDrawable(Util.getDrawableFromAttribute(this, R.attr.media_repeat_off));
|
||||
@ -1353,20 +1354,20 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
|
||||
private void onCurrentChanged()
|
||||
{
|
||||
DownloadService downloadService = getDownloadService();
|
||||
MediaPlayerController mediaPlayerController = getMediaPlayerController();
|
||||
|
||||
if (downloadService == null)
|
||||
if (mediaPlayerController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currentPlaying = downloadService.getCurrentPlaying();
|
||||
currentPlaying = mediaPlayerController.getCurrentPlaying();
|
||||
|
||||
scrollToCurrent();
|
||||
|
||||
long totalDuration = downloadService.getDownloadListDuration();
|
||||
long totalSongs = downloadService.getSongs().size();
|
||||
int currentSongIndex = downloadService.getCurrentPlayingIndex() + 1;
|
||||
long totalDuration = mediaPlayerController.getPlayListDuration();
|
||||
long totalSongs = mediaPlayerController.getPlaylistSize();
|
||||
int currentSongIndex = mediaPlayerController.getCurrentPlayingNumberOnPlaylist() + 1;
|
||||
|
||||
String duration = Util.formatTotalDuration(totalDuration);
|
||||
|
||||
@ -1398,16 +1399,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<Void>(this)
|
||||
{
|
||||
DownloadService downloadService;
|
||||
MediaPlayerController mediaPlayerController;
|
||||
boolean isJukeboxEnabled;
|
||||
int millisPlayed;
|
||||
Integer duration;
|
||||
@ -1416,11 +1417,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 +1458,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 +1504,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 +1515,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 +1530,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 +1563,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;
|
||||
}
|
||||
@ -1580,9 +1581,9 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
if (e1X - e2X > swipeDistance && absX > swipeVelocity)
|
||||
{
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1)
|
||||
if (mediaPlayerController.getCurrentPlayingNumberOnPlaylist() < mediaPlayerController.getPlaylistSize() - 1)
|
||||
{
|
||||
downloadService.next();
|
||||
mediaPlayerController.next();
|
||||
onCurrentChanged();
|
||||
onSliderProgressChanged();
|
||||
}
|
||||
@ -1593,7 +1594,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 +1604,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 +1613,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 +1664,6 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||
return;
|
||||
|
||||
displaySongRating();
|
||||
getDownloadService().setSongRating(rating);
|
||||
getMediaPlayerController().setSongRating(rating);
|
||||
}
|
||||
}
|
||||
|
@ -32,12 +32,15 @@ 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;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static org.koin.java.standalone.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Equalizer controls.
|
||||
*
|
||||
@ -52,6 +55,8 @@ public class EqualizerActivity extends ResultActivity
|
||||
private EqualizerController equalizerController;
|
||||
private Equalizer equalizer;
|
||||
|
||||
private Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle)
|
||||
{
|
||||
@ -123,14 +128,7 @@ public class EqualizerActivity extends ResultActivity
|
||||
|
||||
private void setup()
|
||||
{
|
||||
DownloadService instance = DownloadServiceImpl.getInstance();
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
equalizerController = instance.getEqualizerController();
|
||||
equalizerController = mediaPlayerControllerLazy.getValue().getEqualizerController();
|
||||
equalizer = equalizerController.getEqualizer();
|
||||
|
||||
initEqualizer();
|
||||
|
@ -34,8 +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.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;
|
||||
@ -46,7 +46,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
|
||||
{
|
||||
@ -67,6 +70,8 @@ public class MainActivity extends SubsonicTabActivity
|
||||
private static boolean infoDialogDisplayed;
|
||||
private static boolean shouldUseId3;
|
||||
|
||||
private Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
|
||||
|
||||
/**
|
||||
* Called when the activity is first created.
|
||||
*/
|
||||
@ -79,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)
|
||||
@ -456,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)
|
||||
{
|
||||
@ -476,8 +481,8 @@ public class MainActivity extends SubsonicTabActivity
|
||||
|
||||
private void exit()
|
||||
{
|
||||
stopService(new Intent(this, DownloadServiceImpl.class));
|
||||
Util.unregisterMediaButtonEventReceiver(this);
|
||||
lifecycleSupport.getValue().onDestroy();
|
||||
Util.unregisterMediaButtonEventReceiver(this, false);
|
||||
finish();
|
||||
}
|
||||
|
||||
|
@ -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<MusicDirectory.Entry> 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(downloadService.size() - 1);
|
||||
mediaPlayerController.play(mediaPlayerController.getPlaylistSize() - 1);
|
||||
}
|
||||
|
||||
Util.toast(SearchActivity.this, getResources().getQuantityString(R.plurals.select_album_n_songs_added, 1, 1));
|
||||
|
@ -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 (getDownloadService() == null)
|
||||
MediaPlayerController mediaPlayerController = getMediaPlayerController();
|
||||
if (mediaPlayerController == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -1024,7 +1026,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity
|
||||
|
||||
for (MusicDirectory.Entry song : selection)
|
||||
{
|
||||
DownloadFile downloadFile = getDownloadService().forSong(song);
|
||||
DownloadFile downloadFile = mediaPlayerController.getDownloadFileForSong(song);
|
||||
if (downloadFile.isWorkDone())
|
||||
{
|
||||
deleteEnabled = true;
|
||||
@ -1061,7 +1063,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity
|
||||
|
||||
private void downloadBackground(final boolean save, final List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
if (getDownloadService() == null)
|
||||
if (getMediaPlayerController() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -1072,7 +1074,7 @@ public class SelectAlbumActivity extends SubsonicTabActivity
|
||||
public void run()
|
||||
{
|
||||
warnIfNetworkOrStorageUnavailable();
|
||||
getDownloadService().downloadBackground(songs, save);
|
||||
getMediaPlayerController().downloadBackground(songs, save);
|
||||
|
||||
if (save)
|
||||
{
|
||||
@ -1098,19 +1100,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<MusicDirectory.Entry> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
private Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
|
||||
|
||||
public MenuDrawer menuDrawer;
|
||||
private int activePosition = 1;
|
||||
private int menuActiveViewId;
|
||||
@ -97,8 +102,6 @@ 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));
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
|
||||
if (bundle != null)
|
||||
@ -155,7 +158,9 @@ 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();
|
||||
|
||||
// Make sure to update theme
|
||||
if (theme != null && !theme.equals(Util.getTheme(this)))
|
||||
@ -190,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;
|
||||
@ -203,11 +208,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;
|
||||
}
|
||||
|
||||
@ -257,27 +262,22 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
|
||||
|
||||
if (nowPlayingView != null)
|
||||
{
|
||||
final DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||
PlayerState playerState = mediaPlayerControllerLazy.getValue().getPlayerState();
|
||||
|
||||
if (downloadService != null)
|
||||
if (playerState.equals(PlayerState.PAUSED) || playerState.equals(PlayerState.STARTED))
|
||||
{
|
||||
PlayerState playerState = downloadService.getPlayerState();
|
||||
DownloadFile file = mediaPlayerControllerLazy.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, mediaPlayerControllerLazy.getValue(), song, playerState);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hideNowPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -306,9 +306,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;
|
||||
}
|
||||
@ -387,7 +387,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()
|
||||
@ -403,7 +403,7 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
|
||||
@Override
|
||||
public void onClick(View view)
|
||||
{
|
||||
downloadService.togglePlayPause();
|
||||
mediaPlayerController.togglePlayPause();
|
||||
}
|
||||
});
|
||||
|
||||
@ -762,33 +762,9 @@ public class SubsonicTabActivity extends ResultActivity implements OnClickListen
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadService getDownloadService()
|
||||
public MediaPlayerController getMediaPlayerController()
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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 DownloadServiceImpl.getInstance();
|
||||
return mediaPlayerControllerLazy.getValue();
|
||||
}
|
||||
|
||||
protected void warnIfNetworkOrStorageUnavailable()
|
||||
@ -839,7 +815,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<Entry> songs)
|
||||
{
|
||||
if (getDownloadService() == null)
|
||||
if (getMediaPlayerController() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -851,16 +827,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)
|
||||
@ -1015,23 +991,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);
|
||||
@ -1042,11 +1018,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1374,15 +1350,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
|
||||
@ -1409,12 +1385,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;
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,15 @@ 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;
|
||||
|
||||
import kotlin.Lazy;
|
||||
|
||||
import static org.koin.java.standalone.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* Shows main app settings.
|
||||
*/
|
||||
@ -62,6 +65,8 @@ public class SettingsFragment extends PreferenceFragment
|
||||
private SharedPreferences settings;
|
||||
private int activeServers;
|
||||
|
||||
private Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@ -370,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -401,7 +406,6 @@ public class SettingsFragment extends PreferenceFragment
|
||||
}
|
||||
|
||||
// Clear download queue.
|
||||
DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||
downloadService.clear();
|
||||
mediaPlayerControllerLazy.getValue().clear();
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,9 @@ 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.DownloadServiceImpl;
|
||||
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FileUtil;
|
||||
|
||||
public class UltraSonicAppWidgetProvider extends AppWidgetProvider
|
||||
@ -67,13 +68,13 @@ 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, 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);
|
||||
}
|
||||
@ -194,27 +194,30 @@ 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, DownloadServiceImpl.class));
|
||||
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
|
||||
//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.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, DownloadServiceImpl.class));
|
||||
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
|
||||
//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.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, DownloadServiceImpl.class));
|
||||
intent = new Intent(Constants.CMD_PROCESS_KEYCODE);
|
||||
//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.getService(context, 0, intent, 0);
|
||||
pendingIntent = PendingIntent.getBroadcast(context, 13, intent, 0);
|
||||
views.setOnClickPendingIntent(R.id.control_previous, pendingIntent);
|
||||
}
|
||||
}
|
||||
|
@ -5,31 +5,26 @@ 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.MediaPlayerController;
|
||||
|
||||
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<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent)
|
||||
{
|
||||
|
||||
DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||
|
||||
if (downloadService == null)
|
||||
if (mediaPlayerControllerLazy.getValue().getCurrentPlaying() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (downloadService.getCurrentPlaying() == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Entry song = downloadService.getCurrentPlaying().getSong();
|
||||
Entry song = mediaPlayerControllerLazy.getValue().getCurrentPlaying().getSong();
|
||||
|
||||
if (song == null)
|
||||
{
|
||||
@ -39,8 +34,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();
|
||||
int playerPosition = mediaPlayerControllerLazy.getValue().getPlayerPosition();
|
||||
int listSize = mediaPlayerControllerLazy.getValue().getPlaylistSize();
|
||||
|
||||
if (duration != null)
|
||||
{
|
||||
@ -50,17 +45,13 @@ public class A2dpIntentReceiver extends BroadcastReceiver
|
||||
avrcpIntent.putExtra("position", (long) playerPosition);
|
||||
avrcpIntent.putExtra("ListSize", (long) listSize);
|
||||
|
||||
switch (downloadService.getPlayerState())
|
||||
switch (mediaPlayerControllerLazy.getValue().getPlayerState())
|
||||
{
|
||||
case STARTED:
|
||||
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;
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
@ -65,13 +65,13 @@ 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)
|
||||
{
|
||||
Log.i(TAG, "Disconnected from Bluetooth device, requesting pause.");
|
||||
context.sendBroadcast(new Intent(DownloadServiceImpl.CMD_PAUSE));
|
||||
context.sendBroadcast(new Intent(Constants.CMD_PAUSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,82 +21,63 @@ 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 android.view.KeyEvent;
|
||||
|
||||
import org.moire.ultrasonic.service.DownloadServiceImpl;
|
||||
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport;
|
||||
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<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
|
||||
|
||||
@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();
|
||||
|
||||
if (extras == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
try
|
||||
{
|
||||
Intent serviceIntent = new Intent(Constants.CMD_PROCESS_KEYCODE);
|
||||
serviceIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
|
||||
lifecycleSupport.getValue().receiveIntent(serviceIntent);
|
||||
|
||||
try
|
||||
if (isOrderedBroadcast())
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (isOrderedBroadcast())
|
||||
{
|
||||
abortBroadcast();
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
// Ignored.
|
||||
abortBroadcast();
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
// Ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<MediaPlayerController> 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");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
/**
|
||||
* Abstract class for consumers with two parameters
|
||||
* @param <T> The type of the first object to consume
|
||||
* @param <U> The type of the second object to consume
|
||||
*/
|
||||
public abstract class BiConsumer<T, U>
|
||||
{
|
||||
public abstract void accept(T t, U u);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
/**
|
||||
* Abstract class for consumers with one parameter
|
||||
* @param <T> The type of the object to consume
|
||||
*/
|
||||
public abstract class Consumer<T>
|
||||
{
|
||||
public abstract void accept(T t);
|
||||
}
|
@ -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;
|
||||
@ -37,11 +38,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
|
||||
@ -49,7 +52,6 @@ import static android.os.PowerManager.SCREEN_DIM_WAKE_LOCK;
|
||||
*/
|
||||
public class DownloadFile
|
||||
{
|
||||
|
||||
private static final String TAG = DownloadFile.class.getSimpleName();
|
||||
private final Context context;
|
||||
private final MusicDirectory.Entry song;
|
||||
@ -66,6 +68,8 @@ public class DownloadFile
|
||||
private volatile boolean saveWhenDone;
|
||||
private volatile boolean completeWhenDone;
|
||||
|
||||
private Lazy<Downloader> downloader = inject(Downloader.class);
|
||||
|
||||
public DownloadFile(Context context, MusicDirectory.Entry song, boolean save)
|
||||
{
|
||||
super();
|
||||
@ -282,6 +286,7 @@ public class DownloadFile
|
||||
this.isPlaying = isPlaying;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
@ -304,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));
|
||||
}
|
||||
|
||||
@ -439,15 +444,13 @@ public class DownloadFile
|
||||
wifiLock.release();
|
||||
}
|
||||
|
||||
new CacheCleaner(context, DownloadServiceImpl.getInstance()).cleanSpace();
|
||||
new CacheCleaner(context).cleanSpace();
|
||||
|
||||
if (DownloadServiceImpl.getInstance() != null)
|
||||
{
|
||||
((DownloadServiceImpl) DownloadServiceImpl.getInstance()).checkDownloads();
|
||||
}
|
||||
downloader.getValue().checkDownloads();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
@ -0,0 +1,110 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,441 +0,0 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* @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 BroadcastReceiver headsetEventReceiver;
|
||||
private BroadcastReceiver ejectEventReceiver;
|
||||
private PhoneStateListener phoneStateListener;
|
||||
private boolean externalStorageAvailable = true;
|
||||
private Lock lock = new ReentrantLock();
|
||||
private final AtomicBoolean setup = new AtomicBoolean(false);
|
||||
|
||||
/**
|
||||
* 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(DownloadServiceImpl downloadService)
|
||||
{
|
||||
this.downloadService = downloadService;
|
||||
}
|
||||
|
||||
public void onCreate()
|
||||
{
|
||||
Runnable downloadChecker = new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
downloadService.checkDownloads();
|
||||
}
|
||||
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");
|
||||
downloadService.registerReceiver(ejectEventReceiver, ejectFilter);
|
||||
|
||||
// React to media buttons.
|
||||
Util.registerMediaButtonEventReceiver(downloadService);
|
||||
|
||||
// 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);
|
||||
downloadService.registerReceiver(intentReceiver, commandFilter);
|
||||
|
||||
int instance = Util.getActiveServer(downloadService);
|
||||
downloadService.setJukeboxEnabled(Util.getJukeboxEnabled(downloadService, instance));
|
||||
|
||||
deserializeDownloadQueue();
|
||||
|
||||
new CacheCleaner(downloadService, downloadService).clean();
|
||||
}
|
||||
|
||||
private void registerHeadsetReceiver() {
|
||||
// Pause when headset is unplugged.
|
||||
final SharedPreferences sp = Util.getPreferences(downloadService);
|
||||
final String spKey = downloadService
|
||||
.getString(R.string.settings_playback_resume_play_on_headphones_plug);
|
||||
|
||||
headsetEventReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final Bundle extras = intent.getExtras();
|
||||
|
||||
if (extras == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
} else if (state == 1) {
|
||||
if (!downloadService.isJukeboxEnabled() &&
|
||||
sp.getBoolean(spKey, false) &&
|
||||
downloadService.getPlayerState() == PlayerState.PAUSED) {
|
||||
downloadService.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);
|
||||
downloadService.registerReceiver(headsetEventReceiver, headsetIntentFilter);
|
||||
}
|
||||
|
||||
public void onStart(Intent intent)
|
||||
{
|
||||
if (intent != null && intent.getExtras() != null)
|
||||
{
|
||||
KeyEvent event = (KeyEvent) intent.getExtras().get(Intent.EXTRA_KEY_EVENT);
|
||||
if (event != null)
|
||||
{
|
||||
handleKeyEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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(downloadService, state, FILENAME_DOWNLOADS_SER);
|
||||
}
|
||||
|
||||
private void deserializeDownloadQueue()
|
||||
{
|
||||
new DeserializeTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private void deserializeDownloadQueueNow()
|
||||
{
|
||||
State state = FileUtil.deserialize(downloadService, 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)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getKeyCode())
|
||||
{
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
|
||||
case KeyEvent.KEYCODE_HEADSETHOOK:
|
||||
downloadService.togglePlayPause();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
|
||||
downloadService.previous();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_NEXT:
|
||||
if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1)
|
||||
{
|
||||
downloadService.next();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_STOP:
|
||||
downloadService.stop();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PLAY:
|
||||
if (downloadService.getPlayerState() == PlayerState.IDLE)
|
||||
{
|
||||
downloadService.play();
|
||||
}
|
||||
else if (downloadService.getPlayerState() != PlayerState.STARTED)
|
||||
{
|
||||
downloadService.start();
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_MEDIA_PAUSE:
|
||||
downloadService.pause();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_1:
|
||||
downloadService.setSongRating(1);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_2:
|
||||
downloadService.setSongRating(2);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_3:
|
||||
downloadService.setSongRating(3);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_4:
|
||||
downloadService.setSongRating(4);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_5:
|
||||
downloadService.setSongRating(5);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logic taken from packages/apps/Music. Will pause when an incoming
|
||||
* call rings or if a call (incoming or outgoing) is connected.
|
||||
*/
|
||||
private class MyPhoneStateListener extends PhoneStateListener
|
||||
{
|
||||
private boolean resumeAfterCall;
|
||||
|
||||
@Override
|
||||
public void onCallStateChanged(int state, String incomingNumber)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case TelephonyManager.CALL_STATE_RINGING:
|
||||
case TelephonyManager.CALL_STATE_OFFHOOK:
|
||||
if (downloadService.getPlayerState() == PlayerState.STARTED && !downloadService.isJukeboxEnabled())
|
||||
{
|
||||
resumeAfterCall = true;
|
||||
downloadService.pause();
|
||||
}
|
||||
break;
|
||||
case TelephonyManager.CALL_STATE_IDLE:
|
||||
if (resumeAfterCall)
|
||||
{
|
||||
resumeAfterCall = false;
|
||||
downloadService.start();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,453 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
public final List<DownloadFile> downloadList = new ArrayList<>();
|
||||
public final List<DownloadFile> 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> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
||||
|
||||
private final List<DownloadFile> cleanupCandidates = new ArrayList<>();
|
||||
private final LRUCache<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LRUCache<>(100);
|
||||
private ScheduledExecutorService executorService;
|
||||
private long revision;
|
||||
|
||||
public Downloader(Context context, ShufflePlayBuffer shufflePlayBuffer, ExternalStorageMonitor externalStorageMonitor,
|
||||
LocalMediaPlayer localMediaPlayer)
|
||||
{
|
||||
this.context = context;
|
||||
this.shufflePlayBuffer = shufflePlayBuffer;
|
||||
this.externalStorageMonitor = externalStorageMonitor;
|
||||
this.localMediaPlayer = localMediaPlayer;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
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())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (shufflePlayBuffer.isEnabled)
|
||||
{
|
||||
checkShufflePlay(context);
|
||||
}
|
||||
|
||||
if (jukeboxMediaPlayer.getValue().isEnabled() || !Util.isNetworkConnected(context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (downloadList.isEmpty() && backgroundDownloadList.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to download current playing?
|
||||
if (localMediaPlayer.currentPlaying != null && localMediaPlayer.currentPlaying != currentDownloading && !localMediaPlayer.currentPlaying.isWorkDone())
|
||||
{
|
||||
// Cancel current download, if necessary.
|
||||
if (currentDownloading != null)
|
||||
{
|
||||
currentDownloading.cancelDownload();
|
||||
}
|
||||
|
||||
currentDownloading = localMediaPlayer.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 = localMediaPlayer.currentPlaying == null ? 0 : getCurrentPlayingIndex();
|
||||
if (start == -1) start = 0;
|
||||
|
||||
int i = start;
|
||||
// Check all DownloadFiles on the playlist
|
||||
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))
|
||||
{
|
||||
// The next file on the playlist is currently downloading
|
||||
localMediaPlayer.setNextPlayerState(DOWNLOADING);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (localMediaPlayer.currentPlaying != downloadFile)
|
||||
{
|
||||
preloaded++;
|
||||
}
|
||||
|
||||
i = (i + 1) % n;
|
||||
} 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++)
|
||||
{
|
||||
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(localMediaPlayer.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<DownloadFile> getDownloads()
|
||||
{
|
||||
List<DownloadFile> temp = new ArrayList<>();
|
||||
temp.addAll(downloadList);
|
||||
temp.addAll(backgroundDownloadList);
|
||||
return temp;
|
||||
}
|
||||
|
||||
public long getDownloadListUpdateRevision()
|
||||
{
|
||||
return revision;
|
||||
}
|
||||
|
||||
public synchronized void clear()
|
||||
{
|
||||
downloadList.clear();
|
||||
revision++;
|
||||
if (currentDownloading != null)
|
||||
{
|
||||
currentDownloading.cancelDownload();
|
||||
currentDownloading = null;
|
||||
}
|
||||
}
|
||||
|
||||
private 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<MusicDirectory.Entry> 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<MusicDirectory.Entry> 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 (localMediaPlayer.currentPlaying != null)
|
||||
{
|
||||
downloadList.remove(getCurrentPlayingIndex());
|
||||
downloadList.add(0, localMediaPlayer.currentPlaying);
|
||||
}
|
||||
revision++;
|
||||
}
|
||||
|
||||
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<DownloadFile> iterator = cleanupCandidates.iterator();
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
DownloadFile downloadFile = iterator.next();
|
||||
if (downloadFile != localMediaPlayer.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 = localMediaPlayer.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)
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
}
|
||||
|
||||
if (wasEmpty && !downloadList.isEmpty())
|
||||
{
|
||||
if (jukeboxMediaPlayer.getValue().isEnabled())
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().skip(0, 0);
|
||||
localMediaPlayer.setPlayerState(STARTED);
|
||||
}
|
||||
else
|
||||
{
|
||||
localMediaPlayer.play(downloadList.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Monitors the state of the mobile's external storage
|
||||
*/
|
||||
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; }
|
||||
}
|
@ -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;
|
||||
@ -44,21 +45,22 @@ 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.
|
||||
*
|
||||
* @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 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,6 +69,12 @@ public class JukeboxService
|
||||
private VolumeToast volumeToast;
|
||||
private AtomicBoolean running = new AtomicBoolean();
|
||||
private Thread serviceThread;
|
||||
private boolean enabled = false;
|
||||
private Context context;
|
||||
|
||||
// TODO: These create circular references, try to refactor
|
||||
private Lazy<MediaPlayerControllerImpl> mediaPlayerControllerLazy = inject(MediaPlayerControllerImpl.class);
|
||||
private final Downloader downloader;
|
||||
|
||||
// TODO: Report warning if queue fills up.
|
||||
// TODO: Create shutdown method?
|
||||
@ -74,9 +82,10 @@ public class JukeboxService
|
||||
// TODO: Persist RC state?
|
||||
// TODO: Minimize status updates.
|
||||
|
||||
public JukeboxService(DownloadServiceImpl downloadService)
|
||||
public JukeboxMediaPlayer(Context context, Downloader downloader)
|
||||
{
|
||||
this.downloadService = downloadService;
|
||||
this.context = context;
|
||||
this.downloader = downloader;
|
||||
}
|
||||
|
||||
public void startJukeboxService()
|
||||
@ -149,7 +158,7 @@ public class JukeboxService
|
||||
|
||||
try
|
||||
{
|
||||
if (!Util.isOffline(downloadService))
|
||||
if (!Util.isOffline(context))
|
||||
{
|
||||
task = tasks.take();
|
||||
JukeboxStatus status = task.execute();
|
||||
@ -177,9 +186,9 @@ public class JukeboxService
|
||||
// Track change?
|
||||
Integer index = jukeboxStatus.getCurrentPlayingIndex();
|
||||
|
||||
if (index != null && index != -1 && index != downloadService.getCurrentPlayingIndex())
|
||||
if (index != null && index != -1 && index != downloader.getCurrentPlayingIndex())
|
||||
{
|
||||
downloadService.setCurrentPlaying(index);
|
||||
mediaPlayerControllerLazy.getValue().setCurrentPlaying(index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,26 +216,28 @@ public class JukeboxService
|
||||
{
|
||||
Log.w(TAG, x.toString());
|
||||
|
||||
handler.post(new Runnable()
|
||||
new Handler().post(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
Util.toast(downloadService, resourceId, false);
|
||||
Util.toast(context, resourceId, false);
|
||||
}
|
||||
});
|
||||
|
||||
downloadService.setJukeboxEnabled(false);
|
||||
mediaPlayerControllerLazy.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())
|
||||
List<String> ids = new ArrayList<>();
|
||||
for (DownloadFile file : downloader.getDownloads())
|
||||
{
|
||||
ids.add(file.getSong().getId());
|
||||
}
|
||||
@ -248,7 +259,7 @@ public class JukeboxService
|
||||
}
|
||||
|
||||
tasks.add(new Skip(index, offsetSeconds));
|
||||
downloadService.setPlayerState(PlayerState.STARTED);
|
||||
mediaPlayerControllerLazy.getValue().setPlayerState(PlayerState.STARTED);
|
||||
}
|
||||
|
||||
public void stop()
|
||||
@ -280,16 +291,14 @@ public class JukeboxService
|
||||
tasks.remove(SetGain.class);
|
||||
tasks.add(new SetGain(gain));
|
||||
|
||||
if (volumeToast == null)
|
||||
{
|
||||
volumeToast = new VolumeToast(downloadService);
|
||||
}
|
||||
if (volumeToast == null) volumeToast = new VolumeToast(context);
|
||||
|
||||
volumeToast.setVolume(gain);
|
||||
}
|
||||
|
||||
private MusicService getMusicService()
|
||||
{
|
||||
return MusicServiceFactory.getMusicService(downloadService);
|
||||
return MusicServiceFactory.getMusicService(context);
|
||||
}
|
||||
|
||||
public int getPositionSeconds()
|
||||
@ -318,13 +327,16 @@ public class JukeboxService
|
||||
}
|
||||
|
||||
stop();
|
||||
}
|
||||
|
||||
downloadService.setPlayerState(PlayerState.IDLE);
|
||||
public boolean isEnabled()
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private static class TaskQueue
|
||||
{
|
||||
private final LinkedBlockingQueue<JukeboxTask> queue = new LinkedBlockingQueue<JukeboxTask>();
|
||||
private final LinkedBlockingQueue<JukeboxTask> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
void add(JukeboxTask jukeboxTask)
|
||||
{
|
||||
@ -364,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()
|
||||
{
|
||||
@ -380,7 +393,7 @@ public class JukeboxService
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().getJukeboxStatus(downloadService, null);
|
||||
return getMusicService().getJukeboxStatus(context, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,7 +409,7 @@ public class JukeboxService
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().updateJukeboxPlaylist(ids, downloadService, null);
|
||||
return getMusicService().updateJukeboxPlaylist(ids, context, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -414,7 +427,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 +436,7 @@ public class JukeboxService
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().stopJukebox(downloadService, null);
|
||||
return getMusicService().stopJukebox(context, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -432,7 +445,7 @@ public class JukeboxService
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().startJukebox(downloadService, null);
|
||||
return getMusicService().startJukebox(context, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +462,7 @@ public class JukeboxService
|
||||
@Override
|
||||
JukeboxStatus execute() throws Exception
|
||||
{
|
||||
return getMusicService().setJukeboxGain(gain, downloadService, null);
|
||||
return getMusicService().setJukeboxGain(gain, context, null);
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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 DownloadService
|
||||
public interface MediaPlayerController
|
||||
{
|
||||
|
||||
void download(List<Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, boolean newPlaylist);
|
||||
|
||||
void downloadBackground(List<Entry> songs, boolean save);
|
||||
@ -53,38 +55,14 @@ public interface DownloadService
|
||||
|
||||
boolean getShowVisualization();
|
||||
|
||||
boolean getEqualizerAvailable();
|
||||
|
||||
boolean getVisualizerAvailable();
|
||||
|
||||
void setShowVisualization(boolean showVisualization);
|
||||
|
||||
void clear();
|
||||
|
||||
void clearBackground();
|
||||
|
||||
void clearIncomplete();
|
||||
|
||||
int size();
|
||||
|
||||
void remove(int which);
|
||||
|
||||
void remove(DownloadFile downloadFile);
|
||||
|
||||
long getDownloadListDuration();
|
||||
|
||||
List<DownloadFile> getSongs();
|
||||
|
||||
List<DownloadFile> getDownloads();
|
||||
|
||||
List<DownloadFile> getBackgroundDownloads();
|
||||
|
||||
int getCurrentPlayingIndex();
|
||||
|
||||
DownloadFile getCurrentPlaying();
|
||||
|
||||
DownloadFile getCurrentDownloading();
|
||||
|
||||
void play(int index);
|
||||
|
||||
void seekTo(int position);
|
||||
@ -111,10 +89,6 @@ public interface DownloadService
|
||||
|
||||
void unpin(List<Entry> songs);
|
||||
|
||||
DownloadFile forSong(Entry song);
|
||||
|
||||
long getDownloadListUpdateRevision();
|
||||
|
||||
void setSuggestedPlaylistName(String name);
|
||||
|
||||
String getSuggestedPlaylistName();
|
||||
@ -127,8 +101,6 @@ public interface DownloadService
|
||||
|
||||
boolean isJukeboxAvailable();
|
||||
|
||||
boolean isSharingAvailable();
|
||||
|
||||
void setJukeboxEnabled(boolean b);
|
||||
|
||||
void adjustJukeboxVolume(boolean up);
|
||||
@ -137,15 +109,27 @@ public interface DownloadService
|
||||
|
||||
void setVolume(float volume);
|
||||
|
||||
void swap(boolean mainList, int from, int to);
|
||||
|
||||
void restore(List<Entry> songs, int currentPlayingIndex, int currentPlayingPosition, boolean autoPlay, boolean newPlaylist);
|
||||
|
||||
void stopJukeboxService();
|
||||
|
||||
void startJukeboxService();
|
||||
|
||||
void updateNotification();
|
||||
|
||||
void setSongRating(final int rating);
|
||||
|
||||
DownloadFile getCurrentPlaying();
|
||||
|
||||
int getPlaylistSize();
|
||||
|
||||
int getCurrentPlayingNumberOnPlaylist();
|
||||
|
||||
DownloadFile getCurrentDownloading();
|
||||
|
||||
List<DownloadFile> getPlayList();
|
||||
|
||||
long getPlayListUpdateRevision();
|
||||
|
||||
long getPlayListDuration();
|
||||
|
||||
DownloadFile getDownloadFileForSong(Entry song);
|
||||
}
|
@ -0,0 +1,647 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import org.koin.java.standalone.KoinJavaComponent;
|
||||
import org.moire.ultrasonic.audiofx.EqualizerController;
|
||||
import org.moire.ultrasonic.audiofx.VisualizerController;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
import org.moire.ultrasonic.domain.MusicDirectory.Entry;
|
||||
import org.moire.ultrasonic.domain.PlayerState;
|
||||
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.ShufflePlayBuffer;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
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$
|
||||
*/
|
||||
public class MediaPlayerControllerImpl implements MediaPlayerController
|
||||
{
|
||||
private static final String TAG = MediaPlayerControllerImpl.class.getSimpleName();
|
||||
|
||||
private boolean created = false;
|
||||
private String suggestedPlaylistName;
|
||||
private boolean keepScreenOn;
|
||||
|
||||
private boolean showVisualization;
|
||||
private boolean autoPlayStart;
|
||||
|
||||
private Context context;
|
||||
private Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.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, 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;
|
||||
|
||||
Log.i(TAG, "MediaPlayerControllerImpl constructed");
|
||||
}
|
||||
|
||||
public void onCreate()
|
||||
{
|
||||
if (created) return;
|
||||
this.externalStorageMonitor.onCreate(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized 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)
|
||||
{
|
||||
MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer<MediaPlayerService>() {
|
||||
@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())
|
||||
{
|
||||
localMediaPlayer.doPlay(localMediaPlayer.currentPlaying, currentPlayingPosition, autoPlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
autoPlayStart = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void preload()
|
||||
{
|
||||
MediaPlayerService.getInstance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void play(final int index)
|
||||
{
|
||||
MediaPlayerService.executeOnStartedMediaPlayerService(context,new Consumer<MediaPlayerService>() {
|
||||
@Override
|
||||
public void accept(MediaPlayerService mediaPlayerService) {
|
||||
mediaPlayerService.play(index, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized void play()
|
||||
{
|
||||
MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer<MediaPlayerService>() {
|
||||
@Override
|
||||
public void accept(MediaPlayerService mediaPlayerService) {
|
||||
mediaPlayerService.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void togglePlayPause()
|
||||
{
|
||||
if (localMediaPlayer.playerState == PlayerState.IDLE) autoPlayStart = true;
|
||||
MediaPlayerService.executeOnStartedMediaPlayerService(context,new Consumer<MediaPlayerService>() {
|
||||
@Override
|
||||
public void accept(MediaPlayerService mediaPlayerService) {
|
||||
mediaPlayerService.togglePlayPause();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void start()
|
||||
{
|
||||
MediaPlayerService.executeOnStartedMediaPlayerService(context, new Consumer<MediaPlayerService>() {
|
||||
@Override
|
||||
public void accept(MediaPlayerService mediaPlayerService) {
|
||||
mediaPlayerService.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@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 mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void download(List<MusicDirectory.Entry> songs, boolean save, boolean autoPlay, boolean playNext, boolean shuffle, boolean newPlaylist)
|
||||
{
|
||||
downloader.download(songs, save, autoPlay, playNext, newPlaylist);
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
|
||||
if (shuffle) shuffle();
|
||||
|
||||
if (!playNext && !autoPlay && (downloader.downloadList.size() - 1) == downloader.getCurrentPlayingIndex())
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
|
||||
}
|
||||
|
||||
if (autoPlay)
|
||||
{
|
||||
play(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (localMediaPlayer.currentPlaying == null && downloader.downloadList.size() > 0)
|
||||
{
|
||||
localMediaPlayer.currentPlaying = downloader.downloadList.get(0);
|
||||
localMediaPlayer.currentPlaying.setPlaying(true);
|
||||
}
|
||||
|
||||
downloader.checkDownloads();
|
||||
}
|
||||
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void downloadBackground(List<MusicDirectory.Entry> songs, boolean save)
|
||||
{
|
||||
downloader.downloadBackground(songs, save);
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
}
|
||||
|
||||
public synchronized void setCurrentPlaying(DownloadFile currentPlaying)
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) localMediaPlayer.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) localMediaPlayer.setPlayerState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopJukeboxService()
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().stopJukeboxService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setShufflePlayEnabled(boolean enabled)
|
||||
{
|
||||
shufflePlayBuffer.isEnabled = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
clear();
|
||||
downloader.checkDownloads();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShufflePlayEnabled()
|
||||
{
|
||||
return shufflePlayBuffer.isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shuffle()
|
||||
{
|
||||
downloader.shuffle();
|
||||
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepeatMode getRepeatMode()
|
||||
{
|
||||
return Util.getRepeatMode(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setRepeatMode(RepeatMode repeatMode)
|
||||
{
|
||||
Util.setRepeatMode(context, repeatMode);
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getKeepScreenOn()
|
||||
{
|
||||
return keepScreenOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeepScreenOn(boolean keepScreenOn)
|
||||
{
|
||||
this.keepScreenOn = keepScreenOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getShowVisualization()
|
||||
{
|
||||
return showVisualization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShowVisualization(boolean showVisualization)
|
||||
{
|
||||
this.showVisualization = showVisualization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear()
|
||||
{
|
||||
clear(true);
|
||||
}
|
||||
|
||||
public synchronized void clear(boolean serialize)
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.clear(serialize);
|
||||
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clearIncomplete()
|
||||
{
|
||||
reset();
|
||||
Iterator<DownloadFile> iterator = downloader.downloadList.iterator();
|
||||
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
DownloadFile downloadFile = iterator.next();
|
||||
if (!downloadFile.isCompleteFileAvailable())
|
||||
{
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(DownloadFile downloadFile)
|
||||
{
|
||||
if (downloadFile == localMediaPlayer.currentPlaying)
|
||||
{
|
||||
reset();
|
||||
setCurrentPlaying(null);
|
||||
}
|
||||
|
||||
downloader.removeDownloadFile(downloadFile);
|
||||
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
|
||||
if (downloadFile == localMediaPlayer.nextPlaying)
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void delete(List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
for (MusicDirectory.Entry song : songs)
|
||||
{
|
||||
downloader.getDownloadFileForSong(song).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void unpin(List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
for (MusicDirectory.Entry song : songs)
|
||||
{
|
||||
downloader.getDownloadFileForSong(song).unpin();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void previous()
|
||||
{
|
||||
int index = downloader.getCurrentPlayingIndex();
|
||||
if (index == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart song if played more than five seconds.
|
||||
if (getPlayerPosition() > 5000 || index == 0)
|
||||
{
|
||||
play(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
play(index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void next()
|
||||
{
|
||||
int index = downloader.getCurrentPlayingIndex();
|
||||
if (index != -1)
|
||||
{
|
||||
play(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset()
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) localMediaPlayer.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getPlayerPosition()
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService == null) return 0;
|
||||
return mediaPlayerService.getPlayerPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getPlayerDuration()
|
||||
{
|
||||
if (localMediaPlayer.currentPlaying != null)
|
||||
{
|
||||
Integer duration = localMediaPlayer.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 localMediaPlayer.playerState; }
|
||||
|
||||
@Override
|
||||
public void setSuggestedPlaylistName(String name)
|
||||
{
|
||||
this.suggestedPlaylistName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSuggestedPlaylistName()
|
||||
{
|
||||
return suggestedPlaylistName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EqualizerController getEqualizerController()
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService == null) return null;
|
||||
return localMediaPlayer.getEqualizerController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisualizerController getVisualizerController()
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService == null) return null;
|
||||
return localMediaPlayer.getVisualizerController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJukeboxEnabled()
|
||||
{
|
||||
return jukeboxMediaPlayer.getValue().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJukeboxAvailable()
|
||||
{
|
||||
try
|
||||
{
|
||||
String username = Util.getUserName(context, Util.getActiveServer(context));
|
||||
UserInfo user = MusicServiceFactory.getMusicService(context).getUser(username, context, null);
|
||||
return user.getJukeboxRole();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.w(TAG, "Error getting user information", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setJukeboxEnabled(boolean jukeboxEnabled)
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().setEnabled(jukeboxEnabled);
|
||||
setPlayerState(PlayerState.IDLE);
|
||||
|
||||
if (jukeboxEnabled)
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().startJukeboxService();
|
||||
|
||||
reset();
|
||||
|
||||
// Cancel current download, if necessary.
|
||||
if (downloader.currentDownloading != null)
|
||||
{
|
||||
downloader.currentDownloading.cancelDownload();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().stopJukeboxService();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void adjustJukeboxVolume(boolean up)
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().adjustVolume(up);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVolume(float volume)
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) localMediaPlayer.setVolume(volume);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateNotification()
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.updateNotification(localMediaPlayer.playerState, localMediaPlayer.currentPlaying);
|
||||
}
|
||||
|
||||
public void setSongRating(final int rating)
|
||||
{
|
||||
if (!KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING))
|
||||
return;
|
||||
|
||||
if (localMediaPlayer.currentPlaying == null)
|
||||
return;
|
||||
|
||||
final Entry song = localMediaPlayer.currentPlaying.getSong();
|
||||
song.setUserRating(rating);
|
||||
|
||||
new Thread(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
MusicServiceFactory.getMusicService(context).setRating(song.getId(), rating, context, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.e(TAG, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
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<DownloadFile> 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,297 @@
|
||||
/*
|
||||
This file is part of Subsonic.
|
||||
|
||||
Subsonic is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Subsonic is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2009 (C) Sindre Mehus
|
||||
*/
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.domain.PlayerState;
|
||||
import org.moire.ultrasonic.util.CacheCleaner;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
/**
|
||||
* 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 boolean created = false;
|
||||
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, DownloadQueueSerializer downloadQueueSerializer,
|
||||
final MediaPlayerControllerImpl mediaPlayerController, final Downloader downloader)
|
||||
{
|
||||
this.downloadQueueSerializer = downloadQueueSerializer;
|
||||
this.mediaPlayerController = mediaPlayerController;
|
||||
this.context = context;
|
||||
this.downloader = downloader;
|
||||
|
||||
Log.i(TAG, "LifecycleSupport constructed");
|
||||
}
|
||||
|
||||
public void onCreate()
|
||||
{
|
||||
onCreate(false, null);
|
||||
}
|
||||
|
||||
private void onCreate(final boolean autoPlay, final Runnable afterCreated)
|
||||
{
|
||||
if (created)
|
||||
{
|
||||
if (afterCreated != null) afterCreated.run();
|
||||
return;
|
||||
}
|
||||
|
||||
registerHeadsetReceiver();
|
||||
|
||||
// React to media buttons.
|
||||
Util.registerMediaButtonEventReceiver(context, true);
|
||||
|
||||
// Register the handler for outside intents.
|
||||
IntentFilter commandFilter = new IntentFilter();
|
||||
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);
|
||||
|
||||
mediaPlayerController.onCreate();
|
||||
if (autoPlay) mediaPlayerController.preload();
|
||||
|
||||
this.downloadQueueSerializer.deserializeDownloadQueue(new Consumer<State>() {
|
||||
@Override
|
||||
public void accept(State state) {
|
||||
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,
|
||||
downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition());
|
||||
|
||||
if (afterCreated != null) afterCreated.run();
|
||||
}
|
||||
});
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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);
|
||||
final String spKey = context
|
||||
.getString(R.string.settings_playback_resume_play_on_headphones_plug);
|
||||
|
||||
headsetEventReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final Bundle extras = intent.getExtras();
|
||||
|
||||
if (extras == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, String.format("Headset event for: %s", extras.get("name")));
|
||||
final int state = extras.getInt("state");
|
||||
if (state == 0) {
|
||||
if (!mediaPlayerController.isJukeboxEnabled()) {
|
||||
mediaPlayerController.pause();
|
||||
}
|
||||
} else if (state == 1) {
|
||||
if (!mediaPlayerController.isJukeboxEnabled() &&
|
||||
sp.getBoolean(spKey, false) &&
|
||||
mediaPlayerController.getPlayerState() == PlayerState.PAUSED) {
|
||||
mediaPlayerController.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private void handleKeyEvent(KeyEvent event)
|
||||
{
|
||||
if (event.getAction() != KeyEvent.ACTION_DOWN || event.getRepeatCount() > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
final 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);
|
||||
|
||||
// We can receive intents (e.g. MediaButton) when everything is stopped, so we need to start
|
||||
onCreate(autoStart, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
if (action == null) return;
|
||||
Log.i(TAG, "intentReceiver.onReceive: " + action);
|
||||
|
||||
switch(action)
|
||||
{
|
||||
case Constants.CMD_PLAY:
|
||||
mediaPlayerController.play();
|
||||
break;
|
||||
case Constants.CMD_NEXT:
|
||||
mediaPlayerController.next();
|
||||
break;
|
||||
case Constants.CMD_PREVIOUS:
|
||||
mediaPlayerController.previous();
|
||||
break;
|
||||
case Constants.CMD_TOGGLEPAUSE:
|
||||
mediaPlayerController.togglePlayPause();
|
||||
break;
|
||||
case Constants.CMD_STOP:
|
||||
// TODO: There is a stop() function, shouldn't we use that?
|
||||
mediaPlayerController.pause();
|
||||
mediaPlayerController.seekTo(0);
|
||||
break;
|
||||
case Constants.CMD_PAUSE:
|
||||
mediaPlayerController.pause();
|
||||
break;
|
||||
case Constants.CMD_PROCESS_KEYCODE:
|
||||
receiveIntent(intent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -0,0 +1,702 @@
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
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.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.util.FileUtil;
|
||||
import org.moire.ultrasonic.util.ShufflePlayBuffer;
|
||||
import org.moire.ultrasonic.util.SimpleServiceBinder;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
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;
|
||||
import static org.moire.ultrasonic.domain.PlayerState.PAUSED;
|
||||
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();
|
||||
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;
|
||||
private static final Object instanceLock = new Object();
|
||||
|
||||
private final IBinder binder = new SimpleServiceBinder<>(this);
|
||||
private final Scrobbler scrobbler = new Scrobbler();
|
||||
|
||||
public Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
||||
private Lazy<DownloadQueueSerializer> downloadQueueSerializerLazy = inject(DownloadQueueSerializer.class);
|
||||
private Lazy<ShufflePlayBuffer> shufflePlayBufferLazy = inject(ShufflePlayBuffer.class);
|
||||
private Lazy<Downloader> downloaderLazy = inject(Downloader.class);
|
||||
private Lazy<LocalMediaPlayer> localMediaPlayerLazy = inject(LocalMediaPlayer.class);
|
||||
private LocalMediaPlayer localMediaPlayer;
|
||||
private Downloader downloader;
|
||||
private ShufflePlayBuffer shufflePlayBuffer;
|
||||
private DownloadQueueSerializer downloadQueueSerializer;
|
||||
|
||||
private boolean isInForeground = false;
|
||||
private NotificationCompat.Builder notificationBuilder;
|
||||
|
||||
public RepeatMode getRepeatMode() { return Util.getRepeatMode(this); }
|
||||
|
||||
public static MediaPlayerService getInstance(Context context)
|
||||
{
|
||||
synchronized (instanceLock) {
|
||||
for (int i = 0; i < 20; 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()
|
||||
{
|
||||
synchronized (instanceLock)
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static void executeOnStartedMediaPlayerService(final Context context, final Consumer<MediaPlayerService> taskToExecute)
|
||||
{
|
||||
Thread t = new Thread()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
MediaPlayerService instance = getInstance(context);
|
||||
if (instance == null)
|
||||
{
|
||||
Log.e(TAG, "ExecuteOnStartedMediaPlayerService failed to get a MediaPlayerService instance!");
|
||||
return;
|
||||
}
|
||||
|
||||
taskToExecute.accept(instance);
|
||||
}
|
||||
};
|
||||
t.start();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent)
|
||||
{
|
||||
return binder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
|
||||
downloader = downloaderLazy.getValue();
|
||||
localMediaPlayer = localMediaPlayerLazy.getValue();
|
||||
shufflePlayBuffer = shufflePlayBufferLazy.getValue();
|
||||
downloadQueueSerializer = downloadQueueSerializerLazy.getValue();
|
||||
|
||||
downloader.onCreate();
|
||||
shufflePlayBuffer.onCreate();
|
||||
|
||||
localMediaPlayer.onCreate();
|
||||
setupOnCurrentPlayingChangedHandler();
|
||||
setupOnPlayerStateChangedHandler();
|
||||
setupOnSongCompletedHandler();
|
||||
localMediaPlayer.onPrepared = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList,
|
||||
downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
}
|
||||
};
|
||||
localMediaPlayer.onNextSongRequested = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setNextPlaying();
|
||||
}
|
||||
};
|
||||
|
||||
// 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);
|
||||
// Update notification early. It is better to show an empty one temporarily than waiting too long and letting Android kill the app
|
||||
updateNotification(IDLE, null);
|
||||
instance = this;
|
||||
|
||||
Log.i(TAG, "MediaPlayerService created");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId)
|
||||
{
|
||||
super.onStartCommand(intent, flags, startId);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy()
|
||||
{
|
||||
super.onDestroy();
|
||||
|
||||
instance = null;
|
||||
|
||||
try {
|
||||
localMediaPlayer.onDestroy();
|
||||
downloader.stop();
|
||||
shufflePlayBuffer.onDestroy();
|
||||
} 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 || localMediaPlayer.playerState == STOPPED) stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void seekTo(int position)
|
||||
{
|
||||
if (jukeboxMediaPlayer.getValue().isEnabled())
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().skip(downloader.getCurrentPlayingIndex(), position / 1000);
|
||||
}
|
||||
else
|
||||
{
|
||||
localMediaPlayer.seekTo(position);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized int getPlayerPosition()
|
||||
{
|
||||
if (localMediaPlayer.playerState == IDLE || localMediaPlayer.playerState == DOWNLOADING || localMediaPlayer.playerState == PREPARING)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return jukeboxMediaPlayer.getValue().isEnabled() ? jukeboxMediaPlayer.getValue().getPositionSeconds() * 1000 :
|
||||
localMediaPlayer.getPlayerPosition();
|
||||
}
|
||||
|
||||
public synchronized int getPlayerDuration()
|
||||
{
|
||||
return localMediaPlayer.getPlayerDuration();
|
||||
}
|
||||
|
||||
public synchronized void setCurrentPlaying(int currentPlayingIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
localMediaPlayer.setCurrentPlaying(downloader.downloadList.get(currentPlayingIndex));
|
||||
}
|
||||
catch (IndexOutOfBoundsException x)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
|
||||
public void setupOnCurrentPlayingChangedHandler()
|
||||
{
|
||||
localMediaPlayer.onCurrentPlayingChanged = new Consumer<DownloadFile>() {
|
||||
@Override
|
||||
public void accept(DownloadFile currentPlaying) {
|
||||
if (currentPlaying != null)
|
||||
{
|
||||
Util.broadcastNewTrackInfo(MediaPlayerService.this, currentPlaying.getSong());
|
||||
Util.broadcastA2dpMetaDataChange(MediaPlayerService.this, getPlayerPosition(), currentPlaying,
|
||||
downloader.getDownloads().size(), downloader.getCurrentPlayingIndex() + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Util.broadcastNewTrackInfo(MediaPlayerService.this, null);
|
||||
Util.broadcastA2dpMetaDataChange(MediaPlayerService.this, getPlayerPosition(), null,
|
||||
downloader.getDownloads().size(), downloader.getCurrentPlayingIndex() + 1);
|
||||
}
|
||||
|
||||
// Update widget
|
||||
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);
|
||||
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 (currentPlaying != null)
|
||||
{
|
||||
updateNotification(localMediaPlayer.playerState, currentPlaying);
|
||||
if (tabInstance != null) {
|
||||
tabInstance.showNowPlaying();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tabInstance != null)
|
||||
{
|
||||
tabInstance.hideNowPlaying();
|
||||
}
|
||||
stopForeground(true);
|
||||
localMediaPlayer.clearRemoteControl();
|
||||
isInForeground = false;
|
||||
stopIfIdle();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public synchronized void setNextPlaying()
|
||||
{
|
||||
boolean gaplessPlayback = Util.getGaplessPlaybackPreference(this);
|
||||
|
||||
if (!gaplessPlayback)
|
||||
{
|
||||
localMediaPlayer.setNextPlaying(null);
|
||||
return;
|
||||
}
|
||||
|
||||
int index = downloader.getCurrentPlayingIndex();
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
switch (getRepeatMode())
|
||||
{
|
||||
case OFF:
|
||||
index += 1;
|
||||
break;
|
||||
case ALL:
|
||||
index = (index + 1) % downloader.downloadList.size();
|
||||
break;
|
||||
case SINGLE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
localMediaPlayer.clearNextPlaying();
|
||||
|
||||
if (index < downloader.downloadList.size() && index != -1)
|
||||
{
|
||||
localMediaPlayer.setNextPlaying(downloader.downloadList.get(index));
|
||||
}
|
||||
else
|
||||
{
|
||||
localMediaPlayer.setNextPlaying(null);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void togglePlayPause()
|
||||
{
|
||||
if (localMediaPlayer.playerState == PAUSED || localMediaPlayer.playerState == COMPLETED || localMediaPlayer.playerState == STOPPED)
|
||||
{
|
||||
start();
|
||||
}
|
||||
else if (localMediaPlayer.playerState == IDLE)
|
||||
{
|
||||
play();
|
||||
}
|
||||
else if (localMediaPlayer.playerState == STARTED)
|
||||
{
|
||||
pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays either the current song (resume) or the first/next one in queue.
|
||||
*/
|
||||
public synchronized void play()
|
||||
{
|
||||
int current = downloader.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)
|
||||
{
|
||||
Log.v(TAG, String.format("play requested for %d", index));
|
||||
if (index < 0 || index >= downloader.downloadList.size())
|
||||
{
|
||||
resetPlayback();
|
||||
}
|
||||
else
|
||||
{
|
||||
setCurrentPlaying(index);
|
||||
|
||||
if (start)
|
||||
{
|
||||
if (jukeboxMediaPlayer.getValue().isEnabled())
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().skip(index, 0);
|
||||
localMediaPlayer.setPlayerState(STARTED);
|
||||
}
|
||||
else
|
||||
{
|
||||
localMediaPlayer.play(downloader.downloadList.get(index));
|
||||
}
|
||||
}
|
||||
|
||||
downloader.checkDownloads();
|
||||
setNextPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void resetPlayback()
|
||||
{
|
||||
localMediaPlayer.reset();
|
||||
localMediaPlayer.setCurrentPlaying(null);
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList,
|
||||
downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
}
|
||||
|
||||
public synchronized void pause()
|
||||
{
|
||||
if (localMediaPlayer.playerState == STARTED)
|
||||
{
|
||||
if (jukeboxMediaPlayer.getValue().isEnabled())
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
localMediaPlayer.pause();
|
||||
}
|
||||
localMediaPlayer.setPlayerState(PAUSED);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void stop()
|
||||
{
|
||||
if (localMediaPlayer.playerState == STARTED)
|
||||
{
|
||||
if (jukeboxMediaPlayer.getValue().isEnabled())
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
localMediaPlayer.pause();
|
||||
}
|
||||
}
|
||||
localMediaPlayer.setPlayerState(STOPPED);
|
||||
}
|
||||
|
||||
public synchronized void start()
|
||||
{
|
||||
if (jukeboxMediaPlayer.getValue().isEnabled())
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().start();
|
||||
}
|
||||
else
|
||||
{
|
||||
localMediaPlayer.start();
|
||||
}
|
||||
localMediaPlayer.setPlayerState(STARTED);
|
||||
}
|
||||
|
||||
public void setupOnPlayerStateChangedHandler()
|
||||
{
|
||||
localMediaPlayer.onPlayerStateChanged = new BiConsumer<PlayerState, DownloadFile>() {
|
||||
@Override
|
||||
public void accept(PlayerState playerState, DownloadFile currentPlaying) {
|
||||
if (playerState == PAUSED)
|
||||
{
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
}
|
||||
|
||||
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, song,
|
||||
downloader.downloadList.size() + downloader.backgroundDownloadList.size(),
|
||||
downloader.downloadList.indexOf(currentPlaying) + 1, getPlayerPosition());
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Only update notification if player state is one that will change the icon
|
||||
if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)
|
||||
{
|
||||
updateNotification(playerState, currentPlaying);
|
||||
if (tabInstance != null)
|
||||
{
|
||||
tabInstance.showNowPlaying();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tabInstance != null)
|
||||
{
|
||||
tabInstance.hideNowPlaying();
|
||||
}
|
||||
stopForeground(true);
|
||||
localMediaPlayer.clearRemoteControl();
|
||||
isInForeground = false;
|
||||
stopIfIdle();
|
||||
}
|
||||
|
||||
if (playerState == STARTED)
|
||||
{
|
||||
scrobbler.scrobble(MediaPlayerService.this, currentPlaying, false);
|
||||
}
|
||||
else if (playerState == COMPLETED)
|
||||
{
|
||||
scrobbler.scrobble(MediaPlayerService.this, currentPlaying, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void setupOnSongCompletedHandler()
|
||||
{
|
||||
localMediaPlayer.onSongCompleted = new Consumer<DownloadFile>() {
|
||||
@Override
|
||||
public void accept(DownloadFile currentPlaying) {
|
||||
int index = downloader.getCurrentPlayingIndex();
|
||||
|
||||
if (currentPlaying != null)
|
||||
{
|
||||
final MusicDirectory.Entry song = currentPlaying.getSong();
|
||||
|
||||
if (song != null && song.getBookmarkPosition() > 0 && Util.getShouldClearBookmark(MediaPlayerService.this))
|
||||
{
|
||||
MusicService musicService = MusicServiceFactory.getMusicService(MediaPlayerService.this);
|
||||
try
|
||||
{
|
||||
musicService.deleteBookmark(song.getId(), MediaPlayerService.this, null);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
switch (getRepeatMode())
|
||||
{
|
||||
case OFF:
|
||||
if (index + 1 < 0 || index + 1 >= downloader.downloadList.size())
|
||||
{
|
||||
if (Util.getShouldClearPlaylist(MediaPlayerService.this))
|
||||
{
|
||||
clear(true);
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
}
|
||||
|
||||
resetPlayback();
|
||||
break;
|
||||
}
|
||||
|
||||
play(index + 1);
|
||||
break;
|
||||
case ALL:
|
||||
play((index + 1) % downloader.downloadList.size());
|
||||
break;
|
||||
case SINGLE:
|
||||
play(index);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public synchronized void clear(boolean serialize)
|
||||
{
|
||||
localMediaPlayer.reset();
|
||||
downloader.clear();
|
||||
localMediaPlayer.setCurrentPlaying(null);
|
||||
|
||||
setNextPlaying();
|
||||
|
||||
if (serialize) {
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList,
|
||||
downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
}
|
||||
}
|
||||
|
||||
public void updateNotification(PlayerState playerState, DownloadFile currentPlaying)
|
||||
{
|
||||
if (Util.isNotificationEnabled(this)) {
|
||||
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));
|
||||
}
|
||||
else {
|
||||
final NotificationManagerCompat notificationManager =
|
||||
NotificationManagerCompat.from(this);
|
||||
notificationManager.notify(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying));
|
||||
}
|
||||
Log.w(TAG, "--- Updated notification");
|
||||
}
|
||||
else {
|
||||
startForeground(NOTIFICATION_ID, buildForegroundNotification(playerState, currentPlaying));
|
||||
isInForeground = true;
|
||||
Log.w(TAG, "--- Created Foreground notification");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("IconColors")
|
||||
private Notification buildForegroundNotification(PlayerState playerState, DownloadFile currentPlaying) {
|
||||
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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -0,0 +1,19 @@
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
import org.moire.ultrasonic.domain.MusicDirectory;
|
||||
|
||||
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;
|
||||
|
||||
public List<MusicDirectory.Entry> songs = new ArrayList<MusicDirectory.Entry>();
|
||||
public int currentPlayingIndex;
|
||||
public int currentPlayingPosition;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.moire.ultrasonic.service;
|
||||
|
||||
/**
|
||||
* Abstract class for supplying items to a consumer
|
||||
* @param <T> The type of the item supplied
|
||||
*/
|
||||
public abstract class Supplier<T>
|
||||
{
|
||||
public abstract T get();
|
||||
}
|
@ -7,7 +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.Downloader;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@ -19,10 +19,18 @@ 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$
|
||||
*/
|
||||
|
||||
/**
|
||||
* Responsible for cleaning up files from the offline download cache on the filesystem
|
||||
*/
|
||||
public class CacheCleaner
|
||||
{
|
||||
|
||||
@ -30,12 +38,11 @@ public class CacheCleaner
|
||||
private static final long MIN_FREE_SPACE = 500 * 1024L * 1024L;
|
||||
|
||||
private final Context context;
|
||||
private final DownloadService downloadService;
|
||||
private Lazy<Downloader> downloader = inject(Downloader.class);
|
||||
|
||||
public CacheCleaner(Context context, DownloadService downloadService)
|
||||
public CacheCleaner(Context context)
|
||||
{
|
||||
this.context = context;
|
||||
this.downloadService = downloadService;
|
||||
}
|
||||
|
||||
public void clean()
|
||||
@ -219,7 +226,7 @@ public class CacheCleaner
|
||||
{
|
||||
Set<File> filesToNotDelete = new HashSet<File>(5);
|
||||
|
||||
for (DownloadFile downloadFile : downloadService.getDownloads())
|
||||
for (DownloadFile downloadFile : downloader.getValue().getDownloads())
|
||||
{
|
||||
filesToNotDelete.add(downloadFile.getPartialFile());
|
||||
filesToNotDelete.add(downloadFile.getCompleteFile());
|
||||
@ -234,12 +241,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 +269,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");
|
||||
|
@ -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";
|
||||
|
@ -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<MusicDirectory.Entry> buffer = new ArrayList<MusicDirectory.Entry>();
|
||||
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()
|
||||
{
|
||||
@ -60,6 +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<MusicDirectory.Entry> get(int size)
|
||||
@ -78,13 +90,9 @@ public class ShufflePlayBuffer
|
||||
return result;
|
||||
}
|
||||
|
||||
public void shutdown()
|
||||
{
|
||||
executorService.shutdown();
|
||||
}
|
||||
|
||||
private void refill()
|
||||
{
|
||||
if (!isEnabled) return;
|
||||
|
||||
// Check if active server has changed.
|
||||
clearBufferIfNecessary();
|
||||
|
@ -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<DownloadFile> currentPlaying;
|
||||
|
||||
public StreamProxy(DownloadService downloadService)
|
||||
public StreamProxy(Supplier<DownloadFile> 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 == 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));
|
||||
|
@ -54,8 +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.MediaPlayerController;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
|
||||
import java.io.*;
|
||||
@ -93,9 +92,8 @@ 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;
|
||||
|
||||
private static final Map<Integer, Version> SERVER_REST_VERSIONS = new ConcurrentHashMap<Integer, Version>();
|
||||
|
||||
@ -891,19 +889,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)
|
||||
@ -958,7 +966,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))
|
||||
{
|
||||
@ -968,17 +976,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", "");
|
||||
@ -1011,9 +1011,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);
|
||||
@ -1045,38 +1042,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 +1079,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());
|
||||
}
|
||||
@ -1187,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)
|
||||
{
|
||||
DownloadService downloadService = (DownloadService) context;
|
||||
if ((focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) && !downloadService.isJukeboxEnabled())
|
||||
{
|
||||
if (downloadService.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);
|
||||
}
|
||||
else if (lossPref == 0 || (lossPref == 1))
|
||||
{
|
||||
pauseFocus = true;
|
||||
downloadService.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (focusChange == AudioManager.AUDIOFOCUS_GAIN)
|
||||
{
|
||||
if (pauseFocus)
|
||||
{
|
||||
pauseFocus = false;
|
||||
downloadService.start();
|
||||
}
|
||||
else if (lowerFocus)
|
||||
{
|
||||
lowerFocus = false;
|
||||
downloadService.setVolume(1.0f);
|
||||
}
|
||||
}
|
||||
else if (focusChange == AudioManager.AUDIOFOCUS_LOSS && !downloadService.isJukeboxEnabled())
|
||||
{
|
||||
hasFocus = false;
|
||||
downloadService.pause();
|
||||
audioManager.abandonAudioFocus(this);
|
||||
}
|
||||
}
|
||||
}, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMinDisplayMetric(Context context)
|
||||
{
|
||||
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
|
||||
@ -1289,58 +1225,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, DownloadServiceImpl.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, DownloadServiceImpl.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, DownloadServiceImpl.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, DownloadServiceImpl.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, DownloadServiceImpl.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, DownloadServiceImpl.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, DownloadServiceImpl.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, DownloadServiceImpl.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, DownloadServiceImpl.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);
|
||||
}
|
||||
|
||||
|
@ -37,8 +37,7 @@ 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.MediaPlayerController;
|
||||
import org.moire.ultrasonic.service.MusicService;
|
||||
import org.moire.ultrasonic.service.MusicServiceFactory;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
@ -46,6 +45,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 +75,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<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
|
||||
public SongView(Context context)
|
||||
{
|
||||
super(context);
|
||||
@ -164,10 +168,7 @@ public class SongView extends UpdateView implements Checkable
|
||||
|
||||
this.song = song;
|
||||
|
||||
if (downloadService != null)
|
||||
{
|
||||
this.downloadFile = downloadService.forSong(song);
|
||||
}
|
||||
this.downloadFile = mediaPlayerControllerLazy.getValue().getDownloadFileForSong(song);
|
||||
|
||||
StringBuilder artist = new StringBuilder(60);
|
||||
|
||||
@ -311,10 +312,6 @@ public class SongView extends UpdateView implements Checkable
|
||||
@Override
|
||||
protected void updateBackground()
|
||||
{
|
||||
if (downloadService == null)
|
||||
{
|
||||
downloadService = DownloadServiceImpl.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -322,12 +319,7 @@ public class SongView extends UpdateView implements Checkable
|
||||
{
|
||||
updateBackground();
|
||||
|
||||
if (downloadService == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
downloadFile = downloadService.forSong(this.song);
|
||||
downloadFile = mediaPlayerControllerLazy.getValue().getDownloadFileForSong(this.song);
|
||||
File partialFile = downloadFile.getPartialFile();
|
||||
|
||||
if (downloadFile.isWorkDone())
|
||||
@ -417,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 = downloadService.getCurrentPlaying() == downloadFile;
|
||||
boolean playing = mediaPlayerControllerLazy.getValue().getCurrentPlaying() == downloadFile;
|
||||
|
||||
if (playing)
|
||||
{
|
||||
|
@ -27,8 +27,11 @@ 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;
|
||||
|
||||
import static org.koin.java.standalone.KoinJavaComponent.inject;
|
||||
|
||||
/**
|
||||
* A simple class that draws waveform data received from a
|
||||
@ -39,7 +42,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 +50,8 @@ public class VisualizerView extends View
|
||||
private float[] points;
|
||||
private boolean active;
|
||||
|
||||
private Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
|
||||
public VisualizerView(Context context)
|
||||
{
|
||||
super(context);
|
||||
@ -97,10 +101,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 = mediaPlayerControllerLazy.getValue().getVisualizerController();
|
||||
return visualizerController == null ? null : visualizerController.getVisualizer();
|
||||
}
|
||||
|
||||
@ -120,8 +123,7 @@ public class VisualizerView extends View
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadService downloadService = DownloadServiceImpl.getInstance();
|
||||
if (downloadService != null && downloadService.getPlayerState() != PlayerState.STARTED)
|
||||
if (mediaPlayerControllerLazy.getValue().getPlayerState() != PlayerState.STARTED)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ 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() {
|
||||
@ -20,7 +21,8 @@ class UApp : MultiDexApplication() {
|
||||
appPermanentStorage,
|
||||
baseNetworkModule,
|
||||
featureFlagsModule,
|
||||
musicServiceModule
|
||||
musicServiceModule,
|
||||
mediaPlayerModule
|
||||
),
|
||||
extraProperties = mapOf(
|
||||
DiProperties.APP_CONTEXT to applicationContext
|
||||
|
@ -0,0 +1,32 @@
|
||||
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
|
||||
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<MediaPlayerController> {
|
||||
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(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()) }
|
||||
}
|
@ -334,8 +334,8 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">-:--</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">O player MX não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo.</string>
|
||||
<string name="video.get_mx_player_button">Baixar Player MX</string>
|
||||
<string name="video.get_mx_player_text">O MX Player não está instalado. Baixe da graça pela Play Store ou modifique as configurações de vídeo.</string>
|
||||
<string name="video.get_mx_player_button">Baixar MX Player</string>
|
||||
<string name="widget.initial_text">Toque para selecionar a música</string>
|
||||
<string name="widget.sdcard_busy">Cartão SD indisponível</string>
|
||||
<string name="widget.sdcard_missing">Sem cartão SD</string>
|
||||
@ -371,7 +371,7 @@
|
||||
<string name="settings.share_greeting_default">Saudação Padrão</string>
|
||||
<string name="share_default_greeting">Confira esta música que compartilhei do %s</string>
|
||||
<string name="share_via">Compartilhar músicas via</string>
|
||||
<string name="settings.video_mx_player">Player MX</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Padrão</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Compartilhar</string>
|
||||
|
@ -334,8 +334,8 @@
|
||||
<string name="util.bytes_format.megabyte">0.00 MB</string>
|
||||
<string name="util.no_time">—:——</string>
|
||||
<string name="util.zero_time">0:00</string>
|
||||
<string name="video.get_mx_player_text">O player MX não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo.</string>
|
||||
<string name="video.get_mx_player_button">Descarregar Player MX</string>
|
||||
<string name="video.get_mx_player_text">O MX Player não está instalado. Descarregue da graça pela Play Store ou modifique as configurações de vídeo.</string>
|
||||
<string name="video.get_mx_player_button">Descarregar MX Player</string>
|
||||
<string name="widget.initial_text">Toque para selecionar a música</string>
|
||||
<string name="widget.sdcard_busy">Cartão SD indisponível</string>
|
||||
<string name="widget.sdcard_missing">Sem cartão SD</string>
|
||||
@ -371,7 +371,7 @@
|
||||
<string name="settings.share_greeting_default">Saudação Padrão</string>
|
||||
<string name="share_default_greeting">Confira esta música que compartilhei do %s</string>
|
||||
<string name="share_via">Compartilhar músicas via</string>
|
||||
<string name="settings.video_mx_player">Player MX</string>
|
||||
<string name="settings.video_mx_player">MX Player</string>
|
||||
<string name="settings.video_default">Padrão</string>
|
||||
<string name="settings.video_flash">Flash</string>
|
||||
<string name="menu.share">Compartilhar</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user