diff --git a/app/build.gradle b/app/build.gradle
index a9e44c03..b3d7f366 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,8 +8,8 @@ android {
minSdkVersion 19
targetSdkVersion 30
- versionCode 8
- versionName '1.2.0'
+ versionCode 9
+ versionName '1.2.1'
multiDexEnabled true
vectorDrawables {
diff --git a/app/src/main/java/com/dkanada/gramophone/App.java b/app/src/main/java/com/dkanada/gramophone/App.java
index 6dd0cdb2..ca36012f 100644
--- a/app/src/main/java/com/dkanada/gramophone/App.java
+++ b/app/src/main/java/com/dkanada/gramophone/App.java
@@ -33,9 +33,8 @@ public class App extends Application {
public void onCreate() {
super.onCreate();
- if (BuildConfig.DEBUG) {
- RedScreenOfDeath.init(this);
- }
+ // enable for all builds to help with bug reports
+ RedScreenOfDeath.init(this);
app = this;
database = createDatabase(this);
diff --git a/app/src/main/java/com/dkanada/gramophone/adapter/MusicLibraryPagerAdapter.java b/app/src/main/java/com/dkanada/gramophone/adapter/MusicLibraryPagerAdapter.java
index 1f93edde..0729f818 100644
--- a/app/src/main/java/com/dkanada/gramophone/adapter/MusicLibraryPagerAdapter.java
+++ b/app/src/main/java/com/dkanada/gramophone/adapter/MusicLibraryPagerAdapter.java
@@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
+import com.dkanada.gramophone.fragments.mainactivity.library.pager.FavoritesFragment;
import com.dkanada.gramophone.model.CategoryInfo;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.AlbumsFragment;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.ArtistsFragment;
@@ -154,7 +155,8 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter {
ALBUMS(AlbumsFragment.class),
ARTISTS(ArtistsFragment.class),
GENRES(GenresFragment.class),
- PLAYLISTS(PlaylistsFragment.class);
+ PLAYLISTS(PlaylistsFragment.class),
+ FAVORITES(FavoritesFragment.class);
private final Class extends Fragment> mFragmentClass;
diff --git a/app/src/main/java/com/dkanada/gramophone/fragments/mainactivity/library/LibraryFragment.java b/app/src/main/java/com/dkanada/gramophone/fragments/mainactivity/library/LibraryFragment.java
index fa8fb365..67a5ad9b 100644
--- a/app/src/main/java/com/dkanada/gramophone/fragments/mainactivity/library/LibraryFragment.java
+++ b/app/src/main/java/com/dkanada/gramophone/fragments/mainactivity/library/LibraryFragment.java
@@ -19,6 +19,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.dkanada.gramophone.databinding.FragmentLibraryBinding;
+import com.dkanada.gramophone.fragments.mainactivity.library.pager.FavoritesFragment;
import com.google.android.material.appbar.AppBarLayout;
import com.afollestad.materialcab.MaterialCab;
import com.kabouzeid.appthemehelper.ThemeStore;
@@ -354,7 +355,7 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde
.setChecked(currentSortMethod.equals(SortMethod.ADDED));
sortMethodMenu.add(0, R.id.action_sort_method_random, 4, R.string.sort_method_random)
.setChecked(currentSortMethod.equals(SortMethod.RANDOM));
- } else if (fragment instanceof SongsFragment) {
+ } else if (fragment instanceof SongsFragment || fragment instanceof FavoritesFragment) {
sortMethodMenu.add(0, R.id.action_sort_method_name, 0, R.string.sort_method_name)
.setChecked(currentSortMethod.equals(SortMethod.NAME));
sortMethodMenu.add(0, R.id.action_sort_method_album, 1, R.string.sort_method_album)
diff --git a/app/src/main/java/com/dkanada/gramophone/fragments/mainactivity/library/pager/FavoritesFragment.java b/app/src/main/java/com/dkanada/gramophone/fragments/mainactivity/library/pager/FavoritesFragment.java
new file mode 100644
index 00000000..0dc9c134
--- /dev/null
+++ b/app/src/main/java/com/dkanada/gramophone/fragments/mainactivity/library/pager/FavoritesFragment.java
@@ -0,0 +1,30 @@
+package com.dkanada.gramophone.fragments.mainactivity.library.pager;
+
+import androidx.annotation.NonNull;
+import com.dkanada.gramophone.App;
+import com.dkanada.gramophone.util.PreferenceUtil;
+import com.dkanada.gramophone.util.QueryUtil;
+import org.jellyfin.apiclient.model.querying.ItemFields;
+import org.jellyfin.apiclient.model.querying.ItemFilter;
+import org.jellyfin.apiclient.model.querying.ItemQuery;
+
+public class FavoritesFragment extends SongsFragment {
+ @NonNull
+ @Override
+ protected ItemQuery createQuery() {
+ ItemQuery query = new ItemQuery();
+
+ query.setIncludeItemTypes(new String[]{"Audio"});
+ query.setFields(new ItemFields[]{ItemFields.MediaSources});
+ query.setUserId(App.getApiClient().getCurrentUserId());
+ query.setRecursive(true);
+ query.setLimit(PreferenceUtil.getInstance(App.getInstance()).getPageSize());
+ query.setStartIndex(getAdapter().getItemCount());
+ query.setParentId(QueryUtil.currentLibrary.getId());
+ query.setFilters(new ItemFilter[]{ItemFilter.IsFavorite});
+
+ QueryUtil.applySortMethod(query, PreferenceUtil.getInstance(App.getInstance()).getSongSortMethod());
+ QueryUtil.applySortOrder(query, PreferenceUtil.getInstance(App.getInstance()).getSongSortOrder());
+ return query;
+ }
+}
diff --git a/app/src/main/java/com/dkanada/gramophone/model/CategoryInfo.java b/app/src/main/java/com/dkanada/gramophone/model/CategoryInfo.java
index 214c8221..0b112a6e 100644
--- a/app/src/main/java/com/dkanada/gramophone/model/CategoryInfo.java
+++ b/app/src/main/java/com/dkanada/gramophone/model/CategoryInfo.java
@@ -44,7 +44,8 @@ public class CategoryInfo implements Parcelable {
ALBUMS(R.string.albums),
ARTISTS(R.string.artists),
GENRES(R.string.genres),
- PLAYLISTS(R.string.playlists);
+ PLAYLISTS(R.string.playlists),
+ FAVORITES(R.string.favorites);
public final int stringRes;
diff --git a/app/src/main/java/com/dkanada/gramophone/service/MultiPlayer.java b/app/src/main/java/com/dkanada/gramophone/service/MultiPlayer.java
index cf74a75f..24c63c4f 100644
--- a/app/src/main/java/com/dkanada/gramophone/service/MultiPlayer.java
+++ b/app/src/main/java/com/dkanada/gramophone/service/MultiPlayer.java
@@ -13,12 +13,10 @@ import com.dkanada.gramophone.util.PreferenceUtil;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
-import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
-import com.google.android.exoplayer2.source.MediaSource;
-import com.google.android.exoplayer2.source.ProgressiveMediaSource;
-import com.google.android.exoplayer2.source.hls.HlsMediaSource;
+import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.FileDataSource;
@@ -28,27 +26,13 @@ import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvicto
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import java.io.File;
-import java.io.IOException;
-
-import okhttp3.Call;
-import okhttp3.Callback;
-import okhttp3.Dispatcher;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.internal.annotations.EverythingIsNonNull;
public class MultiPlayer implements Playback {
public static final String TAG = MultiPlayer.class.getSimpleName();
private final Context context;
- private final OkHttpClient httpClient;
-
- private SimpleExoPlayer exoPlayer;
- private ConcatenatingMediaSource mediaSource;
-
+ private final SimpleExoPlayer exoPlayer;
private final SimpleCache simpleCache;
- private final DataSource.Factory dataSource;
private PlaybackCallbacks callbacks;
@@ -68,7 +52,7 @@ public class MultiPlayer implements Playback {
int windowIndex = exoPlayer.getCurrentWindowIndex();
if (windowIndex == 1) {
- mediaSource.removeMediaSource(0);
+ exoPlayer.removeMediaItem(0);
if (exoPlayer.isPlaying()) {
// there are still songs left in the queue
callbacks.onTrackWentToNext();
@@ -88,17 +72,11 @@ public class MultiPlayer implements Playback {
public MultiPlayer(Context context) {
this.context = context;
- Dispatcher dispatcher = new Dispatcher();
- dispatcher.setMaxRequests(1);
-
- httpClient = new OkHttpClient.Builder().dispatcher(dispatcher).build();
-
- exoPlayer = new SimpleExoPlayer.Builder(context).build();
- mediaSource = new ConcatenatingMediaSource();
+ MediaSourceFactory mediaSourceFactory = new UnknownMediaSourceFactory(buildDataSourceFactory());
+ exoPlayer = new SimpleExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build();
exoPlayer.addListener(eventListener);
- exoPlayer.prepare(mediaSource);
- exoPlayer.setRepeatMode(Player.REPEAT_MODE_OFF);
+ exoPlayer.prepare();
long cacheSize = PreferenceUtil.getInstance(context).getMediaCacheSize();
LeastRecentlyUsedCacheEvictor recentlyUsedCache = new LeastRecentlyUsedCacheEvictor(cacheSize);
@@ -106,23 +84,19 @@ public class MultiPlayer implements Playback {
File cacheDirectory = new File(context.getCacheDir(), "exoplayer");
simpleCache = new SimpleCache(cacheDirectory, recentlyUsedCache, databaseProvider);
- dataSource = buildDataSourceFactory();
}
@Override
public void setDataSource(Song song) {
- mediaSource = new ConcatenatingMediaSource();
-
- exoPlayer.addListener(eventListener);
- exoPlayer.prepare(mediaSource);
-
+ exoPlayer.clearMediaItems();
appendDataSource(MusicUtil.getSongFileUri(song));
+ exoPlayer.seekTo(0, 0);
}
@Override
public void queueDataSource(Song song) {
- while (mediaSource.getSize() > 1) {
- mediaSource.removeMediaSource(1);
+ while (exoPlayer.getMediaItemCount() > 1) {
+ exoPlayer.removeMediaItem(1);
}
appendDataSource(MusicUtil.getSongFileUri(song));
@@ -130,36 +104,9 @@ public class MultiPlayer implements Playback {
private void appendDataSource(String path) {
Uri uri = Uri.parse(path);
+ MediaItem mediaItem = MediaItem.fromUri(uri);
- httpClient.newCall(new Request.Builder().url(path).head().build()).enqueue(new Callback() {
- @Override
- @EverythingIsNonNull
- public void onFailure(Call call, IOException e) {
- Toast.makeText(context, context.getResources().getString(R.string.unplayable_file), Toast.LENGTH_SHORT).show();
- e.printStackTrace();
- }
-
- @Override
- @EverythingIsNonNull
- public void onResponse(Call call, Response response) {
- String type = response.header("Content-Type");
- if (type == null) return;
-
- MediaSource source;
- if (type.equals("application/x-mpegURL")) {
- source = new HlsMediaSource.Factory(dataSource)
- .setTag(path)
- .setAllowChunklessPreparation(true)
- .createMediaSource(uri);
- } else {
- source = new ProgressiveMediaSource.Factory(dataSource)
- .setTag(path)
- .createMediaSource(uri);
- }
-
- mediaSource.addMediaSource(source);
- }
- });
+ exoPlayer.addMediaItem(mediaItem);
}
private DataSource.Factory buildDataSourceFactory() {
diff --git a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java
index 79ffc13e..57027e31 100644
--- a/app/src/main/java/com/dkanada/gramophone/service/MusicService.java
+++ b/app/src/main/java/com/dkanada/gramophone/service/MusicService.java
@@ -449,8 +449,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
if (restoredPositionInTrack > 0) seek(restoredPositionInTrack);
notHandledMetaChangedForCurrentTrack = true;
- sendChangeInternal(META_CHANGED);
- sendChangeInternal(QUEUE_CHANGED);
+ handleChangeInternal(META_CHANGED);
+ handleChangeInternal(QUEUE_CHANGED);
}
}
@@ -1291,7 +1291,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
info.setItemId(mService.get().getCurrentSong().id);
info.setPositionTicks(progress * 10000);
- task.cancel(true);
+ if (task != null) task.cancel(true);
executorService.shutdownNow();
}
}
diff --git a/app/src/main/java/com/dkanada/gramophone/service/UnknownMediaSourceFactory.kt b/app/src/main/java/com/dkanada/gramophone/service/UnknownMediaSourceFactory.kt
new file mode 100644
index 00000000..100a7d03
--- /dev/null
+++ b/app/src/main/java/com/dkanada/gramophone/service/UnknownMediaSourceFactory.kt
@@ -0,0 +1,73 @@
+package com.dkanada.gramophone.service
+
+import com.google.android.exoplayer2.MediaItem
+import com.google.android.exoplayer2.drm.DrmSessionManager
+import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
+import com.google.android.exoplayer2.source.MediaSource
+import com.google.android.exoplayer2.source.MediaSourceFactory
+import com.google.android.exoplayer2.source.ProgressiveMediaSource
+import com.google.android.exoplayer2.source.hls.HlsMediaSource
+import com.google.android.exoplayer2.upstream.*
+
+import kotlinx.coroutines.*
+
+import java.net.HttpURLConnection
+import java.net.URL
+
+class UnknownMediaSourceFactory(dataSourceFactory: DataSource.Factory) : MediaSourceFactory {
+ private val hlsMediaSource : HlsMediaSource.Factory
+ private val progressiveMediaSource : ProgressiveMediaSource.Factory
+
+ private var loadErrorHandlingPolicy: LoadErrorHandlingPolicy
+
+ override fun setDrmSessionManager(drmSessionManager: DrmSessionManager?): MediaSourceFactory {
+ return this
+ }
+
+ override fun setDrmHttpDataSourceFactory(drmHttpDataSourceFactory: HttpDataSource.Factory?): MediaSourceFactory {
+ return this
+ }
+
+ override fun setDrmUserAgent(drmUserAgent: String?): MediaSourceFactory {
+ return this
+ }
+
+ override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy?): MediaSourceFactory {
+ this.loadErrorHandlingPolicy = loadErrorHandlingPolicy!!
+ return this
+ }
+
+ override fun getSupportedTypes(): IntArray {
+ return intArrayOf()
+ }
+
+ override fun createMediaSource(mediaItem: MediaItem): MediaSource {
+ val type: String? = runBlocking {
+ httpGet(mediaItem.playbackProperties!!.uri.toString())
+ }
+
+ val sourceFactory: MediaSourceFactory = if (type == "application/x-mpegURL") {
+ hlsMediaSource
+ } else {
+ progressiveMediaSource
+ }
+
+ return sourceFactory.createMediaSource(mediaItem)
+ }
+
+ private suspend fun httpGet(url: String?): String? {
+ return withContext(Dispatchers.IO) {
+ val request = URL(url)
+ val conn = request.openConnection() as HttpURLConnection
+
+ return@withContext conn.getHeaderField("Content-Type")
+ }
+ }
+
+ init {
+ hlsMediaSource = HlsMediaSource.Factory(dataSourceFactory)
+ progressiveMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory())
+
+ loadErrorHandlingPolicy = DefaultLoadErrorHandlingPolicy()
+ }
+}
diff --git a/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java b/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java
index 7217d2f6..da2ff95f 100644
--- a/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java
+++ b/app/src/main/java/com/dkanada/gramophone/util/PreferenceUtil.java
@@ -429,6 +429,7 @@ public final class PreferenceUtil {
defaultCategories.add(new CategoryInfo(CategoryInfo.Category.ARTISTS, true));
defaultCategories.add(new CategoryInfo(CategoryInfo.Category.GENRES, true));
defaultCategories.add(new CategoryInfo(CategoryInfo.Category.PLAYLISTS, true));
+ defaultCategories.add(new CategoryInfo(CategoryInfo.Category.FAVORITES, true));
return defaultCategories;
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e6c53142..b88c2079 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -49,6 +49,7 @@
Genres
Songs
Playlists
+ Favorites
No playlists
No albums
No songs
diff --git a/metadata/en-US/changelogs/9.txt b/metadata/en-US/changelogs/9.txt
new file mode 100644
index 00000000..82d0692b
--- /dev/null
+++ b/metadata/en-US/changelogs/9.txt
@@ -0,0 +1,8 @@
+- move glide cache to the proper location
+- fix issue with lock screen cover display
+- change default sort method to random
+- replace queue store with room database
+- huge improvements for login activity style
+- add crash activity to display errors
+- refactor ExoPlayer wrapper
+- implement favorites tab on home activity