Rolled back to old style image loading and mosaic
This commit is contained in:
parent
d6a961a977
commit
397f42a54a
76 changed files with 1560 additions and 1452 deletions
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.BitmapRequestBuilder;
|
||||
import com.bumptech.glide.DrawableRequestBuilder;
|
||||
import com.bumptech.glide.DrawableTypeRequest;
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.Key;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import code.name.monkey.retromusic.App;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.glide.artistimage.AlbumCover;
|
||||
import code.name.monkey.retromusic.glide.artistimage.ArtistImage;
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder;
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
|
||||
import code.name.monkey.retromusic.model.Album;
|
||||
import code.name.monkey.retromusic.model.Artist;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import code.name.monkey.retromusic.util.ArtistSignatureUtil;
|
||||
import code.name.monkey.retromusic.util.CustomArtistImageUtil;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public class ArtistGlideRequest {
|
||||
|
||||
public static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
|
||||
private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
|
||||
private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_art;
|
||||
|
||||
public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Artist artist, boolean noCustomImage) {
|
||||
boolean hasCustomImage = CustomArtistImageUtil.Companion.getInstance(App.Companion.getContext()).hasCustomArtistImage(artist);
|
||||
if (noCustomImage || !hasCustomImage) {
|
||||
final List<AlbumCover> songs = new ArrayList<>();
|
||||
for (final Album album : artist.getAlbums()) {
|
||||
final Song song = album.safeGetFirstSong();
|
||||
songs.add(new AlbumCover(album.getYear(), song.getData()));
|
||||
}
|
||||
return requestManager.load(new ArtistImage(artist.getName(), songs));
|
||||
} else {
|
||||
return requestManager.load(CustomArtistImageUtil.getFile(artist));
|
||||
}
|
||||
}
|
||||
|
||||
private static Key createSignature(Artist artist) {
|
||||
return ArtistSignatureUtil.getInstance(App.Companion.getContext()).getArtistSignature(artist.getName());
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
final RequestManager requestManager;
|
||||
final Artist artist;
|
||||
boolean noCustomImage;
|
||||
|
||||
private Builder(@NonNull RequestManager requestManager, Artist artist) {
|
||||
this.requestManager = requestManager;
|
||||
this.artist = artist;
|
||||
}
|
||||
|
||||
public static Builder from(@NonNull RequestManager requestManager, Artist artist) {
|
||||
return new Builder(requestManager, artist);
|
||||
}
|
||||
|
||||
public PaletteBuilder generatePalette(Context context) {
|
||||
return new PaletteBuilder(this, context);
|
||||
}
|
||||
|
||||
public BitmapBuilder asBitmap() {
|
||||
return new BitmapBuilder(this);
|
||||
}
|
||||
|
||||
public Builder noCustomImage(boolean noCustomImage) {
|
||||
this.noCustomImage = noCustomImage;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DrawableRequestBuilder<GlideDrawable> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(requestManager, artist, noCustomImage)
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.error(DEFAULT_ERROR_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.priority(Priority.LOW)
|
||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
.signature(createSignature(artist));
|
||||
}
|
||||
}
|
||||
|
||||
public static class BitmapBuilder {
|
||||
private final Builder builder;
|
||||
|
||||
public BitmapBuilder(Builder builder) {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public BitmapRequestBuilder<?, Bitmap> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage)
|
||||
.asBitmap()
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.error(DEFAULT_ERROR_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.priority(Priority.LOW)
|
||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
.signature(createSignature(builder.artist));
|
||||
}
|
||||
}
|
||||
|
||||
public static class PaletteBuilder {
|
||||
final Context context;
|
||||
private final Builder builder;
|
||||
|
||||
public PaletteBuilder(Builder builder, Context context) {
|
||||
this.builder = builder;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public BitmapRequestBuilder<?, BitmapPaletteWrapper> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(builder.requestManager, builder.artist, builder.noCustomImage)
|
||||
.asBitmap()
|
||||
.transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class)
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.error(DEFAULT_ERROR_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.priority(Priority.LOW)
|
||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
.signature(createSignature(builder.artist));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,28 +26,67 @@ import code.name.monkey.retromusic.helper.StackBlur
|
|||
import code.name.monkey.retromusic.util.ImageUtil
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
|
||||
import java.security.MessageDigest
|
||||
|
||||
|
||||
class BlurTransformation : BitmapTransformation {
|
||||
|
||||
private var context: Context? = null
|
||||
private var blurRadius: Float = 0.toFloat()
|
||||
private var sampling: Int = 0
|
||||
|
||||
private constructor(builder: Builder) : super() {
|
||||
init(builder)
|
||||
}
|
||||
|
||||
private constructor(builder: Builder, bitmapPool: BitmapPool) : super() {
|
||||
init(builder)
|
||||
}
|
||||
|
||||
private fun init(builder: Builder) {
|
||||
this.context = builder.context
|
||||
this.blurRadius = builder.blurRadius
|
||||
this.sampling = builder.sampling
|
||||
}
|
||||
|
||||
private constructor(builder: Builder) : super(builder.context) {
|
||||
init(builder)
|
||||
}
|
||||
|
||||
private constructor(builder: Builder, bitmapPool: BitmapPool) : super(bitmapPool) {
|
||||
init(builder)
|
||||
}
|
||||
|
||||
class Builder(val context: Context) {
|
||||
private var bitmapPool: BitmapPool? = null
|
||||
var blurRadius = DEFAULT_BLUR_RADIUS
|
||||
var sampling: Int = 0
|
||||
|
||||
/**
|
||||
* @param blurRadius The radius to use. Must be between 0 and 25. Default is 5.
|
||||
* @return the same Builder
|
||||
*/
|
||||
fun blurRadius(@FloatRange(from = 0.0, to = 25.0) blurRadius: Float): Builder {
|
||||
this.blurRadius = blurRadius
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sampling The inSampleSize to use. Must be a power of 2, or 1 for no down sampling or 0 for auto detect sampling. Default is 0.
|
||||
* @return the same Builder
|
||||
*/
|
||||
fun sampling(sampling: Int): Builder {
|
||||
this.sampling = sampling
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bitmapPool The BitmapPool to use.
|
||||
* @return the same Builder
|
||||
*/
|
||||
fun bitmapPool(bitmapPool: BitmapPool): Builder {
|
||||
this.bitmapPool = bitmapPool
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): BlurTransformation {
|
||||
return if (bitmapPool != null) {
|
||||
BlurTransformation(this, bitmapPool!!)
|
||||
} else BlurTransformation(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap? {
|
||||
val sampling: Int
|
||||
if (this.sampling == 0) {
|
||||
|
@ -99,60 +138,11 @@ class BlurTransformation : BitmapTransformation {
|
|||
return StackBlur.blur(out, blurRadius)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is BlurTransformation
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return ID.hashCode()
|
||||
}
|
||||
|
||||
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
||||
messageDigest.update("BlurTransformation(radius=$blurRadius, sampling=$sampling)".toByteArray(CHARSET))
|
||||
}
|
||||
|
||||
class Builder(val context: Context) {
|
||||
var bitmapPool: BitmapPool? = null
|
||||
var blurRadius = DEFAULT_BLUR_RADIUS
|
||||
var sampling: Int = 0
|
||||
|
||||
/**
|
||||
* @param blurRadius The radius to use. Must be between 0 and 25. Default is 5.
|
||||
* @return the same Builder
|
||||
*/
|
||||
fun blurRadius(@FloatRange(from = 0.0, to = 25.0) blurRadius: Float): Builder {
|
||||
this.blurRadius = blurRadius
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sampling The inSampleSize to use. Must be a power of 2, or 1 for no down sampling or 0 for auto detect sampling. Default is 0.
|
||||
* @return the same Builder
|
||||
*/
|
||||
fun sampling(sampling: Int): Builder {
|
||||
this.sampling = sampling
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bitmapPool The BitmapPool to use.
|
||||
* @return the same Builder
|
||||
*/
|
||||
fun bitmapPool(bitmapPool: BitmapPool): Builder {
|
||||
this.bitmapPool = bitmapPool
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): BlurTransformation {
|
||||
return if (bitmapPool != null) {
|
||||
BlurTransformation(this, bitmapPool!!)
|
||||
} else BlurTransformation(this)
|
||||
}
|
||||
override fun getId(): String {
|
||||
return "BlurTransformation(radius=$blurRadius, sampling=$sampling)"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val DEFAULT_BLUR_RADIUS = 5f
|
||||
private const val ID = "code.name.monkey.retromusic.glide.BlurTransformation"
|
||||
val DEFAULT_BLUR_RADIUS = 5f
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.GenericTransitionOptions;
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.annotation.GlideExtension;
|
||||
import com.bumptech.glide.annotation.GlideOption;
|
||||
import com.bumptech.glide.annotation.GlideType;
|
||||
import com.bumptech.glide.load.Key;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.bumptech.glide.signature.MediaStoreSignature;
|
||||
|
||||
import code.name.monkey.retromusic.App;
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.glide.artistimage.ArtistImage;
|
||||
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover;
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
|
||||
import code.name.monkey.retromusic.model.Artist;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import code.name.monkey.retromusic.util.ArtistSignatureUtil;
|
||||
import code.name.monkey.retromusic.util.CustomArtistImageUtil;
|
||||
import code.name.monkey.retromusic.util.MusicUtil;
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||
|
||||
@GlideExtension
|
||||
public final class RetroGlideExtension {
|
||||
private RetroGlideExtension() {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@GlideType(BitmapPaletteWrapper.class)
|
||||
public static void asBitmapPalette(@NonNull RequestBuilder<BitmapPaletteWrapper> requestBuilder) {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@GlideOption
|
||||
public static RequestOptions artistOptions(@NonNull RequestOptions requestOptions, @NonNull Artist artist) {
|
||||
return requestOptions
|
||||
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
|
||||
.error(R.drawable.default_artist_art)
|
||||
.placeholder(R.drawable.default_artist_art)
|
||||
.priority(Priority.LOW)
|
||||
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
|
||||
.signature(createSignature(artist));
|
||||
}
|
||||
|
||||
@GlideOption
|
||||
@NonNull
|
||||
public static RequestOptions songOptions(@NonNull RequestOptions requestOptions, @NonNull Song song) {
|
||||
return requestOptions
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.error(R.drawable.default_album_art)
|
||||
//.placeholder(R.drawable.default_album_art)
|
||||
.signature(createSignature(song));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Key createSignature(@NonNull Artist artist) {
|
||||
return ArtistSignatureUtil.getInstance().getArtistSignature(artist.getName());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Key createSignature(@NonNull Song song) {
|
||||
return new MediaStoreSignature("", song.getDateModified(), 0);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Object getArtistModel(@NonNull Artist artist) {
|
||||
return getArtistModel(artist, CustomArtistImageUtil.Companion.getInstance(App.Companion.getContext()).hasCustomArtistImage(artist), false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Object getArtistModel(@NonNull Artist artist, boolean forceDownload) {
|
||||
return getArtistModel(artist, CustomArtistImageUtil.Companion.getInstance(App.Companion.getContext()).hasCustomArtistImage(artist), forceDownload);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Object getArtistModel(@NonNull Artist artist, boolean hasCustomImage, boolean forceDownload) {
|
||||
if (!hasCustomImage) {
|
||||
return new ArtistImage(artist.getName(), forceDownload);
|
||||
} else {
|
||||
return CustomArtistImageUtil.getFile(artist);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Object getSongModel(@NonNull Song song) {
|
||||
return getSongModel(song, PreferenceUtil.getInstance(App.Companion.getContext()).ignoreMediaStoreArtwork());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Object getSongModel(@NonNull Song song, boolean ignoreMediaStore) {
|
||||
if (ignoreMediaStore) {
|
||||
return new AudioFileCover(song.getData());
|
||||
} else {
|
||||
return MusicUtil.getMediaStoreAlbumCoverUri(song.getAlbumId());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static <TranscodeType> GenericTransitionOptions<TranscodeType> getDefaultTransition() {
|
||||
return new GenericTransitionOptions<TranscodeType>().transition(android.R.anim.fade_in);
|
||||
}
|
||||
|
||||
}
|
|
@ -23,7 +23,7 @@ import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
|||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil.getColor
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil.getDominantColor
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.bumptech.glide.request.animation.GlideAnimation
|
||||
|
||||
|
||||
abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(view) {
|
||||
|
@ -34,13 +34,13 @@ abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(vi
|
|||
protected val albumArtistFooterColor: Int
|
||||
get() = ATHUtil.resolveColor(getView().context, R.attr.cardBackgroundColor)
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
override fun onLoadFailed(e: Exception, errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(e, errorDrawable)
|
||||
onColorReady(defaultFooterColor)
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: BitmapPaletteWrapper,
|
||||
glideAnimation: Transition<in BitmapPaletteWrapper>?) {
|
||||
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>?) {
|
||||
super.onResourceReady(resource, glideAnimation)
|
||||
|
||||
val defaultColor = defaultFooterColor
|
||||
|
|
|
@ -15,29 +15,22 @@
|
|||
package code.name.monkey.retromusic.glide
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import code.name.monkey.retromusic.glide.artistimage.ArtistImage
|
||||
import code.name.monkey.retromusic.glide.artistimage.ArtistImageLoader
|
||||
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover
|
||||
import code.name.monkey.retromusic.glide.audiocover.AudioFileCoverLoader
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.module.AppGlideModule
|
||||
import com.bumptech.glide.GlideBuilder
|
||||
import com.bumptech.glide.module.GlideModule
|
||||
import java.io.InputStream
|
||||
|
||||
@GlideModule
|
||||
class RetroMusicGlideModule : AppGlideModule() {
|
||||
override fun registerComponents(context: Context, glide: Glide,
|
||||
registry: Registry) {
|
||||
registry.append(AudioFileCover::class.java, InputStream::class.java, AudioFileCoverLoader.Factory())
|
||||
registry.append(ArtistImage::class.java, InputStream::class.java, ArtistImageLoader.Factory(context))
|
||||
registry.register(Bitmap::class.java, BitmapPaletteWrapper::class.java, BitmapPaletteTranscoder())
|
||||
class RetroMusicGlideModule : GlideModule {
|
||||
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||
|
||||
}
|
||||
|
||||
override fun isManifestParsingEnabled(): Boolean {
|
||||
return false
|
||||
override fun registerComponents(context: Context, glide: Glide) {
|
||||
glide.register(AudioFileCover::class.java, InputStream::class.java, AudioFileCoverLoader.Factory())
|
||||
glide.register(ArtistImage::class.java, InputStream::class.java, ArtistImageLoader.Factory())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
|
||||
import com.bumptech.glide.request.Request
|
||||
import com.bumptech.glide.request.target.SizeReadyCallback
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.bumptech.glide.util.Util
|
||||
|
||||
open class RetroSimpleTarget<T> @JvmOverloads constructor(private val width: Int = Target.SIZE_ORIGINAL, private val height: Int = Target.SIZE_ORIGINAL) : Target<T> {
|
||||
|
||||
private var request: Request? = null
|
||||
|
||||
override fun getRequest(): Request? {
|
||||
return request
|
||||
}
|
||||
|
||||
override fun setRequest(request: Request?) {
|
||||
this.request = request
|
||||
}
|
||||
|
||||
override fun onLoadStarted(placeholder: Drawable?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onResourceReady(resource: T, transition: Transition<in T>?) {
|
||||
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {
|
||||
|
||||
}
|
||||
|
||||
override fun getSize(cb: SizeReadyCallback) {
|
||||
if (!Util.isValidDimensions(width, height)) {
|
||||
throw IllegalArgumentException(
|
||||
"Width and height must both be > 0 or Target#SIZE_ORIGINAL, but given" + " width: "
|
||||
+ width + " and height: " + height + ", either provide dimensions in the constructor"
|
||||
+ " or call override()")
|
||||
}
|
||||
cb.onSizeReady(width, height)
|
||||
}
|
||||
|
||||
override fun removeCallback(cb: SizeReadyCallback) {
|
||||
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.BitmapRequestBuilder;
|
||||
import com.bumptech.glide.DrawableRequestBuilder;
|
||||
import com.bumptech.glide.DrawableTypeRequest;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.Key;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.signature.MediaStoreSignature;
|
||||
|
||||
import code.name.monkey.retromusic.R;
|
||||
import code.name.monkey.retromusic.glide.audiocover.AudioFileCover;
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTranscoder;
|
||||
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
|
||||
import code.name.monkey.retromusic.model.Song;
|
||||
import code.name.monkey.retromusic.util.MusicUtil;
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil;
|
||||
|
||||
/**
|
||||
* Created by hemanths on 2019-09-15.
|
||||
*/
|
||||
public class SongGlideRequest {
|
||||
|
||||
public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE;
|
||||
public static final int DEFAULT_ERROR_IMAGE = R.drawable.default_album_art;
|
||||
public static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
|
||||
|
||||
public static class Builder {
|
||||
final RequestManager requestManager;
|
||||
final Song song;
|
||||
boolean ignoreMediaStore;
|
||||
|
||||
public static Builder from(@NonNull RequestManager requestManager, Song song) {
|
||||
return new Builder(requestManager, song);
|
||||
}
|
||||
|
||||
private Builder(@NonNull RequestManager requestManager, Song song) {
|
||||
this.requestManager = requestManager;
|
||||
this.song = song;
|
||||
}
|
||||
|
||||
public PaletteBuilder generatePalette(Context context) {
|
||||
return new PaletteBuilder(this, context);
|
||||
}
|
||||
|
||||
public BitmapBuilder asBitmap() {
|
||||
return new BitmapBuilder(this);
|
||||
}
|
||||
|
||||
public Builder checkIgnoreMediaStore(Context context) {
|
||||
return ignoreMediaStore(PreferenceUtil.getInstance(context).ignoreMediaStoreArtwork());
|
||||
}
|
||||
|
||||
public Builder ignoreMediaStore(boolean ignoreMediaStore) {
|
||||
this.ignoreMediaStore = ignoreMediaStore;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DrawableRequestBuilder<GlideDrawable> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(requestManager, song, ignoreMediaStore)
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.error(DEFAULT_ERROR_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.signature(createSignature(song));
|
||||
}
|
||||
}
|
||||
|
||||
public static class BitmapBuilder {
|
||||
private final Builder builder;
|
||||
|
||||
public BitmapBuilder(Builder builder) {
|
||||
this.builder = builder;
|
||||
}
|
||||
|
||||
public BitmapRequestBuilder<?, Bitmap> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore)
|
||||
.asBitmap()
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.error(DEFAULT_ERROR_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.signature(createSignature(builder.song));
|
||||
}
|
||||
}
|
||||
|
||||
public static class PaletteBuilder {
|
||||
final Context context;
|
||||
private final Builder builder;
|
||||
|
||||
public PaletteBuilder(Builder builder, Context context) {
|
||||
this.builder = builder;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public BitmapRequestBuilder<?, BitmapPaletteWrapper> build() {
|
||||
//noinspection unchecked
|
||||
return createBaseRequest(builder.requestManager, builder.song, builder.ignoreMediaStore)
|
||||
.asBitmap()
|
||||
.transcode(new BitmapPaletteTranscoder(context), BitmapPaletteWrapper.class)
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.error(DEFAULT_ERROR_IMAGE)
|
||||
.animate(DEFAULT_ANIMATION)
|
||||
.signature(createSignature(builder.song));
|
||||
}
|
||||
}
|
||||
|
||||
public static DrawableTypeRequest createBaseRequest(RequestManager requestManager, Song song, boolean ignoreMediaStore) {
|
||||
if (ignoreMediaStore) {
|
||||
return requestManager.load(new AudioFileCover(song.getData()));
|
||||
} else {
|
||||
return requestManager.loadFromMediaStore(MusicUtil.getMediaStoreAlbumCoverUri(song.getAlbumId()));
|
||||
}
|
||||
}
|
||||
|
||||
public static Key createSignature(Song song) {
|
||||
return new MediaStoreSignature("", song.getDateModified(), 0);
|
||||
}
|
||||
}
|
|
@ -15,29 +15,201 @@
|
|||
package code.name.monkey.retromusic.glide.artistimage
|
||||
|
||||
import android.content.Context
|
||||
import code.name.monkey.retromusic.deezer.Data
|
||||
import code.name.monkey.retromusic.deezer.DeezerApiService
|
||||
import code.name.monkey.retromusic.deezer.DeezerResponse
|
||||
import code.name.monkey.retromusic.util.MusicUtil
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.media.MediaMetadataRetriever
|
||||
import code.name.monkey.retromusic.glide.audiocover.AudioFileCoverUtils
|
||||
import code.name.monkey.retromusic.util.ImageUtil
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.model.GlideUrl
|
||||
import com.bumptech.glide.load.model.GenericLoaderFactory
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class AlbumCover(
|
||||
var year: Int,
|
||||
var filePath: String?)
|
||||
|
||||
class ArtistImage(val artistName: String, // filePath to get the image of the artist
|
||||
val albumCovers: List<AlbumCover>
|
||||
) {
|
||||
|
||||
fun toIdString(): String {
|
||||
val id = StringBuilder(artistName)
|
||||
for (albumCover in albumCovers) {
|
||||
id.append(albumCover.year).append(albumCover.filePath)
|
||||
}
|
||||
return id.toString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ArtistImageFetcher(
|
||||
val artistImage: ArtistImage,
|
||||
val ignoreMediaStore: Boolean
|
||||
) : DataFetcher<InputStream> {
|
||||
private var stream: InputStream? = null
|
||||
|
||||
override fun cleanup() {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream?.close()
|
||||
} catch (ignore: IOException) {
|
||||
// can't do much about it
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
|
||||
}
|
||||
|
||||
override fun loadData(priority: Priority?): InputStream {
|
||||
println("MOSAIC load data for" + artistImage.artistName)
|
||||
stream = getMosaic(artistImage.albumCovers)?.let {
|
||||
it
|
||||
}
|
||||
return stream as InputStream
|
||||
}
|
||||
|
||||
private fun getMosaic(albumCovers: List<AlbumCover>): InputStream? {
|
||||
val retriever = MediaMetadataRetriever()
|
||||
val artistBitMapSize = 512
|
||||
val images = HashMap<InputStream, Int>()
|
||||
var result: InputStream? = null
|
||||
var streams = ArrayList<InputStream>()
|
||||
try {
|
||||
for (albumCover in albumCovers) {
|
||||
var picture: ByteArray? = null
|
||||
if (!ignoreMediaStore) {
|
||||
retriever.setDataSource(albumCover.filePath)
|
||||
picture = retriever.embeddedPicture
|
||||
}
|
||||
val stream: InputStream? = if (picture != null) {
|
||||
ByteArrayInputStream(picture)
|
||||
} else {
|
||||
AudioFileCoverUtils.fallback(albumCover.filePath)
|
||||
}
|
||||
if (stream != null) {
|
||||
images[stream] = albumCover.year
|
||||
}
|
||||
val nbImages = images.size
|
||||
if (nbImages > 3) {
|
||||
streams = ArrayList(images.keys)
|
||||
|
||||
var divisor = 1
|
||||
var i = 1
|
||||
while (i < nbImages && Math.pow(i.toDouble(), 2.0) <= nbImages) {
|
||||
divisor = i
|
||||
++i
|
||||
}
|
||||
divisor += 1
|
||||
var nbTiles = Math.pow(divisor.toDouble(), 2.0)
|
||||
|
||||
if (nbImages < nbTiles) {
|
||||
divisor -= 1;
|
||||
nbTiles = Math.pow(divisor.toDouble(), 2.0)
|
||||
}
|
||||
|
||||
val resize = (artistBitMapSize / divisor) + 1
|
||||
|
||||
val bitmap = Bitmap.createBitmap(artistBitMapSize, artistBitMapSize, Bitmap.Config.RGB_565)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
var x = 0F
|
||||
var y = 0F
|
||||
|
||||
var j = 0
|
||||
while (j < streams.size && j < nbTiles) {
|
||||
val tempBitmap = ImageUtil.resize(streams[j], resize, resize)
|
||||
canvas.drawBitmap(tempBitmap, x, y, null)
|
||||
x += resize
|
||||
|
||||
if (x >= artistBitMapSize) {
|
||||
x = 0F
|
||||
y += resize
|
||||
}
|
||||
++j
|
||||
}
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos)
|
||||
result = ByteArrayInputStream(bos.toByteArray())
|
||||
|
||||
} else if (nbImages > 0) {
|
||||
var maxEntryYear: Map.Entry<InputStream, Int>? = null
|
||||
for (entry in images.entries) {
|
||||
if (maxEntryYear == null || entry.value
|
||||
.compareTo(maxEntryYear.value) > 0) {
|
||||
maxEntryYear = entry
|
||||
}
|
||||
|
||||
}
|
||||
result = if (maxEntryYear != null) {
|
||||
maxEntryYear.key
|
||||
} else {
|
||||
images.entries
|
||||
.iterator()
|
||||
.next()
|
||||
.key
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
retriever.release()
|
||||
try {
|
||||
for (stream in streams) {
|
||||
stream.close()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
override fun getId(): String {
|
||||
println("MOSAIC get id for" + artistImage.artistName)
|
||||
// never return NULL here!
|
||||
// this id is used to determine whether the image is already cached
|
||||
// we use the artist name as well as the album years + file paths
|
||||
return artistImage.toIdString() + "ignoremediastore:" + ignoreMediaStore
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class ArtistImageLoader(
|
||||
private val context: Context
|
||||
) : StreamModelLoader<ArtistImage> {
|
||||
|
||||
override fun getResourceFetcher(model: ArtistImage, width: Int, height: Int): DataFetcher<InputStream> {
|
||||
|
||||
return ArtistImageFetcher(model, PreferenceUtil.getInstance(context).ignoreMediaStoreArtwork())
|
||||
}
|
||||
|
||||
class Factory : ModelLoaderFactory<ArtistImage, InputStream> {
|
||||
|
||||
override fun build(context: Context, factories: GenericLoaderFactory): ModelLoader<ArtistImage, InputStream> {
|
||||
return ArtistImageLoader(context)
|
||||
}
|
||||
|
||||
override fun teardown() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
class ArtistImage(val artistName: String, val skipOkHttpCache: Boolean)
|
||||
|
||||
class ArtistImageFetcher(private val context: Context,
|
||||
|
@ -158,4 +330,4 @@ class ArtistImageLoader(private val context: Context,
|
|||
// we need these very low values to make sure our artist image loading calls doesn't block the image loading queue
|
||||
private const val TIMEOUT = 700
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.audiocover;
|
||||
|
||||
/**
|
||||
* @author Karim Abou Zeid (kabouzeid)
|
||||
*/
|
||||
public class AudioFileCover {
|
||||
public final String filePath;
|
||||
|
||||
public AudioFileCover(String filePath) {
|
||||
this.filePath = filePath;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.audiocover;
|
||||
|
||||
import android.media.MediaMetadataRetriever;
|
||||
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
public class AudioFileCoverFetcher implements DataFetcher<InputStream> {
|
||||
private final AudioFileCover model;
|
||||
|
||||
private InputStream stream;
|
||||
|
||||
public AudioFileCoverFetcher(AudioFileCover model) {
|
||||
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
// makes sure we never ever return null here
|
||||
return String.valueOf(model.filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream loadData(final Priority priority) throws Exception {
|
||||
|
||||
final MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
try {
|
||||
retriever.setDataSource(model.filePath);
|
||||
byte[] picture = retriever.getEmbeddedPicture();
|
||||
if (picture != null) {
|
||||
stream = new ByteArrayInputStream(picture);
|
||||
} else {
|
||||
stream = AudioFileCoverUtils.fallback(model.filePath);
|
||||
}
|
||||
} finally {
|
||||
retriever.release();
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
// already cleaned up in loadData and ByteArrayInputStream will be GC'd
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException ignore) {
|
||||
// can't do much about it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
// cannot cancel
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.audiocover;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
import com.bumptech.glide.load.model.GenericLoaderFactory;
|
||||
import com.bumptech.glide.load.model.ModelLoader;
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||
import com.bumptech.glide.load.model.stream.StreamModelLoader;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
|
||||
|
||||
public class AudioFileCoverLoader implements StreamModelLoader<AudioFileCover> {
|
||||
|
||||
@Override
|
||||
public DataFetcher<InputStream> getResourceFetcher(AudioFileCover model, int width, int height) {
|
||||
return new AudioFileCoverFetcher(model);
|
||||
}
|
||||
|
||||
public static class Factory implements ModelLoaderFactory<AudioFileCover, InputStream> {
|
||||
@Override
|
||||
public ModelLoader<AudioFileCover, InputStream> build(Context context, GenericLoaderFactory factories) {
|
||||
return new AudioFileCoverLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardown() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.audiocover
|
||||
|
||||
import android.media.MediaMetadataRetriever
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
import com.bumptech.glide.load.model.ModelLoader.LoadData
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import org.jaudiotagger.audio.mp3.MP3File
|
||||
import java.io.*
|
||||
|
||||
|
||||
class AudioFileCover(val filePath: String)
|
||||
|
||||
class AudioFileCoverFetcher(private val model: AudioFileCover) : DataFetcher<InputStream> {
|
||||
private var stream: FileInputStream? = null
|
||||
|
||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
||||
val retriever = MediaMetadataRetriever()
|
||||
val data: InputStream?
|
||||
try {
|
||||
retriever.setDataSource(model.filePath)
|
||||
val picture = retriever.embeddedPicture
|
||||
if (picture != null) {
|
||||
data = ByteArrayInputStream(picture)
|
||||
} else {
|
||||
data = fallback(model.filePath)
|
||||
}
|
||||
callback.onDataReady(data)
|
||||
} catch (e: FileNotFoundException) {
|
||||
callback.onLoadFailed(e)
|
||||
} finally {
|
||||
retriever.release()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun getDataClass(): Class<InputStream> {
|
||||
return InputStream::class.java
|
||||
}
|
||||
|
||||
|
||||
override fun getDataSource(): DataSource {
|
||||
return DataSource.LOCAL
|
||||
}
|
||||
|
||||
@Throws(FileNotFoundException::class)
|
||||
private fun fallback(path: String): InputStream? {
|
||||
try {
|
||||
val mp3File = MP3File(path)
|
||||
if (mp3File.hasID3v2Tag()) {
|
||||
val art = mp3File.tag.firstArtwork
|
||||
if (art != null) {
|
||||
val imageData = art.binaryData
|
||||
return ByteArrayInputStream(imageData)
|
||||
}
|
||||
}
|
||||
// If there are any exceptions, we ignore them and continue to the other fallback method
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
|
||||
// Method 2: look for album art in external files
|
||||
val parent = File(path).parentFile
|
||||
for (fallback in FALLBACKS) {
|
||||
val cover = File(parent, fallback)
|
||||
if (cover.exists()) {
|
||||
stream = FileInputStream(cover)
|
||||
return stream
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun cleanup() {
|
||||
// already cleaned up in loadData and ByteArrayInputStream will be GC'd
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream!!.close()
|
||||
} catch (ignore: IOException) {
|
||||
// can't do much about it
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
// cannot cancel
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val FALLBACKS = arrayOf("cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png")
|
||||
}
|
||||
}
|
||||
|
||||
class AudioFileCoverLoader : ModelLoader<AudioFileCover, InputStream> {
|
||||
override fun buildLoadData(model: AudioFileCover, width: Int, height: Int,
|
||||
options: Options): LoadData<InputStream>? {
|
||||
return LoadData(ObjectKey(model.filePath), AudioFileCoverFetcher(model))
|
||||
}
|
||||
|
||||
override fun handles(model: AudioFileCover): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
class Factory : ModelLoaderFactory<AudioFileCover, InputStream> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<AudioFileCover, InputStream> {
|
||||
return AudioFileCoverLoader()
|
||||
}
|
||||
|
||||
override fun teardown() {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.audiocover;
|
||||
|
||||
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
|
||||
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
|
||||
import org.jaudiotagger.audio.mp3.MP3File;
|
||||
import org.jaudiotagger.tag.TagException;
|
||||
import org.jaudiotagger.tag.images.Artwork;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class AudioFileCoverUtils {
|
||||
|
||||
public static final String[] FALLBACKS = {"cover.jpg", "album.jpg", "folder.jpg", "cover.png", "album.png", "folder.png"};
|
||||
|
||||
|
||||
public static InputStream fallback(String path) throws FileNotFoundException {
|
||||
// Method 1: use embedded high resolution album art if there is any
|
||||
try {
|
||||
MP3File mp3File = new MP3File(path);
|
||||
if (mp3File.hasID3v2Tag()) {
|
||||
Artwork art = mp3File.getTag().getFirstArtwork();
|
||||
if (art != null) {
|
||||
byte[] imageData = art.getBinaryData();
|
||||
return new ByteArrayInputStream(imageData);
|
||||
}
|
||||
}
|
||||
// If there are any exceptions, we ignore them and continue to the other fallback method
|
||||
} catch (ReadOnlyFileException ignored) {
|
||||
} catch (InvalidAudioFrameException ignored) {
|
||||
} catch (TagException ignored) {
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
|
||||
// Method 2: look for album art in external files
|
||||
final File parent = new File(path).getParentFile();
|
||||
for (String fallback : FALLBACKS) {
|
||||
File cover = new File(parent, fallback);
|
||||
if (cover.exists()) {
|
||||
return new FileInputStream(cover);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.palette;
|
||||
|
||||
import com.bumptech.glide.load.engine.Resource;
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.util.Util;
|
||||
|
||||
public class BitmapPaletteResource implements Resource<BitmapPaletteWrapper> {
|
||||
|
||||
private final BitmapPaletteWrapper bitmapPaletteWrapper;
|
||||
private final BitmapPool bitmapPool;
|
||||
|
||||
public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper, BitmapPool bitmapPool) {
|
||||
this.bitmapPaletteWrapper = bitmapPaletteWrapper;
|
||||
this.bitmapPool = bitmapPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmapPaletteWrapper get() {
|
||||
return bitmapPaletteWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return Util.getBitmapByteSize(bitmapPaletteWrapper.getBitmap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recycle() {
|
||||
if (!bitmapPool.put(bitmapPaletteWrapper.getBitmap())) {
|
||||
bitmapPaletteWrapper.getBitmap().recycle();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.palette
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.widget.ImageView
|
||||
import androidx.palette.graphics.Palette
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
|
||||
import com.bumptech.glide.request.target.ImageViewTarget
|
||||
import com.bumptech.glide.util.Util
|
||||
|
||||
class BitmapPaletteTranscoder : ResourceTranscoder<Bitmap, BitmapPaletteWrapper> {
|
||||
override fun transcode(bitmapResource: Resource<Bitmap>, options: Options): Resource<BitmapPaletteWrapper>? {
|
||||
val bitmap = bitmapResource.get()
|
||||
val bitmapPaletteWrapper = BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap)!!)
|
||||
return BitmapPaletteResource(bitmapPaletteWrapper)
|
||||
}
|
||||
}
|
||||
|
||||
class BitmapPaletteWrapper(val bitmap: Bitmap, val palette: Palette)
|
||||
|
||||
open class BitmapPaletteTarget(view: ImageView) : ImageViewTarget<BitmapPaletteWrapper>(view) {
|
||||
|
||||
override fun setResource(bitmapPaletteWrapper: BitmapPaletteWrapper?) {
|
||||
if (bitmapPaletteWrapper != null) {
|
||||
view.setImageBitmap(bitmapPaletteWrapper.bitmap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BitmapPaletteResource(private val bitmapPaletteWrapper: BitmapPaletteWrapper) : Resource<BitmapPaletteWrapper> {
|
||||
|
||||
override fun get(): BitmapPaletteWrapper {
|
||||
return bitmapPaletteWrapper
|
||||
}
|
||||
|
||||
override fun getResourceClass(): Class<BitmapPaletteWrapper> {
|
||||
return BitmapPaletteWrapper::class.java
|
||||
}
|
||||
|
||||
override fun getSize(): Int {
|
||||
return Util.getBitmapByteSize(bitmapPaletteWrapper.bitmap)
|
||||
}
|
||||
|
||||
override fun recycle() {
|
||||
bitmapPaletteWrapper.bitmap.recycle()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.palette;
|
||||
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.request.target.ImageViewTarget;
|
||||
|
||||
public class BitmapPaletteTarget extends ImageViewTarget<BitmapPaletteWrapper> {
|
||||
public BitmapPaletteTarget(ImageView view) {
|
||||
super(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setResource(BitmapPaletteWrapper bitmapPaletteWrapper) {
|
||||
view.setImageBitmap(bitmapPaletteWrapper.getBitmap());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.palette;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.Resource;
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
|
||||
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
|
||||
|
||||
import code.name.monkey.retromusic.util.RetroColorUtil;
|
||||
|
||||
public class BitmapPaletteTranscoder implements ResourceTranscoder<Bitmap, BitmapPaletteWrapper> {
|
||||
private final BitmapPool bitmapPool;
|
||||
|
||||
public BitmapPaletteTranscoder(Context context) {
|
||||
this(Glide.get(context).getBitmapPool());
|
||||
}
|
||||
|
||||
public BitmapPaletteTranscoder(BitmapPool bitmapPool) {
|
||||
this.bitmapPool = bitmapPool;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource<BitmapPaletteWrapper> transcode(Resource<Bitmap> bitmapResource) {
|
||||
Bitmap bitmap = bitmapResource.get();
|
||||
BitmapPaletteWrapper bitmapPaletteWrapper = new BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap));
|
||||
return new BitmapPaletteResource(bitmapPaletteWrapper, bitmapPool);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "BitmapPaletteTranscoder.com.kabouzeid.gramophone.glide.palette";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Hemanth Savarala.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3
|
||||
*
|
||||
* This 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.
|
||||
*
|
||||
* This software 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.
|
||||
*/
|
||||
|
||||
package code.name.monkey.retromusic.glide.palette;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import androidx.palette.graphics.Palette;
|
||||
|
||||
public class BitmapPaletteWrapper {
|
||||
private final Bitmap mBitmap;
|
||||
private final Palette mPalette;
|
||||
|
||||
public BitmapPaletteWrapper(Bitmap bitmap, Palette palette) {
|
||||
mBitmap = bitmap;
|
||||
mPalette = palette;
|
||||
}
|
||||
|
||||
public Bitmap getBitmap() {
|
||||
return mBitmap;
|
||||
}
|
||||
|
||||
public Palette getPalette() {
|
||||
return mPalette;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue