diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 8c0868eb..6423c4f4 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -25,7 +25,7 @@ ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$String.format("BufferTask (%s)", downloadFile) ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$String.format("CheckCompletionTask (%s)", downloadFile) ImplicitDefaultLocale:ShareHandler.kt$ShareHandler$String.format("%d:%s", timeSpanAmount, timeSpanType) - ImplicitDefaultLocale:ShareHandler.kt$ShareHandler.<no name provided>$String.format("%s\n\n%s", Util.getShareGreeting(context), result.url) + ImplicitDefaultLocale:ShareHandler.kt$ShareHandler.<no name provided>$String.format("%s\n\n%s", Util.getShareGreeting(), result.url) ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber) ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate) ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix) @@ -46,20 +46,15 @@ LongParameterList:ServerRowAdapter.kt$ServerRowAdapter$( private var context: Context, private var data: Array<ServerSetting>, private val model: ServerSettingsModel, private val activeServerProvider: ActiveServerProvider, private val manageMode: Boolean, private val serverDeletedCallback: ((Int) -> Unit), private val serverEditRequestedCallback: ((Int) -> Unit) ) MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192 MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$10 - MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$1000L MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$60 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer$1000 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$1000 MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$60000 MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$100000 - MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$1000L MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$1024L MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8 MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$86400L MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8L MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$5000L MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$50L - MagicNumber:MediaPlayerService.kt$MediaPlayerService$1000 MagicNumber:MediaPlayerService.kt$MediaPlayerService$256 MagicNumber:MediaPlayerService.kt$MediaPlayerService$3 MagicNumber:MediaPlayerService.kt$MediaPlayerService$4 diff --git a/detekt-config.yml b/detekt-config.yml index 6b7db639..9301b0b7 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -63,7 +63,8 @@ style: excludePackageStatements: false excludeImportStatements: false MagicNumber: - ignoreNumbers: ['-1', '0', '1', '2', '100'] + # 100 common in percentage, 1000 in milliseconds + ignoreNumbers: ['-1', '0', '1', '2', '100', '1000'] ignoreEnums: true ignorePropertyDeclaration: true UnnecessaryAbstractClass: diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index 01f5c64f..f8530d95 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -56,6 +56,7 @@ android { kotlinOptions { jvmTarget = "1.8" + freeCompilerArgs += "-Xopt-in=org.koin.core.component.KoinApiExtension" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/ultrasonic/lint-baseline.xml b/ultrasonic/lint-baseline.xml index c80cca17..c34a884d 100644 --- a/ultrasonic/lint-baseline.xml +++ b/ultrasonic/lint-baseline.xml @@ -26,45 +26,45 @@ + errorLine1=" String lhs = lhsArtist.getName().toLowerCase();" + errorLine2=" ~~~~~~~~~~~"> + line="97" + column="37"/> + errorLine1=" String rhs = rhsArtist.getName().toLowerCase();" + errorLine2=" ~~~~~~~~~~~"> + line="98" + column="37"/> + errorLine1=" int index = lhs.indexOf(String.format("%s ", article.toLowerCase()));" + errorLine2=" ~~~~~~~~~~~"> + line="115" + column="58"/> + errorLine1=" index = rhs.indexOf(String.format("%s ", article.toLowerCase()));" + errorLine2=" ~~~~~~~~~~~"> + line="122" + column="54"/> @@ -85,7 +85,7 @@ errorLine2=" ~~~~~~~~~~~"> @@ -238,7 +238,7 @@ errorLine2=" ~~~~"> @@ -491,7 +491,7 @@ errorLine2=" ~~~~~~~~~~~~"> @@ -579,39 +579,6 @@ file="src/main/res/drawable-xhdpi-v14"/> - - - - - - - - - - - - - - - - playlists = musicService.getPlaylists(refresh); if (!ActiveServerProvider.Companion.isOffline()) - new CacheCleaner(getContext()).cleanPlaylists(playlists); + new CacheCleaner().cleanPlaylists(playlists); return playlists; } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java index 240749ed..fcc81219 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java @@ -139,7 +139,7 @@ public class SettingsFragment extends PreferenceFragmentCompat showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE); setupServersCategory(); - sharingDefaultGreeting.setText(Util.getShareGreeting(getActivity())); + sharingDefaultGreeting.setText(Util.getShareGreeting()); setupClearSearchPreference(); setupGaplessControlSettingsV14(); setupFeatureFlagsPreferences(); 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 7874803e..b47929d9 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Downloader.java @@ -1,6 +1,5 @@ package org.moire.ultrasonic.service; -import android.content.Context; import timber.log.Timber; import org.moire.ultrasonic.domain.MusicDirectory; @@ -35,20 +34,18 @@ public class Downloader private final ShufflePlayBuffer shufflePlayBuffer; private final ExternalStorageMonitor externalStorageMonitor; private final LocalMediaPlayer localMediaPlayer; - private final Context context; // TODO: This is a circular reference, try to remove - private Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); + private final Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); private final List cleanupCandidates = new ArrayList<>(); private final LRUCache downloadFileCache = new LRUCache<>(100); private ScheduledExecutorService executorService; private long revision; - public Downloader(Context context, ShufflePlayBuffer shufflePlayBuffer, ExternalStorageMonitor externalStorageMonitor, + public Downloader(ShufflePlayBuffer shufflePlayBuffer, ExternalStorageMonitor externalStorageMonitor, LocalMediaPlayer localMediaPlayer) { - this.context = context; this.shufflePlayBuffer = shufflePlayBuffer; this.externalStorageMonitor = externalStorageMonitor; this.localMediaPlayer = localMediaPlayer; @@ -56,19 +53,14 @@ public class Downloader public void onCreate() { - Runnable downloadChecker = new Runnable() - { - @Override - public void run() + Runnable downloadChecker = () -> { + try { - try - { - checkDownloads(); - } - catch (Throwable x) - { - Timber.e(x,"checkDownloads() failed."); - } + checkDownloads(); + } + catch (Throwable x) + { + Timber.e(x,"checkDownloads() failed."); } }; @@ -100,10 +92,10 @@ public class Downloader if (shufflePlayBuffer.isEnabled) { - checkShufflePlay(context); + checkShufflePlay(); } - if (jukeboxMediaPlayer.getValue().isEnabled() || !Util.isNetworkConnected(context)) + if (jukeboxMediaPlayer.getValue().isEnabled() || !Util.isNetworkConnected()) { return; } @@ -188,7 +180,7 @@ public class Downloader DownloadFile downloadFile = backgroundDownloadList.get(i); if (downloadFile.isWorkDone() && (!downloadFile.shouldSave() || downloadFile.isSaved())) { - Util.scanMedia(context, downloadFile.getCompleteFile()); + Util.scanMedia(downloadFile.getCompleteFile()); // Don't need to keep list like active song list backgroundDownloadList.remove(i); @@ -316,7 +308,7 @@ public class Downloader for (MusicDirectory.Entry song : songs) { - DownloadFile downloadFile = new DownloadFile(context, song, save); + DownloadFile downloadFile = new DownloadFile(song, save); downloadList.add(getCurrentPlayingIndex() + offset, downloadFile); offset++; } @@ -325,7 +317,7 @@ public class Downloader { for (MusicDirectory.Entry song : songs) { - DownloadFile downloadFile = new DownloadFile(context, song, save); + DownloadFile downloadFile = new DownloadFile(song, save); downloadList.add(downloadFile); } } @@ -336,7 +328,7 @@ public class Downloader { for (MusicDirectory.Entry song : songs) { - DownloadFile downloadFile = new DownloadFile(context, song, save); + DownloadFile downloadFile = new DownloadFile(song, save); backgroundDownloadList.add(downloadFile); } @@ -376,7 +368,7 @@ public class Downloader DownloadFile downloadFile = downloadFileCache.get(song); if (downloadFile == null) { - downloadFile = new DownloadFile(context, song, false); + downloadFile = new DownloadFile(song, false); downloadFileCache.put(song, downloadFile); } return downloadFile; @@ -398,7 +390,7 @@ public class Downloader } } - private synchronized void checkShufflePlay(Context context) + private synchronized void checkShufflePlay() { // Get users desired random playlist size int listSize = Util.getMaxSongs(); @@ -412,7 +404,7 @@ public class Downloader { for (MusicDirectory.Entry song : shufflePlayBuffer.get(listSize - size)) { - DownloadFile downloadFile = new DownloadFile(context, song, false); + DownloadFile downloadFile = new DownloadFile(song, false); downloadList.add(downloadFile); revision++; } @@ -426,7 +418,7 @@ public class Downloader int songsToShift = currIndex - 2; for (MusicDirectory.Entry song : shufflePlayBuffer.get(songsToShift)) { - downloadList.add(new DownloadFile(context, song, false)); + downloadList.add(new DownloadFile(song, false)); downloadList.get(0).cancelDownload(); downloadList.remove(0); revision++; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java index f1a7256a..a2813758 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/ExternalStorageMonitor.java @@ -4,6 +4,9 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; + +import org.moire.ultrasonic.app.UApp; + import timber.log.Timber; /** @@ -11,15 +14,9 @@ import timber.log.Timber; */ public class ExternalStorageMonitor { - private Context context; private BroadcastReceiver ejectEventReceiver; private boolean externalStorageAvailable = true; - public ExternalStorageMonitor(Context context) - { - this.context = context; - } - public void onCreate(final Runnable ejectedCallback) { // Stop when SD card is ejected. @@ -44,12 +41,12 @@ public class ExternalStorageMonitor IntentFilter ejectFilter = new IntentFilter(Intent.ACTION_MEDIA_EJECT); ejectFilter.addAction(Intent.ACTION_MEDIA_MOUNTED); ejectFilter.addDataScheme("file"); - context.registerReceiver(ejectEventReceiver, ejectFilter); + UApp.Companion.applicationContext().registerReceiver(ejectEventReceiver, ejectFilter); } public void onDestroy() { - context.unregisterReceiver(ejectEventReceiver); + UApp.Companion.applicationContext().unregisterReceiver(ejectEventReceiver); } public boolean isExternalStorageAvailable() { return externalStorageAvailable; } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java index e6764129..f7c24cd8 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java @@ -30,6 +30,7 @@ import android.widget.Toast; import org.jetbrains.annotations.NotNull; import org.moire.ultrasonic.R; import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException; +import org.moire.ultrasonic.app.UApp; import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.domain.JukeboxStatus; import org.moire.ultrasonic.domain.PlayerState; @@ -70,10 +71,9 @@ public class JukeboxMediaPlayer private final AtomicBoolean running = new AtomicBoolean(); private Thread serviceThread; private boolean enabled = false; - private final Context context; // TODO: These create circular references, try to refactor - private final Lazy mediaPlayerControllerLazy = inject(MediaPlayerControllerImpl.class); + private final Lazy mediaPlayerControllerLazy = inject(MediaPlayerController.class); private final Downloader downloader; // TODO: Report warning if queue fills up. @@ -82,9 +82,8 @@ public class JukeboxMediaPlayer // TODO: Persist RC state? // TODO: Minimize status updates. - public JukeboxMediaPlayer(Context context, Downloader downloader) + public JukeboxMediaPlayer(Downloader downloader) { - this.context = context; this.downloader = downloader; } @@ -217,15 +216,8 @@ public class JukeboxMediaPlayer private void disableJukeboxOnError(Throwable x, final int resourceId) { Timber.w(x.toString()); - - new Handler().post(new Runnable() - { - @Override - public void run() - { - Util.toast(context, resourceId, false); - } - }); + Context context = UApp.Companion.applicationContext(); + new Handler().post(() -> Util.toast(context, resourceId, false)); mediaPlayerControllerLazy.getValue().setJukeboxEnabled(false); } @@ -293,6 +285,7 @@ public class JukeboxMediaPlayer tasks.remove(SetGain.class); tasks.add(new SetGain(gain)); + Context context = UApp.Companion.applicationContext(); if (volumeToast == null) volumeToast = new VolumeToast(context); volumeToast.setVolume(gain); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java deleted file mode 100644 index 4384ad90..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerController.java +++ /dev/null @@ -1,129 +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 2009 (C) Sindre Mehus - */ -package org.moire.ultrasonic.service; - -import org.moire.ultrasonic.domain.MusicDirectory.Entry; -import org.moire.ultrasonic.domain.PlayerState; -import org.moire.ultrasonic.domain.RepeatMode; - -import java.util.List; - -/** - * This interface contains all functions which are necessary for the Application UI - * to control the Media Player implementation. - * - * @author Sindre Mehus - * @version $Id$ - */ -public interface MediaPlayerController -{ - void download(List songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, boolean newPlaylist); - - void downloadBackground(List songs, boolean save); - - void setShufflePlayEnabled(boolean enabled); - - boolean isShufflePlayEnabled(); - - void shuffle(); - - RepeatMode getRepeatMode(); - - void setRepeatMode(RepeatMode repeatMode); - - boolean getKeepScreenOn(); - - void setKeepScreenOn(boolean screenOn); - - boolean getShowVisualization(); - - void setShowVisualization(boolean showVisualization); - - void clear(); - - void clearIncomplete(); - - void remove(DownloadFile downloadFile); - - void play(int index); - - void seekTo(int position); - - void previous(); - - void next(); - - void pause(); - - void stop(); - - void start(); - - void reset(); - - PlayerState getPlayerState(); - - int getPlayerPosition(); - - int getPlayerDuration(); - - void delete(List songs); - - void unpin(List songs); - - void setSuggestedPlaylistName(String name); - - String getSuggestedPlaylistName(); - - boolean isJukeboxEnabled(); - - boolean isJukeboxAvailable(); - - void setJukeboxEnabled(boolean b); - - void adjustJukeboxVolume(boolean up); - - void togglePlayPause(); - - void setVolume(float volume); - - void restore(List songs, int currentPlayingIndex, int currentPlayingPosition, boolean autoPlay, boolean newPlaylist); - - void stopJukeboxService(); - - void updateNotification(); - - void setSongRating(final int rating); - - DownloadFile getCurrentPlaying(); - - int getPlaylistSize(); - - int getCurrentPlayingNumberOnPlaylist(); - - DownloadFile getCurrentDownloading(); - - List getPlayList(); - - long getPlayListUpdateRevision(); - - long getPlayListDuration(); - - DownloadFile getDownloadFileForSong(Entry song); -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java deleted file mode 100644 index 7a9d6af6..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerControllerImpl.java +++ /dev/null @@ -1,661 +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 2009 (C) Sindre Mehus - */ -package org.moire.ultrasonic.service; - -import android.content.Context; -import android.content.Intent; -import timber.log.Timber; - -import org.koin.java.KoinJavaComponent; -import org.moire.ultrasonic.data.ActiveServerProvider; -import org.moire.ultrasonic.domain.MusicDirectory; -import org.moire.ultrasonic.domain.MusicDirectory.Entry; -import org.moire.ultrasonic.domain.PlayerState; -import org.moire.ultrasonic.domain.RepeatMode; -import org.moire.ultrasonic.domain.UserInfo; -import org.moire.ultrasonic.featureflags.Feature; -import org.moire.ultrasonic.featureflags.FeatureStorage; -import org.moire.ultrasonic.util.ShufflePlayBuffer; -import org.moire.ultrasonic.util.Util; - -import java.util.Iterator; -import java.util.List; - -import kotlin.Lazy; - -import static org.koin.java.KoinJavaComponent.inject; - -/** - * The implementation of the Media Player Controller. - * This class contains everything that is necessary for the Application UI - * to control the Media Player implementation. - * - * @author Sindre Mehus, Joshua Bahnsen - * @version $Id$ - */ -public class MediaPlayerControllerImpl implements MediaPlayerController -{ - private boolean created = false; - private String suggestedPlaylistName; - private boolean keepScreenOn; - - private boolean showVisualization; - private boolean autoPlayStart; - - private final Context context; - private final Lazy jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); - private final Lazy activeServerProvider = inject(ActiveServerProvider.class); - - private final DownloadQueueSerializer downloadQueueSerializer; - private final ExternalStorageMonitor externalStorageMonitor; - private final Downloader downloader; - private final ShufflePlayBuffer shufflePlayBuffer; - private final LocalMediaPlayer localMediaPlayer; - - public MediaPlayerControllerImpl(Context context, DownloadQueueSerializer downloadQueueSerializer, - ExternalStorageMonitor externalStorageMonitor, Downloader downloader, - ShufflePlayBuffer shufflePlayBuffer, LocalMediaPlayer localMediaPlayer) - { - this.context = context; - this.downloadQueueSerializer = downloadQueueSerializer; - this.externalStorageMonitor = externalStorageMonitor; - this.downloader = downloader; - this.shufflePlayBuffer = shufflePlayBuffer; - this.localMediaPlayer = localMediaPlayer; - - Timber.i("MediaPlayerControllerImpl constructed"); - } - - public void onCreate() - { - if (created) return; - this.externalStorageMonitor.onCreate(this::reset); - - setJukeboxEnabled(activeServerProvider.getValue().getActiveServer().getJukeboxByDefault()); - created = true; - - Timber.i("MediaPlayerControllerImpl created"); - } - - public void onDestroy() - { - if (!created) return; - externalStorageMonitor.onDestroy(); - context.stopService(new Intent(context, MediaPlayerService.class)); - downloader.onDestroy(); - created = false; - - Timber.i("MediaPlayerControllerImpl destroyed"); - } - - @Override - public synchronized void restore(List songs, final int currentPlayingIndex, final int currentPlayingPosition, final boolean autoPlay, boolean newPlaylist) - { - download(songs, false, false, false, false, newPlaylist); - - if (currentPlayingIndex != -1) - { - MediaPlayerService.executeOnStartedMediaPlayerService(context, (mediaPlayerService) -> - { - mediaPlayerService.play(currentPlayingIndex, autoPlayStart); - - if (localMediaPlayer.currentPlaying != null) - { - if (autoPlay && jukeboxMediaPlayer.getValue().isEnabled()) - { - jukeboxMediaPlayer.getValue().skip(downloader.getCurrentPlayingIndex(), currentPlayingPosition / 1000); - } - else - { - if (localMediaPlayer.currentPlaying.isCompleteFileAvailable()) - { - localMediaPlayer.play(localMediaPlayer.currentPlaying, currentPlayingPosition, autoPlay); - } - } - } - autoPlayStart = false; - return null; - } - ); - } - } - - public synchronized void preload() - { - MediaPlayerService.getInstance(context); - } - - @Override - public synchronized void play(final int index) - { - MediaPlayerService.executeOnStartedMediaPlayerService(context, (mediaPlayerService) -> { - mediaPlayerService.play(index, true); - return null; - } - ); - } - - public synchronized void play() - { - MediaPlayerService.executeOnStartedMediaPlayerService(context, (mediaPlayerService) -> { - - mediaPlayerService.play(); - return null; - } - ); - } - - public synchronized void resumeOrPlay() - { - MediaPlayerService.executeOnStartedMediaPlayerService(context, (mediaPlayerService) -> { - mediaPlayerService.resumeOrPlay(); - return null; - } - ); - } - - - @Override - public synchronized void togglePlayPause() - { - if (localMediaPlayer.playerState == PlayerState.IDLE) autoPlayStart = true; - MediaPlayerService.executeOnStartedMediaPlayerService(context, (mediaPlayerService) -> { - mediaPlayerService.togglePlayPause(); - return null; - } - ); - } - - - @Override - public synchronized void start() - { - MediaPlayerService.executeOnStartedMediaPlayerService(context, (mediaPlayerService) -> { - mediaPlayerService.start(); - return null; - } - ); - } - - @Override - public synchronized void seekTo(final int position) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.seekTo(position); - } - - @Override - public synchronized void pause() - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.pause(); - } - - @Override - public synchronized void stop() - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.stop(); - } - - @Override - public synchronized void download(List songs, boolean save, boolean autoPlay, boolean playNext, boolean shuffle, boolean newPlaylist) - { - downloader.download(songs, save, autoPlay, playNext, newPlaylist); - jukeboxMediaPlayer.getValue().updatePlaylist(); - - if (shuffle) shuffle(); - - if (!playNext && !autoPlay && (downloader.downloadList.size() - 1) == downloader.getCurrentPlayingIndex()) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); - } - - if (autoPlay) - { - play(0); - } - else - { - if (localMediaPlayer.currentPlaying == null && downloader.downloadList.size() > 0) - { - localMediaPlayer.currentPlaying = downloader.downloadList.get(0); - localMediaPlayer.currentPlaying.setPlaying(true); - } - - downloader.checkDownloads(); - } - - downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); - } - - @Override - public synchronized void downloadBackground(List songs, boolean save) - { - downloader.downloadBackground(songs, save); - downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); - } - - public synchronized void setCurrentPlaying(DownloadFile currentPlaying) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) localMediaPlayer.setCurrentPlaying(currentPlaying); - } - - public synchronized void setCurrentPlaying(int index) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setCurrentPlaying(index); - } - - public synchronized void setPlayerState(PlayerState state) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) localMediaPlayer.setPlayerState(state); - } - - @Override - public void stopJukeboxService() - { - jukeboxMediaPlayer.getValue().stopJukeboxService(); - } - - @Override - public synchronized void setShufflePlayEnabled(boolean enabled) - { - shufflePlayBuffer.isEnabled = enabled; - if (enabled) - { - clear(); - downloader.checkDownloads(); - } - } - - @Override - public boolean isShufflePlayEnabled() - { - return shufflePlayBuffer.isEnabled; - } - - @Override - public synchronized void shuffle() - { - downloader.shuffle(); - - downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); - jukeboxMediaPlayer.getValue().updatePlaylist(); - - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); - } - - @Override - public RepeatMode getRepeatMode() - { - return Util.getRepeatMode(); - } - - @Override - public synchronized void setRepeatMode(RepeatMode repeatMode) - { - Util.setRepeatMode(repeatMode); - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); - } - - @Override - public boolean getKeepScreenOn() - { - return keepScreenOn; - } - - @Override - public void setKeepScreenOn(boolean keepScreenOn) - { - this.keepScreenOn = keepScreenOn; - } - - @Override - public boolean getShowVisualization() - { - return showVisualization; - } - - @Override - public void setShowVisualization(boolean showVisualization) - { - this.showVisualization = showVisualization; - } - - @Override - public synchronized void clear() - { - clear(true); - } - - public synchronized void clear(boolean serialize) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) { - mediaPlayerService.clear(serialize); - } else { - // If no MediaPlayerService is available, just empty the playlist - downloader.clear(); - if (serialize) { - downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, - downloader.getCurrentPlayingIndex(), getPlayerPosition()); - } - } - - jukeboxMediaPlayer.getValue().updatePlaylist(); - } - - @Override - public synchronized void clearIncomplete() - { - reset(); - Iterator iterator = downloader.downloadList.iterator(); - - while (iterator.hasNext()) - { - DownloadFile downloadFile = iterator.next(); - if (!downloadFile.isCompleteFileAvailable()) - { - iterator.remove(); - } - } - - downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); - jukeboxMediaPlayer.getValue().updatePlaylist(); - } - - @Override - public synchronized void remove(DownloadFile downloadFile) - { - if (downloadFile == localMediaPlayer.currentPlaying) - { - reset(); - setCurrentPlaying(null); - } - - downloader.removeDownloadFile(downloadFile); - - downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition()); - jukeboxMediaPlayer.getValue().updatePlaylist(); - - if (downloadFile == localMediaPlayer.nextPlaying) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.setNextPlaying(); - } - } - - @Override - public synchronized void delete(List songs) - { - for (MusicDirectory.Entry song : songs) - { - downloader.getDownloadFileForSong(song).delete(); - } - } - - @Override - public synchronized void unpin(List songs) - { - for (MusicDirectory.Entry song : songs) - { - downloader.getDownloadFileForSong(song).unpin(); - } - } - - @Override - public synchronized void previous() - { - int index = downloader.getCurrentPlayingIndex(); - if (index == -1) - { - return; - } - - // Restart song if played more than five seconds. - if (getPlayerPosition() > 5000 || index == 0) - { - play(index); - } - else - { - play(index - 1); - } - } - - @Override - public synchronized void next() - { - int index = downloader.getCurrentPlayingIndex(); - if (index != -1) - { - switch (getRepeatMode()) - { - case SINGLE: - case OFF: - if (index + 1 >= 0 && index + 1 < downloader.downloadList.size()) { - play(index + 1); - } - break; - case ALL: - play((index + 1) % downloader.downloadList.size()); - break; - default: - break; - } - } - } - - @Override - public synchronized void reset() - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) localMediaPlayer.reset(); - } - - @Override - public synchronized int getPlayerPosition() - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService == null) return 0; - return mediaPlayerService.getPlayerPosition(); - } - - @Override - public synchronized int getPlayerDuration() - { - if (localMediaPlayer.currentPlaying != null) - { - Integer duration = localMediaPlayer.currentPlaying.getSong().getDuration(); - if (duration != null) - { - return duration * 1000; - } - } - - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService == null) return 0; - return mediaPlayerService.getPlayerDuration(); - } - - @Override - public PlayerState getPlayerState() { return localMediaPlayer.playerState; } - - @Override - public void setSuggestedPlaylistName(String name) - { - this.suggestedPlaylistName = name; - } - - @Override - public String getSuggestedPlaylistName() - { - return suggestedPlaylistName; - } - - @Override - public boolean isJukeboxEnabled() - { - return jukeboxMediaPlayer.getValue().isEnabled(); - } - - @Override - public boolean isJukeboxAvailable() - { - try - { - String username = activeServerProvider.getValue().getActiveServer().getUserName(); - UserInfo user = MusicServiceFactory.getMusicService().getUser(username); - return user.getJukeboxRole(); - } - catch (Exception e) - { - Timber.w(e, "Error getting user information"); - } - - return false; - } - - @Override - public void setJukeboxEnabled(boolean jukeboxEnabled) - { - jukeboxMediaPlayer.getValue().setEnabled(jukeboxEnabled); - setPlayerState(PlayerState.IDLE); - - if (jukeboxEnabled) - { - jukeboxMediaPlayer.getValue().startJukeboxService(); - - reset(); - - // Cancel current download, if necessary. - if (downloader.currentDownloading != null) - { - downloader.currentDownloading.cancelDownload(); - } - } - else - { - jukeboxMediaPlayer.getValue().stopJukeboxService(); - } - } - - @Override - public void adjustJukeboxVolume(boolean up) - { - jukeboxMediaPlayer.getValue().adjustVolume(up); - } - - @Override - public void setVolume(float volume) - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) localMediaPlayer.setVolume(volume); - } - - @Override - public void updateNotification() - { - MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance(); - if (mediaPlayerService != null) mediaPlayerService.updateNotification(localMediaPlayer.playerState, localMediaPlayer.currentPlaying); - } - - public void toggleSongStarred() { - if (localMediaPlayer.currentPlaying == null) - return; - - final Entry song = localMediaPlayer.currentPlaying.getSong(); - - // Trigger an update - localMediaPlayer.setCurrentPlaying(localMediaPlayer.currentPlaying); - - song.setStarred(!song.getStarred()); - } - - public void setSongRating(final int rating) - { - if (!KoinJavaComponent.get(FeatureStorage.class).isFeatureEnabled(Feature.FIVE_STAR_RATING)) - return; - - if (localMediaPlayer.currentPlaying == null) - return; - - final Entry song = localMediaPlayer.currentPlaying.getSong(); - song.setUserRating(rating); - - new Thread(() -> { - try - { - MusicServiceFactory.getMusicService().setRating(song.getId(), rating); - } - catch (Exception e) - { - Timber.e(e); - } - }).start(); - - updateNotification(); - } - - @Override - public DownloadFile getCurrentPlaying() { - return localMediaPlayer.currentPlaying; - } - - @Override - public int getPlaylistSize() { - return downloader.downloadList.size(); - } - - @Override - public int getCurrentPlayingNumberOnPlaylist() { - return downloader.getCurrentPlayingIndex(); - } - - @Override - public DownloadFile getCurrentDownloading() { - return downloader.currentDownloading; - } - - @Override - public List getPlayList() { - return downloader.downloadList; - } - - @Override - public long getPlayListUpdateRevision() { - return downloader.getDownloadListUpdateRevision(); - } - - @Override - public long getPlayListDuration() { - return downloader.getDownloadListDuration(); - } - - @Override - public DownloadFile getDownloadFileForSong(Entry song) { - return downloader.getDownloadFileForSong(song); - } -} \ No newline at end of file diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java index 10abc831..8713c7e7 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.java @@ -30,6 +30,7 @@ import timber.log.Timber; import android.view.KeyEvent; import org.moire.ultrasonic.R; +import org.moire.ultrasonic.app.UApp; import org.moire.ultrasonic.domain.PlayerState; import org.moire.ultrasonic.util.CacheCleaner; import org.moire.ultrasonic.util.Constants; @@ -43,19 +44,17 @@ import org.moire.ultrasonic.util.Util; public class MediaPlayerLifecycleSupport { private boolean created = false; - private DownloadQueueSerializer downloadQueueSerializer; // From DI - private final MediaPlayerControllerImpl mediaPlayerController; // From DI + private final DownloadQueueSerializer downloadQueueSerializer; // From DI + private final MediaPlayerController mediaPlayerController; // From DI private final Downloader downloader; // From DI - private Context context; private BroadcastReceiver headsetEventReceiver; - public MediaPlayerLifecycleSupport(Context context, DownloadQueueSerializer downloadQueueSerializer, - final MediaPlayerControllerImpl mediaPlayerController, final Downloader downloader) + public MediaPlayerLifecycleSupport(DownloadQueueSerializer downloadQueueSerializer, + final MediaPlayerController mediaPlayerController, final Downloader downloader) { this.downloadQueueSerializer = downloadQueueSerializer; this.mediaPlayerController = mediaPlayerController; - this.context = context; this.downloader = downloader; Timber.i("LifecycleSupport constructed"); @@ -92,7 +91,7 @@ public class MediaPlayerLifecycleSupport } }); - new CacheCleaner(context).clean(); + new CacheCleaner().clean(); created = true; Timber.i("LifecycleSupport created"); } @@ -103,7 +102,7 @@ public class MediaPlayerLifecycleSupport downloadQueueSerializer.serializeDownloadQueueNow(downloader.downloadList, downloader.getCurrentPlayingIndex(), mediaPlayerController.getPlayerPosition()); mediaPlayerController.clear(false); - context.unregisterReceiver(headsetEventReceiver); + UApp.Companion.applicationContext().unregisterReceiver(headsetEventReceiver); mediaPlayerController.onDestroy(); created = false; Timber.i("LifecycleSupport destroyed"); @@ -139,6 +138,7 @@ public class MediaPlayerLifecycleSupport */ private void registerHeadsetReceiver() { final SharedPreferences sp = Util.getPreferences(); + final Context context = UApp.Companion.applicationContext(); final String spKey = context .getString(R.string.settings_playback_resume_play_on_headphones_plug); @@ -177,7 +177,7 @@ public class MediaPlayerLifecycleSupport { headsetIntentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); } - context.registerReceiver(headsetEventReceiver, headsetIntentFilter); + UApp.Companion.applicationContext().registerReceiver(headsetEventReceiver, headsetIntentFilter); } public void handleKeyEvent(KeyEvent event) @@ -205,58 +205,55 @@ public class MediaPlayerLifecycleSupport keyCode == KeyEvent.KEYCODE_MEDIA_NEXT); // We can receive intents (e.g. MediaButton) when everything is stopped, so we need to start - onCreate(autoStart, new Runnable() { - @Override - public void run() { - switch (keyCode) - { - case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: - case KeyEvent.KEYCODE_HEADSETHOOK: - mediaPlayerController.togglePlayPause(); - break; - case KeyEvent.KEYCODE_MEDIA_PREVIOUS: - mediaPlayerController.previous(); - break; - case KeyEvent.KEYCODE_MEDIA_NEXT: - mediaPlayerController.next(); - break; - case KeyEvent.KEYCODE_MEDIA_STOP: - mediaPlayerController.stop(); - break; - case KeyEvent.KEYCODE_MEDIA_PLAY: - if (mediaPlayerController.getPlayerState() == PlayerState.IDLE) - { - mediaPlayerController.play(); - } - else if (mediaPlayerController.getPlayerState() != PlayerState.STARTED) - { - mediaPlayerController.start(); - } - break; - case KeyEvent.KEYCODE_MEDIA_PAUSE: - mediaPlayerController.pause(); - break; - case KeyEvent.KEYCODE_1: - mediaPlayerController.setSongRating(1); - break; - case KeyEvent.KEYCODE_2: - mediaPlayerController.setSongRating(2); - break; - case KeyEvent.KEYCODE_3: - mediaPlayerController.setSongRating(3); - break; - case KeyEvent.KEYCODE_4: - mediaPlayerController.setSongRating(4); - break; - case KeyEvent.KEYCODE_5: - mediaPlayerController.setSongRating(5); - break; - case KeyEvent.KEYCODE_STAR: - mediaPlayerController.toggleSongStarred(); - break; - default: - break; - } + onCreate(autoStart, () -> { + switch (keyCode) + { + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_HEADSETHOOK: + mediaPlayerController.togglePlayPause(); + break; + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + mediaPlayerController.previous(); + break; + case KeyEvent.KEYCODE_MEDIA_NEXT: + mediaPlayerController.next(); + break; + case KeyEvent.KEYCODE_MEDIA_STOP: + mediaPlayerController.stop(); + break; + case KeyEvent.KEYCODE_MEDIA_PLAY: + if (mediaPlayerController.getPlayerState() == PlayerState.IDLE) + { + mediaPlayerController.play(); + } + else if (mediaPlayerController.getPlayerState() != PlayerState.STARTED) + { + mediaPlayerController.start(); + } + break; + case KeyEvent.KEYCODE_MEDIA_PAUSE: + mediaPlayerController.pause(); + break; + case KeyEvent.KEYCODE_1: + mediaPlayerController.setSongRating(1); + break; + case KeyEvent.KEYCODE_2: + mediaPlayerController.setSongRating(2); + break; + case KeyEvent.KEYCODE_3: + mediaPlayerController.setSongRating(3); + break; + case KeyEvent.KEYCODE_4: + mediaPlayerController.setSongRating(4); + break; + case KeyEvent.KEYCODE_5: + mediaPlayerController.setSongRating(5); + break; + case KeyEvent.KEYCODE_STAR: + mediaPlayerController.toggleSongStarred(); + break; + default: + break; } }); } @@ -278,36 +275,33 @@ public class MediaPlayerLifecycleSupport intentAction.equals(Constants.CMD_NEXT)); // We can receive intents when everything is stopped, so we need to start - onCreate(autoStart, new Runnable() { - @Override - public void run() { - switch(intentAction) - { - case Constants.CMD_PLAY: - mediaPlayerController.play(); - break; - case Constants.CMD_RESUME_OR_PLAY: - // If Ultrasonic wasn't running, the autoStart is enough to resume, no need to call anything - if (isRunning) mediaPlayerController.resumeOrPlay(); - break; - case Constants.CMD_NEXT: - mediaPlayerController.next(); - break; - case Constants.CMD_PREVIOUS: - mediaPlayerController.previous(); - break; - case Constants.CMD_TOGGLEPAUSE: - mediaPlayerController.togglePlayPause(); - break; - case Constants.CMD_STOP: - // TODO: There is a stop() function, shouldn't we use that? - mediaPlayerController.pause(); - mediaPlayerController.seekTo(0); - break; - case Constants.CMD_PAUSE: - mediaPlayerController.pause(); - break; - } + onCreate(autoStart, () -> { + switch(intentAction) + { + case Constants.CMD_PLAY: + mediaPlayerController.play(); + break; + case Constants.CMD_RESUME_OR_PLAY: + // If Ultrasonic wasn't running, the autoStart is enough to resume, no need to call anything + if (isRunning) mediaPlayerController.resumeOrPlay(); + break; + case Constants.CMD_NEXT: + mediaPlayerController.next(); + break; + case Constants.CMD_PREVIOUS: + mediaPlayerController.previous(); + break; + case Constants.CMD_TOGGLEPAUSE: + mediaPlayerController.togglePlayPause(); + break; + case Constants.CMD_STOP: + // TODO: There is a stop() function, shouldn't we use that? + mediaPlayerController.pause(); + mediaPlayerController.seekTo(0); + break; + case Constants.CMD_PAUSE: + mediaPlayerController.pause(); + break; } }); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java index 4a3be836..6bff4eb4 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java @@ -21,9 +21,6 @@ package org.moire.ultrasonic.service; import android.graphics.Bitmap; import android.media.MediaMetadataRetriever; -import kotlin.Pair; -import timber.log.Timber; - import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.domain.Artist; import org.moire.ultrasonic.domain.Bookmark; @@ -54,7 +51,6 @@ import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -64,6 +60,8 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import kotlin.Lazy; +import kotlin.Pair; +import timber.log.Timber; import static org.koin.java.KoinJavaComponent.inject; @@ -95,49 +93,44 @@ public class OfflineMusicService implements MusicService String ignoredArticlesString = "The El La Los Las Le Les"; final String[] ignoredArticles = COMPILE.split(ignoredArticlesString); - Collections.sort(artists, new Comparator() - { - @Override - public int compare(Artist lhsArtist, Artist rhsArtist) + Collections.sort(artists, (lhsArtist, rhsArtist) -> { + String lhs = lhsArtist.getName().toLowerCase(); + String rhs = rhsArtist.getName().toLowerCase(); + + char lhs1 = lhs.charAt(0); + char rhs1 = rhs.charAt(0); + + if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) { - String lhs = lhsArtist.getName().toLowerCase(); - String rhs = rhsArtist.getName().toLowerCase(); - - char lhs1 = lhs.charAt(0); - char rhs1 = rhs.charAt(0); - - if (Character.isDigit(lhs1) && !Character.isDigit(rhs1)) - { - return 1; - } - - if (Character.isDigit(rhs1) && !Character.isDigit(lhs1)) - { - return -1; - } - - for (String article : ignoredArticles) - { - int index = lhs.indexOf(String.format("%s ", article.toLowerCase())); - - if (index == 0) - { - lhs = lhs.substring(article.length() + 1); - } - - index = rhs.indexOf(String.format("%s ", article.toLowerCase())); - - if (index == 0) - { - rhs = rhs.substring(article.length() + 1); - } - } - - return lhs.compareTo(rhs); + return 1; } + + if (Character.isDigit(rhs1) && !Character.isDigit(lhs1)) + { + return -1; + } + + for (String article : ignoredArticles) + { + int index = lhs.indexOf(String.format("%s ", article.toLowerCase())); + + if (index == 0) + { + lhs = lhs.substring(article.length() + 1); + } + + index = rhs.indexOf(String.format("%s ", article.toLowerCase())); + + if (index == 0) + { + rhs = rhs.substring(article.length() + 1); + } + } + + return lhs.compareTo(rhs); }); - return new Indexes(0L, ignoredArticlesString, Collections.emptyList(), artists); + return new Indexes(0L, ignoredArticlesString, Collections.emptyList(), artists); } @Override @@ -387,44 +380,31 @@ public class OfflineMusicService implements MusicService } } - Collections.sort(artists, new Comparator() - { - @Override - public int compare(Artist lhs, Artist rhs) + Collections.sort(artists, (lhs, rhs) -> { + if (lhs.getCloseness() == rhs.getCloseness()) { - if (lhs.getCloseness() == rhs.getCloseness()) - { - return 0; - } - - else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; + return 0; } + + else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; }); - Collections.sort(albums, new Comparator() - { - @Override - public int compare(MusicDirectory.Entry lhs, MusicDirectory.Entry rhs) - { - if (lhs.getCloseness() == rhs.getCloseness()) - { - return 0; - } - else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; + Collections.sort(albums, (lhs, rhs) -> { + if (lhs.getCloseness() == rhs.getCloseness()) + { + return 0; } + + else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; }); - Collections.sort(songs, new Comparator() - { - @Override - public int compare(MusicDirectory.Entry lhs, MusicDirectory.Entry rhs) - { - if (lhs.getCloseness() == rhs.getCloseness()) - { - return 0; - } - else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; + Collections.sort(songs, (lhs, rhs) -> { + if (lhs.getCloseness() == rhs.getCloseness()) + { + return 0; } + + else return lhs.getCloseness() > rhs.getCloseness() ? -1 : 1; }); return new SearchResult(artists, albums, songs); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Scrobbler.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Scrobbler.java index 60a5bbe4..83627a08 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Scrobbler.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Scrobbler.java @@ -1,8 +1,6 @@ package org.moire.ultrasonic.service; -import android.content.Context; import timber.log.Timber; - import org.moire.ultrasonic.data.ActiveServerProvider; /** @@ -16,7 +14,7 @@ public class Scrobbler private String lastSubmission; private String lastNowPlaying; - public void scrobble(final Context context, final DownloadFile song, final boolean submission) + public void scrobble(final DownloadFile song, final boolean submission) { if (song == null || !ActiveServerProvider.Companion.isScrobblingEnabled()) return; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java index a61e0946..a63c8875 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/CacheCleaner.java @@ -1,6 +1,5 @@ package org.moire.ultrasonic.util; -import android.content.Context; import android.os.AsyncTask; import android.os.StatFs; import timber.log.Timber; @@ -14,7 +13,6 @@ import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -31,13 +29,8 @@ public class CacheCleaner { private static final long MIN_FREE_SPACE = 500 * 1024L * 1024L; - private final Context context; - private Lazy downloader = inject(Downloader.class); - private Lazy activeServerProvider = inject(ActiveServerProvider.class); - - public CacheCleaner(Context context) + public CacheCleaner() { - this.context = context; } public void clean() @@ -79,7 +72,7 @@ public class CacheCleaner } } - private void deleteEmptyDirs(Iterable dirs, Collection doNotDelete) + private static void deleteEmptyDirs(Iterable dirs, Collection doNotDelete) { for (File dir : dirs) { @@ -108,7 +101,7 @@ public class CacheCleaner } } - private long getMinimumDelete(List files) + private static long getMinimumDelete(List files) { if (files.isEmpty()) { @@ -168,7 +161,7 @@ public class CacheCleaner } } - Timber.i("Deleted : %s", Util.formatBytes(bytesDeleted)); + Timber.i("Deleted: %s", Util.formatBytes(bytesDeleted)); } private static void findCandidatesForDeletion(File file, List files, List dirs) @@ -197,20 +190,14 @@ public class CacheCleaner private static void sortByAscendingModificationTime(List files) { - Collections.sort(files, new Comparator() - { - @Override - public int compare(File a, File b) - { - return Long.compare(a.lastModified(), b.lastModified()); - - } - }); + Collections.sort(files, (a, b) -> Long.compare(a.lastModified(), b.lastModified())); } - private Set findFilesToNotDelete() + private static Set findFilesToNotDelete() { - Set filesToNotDelete = new HashSet(5); + Set filesToNotDelete = new HashSet<>(5); + + Lazy downloader = inject(Downloader.class); for (DownloadFile downloadFile : downloader.getValue().getDownloads()) { @@ -222,7 +209,7 @@ public class CacheCleaner return filesToNotDelete; } - private class BackgroundCleanup extends AsyncTask + private static class BackgroundCleanup extends AsyncTask { @Override protected Void doInBackground(Void... params) @@ -230,8 +217,8 @@ public class CacheCleaner try { Thread.currentThread().setName("BackgroundCleanup"); - List files = new ArrayList(); - List dirs = new ArrayList(); + List files = new ArrayList<>(); + List dirs = new ArrayList<>(); findCandidatesForDeletion(FileUtil.getMusicDirectory(), files, dirs); sortByAscendingModificationTime(files); @@ -250,7 +237,7 @@ public class CacheCleaner } } - private class BackgroundSpaceCleanup extends AsyncTask + private static class BackgroundSpaceCleanup extends AsyncTask { @Override protected Void doInBackground(Void... params) @@ -258,8 +245,8 @@ public class CacheCleaner try { Thread.currentThread().setName("BackgroundSpaceCleanup"); - List files = new ArrayList(); - List dirs = new ArrayList(); + List files = new ArrayList<>(); + List dirs = new ArrayList<>(); findCandidatesForDeletion(FileUtil.getMusicDirectory(), files, dirs); long bytesToDelete = getMinimumDelete(files); @@ -279,13 +266,14 @@ public class CacheCleaner } } - private class BackgroundPlaylistsCleanup extends AsyncTask, Void, Void> + private static class BackgroundPlaylistsCleanup extends AsyncTask, Void, Void> { @Override protected Void doInBackground(List... params) { try { + Lazy activeServerProvider = inject(ActiveServerProvider.class); Thread.currentThread().setName("BackgroundPlaylistsCleanup"); String server = activeServerProvider.getValue().getActiveServer().getName(); SortedSet playlistFiles = FileUtil.listFiles(FileUtil.getPlaylistDirectory(server)); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java index 28e203fa..47654e98 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/LegacyImageLoader.java @@ -79,7 +79,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader { imageSizeDefault = drawable.getIntrinsicHeight(); } - imageSizeLarge = Util.getMaxDisplayMetric(context); + imageSizeLarge = Util.getMaxDisplayMetric(); createLargeUnknownImage(context); createUnknownAvatarImage(context); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java index e13f1ae2..d918a4e0 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/ShufflePlayBuffer.java @@ -18,9 +18,6 @@ */ package org.moire.ultrasonic.util; -import android.content.Context; -import timber.log.Timber; - import org.moire.ultrasonic.data.ActiveServerProvider; import org.moire.ultrasonic.domain.MusicDirectory; import org.moire.ultrasonic.service.MusicService; @@ -32,6 +29,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import timber.log.Timber; + /** * @author Sindre Mehus * @version $Id$ @@ -42,28 +41,19 @@ public class ShufflePlayBuffer private static final int REFILL_THRESHOLD = 40; private final List buffer = new ArrayList<>(); - private final Context context; private ScheduledExecutorService executorService; private int currentServer; public boolean isEnabled = false; - public ShufflePlayBuffer(Context context) + public ShufflePlayBuffer() { - this.context = context; } public void onCreate() { executorService = Executors.newSingleThreadScheduledExecutor(); - Runnable runnable = new Runnable() - { - @Override - public void run() - { - refill(); - } - }; + Runnable runnable = this::refill; executorService.scheduleWithFixedDelay(runnable, 1, 10, TimeUnit.SECONDS); Timber.i("ShufflePlayBuffer created"); } @@ -97,7 +87,7 @@ public class ShufflePlayBuffer // Check if active server has changed. clearBufferIfNecessary(); - if (buffer.size() > REFILL_THRESHOLD || (!Util.isNetworkConnected(context) && !ActiveServerProvider.Companion.isOffline())) + if (buffer.size() > REFILL_THRESHOLD || (!Util.isNetworkConnected() && !ActiveServerProvider.Companion.isOffline())) { return; } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java index dc4ef45e..f89ed103 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/Util.java @@ -167,10 +167,14 @@ public class Util } } - public static int getMaxBitRate(Context context) - { - ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + public static ConnectivityManager getConnectivityManager() { + Context context = appContext(); + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + public static int getMaxBitRate() + { + ConnectivityManager manager = getConnectivityManager(); NetworkInfo networkInfo = manager.getActiveNetworkInfo(); if (networkInfo == null) @@ -548,9 +552,9 @@ public class Util return null; } - public static boolean isNetworkConnected(Context context) + public static boolean isNetworkConnected() { - ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + ConnectivityManager manager = getConnectivityManager(); NetworkInfo networkInfo = manager.getActiveNetworkInfo(); boolean connected = networkInfo != null && networkInfo.isConnected(); @@ -648,9 +652,9 @@ public class Util return bitmap; } - public static WifiManager.WifiLock createWifiLock(Context context, String tag) + public static WifiManager.WifiLock createWifiLock(String tag) { - WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + WifiManager wm = (WifiManager) appContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE); return wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, tag); } @@ -945,15 +949,15 @@ public class Util return size; } - public static int getMinDisplayMetric(Context context) + public static int getMinDisplayMetric() { - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + DisplayMetrics metrics = appContext().getResources().getDisplayMetrics(); return Math.min(metrics.widthPixels, metrics.heightPixels); } - public static int getMaxDisplayMetric(Context context) + public static int getMaxDisplayMetric() { - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); + DisplayMetrics metrics = appContext().getResources().getDisplayMetrics(); return Math.max(metrics.widthPixels, metrics.heightPixels); } @@ -1251,10 +1255,12 @@ public class Util return preferences.getString(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION, ""); } - public static String getShareGreeting(Context context) + public static String getShareGreeting() { SharedPreferences preferences = getPreferences(); - return preferences.getString(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING, String.format(context.getResources().getString(R.string.share_default_greeting), context.getResources().getString(R.string.common_appname))); + Context context = appContext(); + String defaultVal = String.format(context.getResources().getString(R.string.share_default_greeting), context.getResources().getString(R.string.common_appname)); + return preferences.getString(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING, defaultVal); } public static String getDefaultShareExpiration() @@ -1313,11 +1319,11 @@ public class Util return preferences.getBoolean(Constants.PREFERENCES_KEY_SHOW_ALL_SONGS_BY_ARTIST, false); } - public static void scanMedia(Context context, File file) + public static void scanMedia(File file) { Uri uri = Uri.fromFile(file); Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri); - context.sendBroadcast(scanFileIntent); + appContext().sendBroadcast(scanFileIntent); } public static int getImageLoaderConcurrency() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt index 33cddf3e..6f7a751d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/MediaPlayerModule.kt @@ -9,7 +9,6 @@ import org.moire.ultrasonic.service.ExternalStorageMonitor import org.moire.ultrasonic.service.JukeboxMediaPlayer import org.moire.ultrasonic.service.LocalMediaPlayer import org.moire.ultrasonic.service.MediaPlayerController -import org.moire.ultrasonic.service.MediaPlayerControllerImpl import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport import org.moire.ultrasonic.util.ShufflePlayBuffer @@ -17,19 +16,15 @@ import org.moire.ultrasonic.util.ShufflePlayBuffer * This Koin module contains the registration of classes related to the media player */ val mediaPlayerModule = module { - single { - MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) - } - - single { JukeboxMediaPlayer(androidContext(), get()) } - single { MediaPlayerLifecycleSupport(androidContext(), get(), get(), get()) } + single { JukeboxMediaPlayer(get()) } + single { MediaPlayerLifecycleSupport(get(), get(), get()) } single { DownloadQueueSerializer(androidContext()) } - single { ExternalStorageMonitor(androidContext()) } - single { ShufflePlayBuffer(androidContext()) } - single { Downloader(androidContext(), get(), get(), get()) } + single { ExternalStorageMonitor() } + single { ShufflePlayBuffer() } + single { Downloader(get(), get(), get()) } single { LocalMediaPlayer(get(), androidContext()) } single { AudioFocusHandler(get()) } // TODO Ideally this can be cleaned up when all circular references are removed. - single { MediaPlayerControllerImpl(androidContext(), get(), get(), get(), get(), get()) } + single { MediaPlayerController(get(), get(), get(), get(), get()) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt index 1fe45f3f..810e90f5 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt @@ -11,6 +11,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import java.net.ConnectException +import java.net.UnknownHostException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -91,12 +92,18 @@ open class GenericListModel(application: Application) : try { load(isOffline, useId3Tags, musicService, refresh, bundle) } catch (exception: ConnectException) { - Handler(Looper.getMainLooper()).post { - CommunicationErrorHandler.handleError(exception, swipe.context) - } + handleException(exception, swipe.context) + } catch (exception: UnknownHostException) { + handleException(exception, swipe.context) } } + private fun handleException(exception: Exception, context: Context) { + Handler(Looper.getMainLooper()).post { + CommunicationErrorHandler.handleError(exception, context) + } + } + /** * This is the central function you need to implement if you want to extend this class */ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt index 39a72dcf..588ac266 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt @@ -51,7 +51,7 @@ class CommunicationErrorHandler { } fun getErrorMessage(error: Throwable, context: Context): String { - if (error is IOException && !Util.isNetworkConnected(context)) { + if (error is IOException && !Util.isNetworkConnected()) { return context.resources.getString(R.string.background_task_no_network) } else if (error is FileNotFoundException) { return context.resources.getString(R.string.background_task_not_found) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt index 3ccdd06c..6c75e62d 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadFile.kt @@ -21,6 +21,7 @@ import java.io.OutputStream import java.io.RandomAccessFile import org.koin.core.component.KoinApiExtension import org.koin.java.KoinJavaComponent.inject +import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService import org.moire.ultrasonic.util.CacheCleaner @@ -37,7 +38,6 @@ import timber.log.Timber */ @KoinApiExtension class DownloadFile( - private val context: Context, val song: MusicDirectory.Entry, private val save: Boolean ) { @@ -48,7 +48,7 @@ class DownloadFile( var isFailed = false private var retryCount = MAX_RETRIES - private val desiredBitRate: Int = Util.getMaxBitRate(context) + private val desiredBitRate: Int = Util.getMaxBitRate() @Volatile private var isPlaying = false @@ -138,7 +138,7 @@ class DownloadFile( Util.delete(completeFile) Util.delete(saveFile) - Util.scanMedia(context, saveFile) + Util.scanMedia(saveFile) } fun unpin() { @@ -186,7 +186,7 @@ class DownloadFile( } else if (completeWhenDone) { if (save) { Util.renameFile(partialFile, saveFile) - Util.scanMedia(context, saveFile) + Util.scanMedia(saveFile) } else { Util.renameFile(partialFile, completeFile) } @@ -211,7 +211,7 @@ class DownloadFile( var wifiLock: WifiLock? = null try { wakeLock = acquireWakeLock(wakeLock) - wifiLock = Util.createWifiLock(context, toString()) + wifiLock = Util.createWifiLock(toString()) wifiLock.acquire() if (saveFile.exists()) { @@ -285,7 +285,7 @@ class DownloadFile( } else { if (save) { Util.renameFile(partialFile, saveFile) - Util.scanMedia(context, saveFile) + Util.scanMedia(saveFile) } else { Util.renameFile(partialFile, completeFile) } @@ -309,7 +309,7 @@ class DownloadFile( Timber.i("Released wake lock %s", wakeLock) } wifiLock?.release() - CacheCleaner(context).cleanSpace() + CacheCleaner().cleanSpace() downloader.value.checkDownloads() } } @@ -317,6 +317,7 @@ class DownloadFile( private fun acquireWakeLock(wakeLock: WakeLock?): WakeLock? { var wakeLock1 = wakeLock if (Util.isScreenLitOnDownload()) { + val context = UApp.applicationContext() val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager val flags = PowerManager.SCREEN_DIM_WAKE_LOCK or PowerManager.ON_AFTER_RELEASE wakeLock1 = pm.newWakeLock(flags, toString()) @@ -333,7 +334,7 @@ class DownloadFile( private fun downloadAndSaveCoverArt(musicService: MusicService) { try { if (!TextUtils.isEmpty(song.coverArt)) { - val size = Util.getMinDisplayMetric(context) + val size = Util.getMinDisplayMetric() musicService.getCoverArt(song, size, true, true) } } catch (e: Exception) { 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 66c94cfb..26b96e36 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt @@ -25,6 +25,7 @@ import java.net.URLEncoder import java.util.Locale import kotlin.math.abs import kotlin.math.max +import org.koin.core.component.KoinApiExtension import org.moire.ultrasonic.audiofx.EqualizerController import org.moire.ultrasonic.audiofx.VisualizerController import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline @@ -39,6 +40,7 @@ import timber.log.Timber /** * Represents a Media Player which uses the mobile's resources for playback */ +@KoinApiExtension class LocalMediaPlayer( private val audioFocusHandler: AudioFocusHandler, private val context: Context @@ -397,7 +399,7 @@ class LocalMediaPlayer( secondaryProgress = (percent.toDouble() / 100.toDouble() * progressBar.max).toInt() - if (song.transcodedContentType == null && Util.getMaxBitRate(context) == 0) { + if (song.transcodedContentType == null && Util.getMaxBitRate() == 0) { progressBar?.secondaryProgress = secondaryProgress } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt new file mode 100644 index 00000000..1706cf7e --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerController.kt @@ -0,0 +1,514 @@ +/* + * MediaPlayerController.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ +package org.moire.ultrasonic.service + +import android.content.Intent +import org.koin.core.component.KoinApiExtension +import org.koin.java.KoinJavaComponent.get +import org.koin.java.KoinJavaComponent.inject +import org.moire.ultrasonic.app.UApp +import org.moire.ultrasonic.data.ActiveServerProvider +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.service.MediaPlayerService.Companion.executeOnStartedMediaPlayerService +import org.moire.ultrasonic.service.MediaPlayerService.Companion.getInstance +import org.moire.ultrasonic.service.MediaPlayerService.Companion.runningInstance +import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService +import org.moire.ultrasonic.util.ShufflePlayBuffer +import org.moire.ultrasonic.util.Util +import timber.log.Timber + +/** + * The implementation of the Media Player Controller. + * This class contains everything that is necessary for the Application UI + * to control the Media Player implementation. + */ +@KoinApiExtension +@Suppress("TooManyFunctions") +class MediaPlayerController( + private val downloadQueueSerializer: DownloadQueueSerializer, + private val externalStorageMonitor: ExternalStorageMonitor, + private val downloader: Downloader, + private val shufflePlayBuffer: ShufflePlayBuffer, + private val localMediaPlayer: LocalMediaPlayer +) { + + private var created = false + var suggestedPlaylistName: String? = null + var keepScreenOn = false + var showVisualization = false + private var autoPlayStart = false + + private val jukeboxMediaPlayer = inject(JukeboxMediaPlayer::class.java).value + private val activeServerProvider = inject(ActiveServerProvider::class.java).value + + fun onCreate() { + if (created) return + externalStorageMonitor.onCreate { reset() } + isJukeboxEnabled = activeServerProvider.getActiveServer().jukeboxByDefault + created = true + Timber.i("MediaPlayerController created") + } + + fun onDestroy() { + if (!created) return + val context = UApp.applicationContext() + externalStorageMonitor.onDestroy() + context.stopService(Intent(context, MediaPlayerService::class.java)) + downloader.onDestroy() + created = false + Timber.i("MediaPlayerController destroyed") + } + + @Synchronized + fun restore( + songs: List?, + currentPlayingIndex: Int, + currentPlayingPosition: Int, + autoPlay: Boolean, + newPlaylist: Boolean + ) { + download( + songs, + save = false, + autoPlay = false, + playNext = false, + shuffle = false, + newPlaylist = newPlaylist + ) + if (currentPlayingIndex != -1) { + executeOnStartedMediaPlayerService { mediaPlayerService: MediaPlayerService -> + mediaPlayerService.play(currentPlayingIndex, autoPlayStart) + if (localMediaPlayer.currentPlaying != null) { + if (autoPlay && jukeboxMediaPlayer.isEnabled) { + jukeboxMediaPlayer.skip( + downloader.currentPlayingIndex, + currentPlayingPosition / 1000 + ) + } else { + if (localMediaPlayer.currentPlaying!!.isCompleteFileAvailable) { + localMediaPlayer.play( + localMediaPlayer.currentPlaying, + currentPlayingPosition, + autoPlay + ) + } + } + } + autoPlayStart = false + } + } + } + + @Synchronized + fun preload() { + getInstance() + } + + @Synchronized + fun play(index: Int) { + executeOnStartedMediaPlayerService { service: MediaPlayerService -> + service.play(index, true) + } + } + + @Synchronized + fun play() { + executeOnStartedMediaPlayerService { service: MediaPlayerService -> + service.play() + } + } + + @Synchronized + fun resumeOrPlay() { + executeOnStartedMediaPlayerService { service: MediaPlayerService -> + service.resumeOrPlay() + } + } + + @Synchronized + fun togglePlayPause() { + if (localMediaPlayer.playerState === PlayerState.IDLE) autoPlayStart = true + executeOnStartedMediaPlayerService { service: MediaPlayerService -> + service.togglePlayPause() + } + } + + @Synchronized + fun start() { + executeOnStartedMediaPlayerService { service: MediaPlayerService -> + service.start() + } + } + + @Synchronized + fun seekTo(position: Int) { + val mediaPlayerService = runningInstance + mediaPlayerService?.seekTo(position) + } + + @Synchronized + fun pause() { + val mediaPlayerService = runningInstance + mediaPlayerService?.pause() + } + + @Synchronized + fun stop() { + val mediaPlayerService = runningInstance + mediaPlayerService?.stop() + } + + @Synchronized + @Suppress("LongParameterList") + fun download( + songs: List?, + save: Boolean, + autoPlay: Boolean, + playNext: Boolean, + shuffle: Boolean, + newPlaylist: Boolean + ) { + downloader.download(songs, save, autoPlay, playNext, newPlaylist) + jukeboxMediaPlayer.updatePlaylist() + if (shuffle) shuffle() + val isLastTrack = (downloader.downloadList.size - 1 == downloader.currentPlayingIndex) + + if (!playNext && !autoPlay && isLastTrack) { + val mediaPlayerService = runningInstance + mediaPlayerService?.setNextPlaying() + } + + if (autoPlay) { + play(0) + } else { + if (localMediaPlayer.currentPlaying == null && downloader.downloadList.size > 0) { + localMediaPlayer.currentPlaying = downloader.downloadList[0] + downloader.downloadList[0].setPlaying(true) + } + downloader.checkDownloads() + } + + downloadQueueSerializer.serializeDownloadQueue( + downloader.downloadList, + downloader.currentPlayingIndex, + playerPosition + ) + } + + @Synchronized + fun downloadBackground(songs: List?, save: Boolean) { + downloader.downloadBackground(songs, save) + downloadQueueSerializer.serializeDownloadQueue( + downloader.downloadList, + downloader.currentPlayingIndex, + playerPosition + ) + } + + @Synchronized + fun setCurrentPlaying(index: Int) { + val mediaPlayerService = runningInstance + mediaPlayerService?.setCurrentPlaying(index) + } + + fun stopJukeboxService() { + jukeboxMediaPlayer.stopJukeboxService() + } + + @set:Synchronized + var isShufflePlayEnabled: Boolean + get() = shufflePlayBuffer.isEnabled + set(enabled) { + shufflePlayBuffer.isEnabled = enabled + if (enabled) { + clear() + downloader.checkDownloads() + } + } + + @Synchronized + fun shuffle() { + downloader.shuffle() + downloadQueueSerializer.serializeDownloadQueue( + downloader.downloadList, + downloader.currentPlayingIndex, + playerPosition + ) + jukeboxMediaPlayer.updatePlaylist() + val mediaPlayerService = runningInstance + mediaPlayerService?.setNextPlaying() + } + + @set:Synchronized + var repeatMode: RepeatMode? + get() = Util.getRepeatMode() + set(repeatMode) { + Util.setRepeatMode(repeatMode) + val mediaPlayerService = runningInstance + mediaPlayerService?.setNextPlaying() + } + + @Synchronized + fun clear() { + clear(true) + } + + @Synchronized + fun clear(serialize: Boolean) { + val mediaPlayerService = runningInstance + if (mediaPlayerService != null) { + mediaPlayerService.clear(serialize) + } else { + // If no MediaPlayerService is available, just empty the playlist + downloader.clear() + if (serialize) { + downloadQueueSerializer.serializeDownloadQueue( + downloader.downloadList, + downloader.currentPlayingIndex, playerPosition + ) + } + } + jukeboxMediaPlayer.updatePlaylist() + } + + @Synchronized + fun clearIncomplete() { + reset() + val iterator = downloader.downloadList.iterator() + while (iterator.hasNext()) { + val downloadFile = iterator.next() + if (!downloadFile.isCompleteFileAvailable) { + iterator.remove() + } + } + + downloadQueueSerializer.serializeDownloadQueue( + downloader.downloadList, + downloader.currentPlayingIndex, + playerPosition + ) + + jukeboxMediaPlayer.updatePlaylist() + } + + @Synchronized + fun remove(downloadFile: DownloadFile) { + if (downloadFile == localMediaPlayer.currentPlaying) { + reset() + currentPlaying = null + } + downloader.removeDownloadFile(downloadFile) + + downloadQueueSerializer.serializeDownloadQueue( + downloader.downloadList, + downloader.currentPlayingIndex, + playerPosition + ) + + jukeboxMediaPlayer.updatePlaylist() + + if (downloadFile == localMediaPlayer.nextPlaying) { + val mediaPlayerService = runningInstance + mediaPlayerService?.setNextPlaying() + } + } + + @Synchronized + fun delete(songs: List) { + for (song in songs) { + downloader.getDownloadFileForSong(song).delete() + } + } + + @Synchronized + fun unpin(songs: List) { + for (song in songs) { + downloader.getDownloadFileForSong(song).unpin() + } + } + + @Synchronized + fun previous() { + val index = downloader.currentPlayingIndex + if (index == -1) { + return + } + + // Restart song if played more than five seconds. + @Suppress("MagicNumber") + if (playerPosition > 5000 || index == 0) { + play(index) + } else { + play(index - 1) + } + } + + @Synchronized + operator fun next() { + val index = downloader.currentPlayingIndex + if (index != -1) { + when (repeatMode) { + RepeatMode.SINGLE, RepeatMode.OFF -> { + // Play next if exists + if (index + 1 >= 0 && index + 1 < downloader.downloadList.size) { + play(index + 1) + } + } + RepeatMode.ALL -> { + play((index + 1) % downloader.downloadList.size) + } + else -> { + } + } + } + } + + @Synchronized + fun reset() { + val mediaPlayerService = runningInstance + if (mediaPlayerService != null) localMediaPlayer.reset() + } + + @get:Synchronized + val playerPosition: Int + get() { + val mediaPlayerService = runningInstance ?: return 0 + return mediaPlayerService.playerPosition + } + + @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 + } + + @set:Synchronized + var playerState: PlayerState + get() = localMediaPlayer.playerState + set(state) { + val mediaPlayerService = runningInstance + if (mediaPlayerService != null) localMediaPlayer.setPlayerState(state) + } + + @set:Synchronized + var isJukeboxEnabled: Boolean + get() = jukeboxMediaPlayer.isEnabled + set(jukeboxEnabled) { + jukeboxMediaPlayer.isEnabled = jukeboxEnabled + playerState = PlayerState.IDLE + if (jukeboxEnabled) { + jukeboxMediaPlayer.startJukeboxService() + reset() + + // Cancel current download, if necessary. + if (downloader.currentDownloading != null) { + downloader.currentDownloading.cancelDownload() + } + } else { + jukeboxMediaPlayer.stopJukeboxService() + } + } + + @Suppress("TooGenericExceptionCaught") // The interface throws only generic exceptions + val isJukeboxAvailable: Boolean + get() { + try { + val username = activeServerProvider.getActiveServer().userName + val (_, _, _, _, _, _, _, _, _, _, _, _, jukeboxRole) = getMusicService().getUser( + username + ) + return jukeboxRole + } catch (e: Exception) { + Timber.w(e, "Error getting user information") + } + return false + } + + fun adjustJukeboxVolume(up: Boolean) { + jukeboxMediaPlayer.adjustVolume(up) + } + + fun setVolume(volume: Float) { + if (runningInstance != null) localMediaPlayer.setVolume(volume) + } + + private fun updateNotification() { + runningInstance?.updateNotification( + localMediaPlayer.playerState, + localMediaPlayer.currentPlaying + ) + } + + fun toggleSongStarred() { + if (localMediaPlayer.currentPlaying == null) return + val song = localMediaPlayer.currentPlaying!!.song + + // Trigger an update + localMediaPlayer.setCurrentPlaying(localMediaPlayer.currentPlaying) + song.starred = !song.starred + } + + @Suppress("TooGenericExceptionCaught") // The interface throws only generic exceptions + fun setSongRating(rating: Int) { + if (!get(FeatureStorage::class.java).isFeatureEnabled(Feature.FIVE_STAR_RATING)) return + if (localMediaPlayer.currentPlaying == null) return + val song = localMediaPlayer.currentPlaying!!.song + song.userRating = rating + Thread { + try { + getMusicService().setRating(song.id, rating) + } catch (e: Exception) { + Timber.e(e) + } + }.start() + updateNotification() + } + + @set:Synchronized + var currentPlaying: DownloadFile? + get() = localMediaPlayer.currentPlaying + set(currentPlaying) { + if (runningInstance != null) localMediaPlayer.setCurrentPlaying(currentPlaying) + } + + val playlistSize: Int + get() = downloader.downloadList.size + + val currentPlayingNumberOnPlaylist: Int + get() = downloader.currentPlayingIndex + + val currentDownloading: DownloadFile + get() = downloader.currentDownloading + + val playList: List + get() = downloader.downloadList + + val playListUpdateRevision: Long + get() = downloader.downloadListUpdateRevision + + val playListDuration: Long + get() = downloader.downloadListDuration + + fun getDownloadFileForSong(song: MusicDirectory.Entry?): DownloadFile { + return downloader.getDownloadFileForSong(song) + } + + init { + Timber.i("MediaPlayerController constructed") + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt index 325ecf0a..de935eef 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt @@ -24,8 +24,10 @@ import android.view.KeyEvent import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import org.koin.android.ext.android.inject +import org.koin.core.component.KoinApiExtension import org.moire.ultrasonic.R import org.moire.ultrasonic.activity.NavigationActivity +import org.moire.ultrasonic.app.UApp import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.domain.PlayerState import org.moire.ultrasonic.domain.RepeatMode @@ -47,6 +49,7 @@ import timber.log.Timber * Android Foreground Service for playing music * while the rest of the Ultrasonic App is in the background. */ +@KoinApiExtension @Suppress("LargeClass") class MediaPlayerService : Service() { private val binder: IBinder = SimpleServiceBinder(this) @@ -406,9 +409,9 @@ class MediaPlayerService : Service() { } if (playerState === PlayerState.STARTED) { - scrobbler.scrobble(context, currentPlaying, false) + scrobbler.scrobble(currentPlaying, false) } else if (playerState === PlayerState.COMPLETED) { - scrobbler.scrobble(context, currentPlaying, true) + scrobbler.scrobble(currentPlaying, true) } null @@ -475,12 +478,11 @@ class MediaPlayerService : Service() { // Set Metadata val metadata = MediaMetadataCompat.Builder() - val context = applicationContext if (currentPlaying != null) { try { val song = currentPlaying.song val cover = FileUtil.getAlbumArtBitmap( - song, Util.getMinDisplayMetric(context), + song, Util.getMinDisplayMetric(), true ) metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, -1L) @@ -906,7 +908,8 @@ class MediaPlayerService : Service() { private val instanceLock = Any() @JvmStatic - fun getInstance(context: Context): MediaPlayerService? { + fun getInstance(): MediaPlayerService? { + val context = UApp.applicationContext() synchronized(instanceLock) { for (i in 0..19) { if (instance != null) return instance @@ -931,18 +934,18 @@ class MediaPlayerService : Service() { @JvmStatic fun executeOnStartedMediaPlayerService( - context: Context, - taskToExecute: (MediaPlayerService?) -> Unit + taskToExecute: (MediaPlayerService) -> Unit ) { val t: Thread = object : Thread() { override fun run() { - val instance = getInstance(context) + val instance = getInstance() if (instance == null) { Timber.e("ExecuteOnStarted.. failed to get a MediaPlayerService instance!") return + } else { + taskToExecute(instance) } - taskToExecute(instance) } } t.start() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/NetworkAndStorageChecker.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/NetworkAndStorageChecker.kt index aa508821..1bab7f17 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/NetworkAndStorageChecker.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/NetworkAndStorageChecker.kt @@ -12,7 +12,7 @@ class NetworkAndStorageChecker(val context: Context) { fun warnIfNetworkOrStorageUnavailable() { if (!Util.isExternalStoragePresent()) { Util.toast(context, R.string.select_album_no_sdcard) - } else if (!isOffline() && !Util.isNetworkConnected(context)) { + } else if (!isOffline() && !Util.isNetworkConnected()) { Util.toast(context, R.string.select_album_no_network) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt index 99551418..e52e98d9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/ShareHandler.kt @@ -91,7 +91,7 @@ class ShareHandler(val context: Context) { intent.type = "text/plain" intent.putExtra( Intent.EXTRA_TEXT, - String.format("%s\n\n%s", Util.getShareGreeting(context), result.url) + String.format("%s\n\n%s", Util.getShareGreeting(), result.url) ) fragment.activity?.startActivity( Intent.createChooser( diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt index f8afd6a9..fb03a627 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/VideoPlayer.kt @@ -10,7 +10,7 @@ import org.moire.ultrasonic.util.Util */ class VideoPlayer() { fun playVideo(context: Context, entry: MusicDirectory.Entry?) { - if (!Util.isNetworkConnected(context)) { + if (!Util.isNetworkConnected()) { Util.toast(context, R.string.select_album_no_network) return }