feat: added "Recent songs" and "Random" menu items in Android Auto

This commit is contained in:
CappielloAntonio 2024-02-18 19:06:58 +01:00
parent 14d6128df0
commit 54e988b70e
3 changed files with 103 additions and 0 deletions

View file

@ -12,6 +12,9 @@ import java.util.List;
@Dao
public interface ChronologyDao {
@Query("SELECT * FROM chronology WHERE server == :server GROUP BY id ORDER BY timestamp DESC LIMIT :count")
LiveData<List<Chronology>> getLastPlayed(String server, int count);
@Query("SELECT * FROM chronology WHERE timestamp >= :startDate AND timestamp < :endDate AND server == :server GROUP BY id ORDER BY COUNT(id) DESC LIMIT 9")
LiveData<List<Chronology>> getAllFrom(long startDate, long endDate, String server);

View file

@ -2,9 +2,13 @@ package com.cappielloantonio.tempo.repository;
import android.net.Uri;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.UnstableApi;
@ -12,9 +16,13 @@ import androidx.media3.session.LibraryResult;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Chronology;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.SessionMediaItem;
import com.cappielloantonio.tempo.service.DownloaderManager;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Artist;
@ -26,6 +34,7 @@ import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
@ -44,6 +53,7 @@ import retrofit2.Response;
public class AutomotiveRepository {
private final SessionMediaItemDao sessionMediaItemDao = AppDatabase.getInstance().sessionMediaItemDao();
private final ChronologyDao chronologyDao = AppDatabase.getInstance().chronologyDao();
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getAlbums(String prefix, String type, int size) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
@ -132,6 +142,66 @@ public class AutomotiveRepository {
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getRandomSongs(int count) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getRandomSongs(100, null, null)
.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().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
List<Child> songs = response.body().getSubsonicResponse().getRandomSongs().getSongs();
setChildrenMetadata(songs);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(songs);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getRecentlyPlayedSongs(String server, int count) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
chronologyDao.getLastPlayed(server, count).observeForever(new Observer<List<Chronology>>() {
@Override
public void onChanged(List<Chronology> chronology) {
if (chronology != null && !chronology.isEmpty()) {
List<Child> songs = new ArrayList<>(chronology);
setChildrenMetadata(songs);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(songs);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
chronologyDao.getLastPlayed(server, count).removeObserver(this);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredAlbums(String prefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();

View file

@ -1,11 +1,13 @@
package com.cappielloantonio.tempo.service
import android.net.Uri
import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaItem.SubtitleConfiguration
import androidx.media3.common.MediaMetadata
import androidx.media3.session.LibraryResult
import com.cappielloantonio.tempo.repository.AutomotiveRepository
import com.cappielloantonio.tempo.util.Preferences.getServerId
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
@ -31,10 +33,12 @@ object MediaBrowserTree {
private const val MOST_PLAYED_ID = "[mostPlayedID]"
private const val LAST_PLAYED_ID = "[lastPlayedID]"
private const val RECENTLY_ADDED_ID = "[recentlyAddedID]"
private const val RECENT_SONGS_ID = "[recentSongsID]"
private const val MADE_FOR_YOU_ID = "[madeForYouID]"
private const val STARRED_TRACKS_ID = "[starredTracksID]"
private const val STARRED_ALBUMS_ID = "[starredAlbumsID]"
private const val STARRED_ARTISTS_ID = "[starredArtistsID]"
private const val RANDOM_ID = "[randomID]"
// Second level LIBRARY_ID
private const val FOLDER_ID = "[folderID]"
@ -193,6 +197,17 @@ object MediaBrowserTree {
)
)
treeNodes[RECENT_SONGS_ID] =
MediaItemNode(
buildMediaItem(
title = "Recent songs",
mediaId = RECENT_SONGS_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[MADE_FOR_YOU_ID] =
MediaItemNode(
buildMediaItem(
@ -237,13 +252,26 @@ object MediaBrowserTree {
)
)
treeNodes[RANDOM_ID] =
MediaItemNode(
buildMediaItem(
title = "Random",
mediaId = RANDOM_ID,
isPlayable = false,
isBrowsable = true,
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
)
)
treeNodes[HOME_ID]!!.addChild(MOST_PLAYED_ID)
treeNodes[HOME_ID]!!.addChild(LAST_PLAYED_ID)
treeNodes[HOME_ID]!!.addChild(RECENTLY_ADDED_ID)
treeNodes[HOME_ID]!!.addChild(RECENT_SONGS_ID)
treeNodes[HOME_ID]!!.addChild(MADE_FOR_YOU_ID)
treeNodes[HOME_ID]!!.addChild(STARRED_TRACKS_ID)
treeNodes[HOME_ID]!!.addChild(STARRED_ALBUMS_ID)
treeNodes[HOME_ID]!!.addChild(STARRED_ARTISTS_ID)
treeNodes[HOME_ID]!!.addChild(RANDOM_ID)
// Second level LIBRARY_ID
@ -316,10 +344,12 @@ object MediaBrowserTree {
MOST_PLAYED_ID -> automotiveRepository.getAlbums(id, "frequent", 100)
LAST_PLAYED_ID -> automotiveRepository.getAlbums(id, "recent", 100)
RECENTLY_ADDED_ID -> automotiveRepository.getAlbums(id, "newest", 100)
RECENT_SONGS_ID -> automotiveRepository.getRecentlyPlayedSongs(getServerId(),100)
MADE_FOR_YOU_ID -> automotiveRepository.getStarredArtists(id)
STARRED_TRACKS_ID -> automotiveRepository.starredSongs
STARRED_ALBUMS_ID -> automotiveRepository.getStarredAlbums(id)
STARRED_ARTISTS_ID -> automotiveRepository.getStarredArtists(id)
RANDOM_ID -> automotiveRepository.getRandomSongs(100)
FOLDER_ID -> automotiveRepository.getMusicFolders(id)
PLAYLIST_ID -> automotiveRepository.getPlaylists(id)
PODCAST_ID -> automotiveRepository.getNewestPodcastEpisodes(100)