diff --git a/app/src/main/java/com/cappielloantonio/tempo/database/dao/ChronologyDao.java b/app/src/main/java/com/cappielloantonio/tempo/database/dao/ChronologyDao.java index 573c8940..906f7efa 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/database/dao/ChronologyDao.java +++ b/app/src/main/java/com/cappielloantonio/tempo/database/dao/ChronologyDao.java @@ -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> 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> getAllFrom(long startDate, long endDate, String server); diff --git a/app/src/main/java/com/cappielloantonio/tempo/repository/AutomotiveRepository.java b/app/src/main/java/com/cappielloantonio/tempo/repository/AutomotiveRepository.java index 8f1ef4f5..fe24d81d 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/repository/AutomotiveRepository.java +++ b/app/src/main/java/com/cappielloantonio/tempo/repository/AutomotiveRepository.java @@ -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>> getAlbums(String prefix, String type, int size) { final SettableFuture>> listenableFuture = SettableFuture.create(); @@ -132,6 +142,66 @@ public class AutomotiveRepository { return listenableFuture; } + public ListenableFuture>> getRandomSongs(int count) { + final SettableFuture>> listenableFuture = SettableFuture.create(); + + App.getSubsonicClientInstance(false) + .getAlbumSongListClient() + .getRandomSongs(100, null, null) + .enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) { + List songs = response.body().getSubsonicResponse().getRandomSongs().getSongs(); + + setChildrenMetadata(songs); + + List mediaItems = MappingUtil.mapMediaItems(songs); + + LibraryResult> 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 call, @NonNull Throwable t) { + listenableFuture.setException(t); + } + }); + + return listenableFuture; + } + + public ListenableFuture>> getRecentlyPlayedSongs(String server, int count) { + final SettableFuture>> listenableFuture = SettableFuture.create(); + + chronologyDao.getLastPlayed(server, count).observeForever(new Observer>() { + @Override + public void onChanged(List chronology) { + if (chronology != null && !chronology.isEmpty()) { + List songs = new ArrayList<>(chronology); + + setChildrenMetadata(songs); + + List mediaItems = MappingUtil.mapMediaItems(songs); + + LibraryResult> 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>> getStarredAlbums(String prefix) { final SettableFuture>> listenableFuture = SettableFuture.create(); diff --git a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaBrowserTree.kt b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaBrowserTree.kt index 438ab0b4..f88f9b6b 100644 --- a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaBrowserTree.kt +++ b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaBrowserTree.kt @@ -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)