mirror of
https://github.com/antebudimir/tempus.git
synced 2026-04-15 16:27:26 +00:00
Merge branch 'development' into improve/update-zh
This commit is contained in:
commit
53ca88989f
19 changed files with 681 additions and 296 deletions
18
USAGE.md
18
USAGE.md
|
|
@ -160,7 +160,23 @@ If your server supports it - add a internet radio station feed
|
||||||
## Android Auto
|
## Android Auto
|
||||||
|
|
||||||
### Enabling on your head unit
|
### Enabling on your head unit
|
||||||
- You have to enable Android Auto developer options, which are different from actual Android dev options. Then you have to enable "Unknown sources" in Android Auto, otherwise the app won't appear as it isn't downloaded from Play Store. (screenshots needed)
|
To allow the Tempus app on your car's head unit, "Unknown sources" needs to be enabled in the Android Auto "Developer settings". This is because Tempus isn't installed through Play Store. Note that the Android Auto developer settings are different from the global Android "Developer options".
|
||||||
|
1. Switch to developer mode in the Android Auto settings by tapping ten times on the "Version" item at the bottom, followed by giving your permission.
|
||||||
|
<p align="left">
|
||||||
|
<img width="270" height="600" alt="1a" src="https://github.com/user-attachments/assets/f09f6999-9761-4b05-8ec7-bf221a15dda3" />
|
||||||
|
<img width="270" height="600" alt="1b" src="https://github.com/user-attachments/assets/0795e508-ba01-41c5-96a7-7c03b0156591" />
|
||||||
|
<img width="270" height="600" alt="1c" src="https://github.com/user-attachments/assets/51c15f67-fddb-452e-b5d3-5092edeab390" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
2. Go to the "Developer settings" by the menu at the top right.
|
||||||
|
<p align="left">
|
||||||
|
<img width="270" height="600" alt="2" src="https://github.com/user-attachments/assets/1ecd1f3e-026d-4d25-87f2-be7f12efbac6" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
3. Scroll down to the bottom and check "Unknown sources".
|
||||||
|
<p align="left">
|
||||||
|
<img width="270" height="600" alt="3" src="https://github.com/user-attachments/assets/37db88e9-1b76-417f-9c47-da9f3a750fff" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
|
||||||
### Server Settings
|
### Server Settings
|
||||||
|
|
|
||||||
|
|
@ -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