mirror of
https://github.com/antebudimir/tempus.git
synced 2026-04-15 16:27:26 +00:00
Bug/instant mix issue (#330)
This commit is contained in:
commit
a82cf70433
18 changed files with 664 additions and 295 deletions
|
|
@ -11,7 +11,7 @@ android {
|
||||||
targetSdk 35
|
targetSdk 35
|
||||||
|
|
||||||
versionCode 11
|
versionCode 11
|
||||||
versionName '4.6.1'
|
versionName '4.6.2'
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
|
|
||||||
javaCompileOptions {
|
javaCompileOptions {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.repository;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
|
@ -11,6 +12,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
|
@ -204,37 +206,14 @@ public class AlbumRepository {
|
||||||
return albumInfo;
|
return albumInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void getInstantMix(AlbumID3 album, int count, MediaCallback callback) {
|
public void getInstantMix(AlbumID3 album, int count, final MediaCallback callback) {
|
||||||
Log.d("AlbumRepository", "Attempting getInstantMix for AlbumID: " + album.getId());
|
Log.d("AlbumRepository", "Starting Instant Mix for album: " + album.getName());
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
new SongRepository().getInstantMix(album.getId(), SeedType.ALBUM, count, songs -> {
|
||||||
.getBrowsingClient()
|
|
||||||
.getSimilarSongs2(album.getId(), count)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
List<Child> songs = new ArrayList<>();
|
|
||||||
|
|
||||||
if (response.isSuccessful()
|
|
||||||
&& response.body() != null
|
|
||||||
&& response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
|
||||||
|
|
||||||
List<Child> similarSongs = response.body().getSubsonicResponse().getSimilarSongs2().getSongs();
|
|
||||||
|
|
||||||
if (similarSongs == null) {
|
|
||||||
Log.w("AlbumRepository", "API successful but 'songs' list was NULL for AlbumID: " + album.getId());
|
|
||||||
} else {
|
|
||||||
songs.addAll(similarSongs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (songs != null && !songs.isEmpty()) {
|
||||||
callback.onLoadMedia(songs);
|
callback.onLoadMedia(songs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
callback.onLoadMedia(new ArrayList<>());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,7 +227,7 @@ public class AlbumRepository {
|
||||||
@Override
|
@Override
|
||||||
public void onLoadYear(int last) {
|
public void onLoadYear(int last) {
|
||||||
if (first != -1 && last != -1) {
|
if (first != -1 && last != -1) {
|
||||||
List<Integer> decadeList = new ArrayList();
|
List<Integer> decadeList = new ArrayList<>();
|
||||||
|
|
||||||
int startDecade = first - (first % 10);
|
int startDecade = first - (first % 10);
|
||||||
int lastDecade = last - (last % 10);
|
int lastDecade = last - (last % 10);
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,14 @@ import androidx.lifecycle.MutableLiveData;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.IndexID3;
|
import com.cappielloantonio.tempo.subsonic.models.IndexID3;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -149,7 +151,7 @@ public class ArtistRepository {
|
||||||
|
|
||||||
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
|
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
|
||||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||||
if(index != null && index.getArtists() != null) {
|
if(index.getArtists() != null) {
|
||||||
artists.addAll(index.getArtists());
|
artists.addAll(index.getArtists());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -287,28 +289,22 @@ public class ArtistRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<Child>> getInstantMix(ArtistID3 artist, int count) {
|
public MutableLiveData<List<Child>> getInstantMix(ArtistID3 artist, int count) {
|
||||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
// Delegate to the centralized SongRepository
|
||||||
|
return new SongRepository().getInstantMix(artist.getId(), SeedType.ARTIST, count);
|
||||||
App.getSubsonicClientInstance(false)
|
|
||||||
.getBrowsingClient()
|
|
||||||
.getSimilarSongs2(artist.getId(), count)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
|
||||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void getInstantMix(ArtistID3 artist, int count, final MediaCallback callback) {
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
// Delegate to the centralized SongRepository
|
||||||
|
new SongRepository().getInstantMix(artist.getId(), SeedType.ARTIST, count, songs -> {
|
||||||
|
if (songs != null && !songs.isEmpty()) {
|
||||||
|
callback.onLoadMedia(songs);
|
||||||
|
} else {
|
||||||
|
callback.onLoadMedia(Collections.emptyList());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return instantMix;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public MutableLiveData<List<Child>> getRandomSong(ArtistID3 artist, int count) {
|
public MutableLiveData<List<Child>> getRandomSong(ArtistID3 artist, int count) {
|
||||||
MutableLiveData<List<Child>> randomSongs = new MutableLiveData<>();
|
MutableLiveData<List<Child>> randomSongs = new MutableLiveData<>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,33 @@
|
||||||
package com.cappielloantonio.tempo.repository;
|
package com.cappielloantonio.tempo.repository;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.SubsonicResponse;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class SongRepository {
|
public class SongRepository {
|
||||||
|
|
||||||
private static final String TAG = "SongRepository";
|
private static final String TAG = "SongRepository";
|
||||||
|
public interface MediaCallbackInternal {
|
||||||
|
void onSongsAvailable(List<Child> songs);
|
||||||
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<Child>> getStarredSongs(boolean random, int size) {
|
public MutableLiveData<List<Child>> getStarredSongs(boolean random, int size) {
|
||||||
MutableLiveData<List<Child>> starredSongs = new MutableLiveData<>(Collections.emptyList());
|
MutableLiveData<List<Child>> starredSongs = new MutableLiveData<>(Collections.emptyList());
|
||||||
|
|
@ -42,219 +53,332 @@ public class SongRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return starredSongs;
|
return starredSongs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<Child>> getInstantMix(String id, int count) {
|
/**
|
||||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
* Used by ViewModels. Updates the LiveData list incrementally as songs are found.
|
||||||
|
*/
|
||||||
|
public MutableLiveData<List<Child>> getInstantMix(String id, SeedType type, int count) {
|
||||||
|
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
performSmartMix(id, type, count, songs -> {
|
||||||
.getBrowsingClient()
|
List<Child> current = instantMix.getValue();
|
||||||
.getSimilarSongs2(id, count)
|
if (current != null) {
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
for (Child s : songs) {
|
||||||
@Override
|
if (!current.contains(s)) current.add(s);
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
|
||||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
if (current.size() < count / 2) {
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
fillWithRandom(count - current.size(), remainder -> {
|
||||||
instantMix.setValue(null);
|
for (Child r : remainder) {
|
||||||
|
if (!current.contains(r)) current.add(r);
|
||||||
|
}
|
||||||
|
instantMix.postValue(current);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
instantMix.postValue(current);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return instantMix;
|
return instantMix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<Child>> getRandomSample(int number, Integer fromYear, Integer toYear) {
|
/**
|
||||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
* Overloaded method used by other Repositories
|
||||||
|
*/
|
||||||
App.getSubsonicClientInstance(false)
|
public void getInstantMix(String id, SeedType type, int count, MediaCallbackInternal callback) {
|
||||||
.getAlbumSongListClient()
|
new MediaCallbackAccumulator(callback, count).start(id, type);
|
||||||
.getRandomSongs(number, fromYear, toYear)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
List<Child> songs = new ArrayList<>();
|
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
|
||||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
randomSongsSample.setValue(songs);
|
private class MediaCallbackAccumulator {
|
||||||
|
private final MediaCallbackInternal originalCallback;
|
||||||
|
private final int targetCount;
|
||||||
|
private final List<Child> accumulatedSongs = new ArrayList<>();
|
||||||
|
private final Set<String> trackIds = new HashSet<>();
|
||||||
|
private boolean isComplete = false;
|
||||||
|
|
||||||
|
MediaCallbackAccumulator(MediaCallbackInternal callback, int count) {
|
||||||
|
this.originalCallback = callback;
|
||||||
|
this.targetCount = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(String id, SeedType type) {
|
||||||
|
performSmartMix(id, type, targetCount, this::onBatchReceived);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onBatchReceived(List<Child> batch) {
|
||||||
|
if (isComplete || batch == null || batch.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int added = 0;
|
||||||
|
for (Child song : batch) {
|
||||||
|
if (!trackIds.contains(song.getId()) && accumulatedSongs.size() < targetCount) {
|
||||||
|
trackIds.add(song.getId());
|
||||||
|
accumulatedSongs.add(song);
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accumulatedSongs.size() >= targetCount) {
|
||||||
|
originalCallback.onSongsAvailable(new ArrayList<>(accumulatedSongs));
|
||||||
|
isComplete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performSmartMix(final String id, final SeedType type, final int count, final MediaCallbackInternal callback) {
|
||||||
|
switch (type) {
|
||||||
|
case ARTIST:
|
||||||
|
fetchSimilarByArtist(id, count, callback);
|
||||||
|
break;
|
||||||
|
case ALBUM:
|
||||||
|
fetchAlbumSongsThenSimilar(id, count, callback);
|
||||||
|
break;
|
||||||
|
case TRACK:
|
||||||
|
fetchSingleTrackThenSimilar(id, count, callback);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchAlbumSongsThenSimilar(String albumId, int count, MediaCallbackInternal callback) {
|
||||||
|
App.getSubsonicClientInstance(false).getBrowsingClient().getAlbum(albumId).enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null &&
|
||||||
|
response.body().getSubsonicResponse().getAlbum() != null) {
|
||||||
|
List<Child> albumSongs = response.body().getSubsonicResponse().getAlbum().getSongs();
|
||||||
|
if (albumSongs != null && !albumSongs.isEmpty()) {
|
||||||
|
int fromAlbum = Math.min(count, albumSongs.size());
|
||||||
|
List<Child> limitedAlbumSongs = albumSongs.subList(0, fromAlbum);
|
||||||
|
callback.onSongsAvailable(new ArrayList<>(limitedAlbumSongs));
|
||||||
|
|
||||||
|
int remaining = count - fromAlbum;
|
||||||
|
if (remaining > 0 && albumSongs.get(0).getArtistId() != null) {
|
||||||
|
fetchSimilarByArtist(albumSongs.get(0).getArtistId(), remaining, callback);
|
||||||
|
} else if (remaining > 0) {
|
||||||
|
Log.d(TAG, "No artistId available, skipping similar artist fetch");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Album fetch failed or empty, calling fillWithRandom");
|
||||||
|
fillWithRandom(count, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
Log.d(TAG, "Album fetch failed: " + t.getMessage());
|
||||||
|
fillWithRandom(count, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchSimilarByArtist(String artistId, final int count, final MediaCallbackInternal callback) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getBrowsingClient()
|
||||||
|
.getSimilarSongs2(artistId, count)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
List<Child> similar = extractSongs(response, "similarSongs2");
|
||||||
|
if (!similar.isEmpty()) {
|
||||||
|
List<Child> limitedSimilar = similar.subList(0, Math.min(count, similar.size()));
|
||||||
|
callback.onSongsAvailable(limitedSimilar);
|
||||||
|
} else {
|
||||||
|
fillWithRandom(count, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
fillWithRandom(count, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchSingleTrackThenSimilar(String trackId, int count, MediaCallbackInternal callback) {
|
||||||
|
App.getSubsonicClientInstance(false).getBrowsingClient().getSong(trackId).enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
Child song = response.body().getSubsonicResponse().getSong();
|
||||||
|
if (song != null) {
|
||||||
|
callback.onSongsAvailable(Collections.singletonList(song));
|
||||||
|
int remaining = count - 1;
|
||||||
|
if (remaining > 0) {
|
||||||
|
fetchSimilarOnly(trackId, remaining, callback);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fillWithRandom(count, callback);
|
||||||
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
fillWithRandom(count, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchSimilarOnly(String id, int count, MediaCallbackInternal callback) {
|
||||||
|
App.getSubsonicClientInstance(false).getBrowsingClient().getSimilarSongs(id, count).enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
List<Child> songs = extractSongs(response, "similarSongs");
|
||||||
|
if (!songs.isEmpty()) {
|
||||||
|
List<Child> limitedSongs = songs.subList(0, Math.min(count, songs.size()));
|
||||||
|
callback.onSongsAvailable(limitedSongs);
|
||||||
|
} else {
|
||||||
|
fillWithRandom(count, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
fillWithRandom(count, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void fillWithRandom(int target, final MediaCallbackInternal callback) {
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getAlbumSongListClient()
|
||||||
|
.getRandomSongs(target, null, null)
|
||||||
|
.enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
List<Child> random = extractSongs(response, "randomSongs");
|
||||||
|
if (!random.isEmpty()) {
|
||||||
|
List<Child> limitedRandom = random.subList(0, Math.min(target, random.size()));
|
||||||
|
callback.onSongsAvailable(limitedRandom);
|
||||||
|
} else {
|
||||||
|
callback.onSongsAvailable(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
callback.onSongsAvailable(new ArrayList<>());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Child> extractSongs(Response<ApiResponse> response, String type) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
SubsonicResponse res = response.body().getSubsonicResponse();
|
||||||
|
List<Child> list = null;
|
||||||
|
if (type.equals("similarSongs") && res.getSimilarSongs() != null) {
|
||||||
|
list = res.getSimilarSongs().getSongs();
|
||||||
|
} else if (type.equals("similarSongs2") && res.getSimilarSongs2() != null) {
|
||||||
|
list = res.getSimilarSongs2().getSongs();
|
||||||
|
} else if (type.equals("randomSongs") && res.getRandomSongs() != null) {
|
||||||
|
list = res.getRandomSongs().getSongs();
|
||||||
|
}
|
||||||
|
return (list != null) ? list : new ArrayList<>();
|
||||||
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableLiveData<List<Child>> getRandomSample(int number, Integer fromYear, Integer toYear) {
|
||||||
|
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||||
|
App.getSubsonicClientInstance(false).getAlbumSongListClient().getRandomSongs(number, fromYear, toYear).enqueue(new Callback<ApiResponse>() {
|
||||||
|
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
|
List<Child> songs = new ArrayList<>();
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||||
|
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getRandomSongs().getSongs()));
|
||||||
|
}
|
||||||
|
randomSongsSample.setValue(songs);
|
||||||
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
|
});
|
||||||
return randomSongsSample;
|
return randomSongsSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<Child>> getRandomSampleWithGenre(int number, Integer fromYear, Integer toYear, String genre) {
|
public MutableLiveData<List<Child>> getRandomSampleWithGenre(int number, Integer fromYear, Integer toYear, String genre) {
|
||||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||||
|
App.getSubsonicClientInstance(false).getAlbumSongListClient().getRandomSongs(number, fromYear, toYear, genre).enqueue(new Callback<ApiResponse>() {
|
||||||
App.getSubsonicClientInstance(false)
|
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
.getAlbumSongListClient()
|
|
||||||
.getRandomSongs(number, fromYear, toYear, genre)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
List<Child> songs = new ArrayList<>();
|
List<Child> songs = new ArrayList<>();
|
||||||
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getRandomSongs().getSongs()));
|
||||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
randomSongsSample.setValue(songs);
|
randomSongsSample.setValue(songs);
|
||||||
}
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return randomSongsSample;
|
return randomSongsSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void scrobble(String id, boolean submission) {
|
public void scrobble(String id, boolean submission) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false).getMediaAnnotationClient().scrobble(id, submission).enqueue(new Callback<ApiResponse>() {
|
||||||
.getMediaAnnotationClient()
|
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {}
|
||||||
.scrobble(id, submission)
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRating(String id, int rating) {
|
public void setRating(String id, int rating) {
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false).getMediaAnnotationClient().setRating(id, rating).enqueue(new Callback<ApiResponse>() {
|
||||||
.getMediaAnnotationClient()
|
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {}
|
||||||
.setRating(id, rating)
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
||||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||||
|
App.getSubsonicClientInstance(false).getAlbumSongListClient().getSongsByGenre(id, 100, 100 * page).enqueue(new Callback<ApiResponse>() {
|
||||||
App.getSubsonicClientInstance(false)
|
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
.getAlbumSongListClient()
|
|
||||||
.getSongsByGenre(id, 100, 100 * page)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||||
songsByGenre.setValue(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
songsByGenre.setValue(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return songsByGenre;
|
return songsByGenre;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<List<Child>> getSongsByGenres(ArrayList<String> genresId) {
|
public MutableLiveData<List<Child>> getSongsByGenres(ArrayList<String> genresId) {
|
||||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||||
|
for (String id : genresId) {
|
||||||
for (String id : genresId)
|
App.getSubsonicClientInstance(false).getAlbumSongListClient().getSongsByGenre(id, 500, 0).enqueue(new Callback<ApiResponse>() {
|
||||||
App.getSubsonicClientInstance(false)
|
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
.getAlbumSongListClient()
|
|
||||||
.getSongsByGenre(id, 500, 0)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
List<Child> songs = new ArrayList<>();
|
List<Child> songs = new ArrayList<>();
|
||||||
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||||
songs.addAll(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
songs.addAll(Objects.requireNonNull(response.body().getSubsonicResponse().getSongsByGenre().getSongs()));
|
||||||
}
|
}
|
||||||
|
|
||||||
songsByGenre.setValue(songs);
|
songsByGenre.setValue(songs);
|
||||||
}
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return songsByGenre;
|
return songsByGenre;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<Child> getSong(String id) {
|
public MutableLiveData<Child> getSong(String id) {
|
||||||
MutableLiveData<Child> song = new MutableLiveData<>();
|
MutableLiveData<Child> song = new MutableLiveData<>();
|
||||||
|
App.getSubsonicClientInstance(false).getBrowsingClient().getSong(id).enqueue(new Callback<ApiResponse>() {
|
||||||
App.getSubsonicClientInstance(false)
|
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
.getBrowsingClient()
|
|
||||||
.getSong(id)
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
if (response.isSuccessful() && response.body() != null) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
song.setValue(response.body().getSubsonicResponse().getSong());
|
song.setValue(response.body().getSubsonicResponse().getSong());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return song;
|
return song;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MutableLiveData<String> getSongLyrics(Child song) {
|
public MutableLiveData<String> getSongLyrics(Child song) {
|
||||||
MutableLiveData<String> lyrics = new MutableLiveData<>(null);
|
MutableLiveData<String> lyrics = new MutableLiveData<>(null);
|
||||||
|
App.getSubsonicClientInstance(false).getMediaRetrievalClient().getLyrics(song.getArtist(), song.getTitle()).enqueue(new Callback<ApiResponse>() {
|
||||||
App.getSubsonicClientInstance(false)
|
@Override public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
.getMediaRetrievalClient()
|
|
||||||
.getLyrics(song.getArtist(), song.getTitle())
|
|
||||||
.enqueue(new Callback<ApiResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getLyrics() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getLyrics() != null) {
|
||||||
lyrics.setValue(response.body().getSubsonicResponse().getLyrics().getValue());
|
lyrics.setValue(response.body().getSubsonicResponse().getLyrics().getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {}
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return lyrics;
|
return lyrics;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -26,6 +26,7 @@ import com.cappielloantonio.tempo.repository.SongRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||||
|
|
@ -183,11 +184,13 @@ public class MediaManager {
|
||||||
@OptIn(markerClass = UnstableApi.class)
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex) {
|
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex) {
|
||||||
if (mediaBrowserListenableFuture != null) {
|
if (mediaBrowserListenableFuture != null) {
|
||||||
|
|
||||||
mediaBrowserListenableFuture.addListener(() -> {
|
mediaBrowserListenableFuture.addListener(() -> {
|
||||||
try {
|
try {
|
||||||
if (mediaBrowserListenableFuture.isDone()) {
|
if (mediaBrowserListenableFuture.isDone()) {
|
||||||
final MediaBrowser browser = mediaBrowserListenableFuture.get();
|
final MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||||
final List<MediaItem> items = MappingUtil.mapMediaItems(media);
|
final List<MediaItem> items = MappingUtil.mapMediaItems(media);
|
||||||
|
|
||||||
new Handler(Looper.getMainLooper()).post(() -> {
|
new Handler(Looper.getMainLooper()).post(() -> {
|
||||||
justStarted.set(true);
|
justStarted.set(true);
|
||||||
browser.setMediaItems(items, startIndex, 0);
|
browser.setMediaItems(items, startIndex, 0);
|
||||||
|
|
@ -196,28 +199,31 @@ public class MediaManager {
|
||||||
Player.Listener timelineListener = new Player.Listener() {
|
Player.Listener timelineListener = new Player.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||||
|
|
||||||
int itemCount = browser.getMediaItemCount();
|
int itemCount = browser.getMediaItemCount();
|
||||||
if (itemCount > 0 && startIndex >= 0 && startIndex < itemCount) {
|
if (itemCount > 0 && startIndex >= 0 && startIndex < itemCount) {
|
||||||
browser.seekTo(startIndex, 0);
|
browser.seekTo(startIndex, 0);
|
||||||
browser.play();
|
browser.play();
|
||||||
browser.removeListener(this);
|
browser.removeListener(this);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Cannot start playback: itemCount=" + itemCount + ", startIndex=" + startIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
browser.addListener(timelineListener);
|
browser.addListener(timelineListener);
|
||||||
});
|
});
|
||||||
|
|
||||||
backgroundExecutor.execute(() -> {
|
backgroundExecutor.execute(() -> {
|
||||||
|
Log.d(TAG, "Background: enqueuing to database");
|
||||||
enqueueDatabase(media, true, 0);
|
enqueueDatabase(media, true, 0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (ExecutionException | InterruptedException e) {
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
Log.e(TAG, "Error executing startQueue logic: " + e.getMessage(), e);
|
Log.e(TAG, "Error in startQueue: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}, MoreExecutors.directExecutor());
|
}, MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, Child media) {
|
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, Child media) {
|
||||||
|
|
@ -442,7 +448,7 @@ public class MediaManager {
|
||||||
if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) {
|
if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) {
|
||||||
Preferences.setLastInstantMix();
|
Preferences.setLastInstantMix();
|
||||||
|
|
||||||
LiveData<List<Child>> instantMix = getSongRepository().getInstantMix(mediaItem.mediaId, 10);
|
LiveData<List<Child>> instantMix = getSongRepository().getInstantMix(mediaItem.mediaId, SeedType.TRACK, 10);
|
||||||
instantMix.observeForever(new Observer<List<Child>>() {
|
instantMix.observeForever(new Observer<List<Child>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(List<Child> media) {
|
public void onChanged(List<Child> media) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.cappielloantonio.tempo.subsonic.models
|
package com.cappielloantonio.tempo.subsonic.models
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
class SimilarSongs {
|
class SimilarSongs {
|
||||||
|
@SerializedName("song")
|
||||||
var songs: List<Child>? = null
|
var songs: List<Child>? = null
|
||||||
}
|
}
|
||||||
|
|
@ -9,12 +9,12 @@ import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
import android.widget.ToggleButton;
|
import android.widget.ToggleButton;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.MediaBrowser;
|
import androidx.media3.session.MediaBrowser;
|
||||||
|
|
@ -32,6 +32,7 @@ import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
|
||||||
|
|
@ -203,22 +204,27 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private void initPlayButtons() {
|
|
||||||
bind.artistPageShuffleButton.setOnClickListener(v -> artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
|
|
||||||
if (!songs.isEmpty()) {
|
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
|
||||||
activity.setBottomSheetInPeek(true);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
bind.artistPageRadioButton.setOnClickListener(v -> artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
|
private void initPlayButtons() {
|
||||||
|
bind.artistPageShuffleButton.setOnClickListener(v -> artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(List<Child> songs) {
|
||||||
if (songs != null && !songs.isEmpty()) {
|
if (songs != null && !songs.isEmpty()) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
} else {
|
artistPageViewModel.getArtistShuffleList().removeObserver(this);
|
||||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show();
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
bind.artistPageRadioButton.setOnClickListener(v -> artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), new Observer<List<Child>>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(List<Child> songs) {
|
||||||
|
if (songs != null && !songs.isEmpty()) {
|
||||||
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||||
|
activity.setBottomSheetInPeek(true);
|
||||||
|
artistPageViewModel.getArtistInstantMix().removeObserver(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -1253,19 +1255,24 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMediaClick(Bundle bundle) {
|
public void onMediaClick(Bundle bundle) {
|
||||||
if (bundle.containsKey(Constants.MEDIA_MIX)) {
|
if (bundle.containsKey(Constants.MEDIA_MIX)) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelable(Constants.TRACK_OBJECT));
|
Child track = bundle.getParcelable(Constants.TRACK_OBJECT);
|
||||||
activity.setBottomSheetInPeek(true);
|
activity.setBottomSheetInPeek(true);
|
||||||
|
|
||||||
if (mediaBrowserListenableFuture != null) {
|
if (mediaBrowserListenableFuture != null) {
|
||||||
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
|
final boolean[] playbackStarted = {false};
|
||||||
MusicUtil.ratingFilter(songs);
|
|
||||||
|
|
||||||
if (songs != null && !songs.isEmpty()) {
|
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), track)
|
||||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
|
.observe(getViewLifecycleOwner(), songs -> {
|
||||||
}
|
if (playbackStarted[0] || songs == null || songs.isEmpty()) return;
|
||||||
|
|
||||||
|
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||||
|
if (playbackStarted[0]) return;
|
||||||
|
|
||||||
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||||
|
playbackStarted[0] = true;
|
||||||
|
}, 300);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (bundle.containsKey(Constants.MEDIA_CHRONOLOGY)) {
|
} else if (bundle.containsKey(Constants.MEDIA_CHRONOLOGY)) {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import android.content.ClipboardManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -43,7 +44,6 @@ import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
||||||
import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel;
|
import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel;
|
||||||
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -61,7 +61,11 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
private List<Child> currentAlbumTracks = Collections.emptyList();
|
private List<Child> currentAlbumTracks = Collections.emptyList();
|
||||||
private List<MediaItem> currentAlbumMediaItems = Collections.emptyList();
|
private List<MediaItem> currentAlbumMediaItems = Collections.emptyList();
|
||||||
|
|
||||||
|
private boolean playbackStarted = false;
|
||||||
|
private boolean dismissalScheduled = false;
|
||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
private static final String TAG = "AlbumBottomSheetDialog";
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -114,33 +118,74 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
|
|
||||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||||
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
||||||
favoriteToggle.setOnClickListener(v -> {
|
favoriteToggle.setOnClickListener(v -> albumBottomSheetViewModel.setFavorite(requireContext()));
|
||||||
albumBottomSheetViewModel.setFavorite(requireContext());
|
|
||||||
});
|
|
||||||
|
|
||||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||||
playRadio.setOnClickListener(v -> {
|
playRadio.setOnClickListener(v -> {
|
||||||
AlbumRepository albumRepository = new AlbumRepository();
|
playbackStarted = false;
|
||||||
albumRepository.getInstantMix(album, 20, new MediaCallback() {
|
dismissalScheduled = false;
|
||||||
|
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||||
|
final Runnable failsafeTimeout = () -> {
|
||||||
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
Log.w(TAG, "No response received within 3 seconds");
|
||||||
|
if (isAdded() && getActivity() != null) {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
dismissBottomSheet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
view.postDelayed(failsafeTimeout, 3000);
|
||||||
|
|
||||||
|
new AlbumRepository().getInstantMix(album, 20, new MediaCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onError(Exception exception) {
|
public void onError(Exception exception) {
|
||||||
exception.printStackTrace();
|
view.removeCallbacks(failsafeTimeout);
|
||||||
|
Log.e(TAG, "Error: " + exception.getMessage());
|
||||||
|
if (isAdded() && getActivity() != null) {
|
||||||
|
String message = isOffline(exception) ?
|
||||||
|
"You're offline" : "Network error";
|
||||||
|
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadMedia(List<?> media) {
|
public void onLoadMedia(List<?> media) {
|
||||||
|
view.removeCallbacks(failsafeTimeout);
|
||||||
|
if (!isAdded() || getActivity() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MusicUtil.ratingFilter((ArrayList<Child>) media);
|
MusicUtil.ratingFilter((ArrayList<Child>) media);
|
||||||
|
|
||||||
if (!media.isEmpty()) {
|
if (!media.isEmpty()) {
|
||||||
|
boolean isFirstBatch = !playbackStarted;
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
|
||||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
playbackStarted = true;
|
||||||
}
|
|
||||||
|
|
||||||
dismissBottomSheet();
|
if (getActivity() instanceof MainActivity) {
|
||||||
|
((MainActivity) getActivity()).setBottomSheetInPeek(true);
|
||||||
|
}
|
||||||
|
if (isFirstBatch && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
TextView playRandom = view.findViewById(R.id.play_random_text_view);
|
TextView playRandom = view.findViewById(R.id.play_random_text_view);
|
||||||
playRandom.setOnClickListener(v -> {
|
playRandom.setOnClickListener(v -> {
|
||||||
AlbumRepository albumRepository = new AlbumRepository();
|
AlbumRepository albumRepository = new AlbumRepository();
|
||||||
|
|
@ -186,8 +231,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
});
|
});
|
||||||
|
|
||||||
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
|
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
|
||||||
addToPlaylist.setOnClickListener(v -> {
|
addToPlaylist.setOnClickListener(v -> albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
|
||||||
|
|
||||||
|
|
@ -196,8 +240,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||||
|
|
||||||
dismissBottomSheet();
|
dismissBottomSheet();
|
||||||
});
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
removeAllTextView = view.findViewById(R.id.remove_all_text_view);
|
removeAllTextView = view.findViewById(R.id.remove_all_text_view);
|
||||||
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
|
||||||
|
|
@ -291,4 +334,31 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
private void refreshShares() {
|
private void refreshShares() {
|
||||||
homeViewModel.refreshShares(requireActivity());
|
homeViewModel.refreshShares(requireActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void scheduleDelayedDismissal(View view) {
|
||||||
|
if (dismissalScheduled) return;
|
||||||
|
dismissalScheduled = true;
|
||||||
|
|
||||||
|
view.postDelayed(() -> {
|
||||||
|
try {
|
||||||
|
if (mediaBrowserListenableFuture.isDone()) {
|
||||||
|
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||||
|
if (browser != null && browser.isPlaying()) {
|
||||||
|
dismissBottomSheet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error checking playback: " + e.getMessage());
|
||||||
|
}
|
||||||
|
view.postDelayed(() -> dismissBottomSheet(), 200);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOffline(Exception exception) {
|
||||||
|
return exception != null && exception.getMessage() != null &&
|
||||||
|
(exception.getMessage().contains("Network") ||
|
||||||
|
exception.getMessage().contains("timeout") ||
|
||||||
|
exception.getMessage().contains("offline"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
|
||||||
|
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -18,10 +19,12 @@ import androidx.media3.session.SessionToken;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
|
|
@ -29,6 +32,9 @@ import com.cappielloantonio.tempo.viewmodel.ArtistBottomSheetViewModel;
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
||||||
private static final String TAG = "AlbumBottomSheetDialog";
|
private static final String TAG = "AlbumBottomSheetDialog";
|
||||||
|
|
@ -38,6 +44,9 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
|
private boolean playbackStarted = false;
|
||||||
|
private boolean dismissalScheduled = false;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
|
@ -86,20 +95,69 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||||
|
|
||||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||||
playRadio.setOnClickListener(v -> {
|
playRadio.setOnClickListener(v -> {
|
||||||
ArtistRepository artistRepository = new ArtistRepository();
|
Log.d(TAG, "Artist instant mix clicked");
|
||||||
|
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||||
|
playbackStarted = false;
|
||||||
|
dismissalScheduled = false;
|
||||||
|
final Runnable failsafeTimeout = () -> {
|
||||||
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
Log.w(TAG, "No response received within 3 seconds");
|
||||||
|
if (isAdded() && getActivity() != null) {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
dismissBottomSheet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
view.postDelayed(failsafeTimeout, 3000);
|
||||||
|
|
||||||
artistRepository.getInstantMix(artist, 20).observe(getViewLifecycleOwner(), songs -> {
|
new ArtistRepository().getInstantMix(artist, 20, new MediaCallback() {
|
||||||
// navidrome may return null for this
|
@Override
|
||||||
if (songs == null)
|
public void onError(Exception exception) {
|
||||||
return;
|
view.removeCallbacks(failsafeTimeout);
|
||||||
MusicUtil.ratingFilter(songs);
|
Log.e(TAG, "Error: " + exception.getMessage());
|
||||||
|
if (isAdded() && getActivity() != null) {
|
||||||
if (!songs.isEmpty()) {
|
String message = isOffline(exception) ?
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
"You're offline" : "Network error";
|
||||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissBottomSheet();
|
@Override
|
||||||
|
public void onLoadMedia(List<?> media) {
|
||||||
|
view.removeCallbacks(failsafeTimeout);
|
||||||
|
if (!isAdded() || getActivity() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "Received " + media.size() + " songs for artist");
|
||||||
|
|
||||||
|
MusicUtil.ratingFilter((ArrayList<Child>) media);
|
||||||
|
|
||||||
|
if (!media.isEmpty()) {
|
||||||
|
boolean isFirstBatch = !playbackStarted;
|
||||||
|
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
|
||||||
|
playbackStarted = true;
|
||||||
|
|
||||||
|
if (getActivity() instanceof MainActivity) {
|
||||||
|
((MainActivity) getActivity()).setBottomSheetInPeek(true);
|
||||||
|
}
|
||||||
|
if (isFirstBatch && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -108,16 +166,10 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||||
ArtistRepository artistRepository = new ArtistRepository();
|
ArtistRepository artistRepository = new ArtistRepository();
|
||||||
artistRepository.getRandomSong(artist, 50).observe(getViewLifecycleOwner(), songs -> {
|
artistRepository.getRandomSong(artist, 50).observe(getViewLifecycleOwner(), songs -> {
|
||||||
MusicUtil.ratingFilter(songs);
|
MusicUtil.ratingFilter(songs);
|
||||||
|
|
||||||
if (!songs.isEmpty()) {
|
if (!songs.isEmpty()) {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
|
||||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
||||||
|
|
||||||
dismissBottomSheet();
|
|
||||||
} else {
|
|
||||||
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissBottomSheet();
|
dismissBottomSheet();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -139,4 +191,31 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||||
private void releaseMediaBrowser() {
|
private void releaseMediaBrowser() {
|
||||||
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void scheduleDelayedDismissal(View view) {
|
||||||
|
if (dismissalScheduled) return;
|
||||||
|
dismissalScheduled = true;
|
||||||
|
|
||||||
|
view.postDelayed(() -> {
|
||||||
|
try {
|
||||||
|
if (mediaBrowserListenableFuture.isDone()) {
|
||||||
|
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||||
|
if (browser != null && browser.isPlaying()) {
|
||||||
|
dismissBottomSheet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error checking playback: " + e.getMessage());
|
||||||
|
}
|
||||||
|
view.postDelayed(() -> dismissBottomSheet(), 200);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOffline(Exception exception) {
|
||||||
|
return exception != null && exception.getMessage() != null &&
|
||||||
|
(exception.getMessage().contains("Network") ||
|
||||||
|
exception.getMessage().contains("timeout") ||
|
||||||
|
exception.getMessage().contains("offline"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,6 +5,9 @@ import android.content.ClipboardManager;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -23,6 +26,7 @@ import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
|
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
|
|
@ -50,6 +54,7 @@ import com.cappielloantonio.tempo.util.ExternalAudioWriter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
|
||||||
|
|
@ -67,7 +72,11 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
private AssetLinkUtil.AssetLink currentAlbumLink;
|
private AssetLinkUtil.AssetLink currentAlbumLink;
|
||||||
private AssetLinkUtil.AssetLink currentArtistLink;
|
private AssetLinkUtil.AssetLink currentArtistLink;
|
||||||
|
|
||||||
|
private boolean playbackStarted = false;
|
||||||
|
private boolean dismissalScheduled = false;
|
||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
private static final String TAG = "SongBottomSheetDialog";
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -143,22 +152,68 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
|
|
||||||
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
|
||||||
playRadio.setOnClickListener(v -> {
|
playRadio.setOnClickListener(v -> {
|
||||||
MediaManager.startQueue(mediaBrowserListenableFuture, song);
|
playbackStarted = false;
|
||||||
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
|
dismissalScheduled = false;
|
||||||
|
Toast.makeText(requireContext(), R.string.bottom_sheet_generating_instant_mix, Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
songBottomSheetViewModel.getInstantMix(getViewLifecycleOwner(), song).observe(getViewLifecycleOwner(), songs -> {
|
final Runnable failsafeTimeout = () -> {
|
||||||
MusicUtil.ratingFilter(songs);
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
Log.w(TAG, "No response received within 3 seconds");
|
||||||
if (songs == null) {
|
if (isAdded() && getActivity() != null) {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
dismissBottomSheet();
|
dismissBottomSheet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
view.postDelayed(failsafeTimeout, 3000);
|
||||||
|
songBottomSheetViewModel.getInstantMix(song, 20, new MediaCallback() {
|
||||||
|
@Override
|
||||||
|
public void onError(Exception exception) {
|
||||||
|
view.removeCallbacks(failsafeTimeout);
|
||||||
|
Log.e(TAG, "Error: " + exception.getMessage());
|
||||||
|
if (isAdded() && getActivity() != null) {
|
||||||
|
String message = isOffline(exception) ?
|
||||||
|
"You're offline" : "Network error";
|
||||||
|
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadMedia(List<?> media) {
|
||||||
|
view.removeCallbacks(failsafeTimeout);
|
||||||
|
if (!isAdded() || getActivity() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!songs.isEmpty()) {
|
MusicUtil.ratingFilter((ArrayList<Child>) media);
|
||||||
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
|
|
||||||
dismissBottomSheet();
|
if (!media.isEmpty()) {
|
||||||
|
boolean isFirstBatch = !playbackStarted;
|
||||||
|
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
|
||||||
|
playbackStarted = true;
|
||||||
|
|
||||||
|
if (getActivity() instanceof MainActivity) {
|
||||||
|
((MainActivity) getActivity()).setBottomSheetInPeek(true);
|
||||||
|
}
|
||||||
|
if (isFirstBatch && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(),
|
||||||
|
R.string.bottom_sheet_problem_generating_instant_mix,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
if (!playbackStarted && !dismissalScheduled) {
|
||||||
|
scheduleDelayedDismissal(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
TextView playNext = view.findViewById(R.id.play_next_text_view);
|
TextView playNext = view.findViewById(R.id.play_next_text_view);
|
||||||
|
|
@ -397,4 +452,31 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||||
private void refreshShares() {
|
private void refreshShares() {
|
||||||
homeViewModel.refreshShares(requireActivity());
|
homeViewModel.refreshShares(requireActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void scheduleDelayedDismissal(View view) {
|
||||||
|
if (dismissalScheduled) return;
|
||||||
|
dismissalScheduled = true;
|
||||||
|
|
||||||
|
view.postDelayed(() -> {
|
||||||
|
try {
|
||||||
|
if (mediaBrowserListenableFuture.isDone()) {
|
||||||
|
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||||
|
if (browser != null && browser.isPlaying()) {
|
||||||
|
dismissBottomSheet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error checking playback: " + e.getMessage());
|
||||||
|
}
|
||||||
|
view.postDelayed(() -> dismissBottomSheet(), 200);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOffline(Exception exception) {
|
||||||
|
return exception != null && exception.getMessage() != null &&
|
||||||
|
(exception.getMessage().contains("Network") ||
|
||||||
|
exception.getMessage().contains("timeout") ||
|
||||||
|
exception.getMessage().contains("offline"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,4 +133,7 @@ object Constants {
|
||||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF = "android.media3.session.demo.REPEAT_OFF"
|
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF = "android.media3.session.demo.REPEAT_OFF"
|
||||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE = "android.media3.session.demo.REPEAT_ONE"
|
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE = "android.media3.session.demo.REPEAT_ONE"
|
||||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL = "android.media3.session.demo.REPEAT_ALL"
|
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL = "android.media3.session.demo.REPEAT_ALL"
|
||||||
|
enum class SeedType {
|
||||||
|
ARTIST, ALBUM, TRACK
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,10 +4,12 @@ import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.lifecycle.AndroidViewModel;
|
import androidx.lifecycle.AndroidViewModel;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
|
|
@ -33,7 +35,6 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||||
private final ArtistRepository artistRepository;
|
private final ArtistRepository artistRepository;
|
||||||
private final FavoriteRepository favoriteRepository;
|
private final FavoriteRepository favoriteRepository;
|
||||||
private final SharingRepository sharingRepository;
|
private final SharingRepository sharingRepository;
|
||||||
|
|
||||||
private AlbumID3 album;
|
private AlbumID3 album;
|
||||||
|
|
||||||
public AlbumBottomSheetViewModel(@NonNull Application application) {
|
public AlbumBottomSheetViewModel(@NonNull Application application) {
|
||||||
|
|
@ -116,6 +117,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
|
||||||
MutableLiveData<List<Child>> tracksLiveData = albumRepository.getAlbumTracks(album.getId());
|
MutableLiveData<List<Child>> tracksLiveData = albumRepository.getAlbumTracks(album.getId());
|
||||||
|
|
||||||
tracksLiveData.observeForever(new Observer<List<Child>>() {
|
tracksLiveData.observeForever(new Observer<List<Child>>() {
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
@Override
|
@Override
|
||||||
public void onChanged(List<Child> songs) {
|
public void onChanged(List<Child> songs) {
|
||||||
if (songs != null && !songs.isEmpty()) {
|
if (songs != null && !songs.isEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,6 @@ public class ArtistPageViewModel extends AndroidViewModel {
|
||||||
MappingUtil.mapDownloads(songs),
|
MappingUtil.mapDownloads(songs),
|
||||||
songs.stream().map(Download::new).collect(Collectors.toList())
|
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.google.common.reflect.TypeToken;
|
import com.google.common.reflect.TypeToken;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
|
@ -34,7 +35,6 @@ import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class HomeViewModel extends AndroidViewModel {
|
public class HomeViewModel extends AndroidViewModel {
|
||||||
private static final String TAG = "HomeViewModel";
|
private static final String TAG = "HomeViewModel";
|
||||||
|
|
@ -223,7 +223,7 @@ public class HomeViewModel extends AndroidViewModel {
|
||||||
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
||||||
mediaInstantMix.setValue(Collections.emptyList());
|
mediaInstantMix.setValue(Collections.emptyList());
|
||||||
|
|
||||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, mediaInstantMix::postValue);
|
songRepository.getInstantMix(media.getId(), SeedType.TRACK, 20).observe(owner, mediaInstantMix::postValue);
|
||||||
|
|
||||||
return mediaInstantMix;
|
return mediaInstantMix;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.MediaBrowser;
|
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
|
|
@ -278,7 +277,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||||
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
||||||
instantMix.setValue(Collections.emptyList());
|
instantMix.setValue(Collections.emptyList());
|
||||||
|
|
||||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
|
songRepository.getInstantMix(media.getId(), Constants.SeedType.TRACK, 20).observe(owner, instantMix::postValue);
|
||||||
|
|
||||||
return instantMix;
|
return instantMix;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||||
|
|
@ -21,6 +22,7 @@ import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||||
|
import com.cappielloantonio.tempo.util.Constants.SeedType;
|
||||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||||
import com.cappielloantonio.tempo.util.NetworkUtil;
|
import com.cappielloantonio.tempo.util.NetworkUtil;
|
||||||
|
|
@ -128,11 +130,22 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
||||||
public LiveData<List<Child>> getInstantMix(LifecycleOwner owner, Child media) {
|
public LiveData<List<Child>> getInstantMix(LifecycleOwner owner, Child media) {
|
||||||
instantMix.setValue(Collections.emptyList());
|
instantMix.setValue(Collections.emptyList());
|
||||||
|
|
||||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
|
songRepository.getInstantMix(media.getId(), SeedType.TRACK, 20).observe(owner, instantMix::postValue);
|
||||||
|
|
||||||
return instantMix;
|
return instantMix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void getInstantMix(Child media, int count, MediaCallback callback) {
|
||||||
|
|
||||||
|
songRepository.getInstantMix(media.getId(), SeedType.TRACK, count, songs -> {
|
||||||
|
if (songs != null && !songs.isEmpty()) {
|
||||||
|
callback.onLoadMedia(songs);
|
||||||
|
} else {
|
||||||
|
callback.onLoadMedia(Collections.emptyList());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public MutableLiveData<Share> shareTrack() {
|
public MutableLiveData<Share> shareTrack() {
|
||||||
return sharingRepository.createShare(song.getId(), song.getTitle(), null);
|
return sharingRepository.createShare(song.getId(), song.getTitle(), null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@
|
||||||
<string name="battery_optimization_negative_button">Ignore</string>
|
<string name="battery_optimization_negative_button">Ignore</string>
|
||||||
<string name="battery_optimization_neutral_button">Don\'t ask again</string>
|
<string name="battery_optimization_neutral_button">Don\'t ask again</string>
|
||||||
<string name="battery_optimization_positive_button">Disable</string>
|
<string name="battery_optimization_positive_button">Disable</string>
|
||||||
|
<string name="bottom_sheet_generating_instant_mix">Generating instant mix...</string>
|
||||||
|
<string name="bottom_sheet_problem_generating_instant_mix">Could not retrieve tracks from subsonic server.</string>
|
||||||
<string name="connection_alert_dialog_negative_button">Cancel</string>
|
<string name="connection_alert_dialog_negative_button">Cancel</string>
|
||||||
<string name="connection_alert_dialog_neutral_button">Enable data saver</string>
|
<string name="connection_alert_dialog_neutral_button">Enable data saver</string>
|
||||||
<string name="connection_alert_dialog_positive_button">OK</string>
|
<string name="connection_alert_dialog_positive_button">OK</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue