diff --git a/detekt-config.yml b/detekt-config.yml
index 47707e69..7fff31c3 100644
--- a/detekt-config.yml
+++ b/detekt-config.yml
@@ -35,6 +35,9 @@ exceptions:
empty-blocks:
active: true
+ EmptyFunctionBlock:
+ active: true
+ ignoreOverridden: true
complexity:
active: true
diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml
index a0874cbb..037c3e49 100644
--- a/ultrasonic/lint-baseline.xml
+++ b/ultrasonic/lint-baseline.xml
@@ -183,21 +183,10 @@
errorLine2=" ^">
-
-
-
-
@@ -216,7 +205,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -227,7 +216,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -238,7 +227,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -249,7 +238,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -260,7 +249,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -271,7 +260,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -282,7 +271,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -293,7 +282,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -304,7 +293,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -315,7 +304,7 @@
errorLine2=" ^">
@@ -326,7 +315,7 @@
errorLine2=" ^">
@@ -337,7 +326,7 @@
errorLine2=" ^">
@@ -348,7 +337,7 @@
errorLine2=" ^">
@@ -359,7 +348,7 @@
errorLine2=" ^">
@@ -370,7 +359,7 @@
errorLine2=" ^">
@@ -381,7 +370,7 @@
errorLine2=" ^">
@@ -491,17 +480,6 @@
column="19"/>
-
-
-
-
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1219,7 +1197,7 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1230,27 +1208,27 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
-
+
-
+
-
+
-
+
@@ -1469,43 +1447,43 @@
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
@@ -1571,7 +1549,7 @@
errorLine2=" ^">
@@ -1582,7 +1560,7 @@
errorLine2=" ^">
@@ -1593,7 +1571,7 @@
errorLine2=" ^">
@@ -1604,7 +1582,7 @@
errorLine2=" ^">
@@ -1615,7 +1593,7 @@
errorLine2=" ^">
@@ -1626,7 +1604,7 @@
errorLine2=" ^">
@@ -1637,18 +1615,7 @@
errorLine2=" ^">
-
-
-
-
@@ -1659,7 +1626,7 @@
errorLine2=" ^">
@@ -1670,7 +1637,7 @@
errorLine2=" ^">
@@ -1782,61 +1749,6 @@
column="42"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
onProgressChangedTask;
- LinearLayout visualizerViewLayout;
- private MenuItem starMenuItem;
- private ImageView fiveStar1ImageView;
- private ImageView fiveStar2ImageView;
- private ImageView fiveStar3ImageView;
- private ImageView fiveStar4ImageView;
- private ImageView fiveStar5ImageView;
- private boolean useFiveStarRating;
- private Drawable hollowStar;
- private Drawable fullStar;
- private CancellationToken cancellationToken;
-
- private boolean isEqualizerAvailable;
- private boolean isVisualizerAvailable;
-
- private final Lazy networkAndStorageChecker = inject(NetworkAndStorageChecker.class);
- private final Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class);
- private final Lazy shareHandler = inject(ShareHandler.class);
- private final Lazy imageLoaderProvider = inject(ImageLoaderProvider.class);
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- Util.applyTheme(this.getContext());
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.current_playing, container, false);
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
- cancellationToken = new CancellationToken();
- FragmentTitle.Companion.setTitle(this, R.string.common_appname);
-
- final WindowManager windowManager = getActivity().getWindowManager();
- final Display display = windowManager.getDefaultDisplay();
- Point size = new Point();
- display.getSize(size);
- int width = size.x;
- int height = size.y;
-
- setHasOptionsMenu(true);
-
- FeatureStorage features = KoinJavaComponent.get(FeatureStorage.class);
- useFiveStarRating = features.isFeatureEnabled(Feature.FIVE_STAR_RATING);
-
- swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100;
- swipeVelocity = swipeDistance;
- gestureScanner = new GestureDetector(getContext(), this);
-
- playlistFlipper = view.findViewById(R.id.current_playing_playlist_flipper);
- emptyTextView = view.findViewById(R.id.playlist_empty);
- songTitleTextView = view.findViewById(R.id.current_playing_song);
- albumTextView = view.findViewById(R.id.current_playing_album);
- artistTextView = view.findViewById(R.id.current_playing_artist);
- albumArtImageView = view.findViewById(R.id.current_playing_album_art_image);
- positionTextView = view.findViewById(R.id.current_playing_position);
- downloadTrackTextView = view.findViewById(R.id.current_playing_track);
- downloadTotalDurationTextView = view.findViewById(R.id.current_total_duration);
- durationTextView = view.findViewById(R.id.current_playing_duration);
- progressBar = view.findViewById(R.id.current_playing_progress_bar);
- playlistView = view.findViewById(R.id.playlist_view);
- final AutoRepeatButton previousButton = view.findViewById(R.id.button_previous);
- final AutoRepeatButton nextButton = view.findViewById(R.id.button_next);
- pauseButton = view.findViewById(R.id.button_pause);
- stopButton = view.findViewById(R.id.button_stop);
- startButton = view.findViewById(R.id.button_start);
- final View shuffleButton = view.findViewById(R.id.button_shuffle);
- repeatButton = view.findViewById(R.id.button_repeat);
-
- visualizerViewLayout = view.findViewById(R.id.current_playing_visualizer_layout);
-
- LinearLayout ratingLinearLayout = view.findViewById(R.id.song_rating);
- fiveStar1ImageView = view.findViewById(R.id.song_five_star_1);
- fiveStar2ImageView = view.findViewById(R.id.song_five_star_2);
- fiveStar3ImageView = view.findViewById(R.id.song_five_star_3);
- fiveStar4ImageView = view.findViewById(R.id.song_five_star_4);
- fiveStar5ImageView = view.findViewById(R.id.song_five_star_5);
-
- if (!useFiveStarRating) ratingLinearLayout.setVisibility(View.GONE);
-
- hollowStar = Util.getDrawableFromAttribute(view.getContext(), R.attr.star_hollow);
- fullStar = Util.getDrawableFromAttribute(getContext(), R.attr.star_full);
-
- fiveStar1ImageView.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view) {
- setSongRating(1);
- }
- });
- fiveStar2ImageView.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view) {
- setSongRating(2);
- }
- });
- fiveStar3ImageView.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view) {
- setSongRating(3);
- }
- });
- fiveStar4ImageView.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view) {
- setSongRating(4);
- }
- });
- fiveStar5ImageView.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view) {
- setSongRating(5);
- }
- });
-
- albumArtImageView.setOnTouchListener(new View.OnTouchListener()
- {
- @Override
- public boolean onTouch(View view, MotionEvent me)
- {
- return gestureScanner.onTouchEvent(me);
- }
- });
-
- albumArtImageView.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view)
- {
- toggleFullScreenAlbumArt();
- }
- });
-
- previousButton.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
-
- new SilentBackgroundTask(getActivity())
- {
- @Override
- protected Void doInBackground()
- {
- mediaPlayerControllerLazy.getValue().previous();
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- onCurrentChanged();
- onSliderProgressChanged();
- }
- }.execute();
- }
- });
-
- previousButton.setOnRepeatListener(new Runnable()
- {
- @Override
- public void run()
- {
- int incrementTime = Util.getIncrementTime();
- changeProgress(-incrementTime);
- }
- });
-
- nextButton.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
-
- new SilentBackgroundTask(getActivity())
- {
- @Override
- protected Boolean doInBackground()
- {
- mediaPlayerControllerLazy.getValue().next();
- return true;
- }
-
- @Override
- protected void done(final Boolean result)
- {
- if (result)
- {
- onCurrentChanged();
- onSliderProgressChanged();
- }
- }
- }.execute();
- }
- });
-
- nextButton.setOnRepeatListener(new Runnable()
- {
- @Override
- public void run()
- {
- int incrementTime = Util.getIncrementTime();
- changeProgress(incrementTime);
- }
- });
-
- pauseButton.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view)
- {
- new SilentBackgroundTask(getActivity())
- {
- @Override
- protected Void doInBackground()
- {
- mediaPlayerControllerLazy.getValue().pause();
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- onCurrentChanged();
- onSliderProgressChanged();
- }
- }.execute();
- }
- });
-
- stopButton.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view)
- {
- new SilentBackgroundTask(getActivity())
- {
- @Override
- protected Void doInBackground()
- {
- mediaPlayerControllerLazy.getValue().reset();
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- onCurrentChanged();
- onSliderProgressChanged();
- }
- }.execute();
- }
- });
-
- startButton.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
-
- new SilentBackgroundTask(getActivity())
- {
- @Override
- protected Void doInBackground()
- {
- start();
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- onCurrentChanged();
- onSliderProgressChanged();
- }
- }.execute();
- }
- });
-
- shuffleButton.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view)
- {
- mediaPlayerControllerLazy.getValue().shuffle();
- Util.toast(getActivity(), R.string.download_menu_shuffle_notification);
- }
- });
-
- repeatButton.setOnClickListener(new View.OnClickListener()
- {
- @Override
- public void onClick(final View view)
- {
- final RepeatMode repeatMode = mediaPlayerControllerLazy.getValue().getRepeatMode().next();
-
- mediaPlayerControllerLazy.getValue().setRepeatMode(repeatMode);
- onDownloadListChanged();
-
- switch (repeatMode)
- {
- case OFF:
- Util.toast(getContext(), R.string.download_repeat_off);
- break;
- case ALL:
- Util.toast(getContext(), R.string.download_repeat_all);
- break;
- case SINGLE:
- Util.toast(getContext(), R.string.download_repeat_single);
- break;
- default:
- break;
- }
- }
- });
-
- progressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener()
- {
- @Override
- public void onStopTrackingTouch(final SeekBar seekBar)
- {
- new SilentBackgroundTask(getActivity())
- {
- @Override
- protected Void doInBackground()
- {
- mediaPlayerControllerLazy.getValue().seekTo(getProgressBar().getProgress());
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- onSliderProgressChanged();
- }
- }.execute();
- }
-
- @Override
- public void onStartTrackingTouch(final SeekBar seekBar)
- {
- }
-
- @Override
- public void onProgressChanged(final SeekBar seekBar, final int progress, final boolean fromUser)
- {
- }
- });
-
- playlistView.setOnItemClickListener(new AdapterView.OnItemClickListener()
- {
- @Override
- public void onItemClick(final AdapterView> parent, final View view, final int position, final long id)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
-
- new SilentBackgroundTask(getActivity())
- {
- @Override
- protected Void doInBackground()
- {
- mediaPlayerControllerLazy.getValue().play(position);
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- onCurrentChanged();
- onSliderProgressChanged();
- }
- }.execute();
- }
- });
-
- registerForContextMenu(playlistView);
-
- final MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
- if (mediaPlayerController != null && getArguments() != null && getArguments().getBoolean(Constants.INTENT_EXTRA_NAME_SHUFFLE, false))
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
- mediaPlayerController.setShufflePlayEnabled(true);
- }
-
- visualizerViewLayout.setVisibility(View.GONE);
- VisualizerController.get().observe(getActivity(), new Observer() {
- @Override
- public void onChanged(VisualizerController visualizerController) {
- if (visualizerController != null) {
- Timber.d("VisualizerController Observer.onChanged received controller");
- visualizerView = new VisualizerView(getContext());
- visualizerViewLayout.addView(visualizerView, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT));
-
- if (!visualizerView.isActive())
- {
- visualizerViewLayout.setVisibility(View.GONE);
- }
- else
- {
- visualizerViewLayout.setVisibility(View.VISIBLE);
- }
-
- visualizerView.setOnTouchListener(new View.OnTouchListener()
- {
- @Override
- public boolean onTouch(final View view, final MotionEvent motionEvent)
- {
- visualizerView.setActive(!visualizerView.isActive());
- mediaPlayerControllerLazy.getValue().setShowVisualization(visualizerView.isActive());
- return true;
- }
- });
- isVisualizerAvailable = true;
- } else {
- Timber.d("VisualizerController Observer.onChanged has no controller");
- visualizerViewLayout.setVisibility(View.GONE);
- isVisualizerAvailable = false;
- }
- }
- });
-
- EqualizerController.get().observe(getActivity(), new Observer() {
- @Override
- public void onChanged(EqualizerController equalizerController) {
- if (equalizerController != null) {
- Timber.d("EqualizerController Observer.onChanged received controller");
- isEqualizerAvailable = true;
- } else {
- Timber.d("EqualizerController Observer.onChanged has no controller");
- isEqualizerAvailable = false;
- }
- }
- });
-
- new Thread(new Runnable()
- {
- @Override
- public void run()
- {
- try
- {
- MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
- jukeboxAvailable = (mediaPlayerController != null) && (mediaPlayerController.isJukeboxAvailable());
- }
- catch (Exception e)
- {
- Timber.e(e);
- }
- }
- }).start();
-
- view.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return gestureScanner.onTouchEvent(event);
- }
- });
- }
-
- @Override
- public void onResume()
- {
- super.onResume();
-
- final MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
-
- if (mediaPlayerController == null || mediaPlayerController.getCurrentPlaying() == null)
- {
- playlistFlipper.setDisplayedChild(1);
- }
- else
- {
- // Download list and Album art must be updated when Resumed
- onDownloadListChanged();
- onCurrentChanged();
- }
-
-
- final Handler handler = new Handler();
- final Runnable runnable = new Runnable()
- {
- @Override
- public void run()
- {
- handler.post(new Runnable()
- {
- @Override
- public void run()
- {
- update(cancellationToken);
- }
- });
- }
- };
-
- executorService = Executors.newSingleThreadScheduledExecutor();
- executorService.scheduleWithFixedDelay(runnable, 0L, 250L, TimeUnit.MILLISECONDS);
-
- if (mediaPlayerController != null && mediaPlayerController.getKeepScreenOn())
- {
- getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
- else
- {
- getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
-
- if (visualizerView != null)
- {
- visualizerView.setActive(mediaPlayerController != null && mediaPlayerController.getShowVisualization());
- }
-
- getActivity().invalidateOptionsMenu();
- }
-
- // Scroll to current playing/downloading.
- private void scrollToCurrent()
- {
- ListAdapter adapter = playlistView.getAdapter();
-
- if (adapter != null)
- {
- int count = adapter.getCount();
-
- for (int i = 0; i < count; i++)
- {
- if (currentPlaying == playlistView.getItemAtPosition(i))
- {
- playlistView.smoothScrollToPositionFromTop(i, 40);
- return;
- }
- }
-
- final DownloadFile currentDownloading = mediaPlayerControllerLazy.getValue().getCurrentDownloading();
- for (int i = 0; i < count; i++)
- {
- if (currentDownloading == playlistView.getItemAtPosition(i))
- {
- playlistView.smoothScrollToPositionFromTop(i, 40);
- return;
- }
- }
- }
- }
-
- @Override
- public void onPause()
- {
- super.onPause();
- executorService.shutdown();
-
- if (visualizerView != null)
- {
- visualizerView.setActive(false);
- }
- }
-
- @Override
- public void onDestroyView() {
- cancellationToken.cancel();
- super.onDestroyView();
- }
-
- @Override
- public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
- inflater.inflate(R.menu.nowplaying, menu);
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- public void onPrepareOptionsMenu(@NotNull Menu menu) {
- super.onPrepareOptionsMenu(menu);
-
- final MenuItem screenOption = menu.findItem(R.id.menu_item_screen_on_off);
- final MenuItem jukeboxOption = menu.findItem(R.id.menu_item_jukebox);
- final MenuItem equalizerMenuItem = menu.findItem(R.id.menu_item_equalizer);
- final MenuItem visualizerMenuItem = menu.findItem(R.id.menu_item_visualizer);
- final MenuItem shareMenuItem = menu.findItem(R.id.menu_item_share);
- starMenuItem = menu.findItem(R.id.menu_item_star);
- MenuItem bookmarkMenuItem = menu.findItem(R.id.menu_item_bookmark_set);
- MenuItem bookmarkRemoveMenuItem = menu.findItem(R.id.menu_item_bookmark_delete);
-
-
- if (ActiveServerProvider.Companion.isOffline())
- {
- if (shareMenuItem != null)
- {
- shareMenuItem.setVisible(false);
- }
-
- if (starMenuItem != null)
- {
- starMenuItem.setVisible(false);
- }
-
- if (bookmarkMenuItem != null)
- {
- bookmarkMenuItem.setVisible(false);
- }
-
- if (bookmarkRemoveMenuItem != null)
- {
- bookmarkRemoveMenuItem.setVisible(false);
- }
- }
-
- if (equalizerMenuItem != null)
- {
- equalizerMenuItem.setEnabled(isEqualizerAvailable);
- equalizerMenuItem.setVisible(isEqualizerAvailable);
- }
-
- if (visualizerMenuItem != null)
- {
- visualizerMenuItem.setEnabled(isVisualizerAvailable);
- visualizerMenuItem.setVisible(isVisualizerAvailable);
- }
-
- final MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
-
- if (mediaPlayerController != null)
- {
- DownloadFile downloadFile = mediaPlayerController.getCurrentPlaying();
-
- if (downloadFile != null)
- {
- currentSong = downloadFile.getSong();
- }
-
- if (useFiveStarRating) starMenuItem.setVisible(false);
-
- if (currentSong != null)
- {
- if (starMenuItem != null)
- {
- starMenuItem.setIcon(currentSong.getStarred() ? fullStar : hollowStar);
- }
- }
- else
- {
- if (starMenuItem != null)
- {
- starMenuItem.setIcon(hollowStar);
- }
- }
-
-
- if (mediaPlayerController.getKeepScreenOn())
- {
- if (screenOption != null)
- {
- screenOption.setTitle(R.string.download_menu_screen_off);
- }
- }
- else
- {
- if (screenOption != null)
- {
- screenOption.setTitle(R.string.download_menu_screen_on);
- }
- }
-
- if (jukeboxOption != null)
- {
- jukeboxOption.setEnabled(jukeboxAvailable);
- jukeboxOption.setVisible(jukeboxAvailable);
-
- if (mediaPlayerController.isJukeboxEnabled())
- {
- jukeboxOption.setTitle(R.string.download_menu_jukebox_off);
- }
- else
- {
- jukeboxOption.setTitle(R.string.download_menu_jukebox_on);
- }
- }
- }
- }
-
- @Override
- public void onCreateContextMenu(final @NotNull ContextMenu menu, final @NotNull View view, final ContextMenu.ContextMenuInfo menuInfo)
- {
- super.onCreateContextMenu(menu, view, menuInfo);
- if (view == playlistView)
- {
- final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
- final DownloadFile downloadFile = (DownloadFile) playlistView.getItemAtPosition(info.position);
-
- final MenuInflater menuInflater = getActivity().getMenuInflater();
- menuInflater.inflate(R.menu.nowplaying_context, menu);
-
- MusicDirectory.Entry song = null;
-
- if (downloadFile != null)
- {
- song = downloadFile.getSong();
- }
-
- if (song != null && song.getParent() == null)
- {
- MenuItem menuItem = menu.findItem(R.id.menu_show_album);
-
- if (menuItem != null)
- {
- menuItem.setVisible(false);
- }
- }
-
- if (ActiveServerProvider.Companion.isOffline() || !Util.getShouldUseId3Tags())
- {
- MenuItem menuItem = menu.findItem(R.id.menu_show_artist);
-
- if (menuItem != null)
- {
- menuItem.setVisible(false);
- }
- }
-
- if (ActiveServerProvider.Companion.isOffline())
- {
- MenuItem menuItem = menu.findItem(R.id.menu_lyrics);
-
- if (menuItem != null)
- {
- menuItem.setVisible(false);
- }
- }
- }
- }
-
- @Override
- public boolean onContextItemSelected(final MenuItem menuItem)
- {
- final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();
-
- DownloadFile downloadFile = null;
-
- if (info != null)
- {
- downloadFile = (DownloadFile) playlistView.getItemAtPosition(info.position);
- }
-
- return menuItemSelected(menuItem.getItemId(), downloadFile) || super.onContextItemSelected(menuItem);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- return menuItemSelected(item.getItemId(), null) || super.onOptionsItemSelected(item);
- }
-
- private boolean menuItemSelected(final int menuItemId, final DownloadFile song)
- {
- MusicDirectory.Entry entry = null;
- Bundle bundle;
-
- if (song != null)
- {
- entry = song.getSong();
- }
-
- if (menuItemId == R.id.menu_show_artist) {
- if (entry == null) {
- return false;
- }
-
- if (Util.getShouldUseId3Tags()) {
- bundle = new Bundle();
- bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.getArtistId());
- bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getArtist());
- bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.getArtistId());
- bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true);
- Navigation.findNavController(getView()).navigate(R.id.playerToSelectAlbum, bundle);
- }
-
- return true;
- } else if (menuItemId == R.id.menu_show_album) {
- if (entry == null) {
- return false;
- }
-
- String albumId = Util.getShouldUseId3Tags() ? entry.getAlbumId() : entry.getParent();
- bundle = new Bundle();
- bundle.putString(Constants.INTENT_EXTRA_NAME_ID, albumId);
- bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.getAlbum());
- bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.getParent());
- bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, true);
- Navigation.findNavController(getView()).navigate(R.id.playerToSelectAlbum, bundle);
- return true;
- } else if (menuItemId == R.id.menu_lyrics) {
- if (entry == null) {
- return false;
- }
-
- bundle = new Bundle();
- bundle.putString(Constants.INTENT_EXTRA_NAME_ARTIST, entry.getArtist());
- bundle.putString(Constants.INTENT_EXTRA_NAME_TITLE, entry.getTitle());
- Navigation.findNavController(getView()).navigate(R.id.playerToLyrics, bundle);
- return true;
- } else if (menuItemId == R.id.menu_remove) {
- mediaPlayerControllerLazy.getValue().remove(song);
- onDownloadListChanged();
- return true;
- } else if (menuItemId == R.id.menu_item_screen_on_off) {
- if (mediaPlayerControllerLazy.getValue().getKeepScreenOn()) {
- getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- mediaPlayerControllerLazy.getValue().setKeepScreenOn(false);
- } else {
- getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- mediaPlayerControllerLazy.getValue().setKeepScreenOn(true);
- }
- return true;
- } else if (menuItemId == R.id.menu_shuffle) {
- mediaPlayerControllerLazy.getValue().shuffle();
- Util.toast(getContext(), R.string.download_menu_shuffle_notification);
- return true;
- } else if (menuItemId == R.id.menu_item_equalizer) {
- Navigation.findNavController(getView()).navigate(R.id.playerToEqualizer);
- return true;
- } else if (menuItemId == R.id.menu_item_visualizer) {
- final boolean active = !visualizerView.isActive();
- visualizerView.setActive(active);
-
- if (!visualizerView.isActive()) {
- visualizerViewLayout.setVisibility(View.GONE);
- } else {
- visualizerViewLayout.setVisibility(View.VISIBLE);
- }
-
- mediaPlayerControllerLazy.getValue().setShowVisualization(visualizerView.isActive());
- Util.toast(getContext(), active ? R.string.download_visualizer_on : R.string.download_visualizer_off);
- return true;
- } else if (menuItemId == R.id.menu_item_jukebox) {
- final boolean jukeboxEnabled = !mediaPlayerControllerLazy.getValue().isJukeboxEnabled();
- mediaPlayerControllerLazy.getValue().setJukeboxEnabled(jukeboxEnabled);
- Util.toast(getContext(), jukeboxEnabled ? R.string.download_jukebox_on : R.string.download_jukebox_off, false);
- return true;
- } else if (menuItemId == R.id.menu_item_toggle_list) {
- toggleFullScreenAlbumArt();
- return true;
- } else if (menuItemId == R.id.menu_item_clear_playlist) {
- mediaPlayerControllerLazy.getValue().setShufflePlayEnabled(false);
- mediaPlayerControllerLazy.getValue().clear();
- onDownloadListChanged();
- return true;
- } else if (menuItemId == R.id.menu_item_save_playlist) {
- if (mediaPlayerControllerLazy.getValue().getPlaylistSize() > 0) {
- showSavePlaylistDialog();
- }
- return true;
- } else if (menuItemId == R.id.menu_item_star) {
- if (currentSong == null) {
- return true;
- }
-
- final boolean isStarred = currentSong.getStarred();
- final String id = currentSong.getId();
-
- if (isStarred) {
- starMenuItem.setIcon(hollowStar);
- currentSong.setStarred(false);
- } else {
- starMenuItem.setIcon(fullStar);
- currentSong.setStarred(true);
- }
-
- new Thread(new Runnable() {
- @Override
- public void run() {
- final MusicService musicService = MusicServiceFactory.getMusicService();
-
- try {
- if (isStarred) {
- musicService.unstar(id, null, null);
- } else {
- musicService.star(id, null, null);
- }
- } catch (Exception e) {
- Timber.e(e);
- }
- }
- }).start();
-
- return true;
- } else if (menuItemId == R.id.menu_item_bookmark_set) {
- if (currentSong == null) {
- return true;
- }
-
- final String songId = currentSong.getId();
- final int playerPosition = mediaPlayerControllerLazy.getValue().getPlayerPosition();
-
- currentSong.setBookmarkPosition(playerPosition);
-
- String bookmarkTime = Util.formatTotalDuration(playerPosition, true);
-
- new Thread(new Runnable() {
- @Override
- public void run() {
- final MusicService musicService = MusicServiceFactory.getMusicService();
-
- try {
- musicService.createBookmark(songId, playerPosition);
- } catch (Exception e) {
- Timber.e(e);
- }
- }
- }).start();
-
- String msg = getResources().getString(R.string.download_bookmark_set_at_position, bookmarkTime);
-
- Util.toast(getContext(), msg);
-
- return true;
- } else if (menuItemId == R.id.menu_item_bookmark_delete) {
- if (currentSong == null) {
- return true;
- }
-
- final String bookmarkSongId = currentSong.getId();
- currentSong.setBookmarkPosition(0);
-
- new Thread(new Runnable() {
- @Override
- public void run() {
- final MusicService musicService = MusicServiceFactory.getMusicService();
-
- try {
- musicService.deleteBookmark(bookmarkSongId);
- } catch (Exception e) {
- Timber.e(e);
- }
- }
- }).start();
-
- Util.toast(getContext(), R.string.download_bookmark_removed);
-
- return true;
- } else if (menuItemId == R.id.menu_item_share) {
- MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
- List entries = new ArrayList<>();
-
- if (mediaPlayerController != null) {
- List downloadServiceSongs = mediaPlayerController.getPlayList();
-
- if (downloadServiceSongs != null) {
- for (DownloadFile downloadFile : downloadServiceSongs) {
- if (downloadFile != null) {
- MusicDirectory.Entry playlistEntry = downloadFile.getSong();
-
- if (playlistEntry != null) {
- entries.add(playlistEntry);
- }
- }
- }
- }
- }
-
- shareHandler.getValue().createShare(this, entries, null, cancellationToken);
- return true;
- }
- return false;
- }
-
- private void update(CancellationToken cancel)
- {
- if (cancel.isCancellationRequested()) return;
-
- MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
- if (mediaPlayerController == null)
- {
- return;
- }
-
- if (currentRevision != mediaPlayerController.getPlayListUpdateRevision())
- {
- onDownloadListChanged();
- }
-
- if (currentPlaying != mediaPlayerController.getCurrentPlaying())
- {
- onCurrentChanged();
- }
-
- onSliderProgressChanged();
- getActivity().invalidateOptionsMenu();
- }
-
- private void savePlaylistInBackground(final String playlistName)
- {
- Util.toast(getContext(), getResources().getString(R.string.download_playlist_saving, playlistName));
- mediaPlayerControllerLazy.getValue().setSuggestedPlaylistName(playlistName);
- new SilentBackgroundTask(getActivity())
- {
- @Override
- protected Void doInBackground() throws Throwable
- {
- final List entries = new LinkedList<>();
- for (final DownloadFile downloadFile : mediaPlayerControllerLazy.getValue().getPlayList())
- {
- entries.add(downloadFile.getSong());
- }
- final MusicService musicService = MusicServiceFactory.getMusicService();
- musicService.createPlaylist(null, playlistName, entries);
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- Util.toast(getContext(), R.string.download_playlist_done);
- }
-
- @Override
- protected void error(final Throwable error)
- {
- Timber.e(error, "Exception has occurred in savePlaylistInBackground");
- final String msg = String.format("%s %s", getResources().getString(R.string.download_playlist_error), getErrorMessage(error));
- Util.toast(getContext(), msg);
- }
- }.execute();
- }
-
- private void toggleFullScreenAlbumArt()
- {
- if (playlistFlipper.getDisplayedChild() == 1)
- {
- playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.push_down_in));
- playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.push_down_out));
- playlistFlipper.setDisplayedChild(0);
- }
- else
- {
- playlistFlipper.setInAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.push_up_in));
- playlistFlipper.setOutAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.push_up_out));
- playlistFlipper.setDisplayedChild(1);
- }
-
- scrollToCurrent();
- }
-
- private void start()
- {
- final MediaPlayerController service = mediaPlayerControllerLazy.getValue();
- final PlayerState state = service.getPlayerState();
-
- if (state == PAUSED || state == COMPLETED || state == STOPPED)
- {
- service.start();
- }
- else if (state == IDLE)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
-
- final int current = mediaPlayerControllerLazy.getValue().getCurrentPlayingNumberOnPlaylist();
-
- if (current == -1)
- {
- service.play(0);
- }
- else
- {
- service.play(current);
- }
- }
- }
-
- private void onDownloadListChanged()
- {
- final MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
- if (mediaPlayerController == null)
- {
- return;
- }
-
- final List list = mediaPlayerController.getPlayList();
-
- emptyTextView.setText(R.string.download_empty);
- final SongListAdapter adapter = new SongListAdapter(getContext(), list);
- playlistView.setAdapter(adapter);
-
- playlistView.setDragSortListener(new DragSortListView.DragSortListener()
- {
- @Override
- public void drop(int from, int to)
- {
- if (from != to)
- {
- DownloadFile item = adapter.getItem(from);
- adapter.remove(item);
- adapter.notifyDataSetChanged();
- adapter.insert(item, to);
- adapter.notifyDataSetChanged();
- }
- }
-
- @Override
- public void drag(int from, int to)
- {
-
- }
-
- @Override
- public void remove(int which)
- {
- DownloadFile item = adapter.getItem(which);
- MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
-
- if (item == null || mediaPlayerController == null)
- {
- return;
- }
-
- DownloadFile currentPlaying = mediaPlayerController.getCurrentPlaying();
-
- if (currentPlaying == item)
- {
- mediaPlayerControllerLazy.getValue().next();
- }
-
- adapter.remove(item);
- adapter.notifyDataSetChanged();
-
- String songRemoved = String.format(getResources().getString(R.string.download_song_removed), item.getSong().getTitle());
-
- Util.toast(getContext(), songRemoved);
-
- onDownloadListChanged();
- onCurrentChanged();
- }
- });
-
- emptyTextView.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
- currentRevision = mediaPlayerController.getPlayListUpdateRevision();
-
- switch (mediaPlayerController.getRepeatMode())
- {
- case OFF:
- repeatButton.setImageDrawable(Util.getDrawableFromAttribute(getContext(), R.attr.media_repeat_off));
- break;
- case ALL:
- repeatButton.setImageDrawable(Util.getDrawableFromAttribute(getContext(), R.attr.media_repeat_all));
- break;
- case SINGLE:
- repeatButton.setImageDrawable(Util.getDrawableFromAttribute(getContext(), R.attr.media_repeat_single));
- break;
- default:
- break;
- }
- }
-
- private void onCurrentChanged()
- {
- MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
-
- if (mediaPlayerController == null)
- {
- return;
- }
-
- currentPlaying = mediaPlayerController.getCurrentPlaying();
-
- scrollToCurrent();
-
- long totalDuration = mediaPlayerController.getPlayListDuration();
- long totalSongs = mediaPlayerController.getPlaylistSize();
- int currentSongIndex = mediaPlayerController.getCurrentPlayingNumberOnPlaylist() + 1;
-
- String duration = Util.formatTotalDuration(totalDuration);
-
- String trackFormat = String.format(Locale.getDefault(), "%d / %d", currentSongIndex, totalSongs);
-
- if (currentPlaying != null)
- {
- currentSong = currentPlaying.getSong();
- songTitleTextView.setText(currentSong.getTitle());
- albumTextView.setText(currentSong.getAlbum());
- artistTextView.setText(currentSong.getArtist());
- downloadTrackTextView.setText(trackFormat);
- downloadTotalDurationTextView.setText(duration);
- imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, currentSong, true, 0);
-
- displaySongRating();
- }
- else
- {
- currentSong = null;
- songTitleTextView.setText(null);
- albumTextView.setText(null);
- artistTextView.setText(null);
- downloadTrackTextView.setText(null);
- downloadTotalDurationTextView.setText(null);
- imageLoaderProvider.getValue().getImageLoader().loadImage(albumArtImageView, null, true, 0);
- }
- }
-
- private void onSliderProgressChanged()
- {
- MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
-
- if (mediaPlayerController == null || onProgressChangedTask != null)
- {
- return;
- }
-
- onProgressChangedTask = new SilentBackgroundTask(getActivity())
- {
- MediaPlayerController mediaPlayerController;
- boolean isJukeboxEnabled;
- int millisPlayed;
- Integer duration;
- PlayerState playerState;
-
- @Override
- protected Void doInBackground()
- {
- this.mediaPlayerController = mediaPlayerControllerLazy.getValue();
- isJukeboxEnabled = this.mediaPlayerController.isJukeboxEnabled();
- millisPlayed = Math.max(0, this.mediaPlayerController.getPlayerPosition());
- duration = this.mediaPlayerController.getPlayerDuration();
- playerState = mediaPlayerControllerLazy.getValue().getPlayerState();
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- if (cancellationToken.isCancellationRequested()) return;
- if (currentPlaying != null)
- {
- final int millisTotal = duration == null ? 0 : duration;
-
- positionTextView.setText(Util.formatTotalDuration(millisPlayed, true));
- durationTextView.setText(Util.formatTotalDuration(millisTotal, true));
- progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug.
- progressBar.setProgress(millisPlayed);
- progressBar.setEnabled(currentPlaying.isWorkDone() || isJukeboxEnabled);
- }
- else
- {
- positionTextView.setText(R.string.util_zero_time);
- durationTextView.setText(R.string.util_no_time);
- progressBar.setProgress(0);
- progressBar.setMax(0);
- progressBar.setEnabled(false);
- }
-
- switch (playerState)
- {
- case DOWNLOADING:
- int progress = currentPlaying != null ? currentPlaying.getProgress().getValue() : 0;
- String downloadStatus = getResources().getString(R.string.download_playerstate_downloading, Util.formatPercentage(progress));
- FragmentTitle.Companion.setTitle(PlayerFragment.this, downloadStatus);
- break;
- case PREPARING:
- FragmentTitle.Companion.setTitle(PlayerFragment.this, R.string.download_playerstate_buffering);
- break;
- case STARTED:
- final MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
-
- if (mediaPlayerController != null && mediaPlayerController.isShufflePlayEnabled())
- {
- FragmentTitle.Companion.setTitle(PlayerFragment.this, R.string.download_playerstate_playing_shuffle);
- }
- else
- {
- FragmentTitle.Companion.setTitle(PlayerFragment.this, R.string.common_appname);
- }
- break;
- default:
- FragmentTitle.Companion.setTitle(PlayerFragment.this, R.string.common_appname);
- break;
- case IDLE:
- case PREPARED:
- case STOPPED:
- case PAUSED:
- case COMPLETED:
- break;
- }
-
- switch (playerState)
- {
- case STARTED:
- pauseButton.setVisibility(View.VISIBLE);
- stopButton.setVisibility(View.GONE);
- startButton.setVisibility(View.GONE);
- break;
- case DOWNLOADING:
- case PREPARING:
- pauseButton.setVisibility(View.GONE);
- stopButton.setVisibility(View.VISIBLE);
- startButton.setVisibility(View.GONE);
- break;
- default:
- pauseButton.setVisibility(View.GONE);
- stopButton.setVisibility(View.GONE);
- startButton.setVisibility(View.VISIBLE);
- break;
- }
-
- // 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;
- }
- };
- onProgressChangedTask.execute();
- }
-
- private void changeProgress(final int ms)
- {
- final MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
- if (mediaPlayerController == null)
- {
- return;
- }
-
- new SilentBackgroundTask(getActivity())
- {
- int msPlayed;
- Integer duration;
- int seekTo;
-
- @Override
- protected Void doInBackground()
- {
- msPlayed = Math.max(0, mediaPlayerController.getPlayerPosition());
- duration = mediaPlayerController.getPlayerDuration();
-
- final int msTotal = duration;
- seekTo = Math.min(msPlayed + ms, msTotal);
- mediaPlayerController.seekTo(seekTo);
- return null;
- }
-
- @Override
- protected void done(final Void result)
- {
- progressBar.setProgress(seekTo);
- }
- }.execute();
- }
-
- @Override
- public boolean onDown(final MotionEvent me)
- {
- return false;
- }
-
- @Override
- public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY)
- {
-
- final MediaPlayerController mediaPlayerController = mediaPlayerControllerLazy.getValue();
-
- if (mediaPlayerController == null || e1 == null || e2 == null)
- {
- return false;
- }
-
- float e1X = e1.getX();
- float e2X = e2.getX();
- float e1Y = e1.getY();
- float e2Y = e2.getY();
- float absX = Math.abs(velocityX);
- float absY = Math.abs(velocityY);
-
- // Right to Left swipe
- if (e1X - e2X > swipeDistance && absX > swipeVelocity)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
- mediaPlayerController.next();
- onCurrentChanged();
- onSliderProgressChanged();
- return true;
- }
-
- // Left to Right swipe
- if (e2X - e1X > swipeDistance && absX > swipeVelocity)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
- mediaPlayerController.previous();
- onCurrentChanged();
- onSliderProgressChanged();
- return true;
- }
-
- // Top to Bottom swipe
- if (e2Y - e1Y > swipeDistance && absY > swipeVelocity)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
- mediaPlayerController.seekTo(mediaPlayerController.getPlayerPosition() + 30000);
- onSliderProgressChanged();
- return true;
- }
-
- // Bottom to Top swipe
- if (e1Y - e2Y > swipeDistance && absY > swipeVelocity)
- {
- networkAndStorageChecker.getValue().warnIfNetworkOrStorageUnavailable();
- mediaPlayerController.seekTo(mediaPlayerController.getPlayerPosition() - 8000);
- onSliderProgressChanged();
- return true;
- }
-
- return false;
- }
-
- @Override
- public void onLongPress(final MotionEvent e)
- {
- }
-
- @Override
- public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY)
- {
- return false;
- }
-
- @Override
- public void onShowPress(final MotionEvent e)
- {
- }
-
- @Override
- public boolean onSingleTapUp(final MotionEvent e)
- {
- return false;
- }
-
- public static SeekBar getProgressBar()
- {
- return progressBar;
- }
-
- private void displaySongRating()
- {
- int rating = currentSong == null || currentSong.getUserRating() == null ? 0 : currentSong.getUserRating();
- fiveStar1ImageView.setImageDrawable(rating > 0 ? fullStar : hollowStar);
- fiveStar2ImageView.setImageDrawable(rating > 1 ? fullStar : hollowStar);
- fiveStar3ImageView.setImageDrawable(rating > 2 ? fullStar : hollowStar);
- fiveStar4ImageView.setImageDrawable(rating > 3 ? fullStar : hollowStar);
- fiveStar5ImageView.setImageDrawable(rating > 4 ? fullStar : hollowStar);
- }
-
- private void setSongRating(final int rating)
- {
- if (currentSong == null)
- return;
-
- displaySongRating();
- mediaPlayerControllerLazy.getValue().setSongRating(rating);
- }
-
- private void showSavePlaylistDialog() {
- final AlertDialog.Builder builder;
-
- final LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(LAYOUT_INFLATER_SERVICE);
- final View layout = layoutInflater.inflate(R.layout.save_playlist, (ViewGroup) getActivity().findViewById(R.id.save_playlist_root));
-
- if (layout != null)
- {
- playlistNameView = layout.findViewById(R.id.save_playlist_name);
- }
-
- builder = new AlertDialog.Builder(getContext());
- builder.setTitle(R.string.download_playlist_title);
- builder.setMessage(R.string.download_playlist_name);
- builder.setPositiveButton(R.string.common_save, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(final DialogInterface dialog, final int clickId)
- {
- savePlaylistInBackground(String.valueOf(playlistNameView.getText()));
- }
- });
- builder.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener()
- {
- @Override
- public void onClick(final DialogInterface dialog, final int clickId)
- {
- dialog.cancel();
- }
- });
- builder.setView(layout);
- builder.setCancelable(true);
-
- AlertDialog dialog = builder.create();
-
- final String playlistName = mediaPlayerControllerLazy.getValue().getSuggestedPlaylistName();
- if (playlistName != null)
- {
- playlistNameView.setText(playlistName);
- }
- else
- {
- final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
- playlistNameView.setText(dateFormat.format(new Date()));
- }
-
- dialog.show();
- }
-}
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java
index b47929d9..f768de46 100644
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java
+++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java
@@ -1,7 +1,5 @@
package org.moire.ultrasonic.service;
-import timber.log.Timber;
-
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.util.LRUCache;
import org.moire.ultrasonic.util.ShufflePlayBuffer;
@@ -16,6 +14,7 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import kotlin.Lazy;
+import timber.log.Timber;
import static org.koin.java.KoinJavaComponent.inject;
import static org.moire.ultrasonic.domain.PlayerState.DOWNLOADING;
@@ -342,7 +341,7 @@ public class Downloader
Collections.shuffle(downloadList);
if (localMediaPlayer.currentPlaying != null)
{
- downloadList.remove(getCurrentPlayingIndex());
+ downloadList.remove(localMediaPlayer.currentPlaying);
downloadList.add(0, localMediaPlayer.currentPlaying);
}
revision++;
diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/SilentBackgroundTask.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/SilentBackgroundTask.java
deleted file mode 100644
index 6e751ed1..00000000
--- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/SilentBackgroundTask.java
+++ /dev/null
@@ -1,81 +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 .
-
- Copyright 2010 (C) Sindre Mehus
- */
-package org.moire.ultrasonic.util;
-
-import android.app.Activity;
-
-/**
- * @author Sindre Mehus
- */
-public abstract class SilentBackgroundTask extends BackgroundTask
-{
-
- public SilentBackgroundTask(Activity activity)
- {
- super(activity);
- }
-
- @Override
- public void execute()
- {
- Thread thread = new Thread()
- {
- @Override
- public void run()
- {
- try
- {
- final T result = doInBackground();
-
- getHandler().post(new Runnable()
- {
- @Override
- public void run()
- {
- done(result);
- }
- });
-
- }
- catch (final Throwable t)
- {
- getHandler().post(new Runnable()
- {
- @Override
- public void run()
- {
- error(t);
- }
- });
- }
- }
- };
- thread.start();
- }
-
- @Override
- public void updateProgress(int messageId)
- {
- }
-
- @Override
- public void updateProgress(String message)
- {
- }
-}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt
new file mode 100644
index 00000000..af4f7be9
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt
@@ -0,0 +1,1203 @@
+/*
+ * PlayerFragment.kt
+ * Copyright (C) 2009-2021 Ultrasonic developers
+ *
+ * Distributed under terms of the GNU GPLv3 license.
+ */
+
+package org.moire.ultrasonic.fragment
+
+import android.annotation.SuppressLint
+import android.app.AlertDialog
+import android.graphics.Point
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.os.Handler
+import android.view.ContextMenu
+import android.view.ContextMenu.ContextMenuInfo
+import android.view.GestureDetector
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuInflater
+import android.view.MenuItem
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.animation.AnimationUtils
+import android.widget.AdapterView.AdapterContextMenuInfo
+import android.widget.EditText
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.SeekBar
+import android.widget.SeekBar.OnSeekBarChangeListener
+import android.widget.TextView
+import android.widget.ViewFlipper
+import androidx.fragment.app.Fragment
+import androidx.navigation.Navigation
+import com.mobeta.android.dslv.DragSortListView
+import com.mobeta.android.dslv.DragSortListView.DragSortListener
+import java.text.DateFormat
+import java.text.SimpleDateFormat
+import java.util.ArrayList
+import java.util.Date
+import java.util.LinkedList
+import java.util.Locale
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledExecutorService
+import java.util.concurrent.TimeUnit
+import kotlin.math.abs
+import kotlin.math.max
+import org.koin.android.ext.android.inject
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.get
+import org.moire.ultrasonic.R
+import org.moire.ultrasonic.audiofx.EqualizerController
+import org.moire.ultrasonic.audiofx.VisualizerController
+import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
+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.fragment.FragmentTitle.Companion.setTitle
+import org.moire.ultrasonic.service.DownloadFile
+import org.moire.ultrasonic.service.LocalMediaPlayer
+import org.moire.ultrasonic.service.MediaPlayerController
+import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
+import org.moire.ultrasonic.subsonic.ImageLoaderProvider
+import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
+import org.moire.ultrasonic.subsonic.ShareHandler
+import org.moire.ultrasonic.util.CancellationToken
+import org.moire.ultrasonic.util.Constants
+import org.moire.ultrasonic.util.SilentBackgroundTask
+import org.moire.ultrasonic.util.Util
+import org.moire.ultrasonic.view.AutoRepeatButton
+import org.moire.ultrasonic.view.SongListAdapter
+import org.moire.ultrasonic.view.VisualizerView
+import timber.log.Timber
+
+/**
+ * Contains the Music Player screen of Ultrasonic with playback controls and the playlist
+ *
+ * TODO: This class was more or less straight converted from Java legacy code.
+ * There are many places where further cleanup would be nice.
+ * The usage of threads and SilentBackgroundTask can be replaced with Coroutines.
+ */
+@Suppress("LargeClass", "TooManyFunctions", "MagicNumber")
+class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinComponent {
+ // Settings
+ private var currentRevision: Long = 0
+ private var swipeDistance = 0
+ private var swipeVelocity = 0
+ private var jukeboxAvailable = false
+ private var useFiveStarRating = false
+ private var isEqualizerAvailable = false
+ private var isVisualizerAvailable = false
+
+ // Detectors & Callbacks
+ private lateinit var gestureScanner: GestureDetector
+ private lateinit var cancellationToken: CancellationToken
+
+ // Data & Services
+ private val networkAndStorageChecker: NetworkAndStorageChecker by inject()
+ private val mediaPlayerController: MediaPlayerController by inject()
+ private val localMediaPlayer: LocalMediaPlayer by inject()
+ private val shareHandler: ShareHandler by inject()
+ private val imageLoaderProvider: ImageLoaderProvider by inject()
+ private lateinit var executorService: ScheduledExecutorService
+ private var currentPlaying: DownloadFile? = null
+ private var currentSong: MusicDirectory.Entry? = null
+ private var onProgressChangedTask: SilentBackgroundTask? = null
+
+ // Views and UI Elements
+ private lateinit var visualizerViewLayout: LinearLayout
+ private lateinit var visualizerView: VisualizerView
+ private lateinit var playlistNameView: EditText
+ private lateinit var starMenuItem: MenuItem
+ private lateinit var fiveStar1ImageView: ImageView
+ private lateinit var fiveStar2ImageView: ImageView
+ private lateinit var fiveStar3ImageView: ImageView
+ private lateinit var fiveStar4ImageView: ImageView
+ private lateinit var fiveStar5ImageView: ImageView
+ private lateinit var playlistFlipper: ViewFlipper
+ private lateinit var emptyTextView: TextView
+ private lateinit var songTitleTextView: TextView
+ private lateinit var albumTextView: TextView
+ private lateinit var artistTextView: TextView
+ private lateinit var albumArtImageView: ImageView
+ private lateinit var playlistView: DragSortListView
+ private lateinit var positionTextView: TextView
+ private lateinit var downloadTrackTextView: TextView
+ private lateinit var downloadTotalDurationTextView: TextView
+ private lateinit var durationTextView: TextView
+ private lateinit var pauseButton: View
+ private lateinit var stopButton: View
+ private lateinit var startButton: View
+ private lateinit var repeatButton: ImageView
+ private lateinit var hollowStar: Drawable
+ private lateinit var fullStar: Drawable
+ private lateinit var progressBar: SeekBar
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ Util.applyTheme(this.context)
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.current_playing, container, false)
+ }
+
+ fun findViews(view: View) {
+ playlistFlipper = view.findViewById(R.id.current_playing_playlist_flipper)
+ emptyTextView = view.findViewById(R.id.playlist_empty)
+ songTitleTextView = view.findViewById(R.id.current_playing_song)
+ albumTextView = view.findViewById(R.id.current_playing_album)
+ artistTextView = view.findViewById(R.id.current_playing_artist)
+ albumArtImageView = view.findViewById(R.id.current_playing_album_art_image)
+ positionTextView = view.findViewById(R.id.current_playing_position)
+ downloadTrackTextView = view.findViewById(R.id.current_playing_track)
+ downloadTotalDurationTextView = view.findViewById(R.id.current_total_duration)
+ durationTextView = view.findViewById(R.id.current_playing_duration)
+ progressBar = view.findViewById(R.id.current_playing_progress_bar)
+ playlistView = view.findViewById(R.id.playlist_view)
+
+ pauseButton = view.findViewById(R.id.button_pause)
+ stopButton = view.findViewById(R.id.button_stop)
+ startButton = view.findViewById(R.id.button_start)
+ repeatButton = view.findViewById(R.id.button_repeat)
+ visualizerViewLayout = view.findViewById(R.id.current_playing_visualizer_layout)
+ fiveStar1ImageView = view.findViewById(R.id.song_five_star_1)
+ fiveStar2ImageView = view.findViewById(R.id.song_five_star_2)
+ fiveStar3ImageView = view.findViewById(R.id.song_five_star_3)
+ fiveStar4ImageView = view.findViewById(R.id.song_five_star_4)
+ fiveStar5ImageView = view.findViewById(R.id.song_five_star_5)
+ }
+
+ @Suppress("LongMethod")
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ cancellationToken = CancellationToken()
+ setTitle(this, R.string.common_appname)
+ val windowManager = requireActivity().windowManager
+ val display = windowManager.defaultDisplay
+ val size = Point()
+ display.getSize(size)
+ val width = size.x
+ val height = size.y
+ setHasOptionsMenu(true)
+ useFiveStarRating = get().isFeatureEnabled(Feature.FIVE_STAR_RATING)
+ swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100
+ swipeVelocity = swipeDistance
+ gestureScanner = GestureDetector(context, this)
+
+ // The secondary progress is an indicator of how far the song is cached.
+ localMediaPlayer.secondaryProgress.observe(
+ viewLifecycleOwner,
+ {
+ progressBar.secondaryProgress = it
+ }
+ )
+
+ findViews(view)
+ val previousButton: AutoRepeatButton = view.findViewById(R.id.button_previous)
+ val nextButton: AutoRepeatButton = view.findViewById(R.id.button_next)
+ val shuffleButton = view.findViewById(R.id.button_shuffle)
+ val ratingLinearLayout = view.findViewById(R.id.song_rating)
+ if (!useFiveStarRating) ratingLinearLayout.visibility = View.GONE
+ hollowStar = Util.getDrawableFromAttribute(view.context, R.attr.star_hollow)
+ fullStar = Util.getDrawableFromAttribute(context, R.attr.star_full)
+
+ fiveStar1ImageView.setOnClickListener { setSongRating(1) }
+ fiveStar2ImageView.setOnClickListener { setSongRating(2) }
+ fiveStar3ImageView.setOnClickListener { setSongRating(3) }
+ fiveStar4ImageView.setOnClickListener { setSongRating(4) }
+ fiveStar5ImageView.setOnClickListener { setSongRating(5) }
+
+ albumArtImageView.setOnTouchListener { _, me ->
+ gestureScanner.onTouchEvent(me)
+ }
+
+ albumArtImageView.setOnClickListener {
+ toggleFullScreenAlbumArt()
+ }
+
+ previousButton.setOnClickListener {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ object : SilentBackgroundTask(activity) {
+ override fun doInBackground(): Void? {
+ mediaPlayerController.previous()
+ return null
+ }
+
+ override fun done(result: Void?) {
+ onCurrentChanged()
+ onSliderProgressChanged()
+ }
+ }.execute()
+ }
+
+ previousButton.setOnRepeatListener {
+ val incrementTime = Util.getIncrementTime()
+ changeProgress(-incrementTime)
+ }
+
+ nextButton.setOnClickListener {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ object : SilentBackgroundTask(activity) {
+ override fun doInBackground(): Boolean {
+ mediaPlayerController.next()
+ return true
+ }
+
+ override fun done(result: Boolean?) {
+ if (result == true) {
+ onCurrentChanged()
+ onSliderProgressChanged()
+ }
+ }
+ }.execute()
+ }
+
+ nextButton.setOnRepeatListener {
+ val incrementTime = Util.getIncrementTime()
+ changeProgress(incrementTime)
+ }
+ pauseButton.setOnClickListener {
+ object : SilentBackgroundTask(activity) {
+ override fun doInBackground(): Void? {
+ mediaPlayerController.pause()
+ return null
+ }
+
+ override fun done(result: Void?) {
+ onCurrentChanged()
+ onSliderProgressChanged()
+ }
+ }.execute()
+ }
+ stopButton.setOnClickListener {
+ object : SilentBackgroundTask(activity) {
+ override fun doInBackground(): Void? {
+ mediaPlayerController.reset()
+ return null
+ }
+
+ override fun done(result: Void?) {
+ onCurrentChanged()
+ onSliderProgressChanged()
+ }
+ }.execute()
+ }
+ startButton.setOnClickListener {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ object : SilentBackgroundTask(activity) {
+ override fun doInBackground(): Void? {
+ start()
+ return null
+ }
+
+ override fun done(result: Void?) {
+ onCurrentChanged()
+ onSliderProgressChanged()
+ }
+ }.execute()
+ }
+ shuffleButton.setOnClickListener {
+ mediaPlayerController.shuffle()
+ Util.toast(activity, R.string.download_menu_shuffle_notification)
+ }
+
+ repeatButton.setOnClickListener {
+ val repeatMode = mediaPlayerController.repeatMode?.next()
+ mediaPlayerController.repeatMode = repeatMode
+ onDownloadListChanged()
+ when (repeatMode) {
+ RepeatMode.OFF -> Util.toast(
+ context, R.string.download_repeat_off
+ )
+ RepeatMode.ALL -> Util.toast(
+ context, R.string.download_repeat_all
+ )
+ RepeatMode.SINGLE -> Util.toast(
+ context, R.string.download_repeat_single
+ )
+ else -> {
+ }
+ }
+ }
+
+ progressBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
+ override fun onStopTrackingTouch(seekBar: SeekBar) {
+ object : SilentBackgroundTask(activity) {
+ override fun doInBackground(): Void? {
+ mediaPlayerController.seekTo(progressBar.progress)
+ return null
+ }
+
+ override fun done(result: Void?) {
+ onSliderProgressChanged()
+ }
+ }.execute()
+ }
+
+ override fun onStartTrackingTouch(seekBar: SeekBar) {}
+ override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
+ })
+
+ playlistView.setOnItemClickListener { _, _, position, _ ->
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ object : SilentBackgroundTask(activity) {
+ override fun doInBackground(): Void? {
+ mediaPlayerController.play(position)
+ return null
+ }
+
+ override fun done(result: Void?) {
+ onCurrentChanged()
+ onSliderProgressChanged()
+ }
+ }.execute()
+ }
+ registerForContextMenu(playlistView)
+
+ if (arguments != null && requireArguments().getBoolean(
+ Constants.INTENT_EXTRA_NAME_SHUFFLE,
+ false
+ )
+ ) {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ mediaPlayerController.isShufflePlayEnabled = true
+ }
+
+ visualizerViewLayout.visibility = View.GONE
+ VisualizerController.get().observe(
+ requireActivity(),
+ { visualizerController ->
+ if (visualizerController != null) {
+ Timber.d("VisualizerController Observer.onChanged received controller")
+ visualizerView = VisualizerView(context)
+ visualizerViewLayout.addView(
+ visualizerView,
+ LinearLayout.LayoutParams(
+ LinearLayout.LayoutParams.MATCH_PARENT,
+ LinearLayout.LayoutParams.MATCH_PARENT
+ )
+ )
+ if (!visualizerView.isActive) {
+ visualizerViewLayout.visibility = View.GONE
+ } else {
+ visualizerViewLayout.visibility = View.VISIBLE
+ }
+ visualizerView.setOnTouchListener { _, _ ->
+ visualizerView.isActive = !visualizerView.isActive
+ mediaPlayerController.showVisualization = visualizerView.isActive
+ true
+ }
+ isVisualizerAvailable = true
+ } else {
+ Timber.d("VisualizerController Observer.onChanged has no controller")
+ visualizerViewLayout.visibility = View.GONE
+ isVisualizerAvailable = false
+ }
+ }
+ )
+
+ EqualizerController.get().observe(
+ requireActivity(),
+ { equalizerController ->
+ isEqualizerAvailable = if (equalizerController != null) {
+ Timber.d("EqualizerController Observer.onChanged received controller")
+ true
+ } else {
+ Timber.d("EqualizerController Observer.onChanged has no controller")
+ false
+ }
+ }
+ )
+ Thread {
+ try {
+ jukeboxAvailable = mediaPlayerController.isJukeboxAvailable
+ } catch (all: Exception) {
+ Timber.e(all)
+ }
+ }.start()
+ view.setOnTouchListener { _, event -> gestureScanner.onTouchEvent(event) }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (mediaPlayerController.currentPlaying == null) {
+ playlistFlipper.displayedChild = 1
+ } else {
+ // Download list and Album art must be updated when Resumed
+ onDownloadListChanged()
+ onCurrentChanged()
+ }
+ val handler = Handler()
+ val runnable = Runnable { handler.post { update(cancellationToken) } }
+ executorService = Executors.newSingleThreadScheduledExecutor()
+ executorService.scheduleWithFixedDelay(runnable, 0L, 250L, TimeUnit.MILLISECONDS)
+
+ if (mediaPlayerController.keepScreenOn) {
+ requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ } else {
+ requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ }
+
+ if (::visualizerView.isInitialized) {
+ visualizerView.isActive = mediaPlayerController.showVisualization
+ }
+
+ requireActivity().invalidateOptionsMenu()
+ }
+
+ // Scroll to current playing/downloading.
+ private fun scrollToCurrent() {
+ val adapter = playlistView.adapter
+ if (adapter != null) {
+ val count = adapter.count
+ for (i in 0 until count) {
+ if (currentPlaying == playlistView.getItemAtPosition(i)) {
+ playlistView.smoothScrollToPositionFromTop(i, 40)
+ return
+ }
+ }
+ val currentDownloading = mediaPlayerController.currentDownloading
+ for (i in 0 until count) {
+ if (currentDownloading == playlistView.getItemAtPosition(i)) {
+ playlistView.smoothScrollToPositionFromTop(i, 40)
+ return
+ }
+ }
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ executorService.shutdown()
+ if (::visualizerView.isInitialized) {
+ visualizerView.isActive = mediaPlayerController.showVisualization
+ }
+ }
+
+ override fun onDestroyView() {
+ cancellationToken.cancel()
+ super.onDestroyView()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
+ inflater.inflate(R.menu.nowplaying, menu)
+ super.onCreateOptionsMenu(menu, inflater)
+ }
+
+ @Suppress("ComplexMethod", "LongMethod", "NestedBlockDepth")
+ override fun onPrepareOptionsMenu(menu: Menu) {
+ super.onPrepareOptionsMenu(menu)
+ val screenOption = menu.findItem(R.id.menu_item_screen_on_off)
+ val jukeboxOption = menu.findItem(R.id.menu_item_jukebox)
+ val equalizerMenuItem = menu.findItem(R.id.menu_item_equalizer)
+ val visualizerMenuItem = menu.findItem(R.id.menu_item_visualizer)
+ val shareMenuItem = menu.findItem(R.id.menu_item_share)
+ starMenuItem = menu.findItem(R.id.menu_item_star)
+ val bookmarkMenuItem = menu.findItem(R.id.menu_item_bookmark_set)
+ val bookmarkRemoveMenuItem = menu.findItem(R.id.menu_item_bookmark_delete)
+
+ if (isOffline()) {
+ if (shareMenuItem != null) {
+ shareMenuItem.isVisible = false
+ }
+ starMenuItem.isVisible = false
+ if (bookmarkMenuItem != null) {
+ bookmarkMenuItem.isVisible = false
+ }
+ if (bookmarkRemoveMenuItem != null) {
+ bookmarkRemoveMenuItem.isVisible = false
+ }
+ }
+ if (equalizerMenuItem != null) {
+ equalizerMenuItem.isEnabled = isEqualizerAvailable
+ equalizerMenuItem.isVisible = isEqualizerAvailable
+ }
+ if (visualizerMenuItem != null) {
+ visualizerMenuItem.isEnabled = isVisualizerAvailable
+ visualizerMenuItem.isVisible = isVisualizerAvailable
+ }
+ val mediaPlayerController = mediaPlayerController
+ val downloadFile = mediaPlayerController.currentPlaying
+ if (downloadFile != null) {
+ currentSong = downloadFile.song
+ }
+ if (useFiveStarRating) starMenuItem.isVisible = false
+ if (currentSong != null) {
+ starMenuItem.icon = if (currentSong!!.starred) fullStar else hollowStar
+ } else {
+ starMenuItem.icon = hollowStar
+ }
+ if (mediaPlayerController.keepScreenOn) {
+ screenOption?.setTitle(R.string.download_menu_screen_off)
+ } else {
+ screenOption?.setTitle(R.string.download_menu_screen_on)
+ }
+ if (jukeboxOption != null) {
+ jukeboxOption.isEnabled = jukeboxAvailable
+ jukeboxOption.isVisible = jukeboxAvailable
+ if (mediaPlayerController.isJukeboxEnabled) {
+ jukeboxOption.setTitle(R.string.download_menu_jukebox_off)
+ } else {
+ jukeboxOption.setTitle(R.string.download_menu_jukebox_on)
+ }
+ }
+ }
+
+ override fun onCreateContextMenu(menu: ContextMenu, view: View, menuInfo: ContextMenuInfo?) {
+ super.onCreateContextMenu(menu, view, menuInfo)
+ if (view === playlistView) {
+ val info = menuInfo as AdapterContextMenuInfo?
+ val downloadFile = playlistView.getItemAtPosition(info!!.position) as DownloadFile
+ val menuInflater = requireActivity().menuInflater
+ menuInflater.inflate(R.menu.nowplaying_context, menu)
+ val song: MusicDirectory.Entry?
+
+ song = downloadFile.song
+
+ if (song.parent == null) {
+ val menuItem = menu.findItem(R.id.menu_show_album)
+ if (menuItem != null) {
+ menuItem.isVisible = false
+ }
+ }
+
+ if (isOffline() || !Util.getShouldUseId3Tags()) {
+ menu.findItem(R.id.menu_show_artist)?.isVisible = false
+ }
+
+ if (isOffline()) {
+ menu.findItem(R.id.menu_lyrics)?.isVisible = false
+ }
+ }
+ }
+
+ override fun onContextItemSelected(menuItem: MenuItem): Boolean {
+ val info = menuItem.menuInfo as AdapterContextMenuInfo
+ val downloadFile = playlistView.getItemAtPosition(info.position) as DownloadFile
+ return menuItemSelected(menuItem.itemId, downloadFile) || super.onContextItemSelected(
+ menuItem
+ )
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return menuItemSelected(item.itemId, null) || super.onOptionsItemSelected(item)
+ }
+
+ @Suppress("ComplexMethod", "LongMethod", "ReturnCount")
+ private fun menuItemSelected(menuItemId: Int, song: DownloadFile?): Boolean {
+ var entry: MusicDirectory.Entry? = null
+ val bundle: Bundle
+ if (song != null) {
+ entry = song.song
+ }
+
+ when (menuItemId) {
+ R.id.menu_show_artist -> {
+ if (entry == null) {
+ return false
+ }
+ if (Util.getShouldUseId3Tags()) {
+ bundle = Bundle()
+ bundle.putString(Constants.INTENT_EXTRA_NAME_ID, entry.artistId)
+ bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.artist)
+ bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.artistId)
+ bundle.putBoolean(Constants.INTENT_EXTRA_NAME_ARTIST, true)
+ Navigation.findNavController(requireView())
+ .navigate(R.id.playerToSelectAlbum, bundle)
+ }
+ return true
+ }
+ R.id.menu_show_album -> {
+ if (entry == null) {
+ return false
+ }
+ val albumId = if (Util.getShouldUseId3Tags()) entry.albumId else entry.parent
+ bundle = Bundle()
+ bundle.putString(Constants.INTENT_EXTRA_NAME_ID, albumId)
+ bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, entry.album)
+ bundle.putString(Constants.INTENT_EXTRA_NAME_PARENT_ID, entry.parent)
+ bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, true)
+ Navigation.findNavController(requireView())
+ .navigate(R.id.playerToSelectAlbum, bundle)
+ return true
+ }
+ R.id.menu_lyrics -> {
+ if (entry == null) {
+ return false
+ }
+ bundle = Bundle()
+ bundle.putString(Constants.INTENT_EXTRA_NAME_ARTIST, entry.artist)
+ bundle.putString(Constants.INTENT_EXTRA_NAME_TITLE, entry.title)
+ Navigation.findNavController(requireView()).navigate(R.id.playerToLyrics, bundle)
+ return true
+ }
+ R.id.menu_remove -> {
+ mediaPlayerController.remove(song!!)
+ onDownloadListChanged()
+ return true
+ }
+ R.id.menu_item_screen_on_off -> {
+ val window = requireActivity().window
+ if (mediaPlayerController.keepScreenOn) {
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ mediaPlayerController.keepScreenOn = false
+ } else {
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ mediaPlayerController.keepScreenOn = true
+ }
+ return true
+ }
+ R.id.menu_shuffle -> {
+ mediaPlayerController.shuffle()
+ Util.toast(context, R.string.download_menu_shuffle_notification)
+ return true
+ }
+ R.id.menu_item_equalizer -> {
+ Navigation.findNavController(requireView()).navigate(R.id.playerToEqualizer)
+ return true
+ }
+ R.id.menu_item_visualizer -> {
+ val active = !visualizerView.isActive
+ visualizerView.isActive = active
+ if (!visualizerView.isActive) {
+ visualizerViewLayout.visibility = View.GONE
+ } else {
+ visualizerViewLayout.visibility = View.VISIBLE
+ }
+ mediaPlayerController.showVisualization = visualizerView.isActive
+ Util.toast(
+ context,
+ if (active) R.string.download_visualizer_on
+ else R.string.download_visualizer_off
+ )
+ return true
+ }
+ R.id.menu_item_jukebox -> {
+ val jukeboxEnabled = !mediaPlayerController.isJukeboxEnabled
+ mediaPlayerController.isJukeboxEnabled = jukeboxEnabled
+ Util.toast(
+ context,
+ if (jukeboxEnabled) R.string.download_jukebox_on
+ else R.string.download_jukebox_off,
+ false
+ )
+ return true
+ }
+ R.id.menu_item_toggle_list -> {
+ toggleFullScreenAlbumArt()
+ return true
+ }
+ R.id.menu_item_clear_playlist -> {
+ mediaPlayerController.isShufflePlayEnabled = false
+ mediaPlayerController.clear()
+ onDownloadListChanged()
+ return true
+ }
+ R.id.menu_item_save_playlist -> {
+ if (mediaPlayerController.playlistSize > 0) {
+ showSavePlaylistDialog()
+ }
+ return true
+ }
+ R.id.menu_item_star -> {
+ if (currentSong == null) {
+ return true
+ }
+ val isStarred = currentSong!!.starred
+ val id = currentSong!!.id
+ if (isStarred) {
+ starMenuItem.icon = hollowStar
+ currentSong!!.starred = false
+ } else {
+ starMenuItem.icon = fullStar
+ currentSong!!.starred = true
+ }
+ Thread {
+ val musicService = getMusicService()
+ try {
+ if (isStarred) {
+ musicService.unstar(id, null, null)
+ } else {
+ musicService.star(id, null, null)
+ }
+ } catch (all: Exception) {
+ Timber.e(all)
+ }
+ }.start()
+ return true
+ }
+ R.id.menu_item_bookmark_set -> {
+ if (currentSong == null) {
+ return true
+ }
+ val songId = currentSong!!.id
+ val playerPosition = mediaPlayerController.playerPosition
+ currentSong!!.bookmarkPosition = playerPosition
+ val bookmarkTime = Util.formatTotalDuration(playerPosition.toLong(), true)
+ Thread {
+ val musicService = getMusicService()
+ try {
+ musicService.createBookmark(songId, playerPosition)
+ } catch (all: Exception) {
+ Timber.e(all)
+ }
+ }.start()
+ val msg = resources.getString(
+ R.string.download_bookmark_set_at_position,
+ bookmarkTime
+ )
+ Util.toast(context, msg)
+ return true
+ }
+ R.id.menu_item_bookmark_delete -> {
+ if (currentSong == null) {
+ return true
+ }
+ val bookmarkSongId = currentSong!!.id
+ currentSong!!.bookmarkPosition = 0
+ Thread {
+ val musicService = getMusicService()
+ try {
+ musicService.deleteBookmark(bookmarkSongId)
+ } catch (all: Exception) {
+ Timber.e(all)
+ }
+ }.start()
+ Util.toast(context, R.string.download_bookmark_removed)
+ return true
+ }
+ R.id.menu_item_share -> {
+ val mediaPlayerController = mediaPlayerController
+ val entries: MutableList = ArrayList()
+ val downloadServiceSongs = mediaPlayerController.playList
+ for (downloadFile in downloadServiceSongs) {
+ val playlistEntry = downloadFile.song
+ entries.add(playlistEntry)
+ }
+ shareHandler.createShare(this, entries, null, cancellationToken)
+ return true
+ }
+ else -> return false
+ }
+ }
+
+ private fun update(cancel: CancellationToken?) {
+ if (cancel!!.isCancellationRequested) return
+ val mediaPlayerController = mediaPlayerController
+ if (currentRevision != mediaPlayerController.playListUpdateRevision) {
+ onDownloadListChanged()
+ }
+ if (currentPlaying != mediaPlayerController.currentPlaying) {
+ onCurrentChanged()
+ }
+ onSliderProgressChanged()
+ requireActivity().invalidateOptionsMenu()
+ }
+
+ private fun savePlaylistInBackground(playlistName: String) {
+ Util.toast(context, resources.getString(R.string.download_playlist_saving, playlistName))
+ mediaPlayerController.suggestedPlaylistName = playlistName
+ object : SilentBackgroundTask(activity) {
+ @Throws(Throwable::class)
+ override fun doInBackground(): Void? {
+ val entries: MutableList = LinkedList()
+ for (downloadFile in mediaPlayerController.playList) {
+ entries.add(downloadFile.song)
+ }
+ val musicService = getMusicService()
+ musicService.createPlaylist(null, playlistName, entries)
+ return null
+ }
+
+ override fun done(result: Void?) {
+ Util.toast(context, R.string.download_playlist_done)
+ }
+
+ override fun error(error: Throwable) {
+ Timber.e(error, "Exception has occurred in savePlaylistInBackground")
+ val msg = String.format(
+ Locale.ROOT,
+ "%s %s",
+ resources.getString(R.string.download_playlist_error),
+ getErrorMessage(error)
+ )
+ Util.toast(context, msg)
+ }
+ }.execute()
+ }
+
+ private fun toggleFullScreenAlbumArt() {
+ if (playlistFlipper.displayedChild == 1) {
+ playlistFlipper.inAnimation =
+ AnimationUtils.loadAnimation(context, R.anim.push_down_in)
+ playlistFlipper.outAnimation =
+ AnimationUtils.loadAnimation(context, R.anim.push_down_out)
+ playlistFlipper.displayedChild = 0
+ } else {
+ playlistFlipper.inAnimation =
+ AnimationUtils.loadAnimation(context, R.anim.push_up_in)
+ playlistFlipper.outAnimation =
+ AnimationUtils.loadAnimation(context, R.anim.push_up_out)
+ playlistFlipper.displayedChild = 1
+ }
+ scrollToCurrent()
+ }
+
+ private fun start() {
+ val service = mediaPlayerController
+ val state = service.playerState
+ if (state === PlayerState.PAUSED ||
+ state === PlayerState.COMPLETED || state === PlayerState.STOPPED
+ ) {
+ service.start()
+ } else if (state === PlayerState.IDLE) {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ val current = mediaPlayerController.currentPlayingNumberOnPlaylist
+ if (current == -1) {
+ service.play(0)
+ } else {
+ service.play(current)
+ }
+ }
+ }
+
+ private fun onDownloadListChanged() {
+ val mediaPlayerController = mediaPlayerController
+ val list = mediaPlayerController.playList
+ emptyTextView.setText(R.string.download_empty)
+ val adapter = SongListAdapter(context, list)
+ playlistView.adapter = adapter
+ playlistView.setDragSortListener(object : DragSortListener {
+ override fun drop(from: Int, to: Int) {
+ if (from != to) {
+ val item = adapter.getItem(from)
+ adapter.remove(item)
+ adapter.notifyDataSetChanged()
+ adapter.insert(item, to)
+ adapter.notifyDataSetChanged()
+ }
+ }
+
+ override fun drag(from: Int, to: Int) {}
+ override fun remove(which: Int) {
+
+ val item = adapter.getItem(which) ?: return
+
+ val currentPlaying = mediaPlayerController.currentPlaying
+ if (currentPlaying == item) {
+ mediaPlayerController.next()
+ }
+ adapter.remove(item)
+ adapter.notifyDataSetChanged()
+ val songRemoved = String.format(
+ resources.getString(R.string.download_song_removed),
+ item.song.title
+ )
+ Util.toast(context, songRemoved)
+ onDownloadListChanged()
+ onCurrentChanged()
+ }
+ })
+ emptyTextView.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
+ currentRevision = mediaPlayerController.playListUpdateRevision
+ when (mediaPlayerController.repeatMode) {
+ RepeatMode.OFF -> repeatButton.setImageDrawable(
+ Util.getDrawableFromAttribute(
+ context, R.attr.media_repeat_off
+ )
+ )
+ RepeatMode.ALL -> repeatButton.setImageDrawable(
+ Util.getDrawableFromAttribute(
+ context, R.attr.media_repeat_all
+ )
+ )
+ RepeatMode.SINGLE -> repeatButton.setImageDrawable(
+ Util.getDrawableFromAttribute(
+ context, R.attr.media_repeat_single
+ )
+ )
+ else -> {
+ }
+ }
+ }
+
+ private fun onCurrentChanged() {
+ currentPlaying = mediaPlayerController.currentPlaying
+ scrollToCurrent()
+ val totalDuration = mediaPlayerController.playListDuration
+ val totalSongs = mediaPlayerController.playlistSize.toLong()
+ val currentSongIndex = mediaPlayerController.currentPlayingNumberOnPlaylist + 1
+ val duration = Util.formatTotalDuration(totalDuration)
+ val trackFormat =
+ String.format(Locale.getDefault(), "%d / %d", currentSongIndex, totalSongs)
+ if (currentPlaying != null) {
+ currentSong = currentPlaying!!.song
+ songTitleTextView.text = currentSong!!.title
+ albumTextView.text = currentSong!!.album
+ artistTextView.text = currentSong!!.artist
+ downloadTrackTextView.text = trackFormat
+ downloadTotalDurationTextView.text = duration
+ imageLoaderProvider.getImageLoader()
+ .loadImage(albumArtImageView, currentSong, true, 0)
+ displaySongRating()
+ } else {
+ currentSong = null
+ songTitleTextView.text = null
+ albumTextView.text = null
+ artistTextView.text = null
+ downloadTrackTextView.text = null
+ downloadTotalDurationTextView.text = null
+ imageLoaderProvider.getImageLoader()
+ .loadImage(albumArtImageView, null, true, 0)
+ }
+ }
+
+ private fun onSliderProgressChanged() {
+ if (onProgressChangedTask != null) {
+ return
+ }
+ onProgressChangedTask = object : SilentBackgroundTask(activity) {
+ var isJukeboxEnabled = false
+ var millisPlayed = 0
+ var duration: Int? = null
+ var playerState: PlayerState? = null
+ override fun doInBackground(): Void? {
+ isJukeboxEnabled = mediaPlayerController.isJukeboxEnabled
+ millisPlayed = max(0, mediaPlayerController.playerPosition)
+ duration = mediaPlayerController.playerDuration
+ playerState = mediaPlayerController.playerState
+ return null
+ }
+
+ @Suppress("LongMethod")
+ override fun done(result: Void?) {
+ if (cancellationToken.isCancellationRequested) return
+ if (currentPlaying != null) {
+ val millisTotal = if (duration == null) 0 else duration!!
+ positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true)
+ durationTextView.text = Util.formatTotalDuration(millisTotal.toLong(), true)
+ progressBar.max =
+ if (millisTotal == 0) 100 else millisTotal // Work-around for apparent bug.
+ progressBar.progress = millisPlayed
+ progressBar.isEnabled = currentPlaying!!.isWorkDone || isJukeboxEnabled
+ } else {
+ positionTextView.setText(R.string.util_zero_time)
+ durationTextView.setText(R.string.util_no_time)
+ progressBar.progress = 0
+ progressBar.max = 0
+ progressBar.isEnabled = false
+ }
+
+ when (playerState) {
+ PlayerState.DOWNLOADING -> {
+ val progress =
+ if (currentPlaying != null) currentPlaying!!.progress.value!! else 0
+ val downloadStatus = resources.getString(
+ R.string.download_playerstate_downloading,
+ Util.formatPercentage(progress)
+ )
+ setTitle(this@PlayerFragment, downloadStatus)
+ }
+ PlayerState.PREPARING -> setTitle(
+ this@PlayerFragment,
+ R.string.download_playerstate_buffering
+ )
+ PlayerState.STARTED -> {
+ if (mediaPlayerController.isShufflePlayEnabled) {
+ setTitle(
+ this@PlayerFragment,
+ R.string.download_playerstate_playing_shuffle
+ )
+ } else {
+ setTitle(this@PlayerFragment, R.string.common_appname)
+ }
+ }
+ PlayerState.IDLE,
+ PlayerState.PREPARED,
+ PlayerState.STOPPED,
+ PlayerState.PAUSED,
+ PlayerState.COMPLETED -> {
+ }
+ else -> setTitle(this@PlayerFragment, R.string.common_appname)
+ }
+
+ when (playerState) {
+ PlayerState.STARTED -> {
+ pauseButton.visibility = View.VISIBLE
+ stopButton.visibility = View.GONE
+ startButton.visibility = View.GONE
+ }
+ PlayerState.DOWNLOADING, PlayerState.PREPARING -> {
+ pauseButton.visibility = View.GONE
+ stopButton.visibility = View.VISIBLE
+ startButton.visibility = View.GONE
+ }
+ else -> {
+ pauseButton.visibility = View.GONE
+ stopButton.visibility = View.GONE
+ startButton.visibility = View.VISIBLE
+ }
+ }
+
+ // 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
+ }
+ }
+ onProgressChangedTask!!.execute()
+ }
+
+ private fun changeProgress(ms: Int) {
+ object : SilentBackgroundTask(activity) {
+ var msPlayed = 0
+ var duration: Int? = null
+ var seekTo = 0
+ override fun doInBackground(): Void? {
+ msPlayed = max(0, mediaPlayerController.playerPosition)
+ duration = mediaPlayerController.playerDuration
+ val msTotal = duration!!
+ seekTo = (msPlayed + ms).coerceAtMost(msTotal)
+ mediaPlayerController.seekTo(seekTo)
+ return null
+ }
+
+ override fun done(result: Void?) {
+ progressBar.progress = seekTo
+ }
+ }.execute()
+ }
+
+ override fun onDown(me: MotionEvent): Boolean {
+ return false
+ }
+
+ @Suppress("ReturnCount")
+ override fun onFling(
+ e1: MotionEvent,
+ e2: MotionEvent,
+ velocityX: Float,
+ velocityY: Float
+ ): Boolean {
+ val e1X = e1.x
+ val e2X = e2.x
+ val e1Y = e1.y
+ val e2Y = e2.y
+ val absX = abs(velocityX)
+ val absY = abs(velocityY)
+
+ // Right to Left swipe
+ if (e1X - e2X > swipeDistance && absX > swipeVelocity) {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ mediaPlayerController.next()
+ onCurrentChanged()
+ onSliderProgressChanged()
+ return true
+ }
+
+ // Left to Right swipe
+ if (e2X - e1X > swipeDistance && absX > swipeVelocity) {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ mediaPlayerController.previous()
+ onCurrentChanged()
+ onSliderProgressChanged()
+ return true
+ }
+
+ // Top to Bottom swipe
+ if (e2Y - e1Y > swipeDistance && absY > swipeVelocity) {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ mediaPlayerController.seekTo(mediaPlayerController.playerPosition + 30000)
+ onSliderProgressChanged()
+ return true
+ }
+
+ // Bottom to Top swipe
+ if (e1Y - e2Y > swipeDistance && absY > swipeVelocity) {
+ networkAndStorageChecker.warnIfNetworkOrStorageUnavailable()
+ mediaPlayerController.seekTo(mediaPlayerController.playerPosition - 8000)
+ onSliderProgressChanged()
+ return true
+ }
+ return false
+ }
+
+ override fun onLongPress(e: MotionEvent) {}
+ override fun onScroll(
+ e1: MotionEvent,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
+ return false
+ }
+
+ override fun onShowPress(e: MotionEvent) {}
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ return false
+ }
+
+ private fun displaySongRating() {
+ var rating = 0
+
+ if (currentSong?.userRating != null) {
+ rating = currentSong!!.userRating!!
+ }
+
+ fiveStar1ImageView.setImageDrawable(if (rating > 0) fullStar else hollowStar)
+ fiveStar2ImageView.setImageDrawable(if (rating > 1) fullStar else hollowStar)
+ fiveStar3ImageView.setImageDrawable(if (rating > 2) fullStar else hollowStar)
+ fiveStar4ImageView.setImageDrawable(if (rating > 3) fullStar else hollowStar)
+ fiveStar5ImageView.setImageDrawable(if (rating > 4) fullStar else hollowStar)
+ }
+
+ private fun setSongRating(rating: Int) {
+ if (currentSong == null) return
+ displaySongRating()
+ mediaPlayerController.setSongRating(rating)
+ }
+
+ private fun showSavePlaylistDialog() {
+ val layout = LayoutInflater.from(this.context).inflate(R.layout.save_playlist, null)
+
+ playlistNameView = layout.findViewById(R.id.save_playlist_name)
+
+ val builder: AlertDialog.Builder = AlertDialog.Builder(context)
+ builder.setTitle(R.string.download_playlist_title)
+ builder.setMessage(R.string.download_playlist_name)
+
+ builder.setPositiveButton(R.string.common_save) { _, _ ->
+ savePlaylistInBackground(
+ playlistNameView.text.toString()
+ )
+ }
+
+ builder.setNegativeButton(R.string.common_cancel) { dialog, _ -> dialog.cancel() }
+ builder.setView(layout)
+ builder.setCancelable(true)
+ val dialog = builder.create()
+ val playlistName = mediaPlayerController.suggestedPlaylistName
+ if (playlistName != null) {
+ playlistNameView.setText(playlistName)
+ } else {
+ val dateFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
+ playlistNameView.setText(dateFormat.format(Date()))
+ }
+ dialog.show()
+ }
+
+ companion object {
+ private const val PERCENTAGE_OF_SCREEN_FOR_SWIPE = 5
+ }
+}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt
index 6e329d39..f18a0f26 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CachedMusicService.kt
@@ -198,7 +198,7 @@ class CachedMusicService(private val musicService: MusicService) : MusicService,
}
@Throws(Exception::class)
- override fun createPlaylist(id: String, name: String, entries: List) {
+ override fun createPlaylist(id: String?, name: String?, entries: List) {
cachedPlaylists.clear()
musicService.createPlaylist(id, name, entries)
}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt
index eae455c1..a8eb34b0 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt
@@ -20,6 +20,7 @@ import android.os.Looper
import android.os.PowerManager
import android.os.PowerManager.PARTIAL_WAKE_LOCK
import android.os.PowerManager.WakeLock
+import androidx.lifecycle.MutableLiveData
import java.io.File
import java.net.URLEncoder
import java.util.Locale
@@ -29,7 +30,6 @@ import org.moire.ultrasonic.audiofx.EqualizerController
import org.moire.ultrasonic.audiofx.VisualizerController
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.PlayerState
-import org.moire.ultrasonic.fragment.PlayerFragment
import org.moire.ultrasonic.util.CancellableTask
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.StreamProxy
@@ -79,10 +79,12 @@ class LocalMediaPlayer(
private var proxy: StreamProxy? = null
private var bufferTask: CancellableTask? = null
private var positionCache: PositionCache? = null
- private var secondaryProgress = -1
+
private val pm = context.getSystemService(POWER_SERVICE) as PowerManager
private val wakeLock: WakeLock = pm.newWakeLock(PARTIAL_WAKE_LOCK, this.javaClass.name)
+ val secondaryProgress: MutableLiveData = MutableLiveData(0)
+
fun init() {
Thread {
Thread.currentThread().name = "MediaPlayerThread"
@@ -357,7 +359,6 @@ class LocalMediaPlayer(
downloadFile.updateModificationDate()
mediaPlayer.setOnCompletionListener(null)
- secondaryProgress = -1 // Ensure seeking in non StreamProxy playback works
setPlayerState(PlayerState.IDLE)
setAudioAttributes(mediaPlayer)
@@ -388,28 +389,28 @@ class LocalMediaPlayer(
setPlayerState(PlayerState.PREPARING)
mediaPlayer.setOnBufferingUpdateListener { mp, percent ->
- val progressBar = PlayerFragment.getProgressBar()
val song = downloadFile.song
if (percent == 100) {
mp.setOnBufferingUpdateListener(null)
}
- secondaryProgress = (percent.toDouble() / 100.toDouble() * progressBar.max).toInt()
-
+ // The secondary progress is an indicator of how far the song is cached.
if (song.transcodedContentType == null && Util.getMaxBitRate() == 0) {
- progressBar?.secondaryProgress = secondaryProgress
+ val progress = (percent.toDouble() / 100.toDouble() * playerDuration).toInt()
+ secondaryProgress.postValue(progress)
}
}
mediaPlayer.setOnPreparedListener {
Timber.i("Media player prepared")
setPlayerState(PlayerState.PREPARED)
- val progressBar = PlayerFragment.getProgressBar()
- if (progressBar != null && downloadFile.isWorkDone) {
- // Populate seek bar secondary progress if we have a complete file for consistency
- PlayerFragment.getProgressBar().secondaryProgress = 100 * progressBar.max
+
+ // Populate seek bar secondary progress if we have a complete file for consistency
+ if (downloadFile.isWorkDone) {
+ secondaryProgress.postValue(playerDuration)
}
+
synchronized(this@LocalMediaPlayer) {
if (position != 0) {
Timber.i("Restarting player from position %d", position)
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
index ac424e20..b18eb0fa 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt
@@ -386,12 +386,6 @@ class MediaPlayerController(
@get:Synchronized
val playerDuration: Int
get() {
- if (localMediaPlayer.currentPlaying != null) {
- val duration = localMediaPlayer.currentPlaying!!.song.duration
- if (duration != null) {
- return duration * 1000
- }
- }
val mediaPlayerService = runningInstance ?: return 0
return mediaPlayerService.playerDuration
}
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt
index cce41209..6e417358 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MusicService.kt
@@ -73,7 +73,7 @@ interface MusicService {
fun getPlaylists(refresh: Boolean): List
@Throws(Exception::class)
- fun createPlaylist(id: String, name: String, entries: List)
+ fun createPlaylist(id: String?, name: String?, entries: List)
@Throws(Exception::class)
fun deletePlaylist(id: String)
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt
index f8519561..e06a3a22 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/OfflineMusicService.kt
@@ -221,7 +221,7 @@ class OfflineMusicService : MusicService, KoinComponent {
@Suppress("TooGenericExceptionCaught")
@Throws(Exception::class)
- override fun createPlaylist(id: String, name: String, entries: List) {
+ override fun createPlaylist(id: String?, name: String?, entries: List) {
val playlistFile =
FileUtil.getPlaylistFile(activeServerProvider.getActiveServer().name, name)
val fw = FileWriter(playlistFile)
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt
index 4876bd9e..6e0e44e1 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/RESTMusicService.kt
@@ -295,12 +295,20 @@ open class RESTMusicService(
return response.body()!!.playlists.toDomainEntitiesList()
}
+ /**
+ * Either ID or String is required.
+ * ID is required when updating
+ * String is required when creating
+ */
@Throws(Exception::class)
override fun createPlaylist(
- id: String,
- name: String,
+ id: String?,
+ name: String?,
entries: List
) {
+ if (id == null && name == null)
+ throw IllegalArgumentException("Either id or name is required.")
+
val pSongIds: MutableList = ArrayList(entries.size)
for ((id1) in entries) {
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SilentBackgroundTask.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SilentBackgroundTask.kt
new file mode 100644
index 00000000..3639aa2c
--- /dev/null
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SilentBackgroundTask.kt
@@ -0,0 +1,32 @@
+/*
+ * SilentBackgroundTask.kt
+ * Copyright (C) 2009-2021 Ultrasonic developers
+ *
+ * Distributed under terms of the GNU GPLv3 license.
+ */
+
+package org.moire.ultrasonic.util
+
+import android.app.Activity
+
+/**
+ * @author Sindre Mehus
+ */
+abstract class SilentBackgroundTask(activity: Activity?) : BackgroundTask(activity) {
+ override fun execute() {
+ val thread: Thread = object : Thread() {
+ override fun run() {
+ try {
+ val result = doInBackground()
+ handler.post { done(result) }
+ } catch (all: Throwable) {
+ handler.post { error(all) }
+ }
+ }
+ }
+ thread.start()
+ }
+
+ override fun updateProgress(messageId: Int) {}
+ override fun updateProgress(message: String) {}
+}