Here's a list of changes/features: https://github.com/RetroMusicPlayer/RetroMusicPlayer/releases/tag/v5.0

Internal Changes:
1) Migrated to ViewBinding
2) Migrated to Glide V4
3) Migrated to kotlin version of Material Dialogs
This commit is contained in:
Prathamesh More 2021-09-09 00:00:20 +05:30
parent fc42767031
commit bce6dbfa27
421 changed files with 13285 additions and 5757 deletions

View file

@ -1,130 +0,0 @@
package code.name.monkey.retromusic.glide;
import android.content.Context;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
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;
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;
public class AlbumGlideRequest {
private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE;
private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_album_art;
private static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
@NonNull
private static DrawableTypeRequest createBaseRequest(
@NonNull RequestManager requestManager, @NonNull Song song, boolean ignoreMediaStore) {
if (ignoreMediaStore) {
return requestManager.load(new AudioFileCover(song.getData()));
} else {
return requestManager.loadFromMediaStore(
MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId()));
}
}
@NonNull
private static Key createSignature(@NonNull Song song) {
return new MediaStoreSignature("", song.getDateModified(), 0);
}
public static class Builder {
final RequestManager requestManager;
final Song song;
boolean ignoreMediaStore;
private Builder(@NonNull RequestManager requestManager, Song song) {
this.requestManager = requestManager;
this.song = song;
}
@NonNull
public static Builder from(@NonNull RequestManager requestManager, Song song) {
return new Builder(requestManager, song);
}
@NonNull
public PaletteBuilder generatePalette(@NonNull Context context) {
return new PaletteBuilder(this, context);
}
@NonNull
public BitmapBuilder asBitmap() {
return new BitmapBuilder(this);
}
@NonNull
public Builder checkIgnoreMediaStore() {
return ignoreMediaStore(PreferenceUtil.INSTANCE.isIgnoreMediaStoreArtwork());
}
@NonNull
public Builder ignoreMediaStore(boolean ignoreMediaStore) {
this.ignoreMediaStore = ignoreMediaStore;
return this;
}
@NonNull
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;
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)
.dontTransform()
.signature(createSignature(builder.song));
}
}
public static class PaletteBuilder {
private final Context context;
private final Builder builder;
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));
}
}
}

View file

@ -1,171 +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.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.TintHelper;
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.palette.BitmapPaletteTranscoder;
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper;
import code.name.monkey.retromusic.model.Artist;
import code.name.monkey.retromusic.util.ArtistSignatureUtil;
import code.name.monkey.retromusic.util.CustomArtistImageUtil;
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;
public class ArtistGlideRequest {
private static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.SOURCE;
private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_artist_art;
@NonNull
private static Key createSignature(@NonNull Artist artist) {
return ArtistSignatureUtil.getInstance(App.Companion.getContext())
.getArtistSignature(artist.getName());
}
@NonNull
private static DrawableTypeRequest createBaseRequest(
@NonNull RequestManager requestManager,
@NonNull Artist artist,
boolean noCustomImage,
boolean forceDownload) {
boolean hasCustomImage =
CustomArtistImageUtil.Companion.getInstance(App.Companion.getContext())
.hasCustomArtistImage(artist);
if (noCustomImage || !hasCustomImage) {
return requestManager.load(new ArtistImage(artist));
} else {
return requestManager.load(CustomArtistImageUtil.getFile(artist));
}
}
public static class Builder {
final Artist artist;
final RequestManager requestManager;
private Drawable error;
private boolean forceDownload;
private boolean noCustomImage;
private Builder(@NonNull RequestManager requestManager, Artist artist) {
this.requestManager = requestManager;
this.artist = artist;
error =
TintHelper.createTintedDrawable(
ContextCompat.getDrawable(App.Companion.getContext(), R.drawable.default_artist_art),
ThemeStore.Companion.accentColor(App.Companion.getContext()));
}
public static Builder from(@NonNull RequestManager requestManager, Artist artist) {
return new Builder(requestManager, artist);
}
public BitmapBuilder asBitmap() {
return new BitmapBuilder(this);
}
public DrawableRequestBuilder<GlideDrawable> build() {
//noinspection unchecked
return createBaseRequest(requestManager, artist, noCustomImage, forceDownload)
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.animate(DEFAULT_ANIMATION)
.error(DEFAULT_ERROR_IMAGE)
.priority(Priority.LOW)
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.dontTransform()
.signature(createSignature(artist));
}
public Builder forceDownload(boolean forceDownload) {
this.forceDownload = forceDownload;
return this;
}
public PaletteBuilder generatePalette(Context context) {
return new PaletteBuilder(this, context);
}
public Builder noCustomImage(boolean noCustomImage) {
this.noCustomImage = noCustomImage;
return this;
}
}
public static class BitmapBuilder {
private final Builder builder;
BitmapBuilder(Builder builder) {
this.builder = builder;
}
public BitmapRequestBuilder<?, Bitmap> build() {
//noinspection unchecked
return createBaseRequest(
builder.requestManager, builder.artist, builder.noCustomImage, builder.forceDownload)
.asBitmap()
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.animate(DEFAULT_ANIMATION)
.error(DEFAULT_ERROR_IMAGE)
.priority(Priority.LOW)
.override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
.dontTransform()
.signature(createSignature(builder.artist));
}
}
public static class PaletteBuilder {
final Context context;
private final Builder builder;
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, builder.forceDownload)
.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)
.dontTransform()
.signature(createSignature(builder.artist));
}
}
}

View file

@ -1,24 +1,9 @@
/*
* Copyright (c) 2020 Hemanth Savarla.
*
* 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 android.graphics.Canvas
import android.graphics.Paint
import android.os.Build
import android.renderscript.*
import androidx.annotation.FloatRange
import code.name.monkey.retromusic.BuildConfig
@ -26,12 +11,14 @@ 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 var blurRadius = 0f
private var sampling = 0
private fun init(builder: Builder) {
this.context = builder.context
@ -39,18 +26,18 @@ class BlurTransformation : BitmapTransformation {
this.sampling = builder.sampling
}
private constructor(builder: Builder) : super(builder.context) {
private constructor(builder: Builder) : super() {
init(builder)
}
private constructor(builder: Builder, bitmapPool: BitmapPool) : super(bitmapPool) {
private constructor(builder: Builder, bitmapPool: BitmapPool) : super() {
init(builder)
}
class Builder(val context: Context) {
private var bitmapPool: BitmapPool? = null
var blurRadius = DEFAULT_BLUR_RADIUS
var sampling: Int = 0
var sampling = 0
/**
* @param blurRadius The radius to use. Must be between 0 and 25. Default is 5.
@ -74,7 +61,7 @@ class BlurTransformation : BitmapTransformation {
* @param bitmapPool The BitmapPool to use.
* @return the same Builder
*/
fun bitmapPool(bitmapPool: BitmapPool): Builder {
fun bitmapPool(bitmapPool: BitmapPool?): Builder {
this.bitmapPool = bitmapPool
return this
}
@ -91,65 +78,60 @@ class BlurTransformation : BitmapTransformation {
toTransform: Bitmap,
outWidth: Int,
outHeight: Int
): Bitmap? {
val sampling: Int
if (this.sampling == 0) {
sampling = ImageUtil.calculateInSampleSize(toTransform.width, toTransform.height, 100)
): Bitmap {
val sampling = if (this.sampling == 0) {
ImageUtil.calculateInSampleSize(toTransform.width, toTransform.height, 100)
} else {
sampling = this.sampling
this.sampling
}
val width = toTransform.width
val height = toTransform.height
val scaledWidth = width / sampling
val scaledHeight = height / sampling
var out: Bitmap? = pool.get(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888)
if (out == null) {
out = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888)
}
val canvas = Canvas(out!!)
val out = pool[scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888]
val canvas = Canvas(out)
canvas.scale(1 / sampling.toFloat(), 1 / sampling.toFloat())
val paint = Paint()
paint.flags = Paint.FILTER_BITMAP_FLAG
canvas.drawBitmap(toTransform, 0f, 0f, paint)
if (Build.VERSION.SDK_INT >= 17) {
try {
val rs = RenderScript.create(context!!.applicationContext)
val input = Allocation.createFromBitmap(
rs,
out,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT
)
val output = Allocation.createTyped(rs, input.type)
val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
try {
val rs = RenderScript.create(context!!.applicationContext)
val input = Allocation.createFromBitmap(
rs,
out,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT
)
val output = Allocation.createTyped(rs, input.type)
val script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs))
script.setRadius(blurRadius)
script.setInput(input)
script.forEach(output)
script.setRadius(blurRadius)
script.setInput(input)
script.forEach(output)
output.copyTo(out)
output.copyTo(out)
rs.destroy()
rs.destroy()
return out
} catch (e: RSRuntimeException) {
// on some devices RenderScript.create() throws: android.support.v8.renderscript.RSRuntimeException: Error loading libRSSupport library
if (BuildConfig.DEBUG) e.printStackTrace()
}
return out
} catch (e: RSRuntimeException) {
// on some devices RenderScript.create() throws: android.support.v8.renderscript.RSRuntimeException: Error loading libRSSupport library
if (BuildConfig.DEBUG) e.printStackTrace()
}
return StackBlur.blur(out, blurRadius)
}
override fun getId(): String {
return "BlurTransformation(radius=$blurRadius, sampling=$sampling)"
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(
"BlurTransformation(radius=$blurRadius, sampling=$sampling)".toByteArray(
CHARSET
)
)
}
companion object {
val DEFAULT_BLUR_RADIUS = 5f
const val DEFAULT_BLUR_RADIUS = 5f
}
}

View file

@ -1,76 +0,0 @@
package code.name.monkey.retromusic.glide;
import static code.name.monkey.retromusic.Constants.USER_BANNER;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
import code.name.monkey.retromusic.App;
import code.name.monkey.retromusic.R;
import com.bumptech.glide.BitmapRequestBuilder;
import com.bumptech.glide.BitmapTypeRequest;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.signature.MediaStoreSignature;
import java.io.File;
public class ProfileBannerGlideRequest {
private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE;
private static final int DEFAULT_ERROR_IMAGE = R.drawable.material_design_default;
private static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
public static File getBannerModel() {
File dir = App.Companion.getContext().getFilesDir();
return new File(dir, USER_BANNER);
}
private static BitmapTypeRequest<File> createBaseRequest(
RequestManager requestManager, File profile) {
return requestManager.load(profile).asBitmap();
}
private static Key createSignature(File file) {
return new MediaStoreSignature("", file.lastModified(), 0);
}
public static class Builder {
private RequestManager requestManager;
private File profile;
private Builder(RequestManager requestManager, File profile) {
this.requestManager = requestManager;
this.profile = profile;
}
public static Builder from(@NonNull RequestManager requestManager, File profile) {
return new Builder(requestManager, profile);
}
@NonNull
public BitmapRequestBuilder<File, Bitmap> build() {
//noinspection unchecked
return createBaseRequest(requestManager, profile)
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.placeholder(DEFAULT_ERROR_IMAGE)
.animate(DEFAULT_ANIMATION)
.signature(createSignature(profile));
}
}
public static class BitmapBuilder {
private final Builder builder;
BitmapBuilder(Builder builder) {
this.builder = builder;
}
public BitmapRequestBuilder<?, Bitmap> build() {
//noinspection unchecked
return createBaseRequest(builder.requestManager, builder.profile)
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(DEFAULT_ERROR_IMAGE)
.animate(DEFAULT_ANIMATION)
.signature(createSignature(builder.profile));
}
}
}

View file

@ -0,0 +1,192 @@
package code.name.monkey.retromusic.glide
import android.graphics.drawable.Drawable
import code.name.monkey.appthemehelper.ThemeStore.Companion.accentColor
import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.retromusic.App.Companion.getContext
import code.name.monkey.retromusic.Constants.USER_BANNER
import code.name.monkey.retromusic.Constants.USER_PROFILE
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.Companion.getFile
import code.name.monkey.retromusic.util.CustomArtistImageUtil.Companion.getInstance
import code.name.monkey.retromusic.util.MusicUtil.getMediaStoreAlbumCoverUri
import code.name.monkey.retromusic.util.PreferenceUtil
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.BaseRequestOptions
import com.bumptech.glide.request.target.Target.SIZE_ORIGINAL
import com.bumptech.glide.signature.MediaStoreSignature
import java.io.File
@GlideExtension
object RetroGlideExtension {
private const val DEFAULT_ERROR_ARTIST_IMAGE =
R.drawable.default_artist_art
private const val DEFAULT_ERROR_SONG_IMAGE: Int = R.drawable.default_audio_art
private const val DEFAULT_ERROR_ALBUM_IMAGE = R.drawable.default_album_art
private const val DEFAULT_ERROR_IMAGE_BANNER = R.drawable.material_design_default
private val DEFAULT_DISK_CACHE_STRATEGY_ARTIST = DiskCacheStrategy.RESOURCE
private val DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE
private const val DEFAULT_ANIMATION = android.R.anim.fade_in
@JvmStatic
@GlideType(BitmapPaletteWrapper::class)
fun asBitmapPalette(requestBuilder: RequestBuilder<BitmapPaletteWrapper>): RequestBuilder<BitmapPaletteWrapper> {
return requestBuilder
}
private fun getSongModel(song: Song, ignoreMediaStore: Boolean): Any {
return if (ignoreMediaStore) {
AudioFileCover(song.data)
} else {
getMediaStoreAlbumCoverUri(song.albumId)
}
}
fun getSongModel(song: Song): Any {
return getSongModel(song, PreferenceUtil.isIgnoreMediaStoreArtwork)
}
fun getArtistModel(artist: Artist): Any {
return getArtistModel(
artist,
getInstance(getContext()).hasCustomArtistImage(artist),
false
)
}
fun getArtistModel(artist: Artist, forceDownload: Boolean): Any {
return getArtistModel(
artist,
getInstance(getContext()).hasCustomArtistImage(artist),
forceDownload
)
}
private fun getArtistModel(artist: Artist, hasCustomImage: Boolean, forceDownload: Boolean): Any {
return if (!hasCustomImage) {
ArtistImage(artist)
} else {
getFile(artist)
}
}
@JvmStatic
@GlideOption
fun artistImageOptions(
baseRequestOptions: BaseRequestOptions<*>,
artist: Artist
): BaseRequestOptions<*> {
return baseRequestOptions
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY_ARTIST)
.priority(Priority.LOW)
.error(DEFAULT_ERROR_ARTIST_IMAGE)
.override(SIZE_ORIGINAL, SIZE_ORIGINAL)
.signature(createSignature(artist))
}
@JvmStatic
@GlideOption
fun songCoverOptions(
baseRequestOptions: BaseRequestOptions<*>,
song: Song
): BaseRequestOptions<*> {
return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(DEFAULT_ERROR_SONG_IMAGE)
.signature(createSignature(song))
}
@JvmStatic
@GlideOption
fun albumCoverOptions(
baseRequestOptions: BaseRequestOptions<*>,
song: Song
): BaseRequestOptions<*> {
return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(DEFAULT_ERROR_ALBUM_IMAGE)
.signature(createSignature(song))
}
@JvmStatic
@GlideOption
fun userProfileOptions(
baseRequestOptions: BaseRequestOptions<*>,
file: File
): BaseRequestOptions<*> {
return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(getErrorUserProfile())
.signature(createSignature(file))
}
@JvmStatic
@GlideOption
fun profileBannerOptions(
baseRequestOptions: BaseRequestOptions<*>,
file: File
): BaseRequestOptions<*> {
return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.placeholder(DEFAULT_ERROR_IMAGE_BANNER)
.error(DEFAULT_ERROR_IMAGE_BANNER)
.signature(createSignature(file))
}
@JvmStatic
@GlideOption
fun playlistOptions(
baseRequestOptions: BaseRequestOptions<*>
): BaseRequestOptions<*> {
return baseRequestOptions.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(DEFAULT_ERROR_ALBUM_IMAGE)
}
private fun createSignature(song: Song): Key {
return MediaStoreSignature("", song.dateModified, 0)
}
private fun createSignature(file: File): Key {
return MediaStoreSignature("", file.lastModified(), 0)
}
private fun createSignature(artist: Artist): Key {
return ArtistSignatureUtil.getInstance(getContext())
.getArtistSignature(artist.name)
}
fun getUserModel(): File {
val dir = getContext().filesDir
return File(dir, USER_PROFILE)
}
fun getBannerModel(): File {
val dir = getContext().filesDir
return File(dir, USER_BANNER)
}
private fun getErrorUserProfile(): Drawable {
return TintHelper.createTintedDrawable(
getContext(),
R.drawable.ic_account,
accentColor(getContext())
)
}
fun <TranscodeType> getDefaultTransition(): GenericTransitionOptions<TranscodeType> {
return GenericTransitionOptions<TranscodeType>().transition(DEFAULT_ANIMATION)
}
}

View file

@ -22,7 +22,7 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.transition.Transition
abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(view) {
@ -31,21 +31,19 @@ abstract class RetroMusicColoredTarget(view: ImageView) : BitmapPaletteTarget(vi
abstract fun onColorReady(colors: MediaNotificationProcessor)
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
val colors = MediaNotificationProcessor(App.getContext(), errorDrawable)
onColorReady(colors)
}
override fun onResourceReady(
resource: BitmapPaletteWrapper?,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>?
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
super.onResourceReady(resource, glideAnimation)
resource?.let { bitmapWrap ->
MediaNotificationProcessor(App.getContext()).getPaletteAsync({
onColorReady(it)
}, bitmapWrap.bitmap)
}
super.onResourceReady(resource, transition)
MediaNotificationProcessor(App.getContext()).getPaletteAsync({
onColorReady(it)
}, resource.bitmap)
}
}

View file

@ -15,25 +15,38 @@
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.Factory
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 code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreview
import code.name.monkey.retromusic.glide.playlistPreview.PlaylistPreviewLoader
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.module.GlideModule
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule
import java.io.InputStream
class RetroMusicGlideModule : GlideModule {
override fun applyOptions(context: Context, builder: GlideBuilder) {
}
override fun registerComponents(context: Context, glide: Glide) {
glide.register(
@GlideModule
class RetroMusicGlideModule : AppGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
registry.prepend(PlaylistPreview::class.java, Bitmap::class.java, PlaylistPreviewLoader.Factory(context))
registry.prepend(
AudioFileCover::class.java,
InputStream::class.java,
AudioFileCoverLoader.Factory()
)
glide.register(ArtistImage::class.java, InputStream::class.java, Factory(context))
registry.prepend(ArtistImage::class.java, InputStream::class.java, Factory(context))
registry.register(
Bitmap::class.java,
BitmapPaletteWrapper::class.java, BitmapPaletteTranscoder()
)
}
}
override fun isManifestParsingEnabled(): Boolean {
return false
}
}

View file

@ -21,7 +21,7 @@ import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.glide.palette.BitmapPaletteTarget
import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.util.ColorUtil
import com.bumptech.glide.request.animation.GlideAnimation
import com.bumptech.glide.request.transition.Transition
abstract class SingleColorTarget(view: ImageView) : BitmapPaletteTarget(view) {
@ -30,23 +30,21 @@ abstract class SingleColorTarget(view: ImageView) : BitmapPaletteTarget(view) {
abstract fun onColorReady(color: Int)
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
super.onLoadFailed(e, errorDrawable)
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
onColorReady(defaultFooterColor)
}
override fun onResourceReady(
resource: BitmapPaletteWrapper?,
glideAnimation: GlideAnimation<in BitmapPaletteWrapper>?
resource: BitmapPaletteWrapper,
transition: Transition<in BitmapPaletteWrapper>?
) {
super.onResourceReady(resource, glideAnimation)
resource?.let {
onColorReady(
ColorUtil.getColor(
it.palette,
ATHUtil.resolveColor(view.context, R.attr.colorPrimary)
)
super.onResourceReady(resource, transition)
onColorReady(
ColorUtil.getColor(
resource.palette,
ATHUtil.resolveColor(view.context, R.attr.colorPrimary)
)
}
)
}
}

View file

@ -1,144 +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.content.Context;
import android.graphics.Bitmap;
import androidx.annotation.NonNull;
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;
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;
/** Created by hemanths on 2019-09-15. */
public class SongGlideRequest {
private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE;
private static final int DEFAULT_ERROR_IMAGE = R.drawable.default_audio_art;
private static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
@NonNull
private static DrawableTypeRequest createBaseRequest(
@NonNull RequestManager requestManager, @NonNull Song song, boolean ignoreMediaStore) {
if (ignoreMediaStore) {
return requestManager.load(new AudioFileCover(song.getData()));
} else {
return requestManager.loadFromMediaStore(
MusicUtil.INSTANCE.getMediaStoreAlbumCoverUri(song.getAlbumId()));
}
}
@NonNull
private static Key createSignature(@NonNull Song song) {
return new MediaStoreSignature("", song.getDateModified(), 0);
}
public static class Builder {
final RequestManager requestManager;
final Song song;
boolean ignoreMediaStore;
private Builder(@NonNull RequestManager requestManager, Song song) {
this.requestManager = requestManager;
this.song = song;
}
@NonNull
public static Builder from(@NonNull RequestManager requestManager, Song song) {
return new Builder(requestManager, song);
}
@NonNull
public PaletteBuilder generatePalette(@NonNull Context context) {
return new PaletteBuilder(this, context);
}
@NonNull
public BitmapBuilder asBitmap() {
return new BitmapBuilder(this);
}
@NonNull
public Builder checkIgnoreMediaStore(@NonNull Context context) {
return ignoreMediaStore(PreferenceUtil.INSTANCE.isIgnoreMediaStoreArtwork());
}
@NonNull
public Builder ignoreMediaStore(boolean ignoreMediaStore) {
this.ignoreMediaStore = ignoreMediaStore;
return this;
}
@NonNull
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;
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;
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));
}
}
}

View file

@ -1,83 +0,0 @@
package code.name.monkey.retromusic.glide;
import static code.name.monkey.retromusic.Constants.USER_PROFILE;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import code.name.monkey.appthemehelper.ThemeStore;
import code.name.monkey.appthemehelper.util.TintHelper;
import code.name.monkey.retromusic.App;
import code.name.monkey.retromusic.R;
import com.bumptech.glide.BitmapRequestBuilder;
import com.bumptech.glide.BitmapTypeRequest;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.signature.MediaStoreSignature;
import java.io.File;
public class UserProfileGlideRequest {
private static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.NONE;
private static final int DEFAULT_ERROR_IMAGE = R.drawable.ic_account;
private static final int DEFAULT_ANIMATION = android.R.anim.fade_in;
public static File getUserModel() {
File dir = App.Companion.getContext().getFilesDir();
return new File(dir, USER_PROFILE);
}
private static BitmapTypeRequest<File> createBaseRequest(
RequestManager requestManager, File profile) {
return requestManager.load(profile).asBitmap();
}
private static Key createSignature(File file) {
return new MediaStoreSignature("", file.lastModified(), 0);
}
public static class Builder {
private RequestManager requestManager;
private File profile;
private Drawable error;
private Builder(RequestManager requestManager, File profile) {
this.requestManager = requestManager;
this.profile = profile;
error =
TintHelper.createTintedDrawable(
App.Companion.getContext(),
R.drawable.ic_account,
ThemeStore.Companion.accentColor(App.Companion.getContext()));
}
public static Builder from(@NonNull RequestManager requestManager, File profile) {
return new Builder(requestManager, profile);
}
@NonNull
public BitmapRequestBuilder<File, Bitmap> build() {
return createBaseRequest(requestManager, profile)
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(error)
.animate(DEFAULT_ANIMATION)
.signature(createSignature(profile));
}
}
public static class BitmapBuilder {
private final Builder builder;
BitmapBuilder(Builder builder) {
this.builder = builder;
}
public BitmapRequestBuilder<?, Bitmap> build() {
return createBaseRequest(builder.requestManager, builder.profile)
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.error(builder.error)
.animate(DEFAULT_ANIMATION)
.signature(createSignature(builder.profile));
}
}
}

View file

@ -0,0 +1,16 @@
package code.name.monkey.retromusic.glide.artistimage
import code.name.monkey.retromusic.model.Artist
class ArtistImage(val artist: Artist){
override fun equals(other: Any?): Boolean {
if (other is ArtistImage){
return other.artist == artist
}
return false
}
override fun hashCode(): Int {
return artist.hashCode()
}
}

View file

@ -0,0 +1,121 @@
package code.name.monkey.retromusic.glide.artistimage
import android.content.Context
import code.name.monkey.retromusic.model.Data
import code.name.monkey.retromusic.model.DeezerResponse
import code.name.monkey.retromusic.network.DeezerService
import code.name.monkey.retromusic.util.MusicUtil
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.data.DataFetcher
import com.bumptech.glide.load.model.GlideUrl
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
class ArtistImageFetcher(
private val context: Context,
private val deezerService: DeezerService,
val model: ArtistImage,
private val okhttp: OkHttpClient
) : DataFetcher<InputStream> {
private var streamFetcher: OkHttpStreamFetcher? = null
private var response: Call<DeezerResponse>? = null
private var isCancelled: Boolean = false
override fun getDataClass(): Class<InputStream> {
return InputStream::class.java
}
override fun getDataSource(): DataSource {
return DataSource.REMOTE
}
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
try {
if (!MusicUtil.isArtistNameUnknown(model.artist.name) &&
PreferenceUtil.isAllowedToDownloadMetadata()
) {
val artists = model.artist.name.split(",", "&")
response = deezerService.getArtistImage(artists[0])
response?.enqueue(object : Callback<DeezerResponse> {
override fun onResponse(
call: Call<DeezerResponse>,
response: Response<DeezerResponse>
) {
if (!response.isSuccessful) {
throw IOException("Request failed with code: " + response.code())
}
if (isCancelled) {
callback.onDataReady(null)
return
}
try {
val deezerResponse = response.body()
val imageUrl =
deezerResponse?.data?.get(0)?.let { getHighestQuality(it) }
// Fragile way to detect a place holder image returned from Deezer:
// ex: "https://e-cdns-images.dzcdn.net/images/artist//250x250-000000-80-0-0.jpg"
// the double slash implies no artist identified
val placeHolder = imageUrl?.contains("/images/artist//") ?: false
if (!placeHolder) {
streamFetcher = OkHttpStreamFetcher(okhttp, GlideUrl(imageUrl))
streamFetcher?.loadData(priority, callback)
} else {
callback.onDataReady(getFallbackAlbumImage())
}
} catch (e: Exception) {
callback.onDataReady(getFallbackAlbumImage())
}
}
override fun onFailure(call: Call<DeezerResponse>, t: Throwable) {
callback.onDataReady(getFallbackAlbumImage())
}
})
} else callback.onDataReady(null)
} catch (e: Exception) {
callback.onLoadFailed(e)
}
}
private fun getFallbackAlbumImage(): InputStream? {
val imageUri = MusicUtil.getMediaStoreAlbumCoverUri(model.artist.safeGetFirstAlbum().id)
return try {
context.contentResolver.openInputStream(imageUri)
} catch (e: FileNotFoundException){
null
}
}
private fun getHighestQuality(imageUrl: Data): String {
return when {
imageUrl.pictureXl.isNotEmpty() -> imageUrl.pictureXl
imageUrl.pictureBig.isNotEmpty() -> imageUrl.pictureBig
imageUrl.pictureMedium.isNotEmpty() -> imageUrl.pictureMedium
imageUrl.pictureSmall.isNotEmpty() -> imageUrl.pictureSmall
imageUrl.picture.isNotEmpty() -> imageUrl.picture
else -> ""
}
}
override fun cleanup() {
streamFetcher?.cleanup()
}
override fun cancel() {
isCancelled = true
response?.cancel()
streamFetcher?.cancel()
}
}

