merge branch 'master' into buffering-indicator

This commit is contained in:
dkanada 2021-02-21 10:20:31 +09:00
commit 185076c4ec
12 changed files with 140 additions and 77 deletions

View file

@ -8,8 +8,8 @@ android {
minSdkVersion 19 minSdkVersion 19
targetSdkVersion 30 targetSdkVersion 30
versionCode 8 versionCode 9
versionName '1.2.0' versionName '1.2.1'
multiDexEnabled true multiDexEnabled true
vectorDrawables { vectorDrawables {

View file

@ -33,9 +33,8 @@ public class App extends Application {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
if (BuildConfig.DEBUG) { // enable for all builds to help with bug reports
RedScreenOfDeath.init(this); RedScreenOfDeath.init(this);
}
app = this; app = this;
database = createDatabase(this); database = createDatabase(this);

View file

@ -10,6 +10,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter; import androidx.fragment.app.FragmentPagerAdapter;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.FavoritesFragment;
import com.dkanada.gramophone.model.CategoryInfo; import com.dkanada.gramophone.model.CategoryInfo;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.AlbumsFragment; import com.dkanada.gramophone.fragments.mainactivity.library.pager.AlbumsFragment;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.ArtistsFragment; import com.dkanada.gramophone.fragments.mainactivity.library.pager.ArtistsFragment;
@ -154,7 +155,8 @@ public class MusicLibraryPagerAdapter extends FragmentPagerAdapter {
ALBUMS(AlbumsFragment.class), ALBUMS(AlbumsFragment.class),
ARTISTS(ArtistsFragment.class), ARTISTS(ArtistsFragment.class),
GENRES(GenresFragment.class), GENRES(GenresFragment.class),
PLAYLISTS(PlaylistsFragment.class); PLAYLISTS(PlaylistsFragment.class),
FAVORITES(FavoritesFragment.class);
private final Class<? extends Fragment> mFragmentClass; private final Class<? extends Fragment> mFragmentClass;

View file

@ -19,6 +19,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.dkanada.gramophone.databinding.FragmentLibraryBinding; import com.dkanada.gramophone.databinding.FragmentLibraryBinding;
import com.dkanada.gramophone.fragments.mainactivity.library.pager.FavoritesFragment;
import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.AppBarLayout;
import com.afollestad.materialcab.MaterialCab; import com.afollestad.materialcab.MaterialCab;
import com.kabouzeid.appthemehelper.ThemeStore; import com.kabouzeid.appthemehelper.ThemeStore;
@ -354,7 +355,7 @@ public class LibraryFragment extends AbsMainActivityFragment implements CabHolde
.setChecked(currentSortMethod.equals(SortMethod.ADDED)); .setChecked(currentSortMethod.equals(SortMethod.ADDED));
sortMethodMenu.add(0, R.id.action_sort_method_random, 4, R.string.sort_method_random) sortMethodMenu.add(0, R.id.action_sort_method_random, 4, R.string.sort_method_random)
.setChecked(currentSortMethod.equals(SortMethod.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) sortMethodMenu.add(0, R.id.action_sort_method_name, 0, R.string.sort_method_name)
.setChecked(currentSortMethod.equals(SortMethod.NAME)); .setChecked(currentSortMethod.equals(SortMethod.NAME));
sortMethodMenu.add(0, R.id.action_sort_method_album, 1, R.string.sort_method_album) sortMethodMenu.add(0, R.id.action_sort_method_album, 1, R.string.sort_method_album)

View file

@ -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;
}
}

View file

@ -44,7 +44,8 @@ public class CategoryInfo implements Parcelable {
ALBUMS(R.string.albums), ALBUMS(R.string.albums),
ARTISTS(R.string.artists), ARTISTS(R.string.artists),
GENRES(R.string.genres), GENRES(R.string.genres),
PLAYLISTS(R.string.playlists); PLAYLISTS(R.string.playlists),
FAVORITES(R.string.favorites);
public final int stringRes; public final int stringRes;

View file

@ -13,12 +13,10 @@ import com.dkanada.gramophone.util.PreferenceUtil;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.source.ConcatenatingMediaSource; import com.google.android.exoplayer2.source.MediaSourceFactory;
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.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.FileDataSource; 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 com.google.android.exoplayer2.upstream.cache.SimpleCache;
import java.io.File; 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 class MultiPlayer implements Playback {
public static final String TAG = MultiPlayer.class.getSimpleName(); public static final String TAG = MultiPlayer.class.getSimpleName();
private final Context context; private final Context context;
private final OkHttpClient httpClient; private final SimpleExoPlayer exoPlayer;
private SimpleExoPlayer exoPlayer;
private ConcatenatingMediaSource mediaSource;
private final SimpleCache simpleCache; private final SimpleCache simpleCache;
private final DataSource.Factory dataSource;
private PlaybackCallbacks callbacks; private PlaybackCallbacks callbacks;
@ -68,7 +52,7 @@ public class MultiPlayer implements Playback {
int windowIndex = exoPlayer.getCurrentWindowIndex(); int windowIndex = exoPlayer.getCurrentWindowIndex();
if (windowIndex == 1) { if (windowIndex == 1) {
mediaSource.removeMediaSource(0); exoPlayer.removeMediaItem(0);
if (exoPlayer.isPlaying()) { if (exoPlayer.isPlaying()) {
// there are still songs left in the queue // there are still songs left in the queue
callbacks.onTrackWentToNext(); callbacks.onTrackWentToNext();
@ -88,17 +72,11 @@ public class MultiPlayer implements Playback {
public MultiPlayer(Context context) { public MultiPlayer(Context context) {
this.context = context; this.context = context;
Dispatcher dispatcher = new Dispatcher(); MediaSourceFactory mediaSourceFactory = new UnknownMediaSourceFactory(buildDataSourceFactory());
dispatcher.setMaxRequests(1); exoPlayer = new SimpleExoPlayer.Builder(context).setMediaSourceFactory(mediaSourceFactory).build();
httpClient = new OkHttpClient.Builder().dispatcher(dispatcher).build();
exoPlayer = new SimpleExoPlayer.Builder(context).build();
mediaSource = new ConcatenatingMediaSource();
exoPlayer.addListener(eventListener); exoPlayer.addListener(eventListener);
exoPlayer.prepare(mediaSource); exoPlayer.prepare();
exoPlayer.setRepeatMode(Player.REPEAT_MODE_OFF);
long cacheSize = PreferenceUtil.getInstance(context).getMediaCacheSize(); long cacheSize = PreferenceUtil.getInstance(context).getMediaCacheSize();
LeastRecentlyUsedCacheEvictor recentlyUsedCache = new LeastRecentlyUsedCacheEvictor(cacheSize); LeastRecentlyUsedCacheEvictor recentlyUsedCache = new LeastRecentlyUsedCacheEvictor(cacheSize);
@ -106,23 +84,19 @@ public class MultiPlayer implements Playback {
File cacheDirectory = new File(context.getCacheDir(), "exoplayer"); File cacheDirectory = new File(context.getCacheDir(), "exoplayer");
simpleCache = new SimpleCache(cacheDirectory, recentlyUsedCache, databaseProvider); simpleCache = new SimpleCache(cacheDirectory, recentlyUsedCache, databaseProvider);
dataSource = buildDataSourceFactory();
} }
@Override @Override
public void setDataSource(Song song) { public void setDataSource(Song song) {
mediaSource = new ConcatenatingMediaSource(); exoPlayer.clearMediaItems();
exoPlayer.addListener(eventListener);
exoPlayer.prepare(mediaSource);
appendDataSource(MusicUtil.getSongFileUri(song)); appendDataSource(MusicUtil.getSongFileUri(song));
exoPlayer.seekTo(0, 0);
} }
@Override @Override
public void queueDataSource(Song song) { public void queueDataSource(Song song) {
while (mediaSource.getSize() > 1) { while (exoPlayer.getMediaItemCount() > 1) {
mediaSource.removeMediaSource(1); exoPlayer.removeMediaItem(1);
} }
appendDataSource(MusicUtil.getSongFileUri(song)); appendDataSource(MusicUtil.getSongFileUri(song));
@ -130,36 +104,9 @@ public class MultiPlayer implements Playback {
private void appendDataSource(String path) { private void appendDataSource(String path) {
Uri uri = Uri.parse(path); Uri uri = Uri.parse(path);
MediaItem mediaItem = MediaItem.fromUri(uri);
httpClient.newCall(new Request.Builder().url(path).head().build()).enqueue(new Callback() { exoPlayer.addMediaItem(mediaItem);
@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);
}
});
} }
private DataSource.Factory buildDataSourceFactory() { private DataSource.Factory buildDataSourceFactory() {

View file

@ -449,8 +449,8 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
if (restoredPositionInTrack > 0) seek(restoredPositionInTrack); if (restoredPositionInTrack > 0) seek(restoredPositionInTrack);
notHandledMetaChangedForCurrentTrack = true; notHandledMetaChangedForCurrentTrack = true;
sendChangeInternal(META_CHANGED); handleChangeInternal(META_CHANGED);
sendChangeInternal(QUEUE_CHANGED); handleChangeInternal(QUEUE_CHANGED);
} }
} }
@ -1291,7 +1291,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
info.setItemId(mService.get().getCurrentSong().id); info.setItemId(mService.get().getCurrentSong().id);
info.setPositionTicks(progress * 10000); info.setPositionTicks(progress * 10000);
task.cancel(true); if (task != null) task.cancel(true);
executorService.shutdownNow(); executorService.shutdownNow();
} }
} }

View file

@ -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()
}
}

View file

@ -429,6 +429,7 @@ public final class PreferenceUtil {
defaultCategories.add(new CategoryInfo(CategoryInfo.Category.ARTISTS, true)); defaultCategories.add(new CategoryInfo(CategoryInfo.Category.ARTISTS, true));
defaultCategories.add(new CategoryInfo(CategoryInfo.Category.GENRES, true)); defaultCategories.add(new CategoryInfo(CategoryInfo.Category.GENRES, true));
defaultCategories.add(new CategoryInfo(CategoryInfo.Category.PLAYLISTS, true)); defaultCategories.add(new CategoryInfo(CategoryInfo.Category.PLAYLISTS, true));
defaultCategories.add(new CategoryInfo(CategoryInfo.Category.FAVORITES, true));
return defaultCategories; return defaultCategories;
} }

View file

@ -49,6 +49,7 @@
<string name="genres">Genres</string> <string name="genres">Genres</string>
<string name="songs">Songs</string> <string name="songs">Songs</string>
<string name="playlists">Playlists</string> <string name="playlists">Playlists</string>
<string name="favorites">Favorites</string>
<string name="no_playlists">No playlists</string> <string name="no_playlists">No playlists</string>
<string name="no_albums">No albums</string> <string name="no_albums">No albums</string>
<string name="no_songs">No songs</string> <string name="no_songs">No songs</string>

View file

@ -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