use blurhash for most objects

This commit is contained in:
dkanada 2020-09-23 01:19:44 +09:00
commit a66f0d5fcd
24 changed files with 85 additions and 61 deletions

View file

@ -47,7 +47,6 @@ android {
dependencies { dependencies {
implementation 'com.github.jellyfin.jellyfin-apiclient-java:android:0.7.3' implementation 'com.github.jellyfin.jellyfin-apiclient-java:android:0.7.3'
implementation 'com.github.woltapp:blurhash:f41a23cc50' implementation 'com.github.woltapp:blurhash:f41a23cc50'
implementation 'com.github.florent37:glidepalette:2.1.2'
implementation 'com.google.android.exoplayer:exoplayer:2.11.4' implementation 'com.google.android.exoplayer:exoplayer:2.11.4'
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.2.1'

View file

@ -108,8 +108,8 @@ public class AlbumCoverPagerAdapter extends CustomFragmentStatePagerAdapter {
private void loadAlbumCover() { private void loadAlbumCover() {
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(getContext()), song.primary) .from(getContext(), song.primary, song.blurHash)
.palette(getActivity()).build() .palette().build()
.into(new CustomPaletteTarget(binding.playerImage) { .into(new CustomPaletteTarget(binding.playerImage) {
@Override @Override
public void onColorReady(int color) { public void onColorReady(int color) {

View file

@ -83,8 +83,8 @@ public class GenreAdapter extends RecyclerView.Adapter<GenreAdapter.ViewHolder>
if (holder.image == null) return; if (holder.image == null) return;
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), genre.id) .from(activity, genre.id, genre.id)
.palette(activity).build() .palette().build()
.into(new CustomPaletteTarget(holder.image) { .into(new CustomPaletteTarget(holder.image) {
@Override @Override
public void onLoadCleared(Drawable placeholder) { public void onLoadCleared(Drawable placeholder) {

View file

@ -93,8 +93,8 @@ public class PlaylistAdapter extends AbsMultiSelectAdapter<PlaylistAdapter.ViewH
if (holder.image == null) return; if (holder.image == null) return;
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), playlist.id) .from(activity, playlist.id, playlist.id)
.palette(activity).build() .palette().build()
.into(new CustomPaletteTarget(holder.image) { .into(new CustomPaletteTarget(holder.image) {
@Override @Override
public void onLoadCleared(Drawable placeholder) { public void onLoadCleared(Drawable placeholder) {

View file

@ -70,7 +70,7 @@ public class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.ViewHolder
holder.title.setText(album.title); holder.title.setText(album.title);
holder.text.setText(MusicUtil.getAlbumInfoString(activity, album)); holder.text.setText(MusicUtil.getAlbumInfoString(activity, album));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), album.primary) .from(activity, album.primary, album.blurHash)
.build().into(holder.image); .build().into(holder.image);
break; break;
case ARTIST: case ARTIST:
@ -78,7 +78,7 @@ public class SearchAdapter extends RecyclerView.Adapter<SearchAdapter.ViewHolder
holder.title.setText(artist.name); holder.title.setText(artist.name);
holder.text.setText(MusicUtil.getArtistInfoString(activity, artist)); holder.text.setText(MusicUtil.getArtistInfoString(activity, artist));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), artist.primary) .from(activity, artist.primary, artist.blurHash)
.build().into(holder.image); .build().into(holder.image);
break; break;
case SONG: case SONG:

View file

@ -129,8 +129,8 @@ public class AlbumAdapter extends AbsMultiSelectAdapter<AlbumAdapter.ViewHolder,
if (holder.image == null) return; if (holder.image == null) return;
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), album.primary) .from(activity, album.primary, album.blurHash)
.palette(activity).build() .palette().build()
.into(new CustomPaletteTarget(holder.image) { .into(new CustomPaletteTarget(holder.image) {
@Override @Override
public void onLoadCleared(Drawable placeholder) { public void onLoadCleared(Drawable placeholder) {

View file

@ -52,8 +52,8 @@ public class HorizontalAlbumAdapter extends AlbumAdapter {
if (holder.image == null) return; if (holder.image == null) return;
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), album.primary) .from(activity, album.primary, album.blurHash)
.palette(activity).build() .palette().build()
.into(new CustomPaletteTarget(holder.image) { .into(new CustomPaletteTarget(holder.image) {
@Override @Override
public void onLoadCleared(Drawable placeholder) { public void onLoadCleared(Drawable placeholder) {

View file

@ -123,8 +123,8 @@ public class ArtistAdapter extends AbsMultiSelectAdapter<ArtistAdapter.ViewHolde
if (holder.image == null) return; if (holder.image == null) return;
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), artist.primary) .from(activity, artist.primary, artist.blurHash)
.palette(activity).build() .palette().build()
.into(new CustomPaletteTarget(holder.image) { .into(new CustomPaletteTarget(holder.image) {
@Override @Override
public void onLoadCleared(Drawable placeholder) { public void onLoadCleared(Drawable placeholder) {

View file

@ -85,7 +85,7 @@ public class ArtistSongAdapter extends ArrayAdapter<Song> implements MaterialCab
songInfo.setText(song.albumName); songInfo.setText(song.albumName);
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), song.primary) .from(activity, song.primary, song.blurHash)
.build().into(albumArt); .build().into(albumArt);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

View file

@ -134,8 +134,8 @@ public class SongAdapter extends AbsMultiSelectAdapter<SongAdapter.ViewHolder, S
if (holder.image == null) return; if (holder.image == null) return;
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(activity), song.primary) .from(activity, song.primary, song.blurHash)
.palette(activity).build() .palette().build()
.into(new CustomPaletteTarget(holder.image) { .into(new CustomPaletteTarget(holder.image) {
@Override @Override
public void onLoadCleared(Drawable placeholder) { public void onLoadCleared(Drawable placeholder) {

View file

@ -2,10 +2,12 @@ package com.dkanada.gramophone.glide;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull; import androidx.core.content.res.ResourcesCompat;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.RequestManager; import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
@ -17,6 +19,7 @@ import com.dkanada.gramophone.App;
import com.dkanada.gramophone.R; import com.dkanada.gramophone.R;
import com.dkanada.gramophone.glide.palette.BitmapPaletteCrossFadeFactory; import com.dkanada.gramophone.glide.palette.BitmapPaletteCrossFadeFactory;
import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper; import com.dkanada.gramophone.glide.palette.BitmapPaletteWrapper;
import com.wolt.blurhashkt.BlurHashDecoder;
import org.jellyfin.apiclient.model.dto.ImageOptions; import org.jellyfin.apiclient.model.dto.ImageOptions;
import org.jellyfin.apiclient.model.entities.ImageType; import org.jellyfin.apiclient.model.entities.ImageType;
@ -26,24 +29,34 @@ import static com.bumptech.glide.GenericTransitionOptions.with;
public class CustomGlideRequest { public class CustomGlideRequest {
public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL; public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
public static final int DEFAULT_IMAGE = R.drawable.default_album_art; public static final int DEFAULT_IMAGE = R.drawable.default_album_art;
public static final int DEFAULT_DURATION = 200;
public static class Builder { public static class Builder {
final RequestManager requestManager; private final RequestManager requestManager;
final String item; private final Object item;
private final Context context;
private Builder(@NonNull RequestManager requestManager, String item) { private Builder(Context context, String item, String placeholder) {
requestManager.applyDefaultRequestOptions(createRequestOptions(item)); this.requestManager = Glide.with(context);
this.item = item != null ? createUrl(item) : DEFAULT_IMAGE;
this.context = context;
this.requestManager = requestManager; if (placeholder != null) {
this.item = item; Bitmap bitmap = BlurHashDecoder.INSTANCE.decode(placeholder, 20, 20, 1, true);
BitmapDrawable drawable = new BitmapDrawable(context.getResources(), bitmap);
requestManager.applyDefaultRequestOptions(createRequestOptions(item, drawable));
} else {
Drawable drawable = ResourcesCompat.getDrawable(context.getResources(), DEFAULT_IMAGE, null);
requestManager.applyDefaultRequestOptions(createRequestOptions(item, drawable));
}
} }
public static Builder from(@NonNull RequestManager requestManager, String item) { public static Builder from(Context context, String item, String placeholder) {
return new Builder(requestManager, item); return new Builder(context, item, placeholder);
} }
public PaletteBuilder palette(Context context) { public PaletteBuilder palette() {
return new PaletteBuilder(this, context); return new PaletteBuilder(this, this.context);
} }
public BitmapBuilder bitmap() { public BitmapBuilder bitmap() {
@ -51,10 +64,8 @@ public class CustomGlideRequest {
} }
public RequestBuilder<Drawable> build() { public RequestBuilder<Drawable> build() {
Object uri = item != null ? createUrl(item) : DEFAULT_IMAGE; return requestManager.load(item)
.transition(DrawableTransitionOptions.withCrossFade(DEFAULT_DURATION));
return requestManager.load(uri)
.transition(DrawableTransitionOptions.withCrossFade());
} }
} }
@ -66,10 +77,8 @@ public class CustomGlideRequest {
} }
public RequestBuilder<Bitmap> build() { public RequestBuilder<Bitmap> build() {
Object uri = builder.item != null ? createUrl(builder.item) : DEFAULT_IMAGE; return builder.requestManager.asBitmap().load(builder.item)
.transition(BitmapTransitionOptions.withCrossFade(DEFAULT_DURATION));
return builder.requestManager.asBitmap().load(uri)
.transition(BitmapTransitionOptions.withCrossFade());
} }
} }
@ -83,16 +92,15 @@ public class CustomGlideRequest {
} }
public RequestBuilder<BitmapPaletteWrapper> build() { public RequestBuilder<BitmapPaletteWrapper> build() {
Object uri = builder.item != null ? createUrl(builder.item) : DEFAULT_IMAGE; return builder.requestManager.as(BitmapPaletteWrapper.class).load(builder.item)
return builder.requestManager.as(BitmapPaletteWrapper.class).load(uri)
.transition(with(new BitmapPaletteCrossFadeFactory())); .transition(with(new BitmapPaletteCrossFadeFactory()));
} }
} }
public static RequestOptions createRequestOptions(String item) { public static RequestOptions createRequestOptions(String item, Drawable placeholder) {
return new RequestOptions() return new RequestOptions()
.centerCrop() .centerCrop()
.placeholder(placeholder)
.error(DEFAULT_IMAGE) .error(DEFAULT_IMAGE)
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY) .diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.signature(new ObjectKey(item != null ? item : 0)); .signature(new ObjectKey(item != null ? item : 0));

View file

@ -4,10 +4,11 @@ import android.graphics.Bitmap;
import com.bumptech.glide.request.transition.BitmapContainerTransitionFactory; import com.bumptech.glide.request.transition.BitmapContainerTransitionFactory;
import com.bumptech.glide.request.transition.DrawableCrossFadeFactory; import com.bumptech.glide.request.transition.DrawableCrossFadeFactory;
import com.dkanada.gramophone.glide.CustomGlideRequest;
public class BitmapPaletteCrossFadeFactory extends BitmapContainerTransitionFactory<BitmapPaletteWrapper> { public class BitmapPaletteCrossFadeFactory extends BitmapContainerTransitionFactory<BitmapPaletteWrapper> {
public BitmapPaletteCrossFadeFactory() { public BitmapPaletteCrossFadeFactory() {
super(new DrawableCrossFadeFactory.Builder().build()); super(new DrawableCrossFadeFactory.Builder(CustomGlideRequest.DEFAULT_DURATION).build());
} }
@Override @Override

View file

@ -20,6 +20,7 @@ public class Album implements Parcelable {
public String artistName; public String artistName;
public String primary; public String primary;
public String blurHash;
public Album(BaseItemDto itemDto) { public Album(BaseItemDto itemDto) {
this.id = itemDto.getId(); this.id = itemDto.getId();
@ -35,6 +36,9 @@ public class Album implements Parcelable {
} }
this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null; this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null;
if (itemDto.getImageBlurHashes().get(ImageType.Primary) != null) {
this.blurHash = (String) itemDto.getImageBlurHashes().get(ImageType.Primary).values().toArray()[0];
}
this.songs = new ArrayList<>(); this.songs = new ArrayList<>();
} }

View file

@ -19,12 +19,16 @@ public class Artist implements Parcelable {
public String name; public String name;
public String primary; public String primary;
public String blurHash;
public Artist(BaseItemDto itemDto) { public Artist(BaseItemDto itemDto) {
this.id = itemDto.getId(); this.id = itemDto.getId();
this.name = itemDto.getName(); this.name = itemDto.getName();
this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null; this.primary = itemDto.getImageTags().containsKey(ImageType.Primary) ? id : null;
if (itemDto.getImageBlurHashes().get(ImageType.Primary) != null) {
this.blurHash = (String) itemDto.getImageBlurHashes().get(ImageType.Primary).values().toArray()[0];
}
this.genres = new ArrayList<>(); this.genres = new ArrayList<>();
this.albums = new ArrayList<>(); this.albums = new ArrayList<>();

View file

@ -5,6 +5,7 @@ import android.os.Parcelable;
import org.jellyfin.apiclient.model.dto.BaseItemDto; import org.jellyfin.apiclient.model.dto.BaseItemDto;
import org.jellyfin.apiclient.model.dto.MediaSourceInfo; import org.jellyfin.apiclient.model.dto.MediaSourceInfo;
import org.jellyfin.apiclient.model.entities.ImageType;
import org.jellyfin.apiclient.model.entities.MediaStream; import org.jellyfin.apiclient.model.entities.MediaStream;
public class Song implements Parcelable { public class Song implements Parcelable {
@ -24,6 +25,7 @@ public class Song implements Parcelable {
public String artistName; public String artistName;
public String primary; public String primary;
public String blurHash;
public boolean favorite; public boolean favorite;
public String path; public String path;
@ -59,6 +61,10 @@ public class Song implements Parcelable {
this.primary = itemDto.getAlbumPrimaryImageTag() != null ? albumId : null; this.primary = itemDto.getAlbumPrimaryImageTag() != null ? albumId : null;
this.favorite = itemDto.getUserData() != null && itemDto.getUserData().getIsFavorite(); this.favorite = itemDto.getUserData() != null && itemDto.getUserData().getIsFavorite();
if (itemDto.getImageBlurHashes().get(ImageType.Primary) != null) {
this.blurHash = (String) itemDto.getImageBlurHashes().get(ImageType.Primary).values().toArray()[0];
}
if (itemDto.getMediaSources() != null && itemDto.getMediaSources().get(0) != null) { if (itemDto.getMediaSources() != null && itemDto.getMediaSources().get(0) != null) {
MediaSourceInfo source = itemDto.getMediaSources().get(0); MediaSourceInfo source = itemDto.getMediaSources().get(0);

View file

@ -1,5 +1,6 @@
package com.dkanada.gramophone.service; package com.dkanada.gramophone.service;
import android.annotation.SuppressLint;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
@ -520,6 +521,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
.build()); .build());
} }
@SuppressLint("CheckResult")
private void updateMediaSessionMetaData() { private void updateMediaSessionMetaData() {
final Song song = getCurrentSong(); final Song song = getCurrentSong();
@ -545,7 +547,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
if (PreferenceUtil.getInstance(this).getShowAlbumCover()) { if (PreferenceUtil.getInstance(this).getShowAlbumCover()) {
final Point screenSize = Util.getScreenSize(MusicService.this); final Point screenSize = Util.getScreenSize(MusicService.this);
final RequestBuilder<Bitmap> request = CustomGlideRequest.Builder final RequestBuilder<Bitmap> request = CustomGlideRequest.Builder
.from(Glide.with(MusicService.this), song.primary) .from(MusicService.this, song.primary, song.blurHash)
.bitmap().build(); .bitmap().build();
if (PreferenceUtil.getInstance(this).getBlurAlbumCover()) { if (PreferenceUtil.getInstance(this).getBlurAlbumCover()) {

View file

@ -89,8 +89,9 @@ public class PlayingNotificationImpl extends PlayingNotification {
Glide.with(service).clear(target); Glide.with(service).clear(target);
} }
target = CustomGlideRequest.Builder.from(Glide.with(service), song.primary) target = CustomGlideRequest.Builder
.palette(service).build() .from(service, song.primary, song.blurHash)
.palette().build()
.into(new SimpleTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) { .into(new SimpleTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) {
@Override @Override
public void onResourceReady(BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) { public void onResourceReady(BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) {

View file

@ -51,8 +51,8 @@ public class PlayingNotificationImpl24 extends PlayingNotification {
final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size); final int bigNotificationImageSize = service.getResources().getDimensionPixelSize(R.dimen.notification_big_image_size);
service.runOnUiThread(() -> CustomGlideRequest.Builder service.runOnUiThread(() -> CustomGlideRequest.Builder
.from(Glide.with(service), song.primary) .from(service, song.primary, song.blurHash)
.palette(service).build() .palette().build()
.into(new SimpleTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) { .into(new SimpleTarget<BitmapPaletteWrapper>(bigNotificationImageSize, bigNotificationImageSize) {
@Override @Override
public void onResourceReady(BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) { public void onResourceReady(BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) {

View file

@ -110,9 +110,8 @@ public class AlbumDetailActivity extends AbsSlidingMusicPanelActivity implements
private void loadAlbumCover(String primary) { private void loadAlbumCover(String primary) {
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(this), primary) .from(this, primary, primary)
.palette(this).build() .palette().build().dontAnimate()
.dontAnimate()
.into(new CustomPaletteTarget(binding.image) { .into(new CustomPaletteTarget(binding.image) {
@Override @Override
public void onColorReady(int color) { public void onColorReady(int color) {

View file

@ -146,9 +146,8 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement
private void loadArtistImage(String primary) { private void loadArtistImage(String primary) {
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(this), primary) .from(this, primary, primary)
.palette(this).build() .palette().build().dontAnimate()
.dontAnimate()
.into(new CustomPaletteTarget(binding.image) { .into(new CustomPaletteTarget(binding.image) {
@Override @Override
public void onColorReady(int color) { public void onColorReady(int color) {

View file

@ -189,7 +189,7 @@ public class MainActivity extends AbsSlidingMusicPanelActivity {
navigationBinding.text.setText(MusicUtil.getSongInfoString(song)); navigationBinding.text.setText(MusicUtil.getSongInfoString(song));
CustomGlideRequest.Builder CustomGlideRequest.Builder
.from(Glide.with(this), song.primary) .from(this, song.primary, song.blurHash)
.build().into(navigationBinding.image); .build().into(navigationBinding.image);
} else if (binding.navigationView.getHeaderCount() != 0) { } else if (binding.navigationView.getHeaderCount() != 0) {
binding.navigationView.removeHeaderView(navigationBinding.getRoot()); binding.navigationView.removeHeaderView(navigationBinding.getRoot());

View file

@ -96,7 +96,8 @@ public class AppWidgetAlbum extends BaseAppWidget {
Glide.with(appContext).clear(target); Glide.with(appContext).clear(target);
} }
target = CustomGlideRequest.Builder.from(Glide.with(appContext), song.primary) target = CustomGlideRequest.Builder
.from(appContext, song.primary, song.blurHash)
.bitmap().build() .bitmap().build()
.into(new SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) { .into(new SimpleTarget<Bitmap>(widgetImageSize, widgetImageSize) {
@Override @Override

View file

@ -100,9 +100,9 @@ public class AppWidgetCard extends BaseAppWidget {
Glide.with(service).clear(target); Glide.with(service).clear(target);
} }
target = CustomGlideRequest.Builder.from(Glide.with(service), song.primary) target = CustomGlideRequest.Builder
.palette(service).build() .from(service, song.primary, song.blurHash)
.centerCrop() .palette().build()
.into(new SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) { .into(new SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
@Override @Override
public void onResourceReady(BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) { public void onResourceReady(BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) {

View file

@ -93,9 +93,9 @@ public class AppWidgetClassic extends BaseAppWidget {
Glide.with(appContext).clear(target); Glide.with(appContext).clear(target);
} }
target = CustomGlideRequest.Builder.from(Glide.with(appContext), song.primary) target = CustomGlideRequest.Builder
.palette(service).build() .from(appContext, song.primary, song.blurHash)
.centerCrop() .palette().build()
.into(new SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) { .into(new SimpleTarget<BitmapPaletteWrapper>(imageSize, imageSize) {
@Override @Override
public void onResourceReady(BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) { public void onResourceReady(BitmapPaletteWrapper resource, Transition<? super BitmapPaletteWrapper> glideAnimation) {