View file

@ -15,115 +15,39 @@
package code.name.monkey.retromusic.glide.artistimage
import android.content.Context
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Data
import code.name.monkey.retromusic.network.DeezerService
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Priority
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.model.GenericLoaderFactory
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.Options
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.stream.StreamModelLoader
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.TimeUnit
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import com.bumptech.glide.signature.ObjectKey
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
class ArtistImage(val artist: Artist)
class ArtistImageFetcher(
private val context: Context,
private val deezerService: DeezerService,
val model: ArtistImage,
val urlLoader: ModelLoader<GlideUrl, InputStream>,
val width: Int,
val height: Int
) : DataFetcher<InputStream> {
private var urlFetcher: DataFetcher<InputStream>? = null
private var isCancelled: Boolean = false
override fun cleanup() {
urlFetcher?.cleanup()
}
override fun getId(): String {
return model.artist.name
}
override fun cancel() {
isCancelled = true
urlFetcher?.cancel()
}
override fun loadData(priority: Priority?): InputStream? {
if (!MusicUtil.isArtistNameUnknown(model.artist.name) &&
PreferenceUtil.isAllowedToDownloadMetadata()
) {
val artists = model.artist.name.split(",")
val response = deezerService.getArtistImage(artists[0]).execute()
if (!response.isSuccessful) {
throw IOException("Request failed with code: " + response.code())
}
if (isCancelled) return null
return try {
val deezerResponse = response.body()
val imageUrl = deezerResponse?.data?.get(0)?.let { getHighestQuality(it) }
// Fragile way to detect a place holder image returned from Deezer:
// ex: "https://e-cdns-images.dzcdn.net/images/artist//250x250-000000-80-0-0.jpg"
// the double slash implies no artist identified
val placeHolder = imageUrl?.contains("/images/artist//") ?: false
if (!placeHolder) {
val glideUrl = GlideUrl(imageUrl)
urlFetcher = urlLoader.getResourceFetcher(glideUrl, width, height)
urlFetcher?.loadData(priority)
} else {
getFallbackAlbumImage()
}
} catch (e: Exception) {
getFallbackAlbumImage()
}
} else return null
}
private fun getFallbackAlbumImage(): InputStream? {
val imageUri = MusicUtil.getMediaStoreAlbumCoverUri(model.artist.safeGetFirstAlbum().id)
return context.contentResolver.openInputStream(imageUri)
}
private fun getHighestQuality(imageUrl: Data): String {
return when {
imageUrl.pictureXl.isNotEmpty() -> imageUrl.pictureXl
imageUrl.pictureBig.isNotEmpty() -> imageUrl.pictureBig
imageUrl.pictureMedium.isNotEmpty() -> imageUrl.pictureMedium
imageUrl.pictureSmall.isNotEmpty() -> imageUrl.pictureSmall
imageUrl.picture.isNotEmpty() -> imageUrl.picture
else -> ""
}
}
}
import java.io.InputStream
import java.util.concurrent.TimeUnit
class ArtistImageLoader(
val context: Context,
private val deezerService: DeezerService,
private val urlLoader: ModelLoader<GlideUrl, InputStream>
) : StreamModelLoader<ArtistImage> {
private val okhttp: OkHttpClient
) : ModelLoader<ArtistImage, InputStream> {
override fun getResourceFetcher(
override fun buildLoadData(
model: ArtistImage,
width: Int,
height: Int
): DataFetcher<InputStream> {
return ArtistImageFetcher(context, deezerService, model, urlLoader, width, height)
height: Int,
options: Options
): LoadData<InputStream> {
return LoadData(
ObjectKey(model.artist.name),
ArtistImageFetcher(context, deezerService, model, okhttp)
)
}
override fun handles(model: ArtistImage): Boolean {
return true
}
}
@ -132,16 +56,15 @@ class Factory(
) : ModelLoaderFactory<ArtistImage, InputStream> {
private var deezerService: DeezerService
private var okHttpFactory: OkHttpUrlLoader.Factory
private var okHttp: OkHttpClient
init {
okHttpFactory = OkHttpUrlLoader.Factory(
okHttp =
OkHttpClient.Builder()
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.build()
)
deezerService = DeezerService.invoke(
DeezerService.createDefaultOkHttpClient(context)
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
@ -158,23 +81,18 @@ class Factory(
return interceptor
}
override fun build(
context: Context?,
factories: GenericLoaderFactory?
): ModelLoader<ArtistImage, InputStream> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ArtistImage, InputStream> {
return ArtistImageLoader(
context!!,
context,
deezerService,
okHttpFactory.build(context, factories)
okHttp
)
}
override fun teardown() {
okHttpFactory.teardown()
}
override fun teardown() {}
companion object {
// 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: Long = 700
private const val TIMEOUT: Long = 500
}
}

View file

@ -14,6 +14,8 @@
package code.name.monkey.retromusic.glide.audiocover;
import androidx.annotation.Nullable;
/** @author Karim Abou Zeid (kabouzeid) */
public class AudioFileCover {
public final String filePath;
@ -21,4 +23,17 @@ public class AudioFileCover {
public AudioFileCover(String filePath) {
this.filePath = filePath;
}
@Override
public int hashCode() {
return filePath.hashCode();
}
@Override
public boolean equals(@Nullable Object object) {
if (object instanceof AudioFileCover){
return ((AudioFileCover) object).filePath.equals(filePath);
}
return false;
}
}

View file

@ -15,9 +15,15 @@
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.data.DataFetcher;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@ -27,19 +33,11 @@ public class AudioFileCoverFetcher implements DataFetcher<InputStream> {
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 {
public void loadData(@NotNull Priority priority, @NotNull DataCallback<? super InputStream> callback) {
final MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(model.filePath);
@ -49,11 +47,12 @@ public class AudioFileCoverFetcher implements DataFetcher<InputStream> {
} else {
stream = AudioFileCoverUtils.fallback(model.filePath);
}
callback.onDataReady(stream);
} catch (FileNotFoundException e) {
callback.onLoadFailed(e);
} finally {
retriever.release();
}
return stream;
}
@Override
@ -72,4 +71,16 @@ public class AudioFileCoverFetcher implements DataFetcher<InputStream> {
public void cancel() {
// cannot cancel
}
@NotNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NotNull
@Override
public DataSource getDataSource() {
return DataSource.LOCAL;
}
}

View file

@ -14,29 +14,40 @@
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 androidx.annotation.NonNull;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.model.ModelLoader;
import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.stream.StreamModelLoader;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import com.bumptech.glide.signature.ObjectKey;
import org.jetbrains.annotations.NotNull;
import java.io.InputStream;
public class AudioFileCoverLoader implements StreamModelLoader<AudioFileCover> {
public class AudioFileCoverLoader implements ModelLoader<AudioFileCover, InputStream> {
@Override
public DataFetcher<InputStream> getResourceFetcher(AudioFileCover model, int width, int height) {
return new AudioFileCoverFetcher(model);
public LoadData<InputStream> buildLoadData(@NonNull @NotNull AudioFileCover audioFileCover, int width, int height, @NonNull @NotNull Options options) {
return new LoadData<>(new ObjectKey(audioFileCover.filePath), new AudioFileCoverFetcher(audioFileCover));
}
@Override
public boolean handles(@NonNull @NotNull AudioFileCover audioFileCover) {
return audioFileCover.filePath != null;
}
public static class Factory implements ModelLoaderFactory<AudioFileCover, InputStream> {
@NotNull
@Override
public ModelLoader<AudioFileCover, InputStream> build(
Context context, GenericLoaderFactory factories) {
public ModelLoader<AudioFileCover, InputStream> build(@NonNull @NotNull MultiModelLoaderFactory multiFactory) {
return new AudioFileCoverLoader();
}
@Override
public void teardown() {}
public void teardown() {
}
}
}

View file

@ -14,17 +14,18 @@
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;
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;
public class AudioFileCoverUtils {
@ -44,10 +45,7 @@ public class AudioFileCoverUtils {
}
}
// 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) {
} catch (ReadOnlyFileException | InvalidAudioFrameException | TagException | IOException ignored) {
}
// Method 2: look for album art in external files

View file

@ -14,25 +14,31 @@
package code.name.monkey.retromusic.glide.palette;
import androidx.annotation.NonNull;
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) {
public BitmapPaletteResource(BitmapPaletteWrapper bitmapPaletteWrapper) {
this.bitmapPaletteWrapper = bitmapPaletteWrapper;
this.bitmapPool = bitmapPool;
}
@NonNull
@Override
public BitmapPaletteWrapper get() {
return bitmapPaletteWrapper;
}
@NonNull
@Override
public Class<BitmapPaletteWrapper> getResourceClass() {
return BitmapPaletteWrapper.class;
}
@Override
public int getSize() {
return Util.getBitmapByteSize(bitmapPaletteWrapper.getBitmap());
@ -40,8 +46,6 @@ public class BitmapPaletteResource implements Resource<BitmapPaletteWrapper> {
@Override
public void recycle() {
if (!bitmapPool.put(bitmapPaletteWrapper.getBitmap())) {
bitmapPaletteWrapper.getBitmap().recycle();
}
bitmapPaletteWrapper.getBitmap().recycle();
}
}

View file

@ -15,6 +15,7 @@
package code.name.monkey.retromusic.glide.palette;
import android.widget.ImageView;
import com.bumptech.glide.request.target.ImageViewTarget;
public class BitmapPaletteTarget extends ImageViewTarget<BitmapPaletteWrapper> {
@ -24,6 +25,8 @@ public class BitmapPaletteTarget extends ImageViewTarget<BitmapPaletteWrapper> {
@Override
protected void setResource(BitmapPaletteWrapper bitmapPaletteWrapper) {
view.setImageBitmap(bitmapPaletteWrapper.getBitmap());
if (bitmapPaletteWrapper != null) {
view.setImageBitmap(bitmapPaletteWrapper.getBitmap());
}
}
}

View file

@ -14,35 +14,23 @@
package code.name.monkey.retromusic.glide.palette;
import android.content.Context;
import android.graphics.Bitmap;
import code.name.monkey.retromusic.util.RetroColorUtil;
import com.bumptech.glide.Glide;
import androidx.annotation.NonNull;
import com.bumptech.glide.load.Options;
import com.bumptech.glide.load.engine.Resource;
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
public class BitmapPaletteTranscoder implements ResourceTranscoder<Bitmap, BitmapPaletteWrapper> {
private final BitmapPool bitmapPool;
import code.name.monkey.retromusic.util.RetroColorUtil;
public BitmapPaletteTranscoder(Context context) {
this(Glide.get(context).getBitmapPool());
}
public class BitmapPaletteTranscoder implements ResourceTranscoder<Bitmap, BitmapPaletteWrapper> {
public BitmapPaletteTranscoder(BitmapPool bitmapPool) {
this.bitmapPool = bitmapPool;
}
@Override
public Resource<BitmapPaletteWrapper> transcode(Resource<Bitmap> bitmapResource) {
Bitmap bitmap = bitmapResource.get();
@Override
public Resource<BitmapPaletteWrapper> transcode(@NonNull Resource<Bitmap> toTranscode, @NonNull Options options) {
Bitmap bitmap = toTranscode.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";
new BitmapPaletteWrapper(bitmap, RetroColorUtil.generatePalette(bitmap));
return new BitmapPaletteResource(bitmapPaletteWrapper);
}
}

View file

@ -15,6 +15,7 @@
package code.name.monkey.retromusic.glide.palette;
import android.graphics.Bitmap;
import androidx.palette.graphics.Palette;
public class BitmapPaletteWrapper {

View file

@ -0,0 +1,31 @@
package code.name.monkey.retromusic.glide.playlistPreview
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.toSongs
import code.name.monkey.retromusic.model.Song
class PlaylistPreview(val playlistWithSongs: PlaylistWithSongs) {
val playlistEntity: PlaylistEntity get() = playlistWithSongs.playlistEntity
val songs: List<Song> get() = playlistWithSongs.songs.toSongs()
override fun equals(other: Any?): Boolean {
if (other is PlaylistPreview) {
if (other.playlistEntity.playListId != playlistEntity.playListId) {
return false
}
if (other.songs.size != songs.size) {
return false
}
return true
}
return false
}
override fun hashCode(): Int {
var result = playlistEntity.playListId.hashCode()
result = 31 * result + playlistWithSongs.songs.size
return result
}
}

View file

@ -0,0 +1,51 @@
package code.name.monkey.retromusic.glide.playlistPreview
import android.content.Context
import android.graphics.Bitmap
import code.name.monkey.retromusic.util.AutoGeneratedPlaylistBitmap
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.data.DataFetcher
import kotlinx.coroutines.*
import java.util.concurrent.Executors
class PlaylistPreviewFetcher(val context: Context, private val playlistPreview: PlaylistPreview) :
DataFetcher<Bitmap>, CoroutineScope by GlideScope() {
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in Bitmap>) {
launch {
try {
val bitmap =
AutoGeneratedPlaylistBitmap.getBitmap(
context,
playlistPreview.songs.shuffled(),
true,
false
)
callback.onDataReady(bitmap)
} catch (e: Exception) {
callback.onLoadFailed(e)
}
}
}
override fun cleanup() {}
override fun cancel() {
cancel(null)
}
override fun getDataClass(): Class<Bitmap> {
return Bitmap::class.java
}
override fun getDataSource(): DataSource {
return DataSource.LOCAL
}
}
private val glideDispatcher: CoroutineDispatcher by lazy {
Executors.newFixedThreadPool(4).asCoroutineDispatcher()
}
@Suppress("FunctionName")
internal fun GlideScope(): CoroutineScope = CoroutineScope(SupervisorJob() + glideDispatcher)

View file

@ -0,0 +1,36 @@
package code.name.monkey.retromusic.glide.playlistPreview
import android.content.Context
import android.graphics.Bitmap
import com.bumptech.glide.load.Options
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
class PlaylistPreviewLoader(val context: Context) : ModelLoader<PlaylistPreview, Bitmap> {
override fun buildLoadData(
model: PlaylistPreview,
width: Int,
height: Int,
options: Options
): LoadData<Bitmap> {
return LoadData(
ObjectKey(model),
PlaylistPreviewFetcher(context, model)
)
}
override fun handles(model: PlaylistPreview): Boolean {
return true
}
class Factory(val context: Context) : ModelLoaderFactory<PlaylistPreview, Bitmap> {
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<PlaylistPreview, Bitmap> {
return PlaylistPreviewLoader(context)
}
override fun teardown() {}
}
}