Added Deezer for loading Artist images

main
h4h13 2019-06-06 21:57:42 +05:30
parent e082da1dcc
commit b43f71cc32
6 changed files with 227 additions and 55 deletions

View File

@ -29,6 +29,7 @@ import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.NavigationUtil
import java.util.*
class PlaylistAdapter(protected val activity: AppCompatActivity, dataSet: ArrayList<Playlist>,
@param:LayoutRes protected var itemLayoutRes: Int, cabHolder: CabHolder?) : AbsMultiSelectAdapter<PlaylistAdapter.ViewHolder, Playlist>(activity, cabHolder, R.menu.menu_playlists_selection) {
var dataSet: ArrayList<Playlist>
@ -60,6 +61,14 @@ class PlaylistAdapter(protected val activity: AppCompatActivity, dataSet: ArrayL
return ViewHolder(view)
}
protected fun getPlaylistTitle(playlist: Playlist): String {
return playlist.name
}
protected fun getPlaylistText(playlist: Playlist): String {
return playlist.getInfoString(activity)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val playlist = dataSet[position]
@ -67,10 +76,10 @@ class PlaylistAdapter(protected val activity: AppCompatActivity, dataSet: ArrayL
holder.itemView.isActivated = isChecked(playlist)
if (holder.title != null) {
holder.title!!.text = playlist.name
holder.title!!.text = getPlaylistTitle(playlist)
}
if (holder.text != null) {
holder.text!!.text = String.format(Locale.getDefault(), "%d Songs", songs!!.size)
holder.text!!.text = getPlaylistText(playlist)
}
if (holder.image != null) {
holder.image!!.setImageDrawable(getIconRes(playlist))

View File

@ -0,0 +1,63 @@
package code.name.monkey.retromusic.deezer
import android.content.Context
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create
import retrofit2.http.GET
import retrofit2.http.Query
import java.io.File
import java.util.*
private const val BASE_QUERY_ARTIST = "search/artist"
private const val BASE_URL = "https://api.deezer.com/"
interface DeezerApiService {
@GET("$BASE_QUERY_ARTIST&limit=1")
fun getArtistImage(
@Query("q") artistName: String
): Call<DeezerResponse>
companion object {
operator fun invoke(client: okhttp3.Call.Factory): DeezerApiService {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.callFactory(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create()
}
fun createDefaultOkHttpClient(context: Context): OkHttpClient.Builder =
OkHttpClient.Builder()
.cache(createDefaultCache(context))
.addInterceptor(createCacheControlInterceptor())
private fun createDefaultCache(context: Context): Cache? {
val cacheDir = File(context.applicationContext.cacheDir.absolutePath, "/okhttp-deezer/")
if (cacheDir.mkdir() or cacheDir.isDirectory) {
return Cache(cacheDir, 1024 * 1024 * 10)
}
return null
}
private fun createCacheControlInterceptor(): Interceptor {
return Interceptor { chain ->
val modifiedRequest = chain.request().newBuilder()
.addHeader("Cache-Control",
String.format(
Locale.getDefault(),
"max-age=%d, max-stale=%d",
31536000, 31536000
)
).build()
chain.proceed(modifiedRequest)
}
}
}
}

View File

@ -0,0 +1,31 @@
package code.name.monkey.retromusic.deezer
import com.google.gson.annotations.SerializedName
data class Data(
val id: String,
val link: String,
val name: String,
@SerializedName("nb_album")
val nbAlbum: Int,
@SerializedName("nb_fan")
val nbFan: Int,
val picture: String,
@SerializedName("picture_big")
val pictureBig: String,
@SerializedName("picture_medium")
val pictureMedium: String,
@SerializedName("picture_small")
val pictureSmall: String,
@SerializedName("picture_xl")
val pictureXl: String,
val radio: Boolean,
val tracklist: String,
val type: String
)
data class DeezerResponse(
val data: List<Data>,
val next: String,
val total: Int
)

View File

@ -17,6 +17,8 @@ package code.name.monkey.retromusic.glide.artistimage
import android.content.Context
import android.text.TextUtils
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.deezer.DeezerApiService
import code.name.monkey.retromusic.deezer.DeezerResponse
import code.name.monkey.retromusic.rest.LastFMRestClient
import code.name.monkey.retromusic.rest.model.LastFmArtist
import code.name.monkey.retromusic.util.LastFMUtil
@ -43,12 +45,12 @@ import java.util.concurrent.TimeUnit
class ArtistImage(val artistName: String, val skipOkHttpCache: Boolean)
class ArtistImageFetcher(private val context: Context,
private val lastFMRestClient: LastFMRestClient,
private val deezerApiService: DeezerApiService,
private val okHttp: OkHttpClient,
private val model: ArtistImage) : DataFetcher<InputStream> {
@Volatile
private var isCancelled: Boolean = false
private var call: Call<LastFmArtist>? = null
private var call: Call<DeezerResponse>? = null
private var streamFetcher: OkHttpStreamFetcher? = null
@ -62,37 +64,30 @@ class ArtistImageFetcher(private val context: Context,
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
try {
if (!MusicUtil.isArtistNameUnknown(model.artistName) && RetroUtil.isAllowedToDownloadMetadata(context) ) {
call = lastFMRestClient.apiService.getArtistInfo(model.artistName, null, if (model.skipOkHttpCache) "no-cache" else null)
call!!.enqueue(object : Callback<LastFmArtist> {
override fun onResponse(call: Call<LastFmArtist>, response: Response<LastFmArtist>) {
if (!MusicUtil.isArtistNameUnknown(model.artistName) && RetroUtil.isAllowedToDownloadMetadata(context)) {
call = deezerApiService.getArtistImage(model.artistName)
call?.enqueue(object : Callback<DeezerResponse> {
override fun onFailure(call: Call<DeezerResponse>, t: Throwable) {
callback.onLoadFailed(Exception(t))
}
override fun onResponse(call: Call<DeezerResponse>, response: Response<DeezerResponse>) {
if (isCancelled) {
callback.onDataReady(null)
return
}
val lastFmArtist = response.body()
if (lastFmArtist == null || lastFmArtist.artist == null || lastFmArtist.artist.image == null) {
try {
val deezerResponse: DeezerResponse? = response.body()
println(deezerResponse)
val url = deezerResponse?.data?.get(0)?.pictureXl
streamFetcher = OkHttpStreamFetcher(okHttp, GlideUrl(url))
streamFetcher?.loadData(priority, callback)
} catch (e: Exception) {
callback.onLoadFailed(Exception("No artist image url found"))
return
}
val url = LastFMUtil.getLargestArtistImageUrl(lastFmArtist.artist.image)
if (TextUtils.isEmpty(url) || TextUtils.isEmpty(url.trim { it <= ' ' })) {
callback.onLoadFailed(Exception("No artist image url found"))
return
}
streamFetcher = OkHttpStreamFetcher(okHttp, GlideUrl(url))
streamFetcher!!.loadData(priority, callback)
}
override fun onFailure(call: Call<LastFmArtist>, throwable: Throwable) {
callback.onLoadFailed(Exception(throwable))
}
})
}
} catch (e: Exception) {
callback.onLoadFailed(e)
@ -108,9 +103,7 @@ class ArtistImageFetcher(private val context: Context,
override fun cancel() {
isCancelled = true
if (call != null) {
call!!.cancel()
}
call?.cancel()
if (streamFetcher != null) {
streamFetcher!!.cancel()
}
@ -121,10 +114,12 @@ class ArtistImageFetcher(private val context: Context,
}
}
class ArtistImageLoader(private val context: Context, private val lastFMClient: LastFMRestClient, private val okhttp: OkHttpClient) : ModelLoader<ArtistImage, InputStream> {
class ArtistImageLoader(private val context: Context,
private val deezerApiService: DeezerApiService,
private val okhttp: OkHttpClient) : ModelLoader<ArtistImage, InputStream> {
override fun buildLoadData(model: ArtistImage, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream>? {
return ModelLoader.LoadData(ObjectKey(model.artistName), ArtistImageFetcher(context, lastFMClient, okhttp, model))
return ModelLoader.LoadData(ObjectKey(model.artistName), ArtistImageFetcher(context, deezerApiService, okhttp, model))
}
override fun handles(model: ArtistImage): Boolean {
@ -132,7 +127,7 @@ class ArtistImageLoader(private val context: Context, private val lastFMClient:
}
class Factory(private val context: Context) : ModelLoaderFactory<ArtistImage, InputStream> {
private val lastFMClient: LastFMRestClient = LastFMRestClient(LastFMRestClient.createDefaultOkHttpClientBuilder(context)
private val deezerApiService: DeezerApiService = DeezerApiService.invoke(DeezerApiService.createDefaultOkHttpClient(context)
.connectTimeout(TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
@ -145,7 +140,7 @@ class ArtistImageLoader(private val context: Context, private val lastFMClient:
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<ArtistImage, InputStream> {
return ArtistImageLoader(context, lastFMClient, okHttp)
return ArtistImageLoader(context, deezerApiService, okHttp)
}
override fun teardown() {}

View File

@ -14,9 +14,17 @@
package code.name.monkey.retromusic.model;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import code.name.monkey.retromusic.loaders.PlaylistSongsLoader;
import code.name.monkey.retromusic.util.MusicUtil;
/**
* @author Karim Abou Zeid (kabouzeid)
*/
@ -34,6 +42,19 @@ public class Playlist implements Parcelable {
this.name = "";
}
@NonNull
public String getInfoString(@NonNull Context context) {
int songCount = getSongs(context).size();
String songCountString = MusicUtil.getSongCountString(context, songCount);
return MusicUtil.buildInfoString(songCountString, "");
}
@NonNull
public ArrayList<Song> getSongs(Context context) {
// this default implementation covers static playlists
return PlaylistSongsLoader.INSTANCE.getPlaylistSongList(context, id).blockingFirst();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View File

@ -29,6 +29,10 @@ import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.tag.FieldKey;
@ -39,14 +43,12 @@ import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.helper.MusicPlayerRemote;
import code.name.monkey.retromusic.loaders.PlaylistLoader;
import code.name.monkey.retromusic.loaders.SongLoader;
import code.name.monkey.retromusic.model.Artist;
import code.name.monkey.retromusic.model.Genre;
import code.name.monkey.retromusic.model.Playlist;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics;
@ -83,6 +85,12 @@ public class MusicUtil {
}
}
@NonNull
public static String getSongCountString(@NonNull final Context context, int songCount) {
final String songString = songCount == 1 ? context.getResources().getString(R.string.song) : context.getResources().getString(R.string.songs);
return songCount + " " + songString;
}
@NonNull
public static String getSongInfoString(@NonNull Song song) {
return MusicUtil.buildInfoString(
@ -91,24 +99,6 @@ public class MusicUtil {
);
}
/**
* Build a concatenated string from the provided arguments
* The intended purpose is to show extra annotations
* to a music library item.
* Ex: for a given album --> buildInfoString(album.artist, album.songCount)
*/
public static String buildInfoString(@NonNull final String string1, @NonNull final String string2) {
if (TextUtils.isEmpty(string1)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string2) ? "" : string2;
}
if (TextUtils.isEmpty(string2)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string1) ? "" : string1;
}
return string1 + " • " + string2;
}
@NonNull
public static String getArtistInfoString(@NonNull final Context context,
@NonNull final Artist artist) {
@ -130,7 +120,7 @@ public class MusicUtil {
return songCount + " " + songString;
}
@NonNull
/*@NonNull
public static String getPlaylistInfoString(@NonNull final Context context,
@NonNull List<Song> songs) {
final int songCount = songs.size();
@ -143,6 +133,69 @@ public class MusicUtil {
}
return songCount + " " + songString + " • " + MusicUtil.getReadableDurationString(duration);
}*/
@NonNull
public static String getGenreInfoString(@NonNull final Context context, @NonNull final Genre genre) {
int songCount = genre.getSongCount();
return MusicUtil.getSongCountString(context, songCount);
}
@NonNull
public static String getPlaylistInfoString(@NonNull final Context context, @NonNull List<Song> songs) {
final long duration = getTotalDuration(context, songs);
return MusicUtil.buildInfoString(
MusicUtil.getSongCountString(context, songs.size()),
MusicUtil.getReadableDurationString(duration)
);
}
/**
* Build a concatenated string from the provided arguments
* The intended purpose is to show extra annotations
* to a music library item.
* Ex: for a given album --> buildInfoString(album.artist, album.songCount)
*/
@NonNull
public static String buildInfoString(@Nullable final String string1, @Nullable final String string2) {
// Skip empty strings
if (TextUtils.isEmpty(string1)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string2) ? "" : string2;
}
if (TextUtils.isEmpty(string2)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string1) ? "" : string1;
}
return string1 + " • " + string2;
}
/**
* Build a concatenated string from the provided arguments
* The intended purpose is to show extra annotations
* to a music library item.
* Ex: for a given album --> buildInfoString(album.artist, album.songCount)
*/
@NonNull
public static String buildInfoString(@Nullable final String string1, @Nullable final String string2, @NonNull final String string3) {
// Skip empty strings
if (TextUtils.isEmpty(string1)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string2) ? "" : string2;
}
if (TextUtils.isEmpty(string2)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string1) ? "" : string1;
}
if (TextUtils.isEmpty(string3)) {
//noinspection ConstantConditions
return TextUtils.isEmpty(string1) ? "" : string3;
}
return string1 + " • " + string2 + " • " + string3;
}
public static String getReadableDurationString(long songDurationMillis) {