mirror of
https://github.com/ultrasonic/ultrasonic
synced 2025-02-17 04:00:39 +01:00
Merge pull request #501 from tzugen/impl
Migrate MediaPlayerController to Kotlin
This commit is contained in:
commit
04b4b154e4
@ -25,7 +25,7 @@
|
||||
<ID>ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$String.format("BufferTask (%s)", downloadFile)</ID>
|
||||
<ID>ImplicitDefaultLocale:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$String.format("CheckCompletionTask (%s)", downloadFile)</ID>
|
||||
<ID>ImplicitDefaultLocale:ShareHandler.kt$ShareHandler$String.format("%d:%s", timeSpanAmount, timeSpanType)</ID>
|
||||
<ID>ImplicitDefaultLocale:ShareHandler.kt$ShareHandler.<no name provided>$String.format("%s\n\n%s", Util.getShareGreeting(context), result.url)</ID>
|
||||
<ID>ImplicitDefaultLocale:ShareHandler.kt$ShareHandler.<no name provided>$String.format("%s\n\n%s", Util.getShareGreeting(), result.url)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%02d.", trackNumber)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s ", bitRate)</ID>
|
||||
<ID>ImplicitDefaultLocale:SongView.kt$SongView$String.format("%s > %s", suffix, transcodedSuffix)</ID>
|
||||
@ -46,20 +46,15 @@
|
||||
<ID>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) )</ID>
|
||||
<ID>MagicNumber:ActiveServerProvider.kt$ActiveServerProvider$8192</ID>
|
||||
<ID>MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$10</ID>
|
||||
<ID>MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$1000L</ID>
|
||||
<ID>MagicNumber:DownloadFile.kt$DownloadFile.DownloadTask$60</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer$1000</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$1000</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.<no name provided>$60000</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$100000</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$1000L</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$1024L</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$86400L</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.BufferTask$8L</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.CheckCompletionTask$5000L</ID>
|
||||
<ID>MagicNumber:LocalMediaPlayer.kt$LocalMediaPlayer.PositionCache$50L</ID>
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$1000</ID>
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$256</ID>
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$3</ID>
|
||||
<ID>MagicNumber:MediaPlayerService.kt$MediaPlayerService$4</ID>
|
||||
|
@ -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:
|
||||
|
@ -56,6 +56,7 @@ android {
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs += "-Xopt-in=org.koin.core.component.KoinApiExtension"
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -26,45 +26,45 @@
|
||||
<issue
|
||||
id="DefaultLocale"
|
||||
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
|
||||
errorLine1=" String lhs = lhsArtist.getName().toLowerCase();"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
errorLine1=" String lhs = lhsArtist.getName().toLowerCase();"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java"
|
||||
line="103"
|
||||
column="38"/>
|
||||
line="97"
|
||||
column="37"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="DefaultLocale"
|
||||
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
|
||||
errorLine1=" String rhs = rhsArtist.getName().toLowerCase();"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
errorLine1=" String rhs = rhsArtist.getName().toLowerCase();"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java"
|
||||
line="104"
|
||||
column="38"/>
|
||||
line="98"
|
||||
column="37"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="DefaultLocale"
|
||||
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
|
||||
errorLine1=" int index = lhs.indexOf(String.format("%s ", article.toLowerCase()));"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
errorLine1=" int index = lhs.indexOf(String.format("%s ", article.toLowerCase()));"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java"
|
||||
line="121"
|
||||
column="59"/>
|
||||
line="115"
|
||||
column="58"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="DefaultLocale"
|
||||
message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`."
|
||||
errorLine1=" index = rhs.indexOf(String.format("%s ", article.toLowerCase()));"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
errorLine1=" index = rhs.indexOf(String.format("%s ", article.toLowerCase()));"
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java"
|
||||
line="128"
|
||||
column="55"/>
|
||||
line="122"
|
||||
column="54"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
@ -74,7 +74,7 @@
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java"
|
||||
line="486"
|
||||
line="466"
|
||||
column="38"/>
|
||||
</issue>
|
||||
|
||||
@ -85,7 +85,7 @@
|
||||
errorLine2=" ~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java"
|
||||
line="488"
|
||||
line="468"
|
||||
column="43"/>
|
||||
</issue>
|
||||
|
||||
@ -238,7 +238,7 @@
|
||||
errorLine2=" ~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/service/JukeboxMediaPlayer.java"
|
||||
line="483"
|
||||
line="476"
|
||||
column="58"/>
|
||||
</issue>
|
||||
|
||||
@ -491,7 +491,7 @@
|
||||
errorLine2=" ~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/service/OfflineMusicService.java"
|
||||
line="653"
|
||||
line="633"
|
||||
column="37"/>
|
||||
</issue>
|
||||
|
||||
@ -579,39 +579,6 @@
|
||||
file="src/main/res/drawable-xhdpi-v14"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="StaticFieldLeak"
|
||||
message="This `AsyncTask` class should be static or leaks might occur (org.moire.ultrasonic.util.CacheCleaner.BackgroundCleanup)"
|
||||
errorLine1=" private class BackgroundCleanup extends AsyncTask<Void, Void, Void>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/util/CacheCleaner.java"
|
||||
line="225"
|
||||
column="16"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="StaticFieldLeak"
|
||||
message="This `AsyncTask` class should be static or leaks might occur (org.moire.ultrasonic.util.CacheCleaner.BackgroundSpaceCleanup)"
|
||||
errorLine1=" private class BackgroundSpaceCleanup extends AsyncTask<Void, Void, Void>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/util/CacheCleaner.java"
|
||||
line="253"
|
||||
column="16"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="StaticFieldLeak"
|
||||
message="This `AsyncTask` class should be static or leaks might occur (org.moire.ultrasonic.util.CacheCleaner.BackgroundPlaylistsCleanup)"
|
||||
errorLine1=" private class BackgroundPlaylistsCleanup extends AsyncTask<List<Playlist>, Void, Void>"
|
||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||
<location
|
||||
file="src/main/java/org/moire/ultrasonic/util/CacheCleaner.java"
|
||||
line="282"
|
||||
column="16"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="StaticFieldLeak"
|
||||
message="This `AsyncTask` class should be static or leaks might occur (org.moire.ultrasonic.service.DownloadQueueSerializer.SerializeTask)"
|
||||
@ -2117,13 +2084,6 @@
|
||||
file="src/main/res/drawable-hdpi/list_pressed_holo_dark.9.png"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="IconMissingDensityFolder"
|
||||
message="Missing density variation folders in `/home/xyz/08-Tech/02-Android/ultrasonic/ultrasonic/src/main/res`: drawable-mdpi, drawable-xhdpi, drawable-xxhdpi">
|
||||
<location
|
||||
file="src/main/res"/>
|
||||
</issue>
|
||||
|
||||
<issue
|
||||
id="Autofill"
|
||||
message="Missing `autofillHints` attribute"
|
||||
|
@ -235,10 +235,11 @@ public class MainFragment extends Fragment {
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToArtistList, bundle);
|
||||
}
|
||||
|
||||
private void showAlbumList(final String type, final int title) {
|
||||
private void showAlbumList(final String type, final int titleIndex) {
|
||||
Bundle bundle = new Bundle();
|
||||
String title = getContext().getResources().getString(titleIndex, "");
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, title);
|
||||
bundle.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TITLE, title);
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_SIZE, Util.getMaxAlbums());
|
||||
bundle.putInt(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_OFFSET, 0);
|
||||
Navigation.findNavController(getView()).navigate(R.id.mainToAlbumList, bundle);
|
||||
|
@ -131,7 +131,7 @@ public class PlaylistsFragment extends Fragment {
|
||||
List<Playlist> playlists = musicService.getPlaylists(refresh);
|
||||
|
||||
if (!ActiveServerProvider.Companion.isOffline())
|
||||
new CacheCleaner(getContext()).cleanPlaylists(playlists);
|
||||
new CacheCleaner().cleanPlaylists(playlists);
|
||||
return playlists;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
||||
private final Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
||||
|
||||
private final List<DownloadFile> cleanupCandidates = new ArrayList<>();
|
||||
private final LRUCache<MusicDirectory.Entry, DownloadFile> downloadFileCache = new LRUCache<>(100);
|
||||
private ScheduledExecutorService executorService;
|
||||
private long revision;
|
||||
|
||||
public Downloader(Context context, ShufflePlayBuffer shufflePlayBuffer, ExternalStorageMonitor externalStorageMonitor,
|
||||
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++;
|
||||
|
@ -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; }
|
||||
|
@ -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<MediaPlayerControllerImpl> mediaPlayerControllerLazy = inject(MediaPlayerControllerImpl.class);
|
||||
private final Lazy<MediaPlayerController> 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);
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<Entry> songs, boolean save, boolean autoplay, boolean playNext, boolean shuffle, boolean newPlaylist);
|
||||
|
||||
void downloadBackground(List<Entry> 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<Entry> songs);
|
||||
|
||||
void unpin(List<Entry> 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<Entry> 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<DownloadFile> getPlayList();
|
||||
|
||||
long getPlayListUpdateRevision();
|
||||
|
||||
long getPlayListDuration();
|
||||
|
||||
DownloadFile getDownloadFileForSong(Entry song);
|
||||
}
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
|
||||
private final Lazy<ActiveServerProvider> 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<MusicDirectory.Entry> songs, final int currentPlayingIndex, final int currentPlayingPosition, final boolean autoPlay, boolean newPlaylist)
|
||||
{
|
||||
download(songs, false, false, false, false, newPlaylist);
|
||||
|
||||
if (currentPlayingIndex != -1)
|
||||
{
|
||||
MediaPlayerService.executeOnStartedMediaPlayerService(context, (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<MusicDirectory.Entry> songs, boolean save, boolean autoPlay, boolean playNext, boolean shuffle, boolean newPlaylist)
|
||||
{
|
||||
downloader.download(songs, save, autoPlay, playNext, newPlaylist);
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
|
||||
if (shuffle) shuffle();
|
||||
|
||||
if (!playNext && !autoPlay && (downloader.downloadList.size() - 1) == downloader.getCurrentPlayingIndex())
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
|
||||
}
|
||||
|
||||
if (autoPlay)
|
||||
{
|
||||
play(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (localMediaPlayer.currentPlaying == null && downloader.downloadList.size() > 0)
|
||||
{
|
||||
localMediaPlayer.currentPlaying = downloader.downloadList.get(0);
|
||||
localMediaPlayer.currentPlaying.setPlaying(true);
|
||||
}
|
||||
|
||||
downloader.checkDownloads();
|
||||
}
|
||||
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void downloadBackground(List<MusicDirectory.Entry> songs, boolean save)
|
||||
{
|
||||
downloader.downloadBackground(songs, save);
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
}
|
||||
|
||||
public synchronized void setCurrentPlaying(DownloadFile currentPlaying)
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) localMediaPlayer.setCurrentPlaying(currentPlaying);
|
||||
}
|
||||
|
||||
public synchronized void setCurrentPlaying(int index)
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.setCurrentPlaying(index);
|
||||
}
|
||||
|
||||
public synchronized void setPlayerState(PlayerState state)
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) localMediaPlayer.setPlayerState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopJukeboxService()
|
||||
{
|
||||
jukeboxMediaPlayer.getValue().stopJukeboxService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setShufflePlayEnabled(boolean enabled)
|
||||
{
|
||||
shufflePlayBuffer.isEnabled = enabled;
|
||||
if (enabled)
|
||||
{
|
||||
clear();
|
||||
downloader.checkDownloads();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShufflePlayEnabled()
|
||||
{
|
||||
return shufflePlayBuffer.isEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void shuffle()
|
||||
{
|
||||
downloader.shuffle();
|
||||
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepeatMode getRepeatMode()
|
||||
{
|
||||
return Util.getRepeatMode();
|
||||
}
|
||||
|
||||
@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<DownloadFile> iterator = downloader.downloadList.iterator();
|
||||
|
||||
while (iterator.hasNext())
|
||||
{
|
||||
DownloadFile downloadFile = iterator.next();
|
||||
if (!downloadFile.isCompleteFileAvailable())
|
||||
{
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(DownloadFile downloadFile)
|
||||
{
|
||||
if (downloadFile == localMediaPlayer.currentPlaying)
|
||||
{
|
||||
reset();
|
||||
setCurrentPlaying(null);
|
||||
}
|
||||
|
||||
downloader.removeDownloadFile(downloadFile);
|
||||
|
||||
downloadQueueSerializer.serializeDownloadQueue(downloader.downloadList, downloader.getCurrentPlayingIndex(), getPlayerPosition());
|
||||
jukeboxMediaPlayer.getValue().updatePlaylist();
|
||||
|
||||
if (downloadFile == localMediaPlayer.nextPlaying)
|
||||
{
|
||||
MediaPlayerService mediaPlayerService = MediaPlayerService.getRunningInstance();
|
||||
if (mediaPlayerService != null) mediaPlayerService.setNextPlaying();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void delete(List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
for (MusicDirectory.Entry song : songs)
|
||||
{
|
||||
downloader.getDownloadFileForSong(song).delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void unpin(List<MusicDirectory.Entry> songs)
|
||||
{
|
||||
for (MusicDirectory.Entry song : songs)
|
||||
{
|
||||
downloader.getDownloadFileForSong(song).unpin();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void previous()
|
||||
{
|
||||
int index = downloader.getCurrentPlayingIndex();
|
||||
if (index == -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Restart song if played more than five seconds.
|
||||
if (getPlayerPosition() > 5000 || index == 0)
|
||||
{
|
||||
play(index);
|
||||
}
|
||||
else
|
||||
{
|
||||
play(index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void next()
|
||||
{
|
||||
int index = downloader.getCurrentPlayingIndex();
|
||||
if (index != -1)
|
||||
{
|
||||
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<DownloadFile> getPlayList() {
|
||||
return downloader.downloadList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPlayListUpdateRevision() {
|
||||
return downloader.getDownloadListUpdateRevision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPlayListDuration() {
|
||||
return downloader.getDownloadListDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownloadFile getDownloadFileForSong(Entry song) {
|
||||
return downloader.getDownloadFileForSong(song);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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<Artist>()
|
||||
{
|
||||
@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.<Artist>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<Artist>()
|
||||
{
|
||||
@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<MusicDirectory.Entry>()
|
||||
{
|
||||
@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<MusicDirectory.Entry>()
|
||||
{
|
||||
@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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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> downloader = inject(Downloader.class);
|
||||
private Lazy<ActiveServerProvider> 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<File> dirs, Collection<File> doNotDelete)
|
||||
private static void deleteEmptyDirs(Iterable<File> dirs, Collection<File> doNotDelete)
|
||||
{
|
||||
for (File dir : dirs)
|
||||
{
|
||||
@ -108,7 +101,7 @@ public class CacheCleaner
|
||||
}
|
||||
}
|
||||
|
||||
private long getMinimumDelete(List<File> files)
|
||||
private static long getMinimumDelete(List<File> 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<File> files, List<File> dirs)
|
||||
@ -197,20 +190,14 @@ public class CacheCleaner
|
||||
|
||||
private static void sortByAscendingModificationTime(List<File> files)
|
||||
{
|
||||
Collections.sort(files, new Comparator<File>()
|
||||
{
|
||||
@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<File> findFilesToNotDelete()
|
||||
private static Set<File> findFilesToNotDelete()
|
||||
{
|
||||
Set<File> filesToNotDelete = new HashSet<File>(5);
|
||||
Set<File> filesToNotDelete = new HashSet<>(5);
|
||||
|
||||
Lazy<Downloader> downloader = inject(Downloader.class);
|
||||
|
||||
for (DownloadFile downloadFile : downloader.getValue().getDownloads())
|
||||
{
|
||||
@ -222,7 +209,7 @@ public class CacheCleaner
|
||||
return filesToNotDelete;
|
||||
}
|
||||
|
||||
private class BackgroundCleanup extends AsyncTask<Void, Void, Void>
|
||||
private static class BackgroundCleanup extends AsyncTask<Void, Void, Void>
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
@ -230,8 +217,8 @@ public class CacheCleaner
|
||||
try
|
||||
{
|
||||
Thread.currentThread().setName("BackgroundCleanup");
|
||||
List<File> files = new ArrayList<File>();
|
||||
List<File> dirs = new ArrayList<File>();
|
||||
List<File> files = new ArrayList<>();
|
||||
List<File> dirs = new ArrayList<>();
|
||||
|
||||
findCandidatesForDeletion(FileUtil.getMusicDirectory(), files, dirs);
|
||||
sortByAscendingModificationTime(files);
|
||||
@ -250,7 +237,7 @@ public class CacheCleaner
|
||||
}
|
||||
}
|
||||
|
||||
private class BackgroundSpaceCleanup extends AsyncTask<Void, Void, Void>
|
||||
private static class BackgroundSpaceCleanup extends AsyncTask<Void, Void, Void>
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground(Void... params)
|
||||
@ -258,8 +245,8 @@ public class CacheCleaner
|
||||
try
|
||||
{
|
||||
Thread.currentThread().setName("BackgroundSpaceCleanup");
|
||||
List<File> files = new ArrayList<File>();
|
||||
List<File> dirs = new ArrayList<File>();
|
||||
List<File> files = new ArrayList<>();
|
||||
List<File> dirs = new ArrayList<>();
|
||||
findCandidatesForDeletion(FileUtil.getMusicDirectory(), files, dirs);
|
||||
|
||||
long bytesToDelete = getMinimumDelete(files);
|
||||
@ -279,13 +266,14 @@ public class CacheCleaner
|
||||
}
|
||||
}
|
||||
|
||||
private class BackgroundPlaylistsCleanup extends AsyncTask<List<Playlist>, Void, Void>
|
||||
private static class BackgroundPlaylistsCleanup extends AsyncTask<List<Playlist>, Void, Void>
|
||||
{
|
||||
@Override
|
||||
protected Void doInBackground(List<Playlist>... params)
|
||||
{
|
||||
try
|
||||
{
|
||||
Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
|
||||
Thread.currentThread().setName("BackgroundPlaylistsCleanup");
|
||||
String server = activeServerProvider.getValue().getActiveServer().getName();
|
||||
SortedSet<File> playlistFiles = FileUtil.listFiles(FileUtil.getPlaylistDirectory(server));
|
||||
|
@ -79,7 +79,7 @@ public class LegacyImageLoader implements Runnable, ImageLoader {
|
||||
imageSizeDefault = drawable.getIntrinsicHeight();
|
||||
}
|
||||
|
||||
imageSizeLarge = Util.getMaxDisplayMetric(context);
|
||||
imageSizeLarge = Util.getMaxDisplayMetric();
|
||||
createLargeUnknownImage(context);
|
||||
createUnknownAvatarImage(context);
|
||||
}
|
||||
|
@ -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<MusicDirectory.Entry> 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;
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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<MediaPlayerController> {
|
||||
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()) }
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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<MusicDirectory.Entry?>?,
|
||||
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<MusicDirectory.Entry?>?,
|
||||
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<MusicDirectory.Entry?>?, 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<MusicDirectory.Entry?>) {
|
||||
for (song in songs) {
|
||||
downloader.getDownloadFileForSong(song).delete()
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun unpin(songs: List<MusicDirectory.Entry?>) {
|
||||
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<DownloadFile>
|
||||
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")
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user