mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
feat: Implemented search functionality for Android Auto, "Made for You" section, starred songs, albums, artists, podcasts, and radio
This commit is contained in:
parent
d6cc4fc028
commit
68512b7e12
4 changed files with 243 additions and 111 deletions
|
|
@ -7,6 +7,7 @@ import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MediaItem.RequestMetadata
|
import androidx.media3.common.MediaItem.RequestMetadata
|
||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.MimeTypes
|
import androidx.media3.common.MimeTypes
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
|
@ -19,6 +20,7 @@ import com.cappielloantonio.tempo.util.MusicUtil
|
||||||
import com.cappielloantonio.tempo.util.Preferences.getImageSize
|
import com.cappielloantonio.tempo.util.Preferences.getImageSize
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
@Keep
|
@Keep
|
||||||
@Entity(tableName = "session_media_item")
|
@Entity(tableName = "session_media_item")
|
||||||
class SessionMediaItem() {
|
class SessionMediaItem() {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ package com.cappielloantonio.tempo.repository;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.LibraryResult;
|
import androidx.media3.session.LibraryResult;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.App;
|
import com.cappielloantonio.tempo.App;
|
||||||
|
|
@ -183,7 +185,7 @@ public class AutomotiveRepository {
|
||||||
return listenableFuture;
|
return listenableFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredArtists(String prefix, boolean playFromThere) {
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredArtists(String prefix) {
|
||||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
|
|
@ -195,6 +197,8 @@ public class AutomotiveRepository {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getArtists() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null && response.body().getSubsonicResponse().getStarred2().getArtists() != null) {
|
||||||
List<ArtistID3> artists = response.body().getSubsonicResponse().getStarred2().getArtists();
|
List<ArtistID3> artists = response.body().getSubsonicResponse().getStarred2().getArtists();
|
||||||
|
|
||||||
|
Collections.shuffle(artists);
|
||||||
|
|
||||||
List<MediaItem> mediaItems = new ArrayList<>();
|
List<MediaItem> mediaItems = new ArrayList<>();
|
||||||
|
|
||||||
for (ArtistID3 artist : artists) {
|
for (ArtistID3 artist : artists) {
|
||||||
|
|
@ -202,8 +206,8 @@ public class AutomotiveRepository {
|
||||||
|
|
||||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||||
.setTitle(artist.getName())
|
.setTitle(artist.getName())
|
||||||
.setIsBrowsable(!playFromThere)
|
.setIsBrowsable(true)
|
||||||
.setIsPlayable(playFromThere)
|
.setIsPlayable(false)
|
||||||
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
|
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
|
||||||
.setArtworkUri(artworkUri)
|
.setArtworkUri(artworkUri)
|
||||||
.build();
|
.build();
|
||||||
|
|
@ -461,7 +465,7 @@ public class AutomotiveRepository {
|
||||||
return listenableFuture;
|
return listenableFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getNewestPodcastEpisodes(String prefix, int count) {
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getNewestPodcastEpisodes(int count) {
|
||||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
|
|
@ -487,7 +491,7 @@ public class AutomotiveRepository {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
MediaItem mediaItem = new MediaItem.Builder()
|
MediaItem mediaItem = new MediaItem.Builder()
|
||||||
.setMediaId(prefix + episode.getId())
|
.setMediaId(episode.getId())
|
||||||
.setMediaMetadata(mediaMetadata)
|
.setMediaMetadata(mediaMetadata)
|
||||||
.setUri(MusicUtil.getStreamUri(episode.getStreamId()))
|
.setUri(MusicUtil.getStreamUri(episode.getStreamId()))
|
||||||
.build();
|
.build();
|
||||||
|
|
@ -514,7 +518,7 @@ public class AutomotiveRepository {
|
||||||
return listenableFuture;
|
return listenableFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getInternetRadioStations(String prefix) {
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getInternetRadioStations() {
|
||||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||||
|
|
||||||
App.getSubsonicClientInstance(false)
|
App.getSubsonicClientInstance(false)
|
||||||
|
|
@ -538,7 +542,7 @@ public class AutomotiveRepository {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
MediaItem mediaItem = new MediaItem.Builder()
|
MediaItem mediaItem = new MediaItem.Builder()
|
||||||
.setMediaId(prefix + radioStation.getId())
|
.setMediaId(radioStation.getId())
|
||||||
.setMediaMetadata(mediaMetadata)
|
.setMediaMetadata(mediaMetadata)
|
||||||
.setUri(radioStation.getStreamUrl())
|
.setUri(radioStation.getStreamUrl())
|
||||||
.build();
|
.build();
|
||||||
|
|
@ -575,7 +579,6 @@ public class AutomotiveRepository {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null && response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
|
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null && response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
|
||||||
|
|
||||||
List<Child> tracks = response.body().getSubsonicResponse().getAlbum().getSongs();
|
List<Child> tracks = response.body().getSubsonicResponse().getAlbum().getSongs();
|
||||||
|
|
||||||
setChildrenMetadata(tracks);
|
setChildrenMetadata(tracks);
|
||||||
|
|
@ -599,6 +602,58 @@ public class AutomotiveRepository {
|
||||||
return listenableFuture;
|
return listenableFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getArtistAlbum(String prefix, String id) {
|
||||||
|
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||||
|
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getBrowsingClient()
|
||||||
|
.getArtist(id)
|
||||||
|
.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().getArtist() != null && response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
|
||||||
|
List<AlbumID3> albums = response.body().getSubsonicResponse().getArtist().getAlbums();
|
||||||
|
|
||||||
|
List<MediaItem> mediaItems = new ArrayList<>();
|
||||||
|
|
||||||
|
for (AlbumID3 album : albums) {
|
||||||
|
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||||
|
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||||
|
.setTitle(album.getName())
|
||||||
|
.setAlbumTitle(album.getName())
|
||||||
|
.setArtist(album.getArtist())
|
||||||
|
.setGenre(album.getGenre())
|
||||||
|
.setIsBrowsable(true)
|
||||||
|
.setIsPlayable(false)
|
||||||
|
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
|
||||||
|
.setArtworkUri(artworkUri)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder()
|
||||||
|
.setMediaId(prefix + album.getId())
|
||||||
|
.setMediaMetadata(mediaMetadata)
|
||||||
|
.setUri("")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mediaItems.add(mediaItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||||
|
|
||||||
|
listenableFuture.set(libraryResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
listenableFuture.setException(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return listenableFuture;
|
||||||
|
}
|
||||||
|
|
||||||
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getPlaylistSongs(String id) {
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getPlaylistSongs(String id) {
|
||||||
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||||
|
|
||||||
|
|
@ -630,6 +685,118 @@ public class AutomotiveRepository {
|
||||||
return listenableFuture;
|
return listenableFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getMadeForYou(String id, int count) {
|
||||||
|
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||||
|
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getBrowsingClient()
|
||||||
|
.getSimilarSongs2(id, 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 && response.body().getSubsonicResponse().getSimilarSongs2().getSongs() != null) {
|
||||||
|
List<Child> tracks = response.body().getSubsonicResponse().getSimilarSongs2().getSongs();
|
||||||
|
|
||||||
|
setChildrenMetadata(tracks);
|
||||||
|
|
||||||
|
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(tracks);
|
||||||
|
|
||||||
|
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||||
|
|
||||||
|
listenableFuture.set(libraryResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
listenableFuture.setException(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return listenableFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> search(String query, String albumPrefix, String artistPrefix) {
|
||||||
|
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
|
||||||
|
|
||||||
|
App.getSubsonicClientInstance(false)
|
||||||
|
.getSearchingClient()
|
||||||
|
.search3(query, 20, 20, 20)
|
||||||
|
.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().getSearchResult3() != null) {
|
||||||
|
List<MediaItem> mediaItems = new ArrayList<>();
|
||||||
|
|
||||||
|
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
|
||||||
|
for (ArtistID3 artist : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
|
||||||
|
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||||
|
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||||
|
.setTitle(artist.getName())
|
||||||
|
.setIsBrowsable(true)
|
||||||
|
.setIsPlayable(false)
|
||||||
|
.setMediaType(MediaMetadata.MEDIA_TYPE_PLAYLIST)
|
||||||
|
.setArtworkUri(artworkUri)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder()
|
||||||
|
.setMediaId(artistPrefix + artist.getId())
|
||||||
|
.setMediaMetadata(mediaMetadata)
|
||||||
|
.setUri("")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mediaItems.add(mediaItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.body().getSubsonicResponse().getSearchResult3().getAlbums() != null) {
|
||||||
|
for (AlbumID3 album : response.body().getSubsonicResponse().getSearchResult3().getAlbums()) {
|
||||||
|
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||||
|
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||||
|
.setTitle(album.getName())
|
||||||
|
.setAlbumTitle(album.getName())
|
||||||
|
.setArtist(album.getArtist())
|
||||||
|
.setGenre(album.getGenre())
|
||||||
|
.setIsBrowsable(true)
|
||||||
|
.setIsPlayable(false)
|
||||||
|
.setMediaType(MediaMetadata.MEDIA_TYPE_ALBUM)
|
||||||
|
.setArtworkUri(artworkUri)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder()
|
||||||
|
.setMediaId(albumPrefix + album.getId())
|
||||||
|
.setMediaMetadata(mediaMetadata)
|
||||||
|
.setUri("")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
mediaItems.add(mediaItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.body().getSubsonicResponse().getSearchResult3().getSongs() != null) {
|
||||||
|
List<Child> tracks = response.body().getSubsonicResponse().getSearchResult3().getSongs();
|
||||||
|
setChildrenMetadata(tracks);
|
||||||
|
mediaItems.addAll(MappingUtil.mapMediaItems(tracks));
|
||||||
|
}
|
||||||
|
|
||||||
|
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
|
||||||
|
|
||||||
|
listenableFuture.set(libraryResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||||
|
listenableFuture.setException(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return listenableFuture;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public void setChildrenMetadata(List<Child> children) {
|
public void setChildrenMetadata(List<Child> children) {
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
||||||
|
|
@ -645,6 +812,7 @@ public class AutomotiveRepository {
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public void setPodcastEpisodesMetadata(List<PodcastEpisode> podcastEpisodes) {
|
public void setPodcastEpisodesMetadata(List<PodcastEpisode> podcastEpisodes) {
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
||||||
|
|
@ -660,6 +828,7 @@ public class AutomotiveRepository {
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public void setInternetRadioStationsMetadata(List<InternetRadioStation> internetRadioStations) {
|
public void setInternetRadioStationsMetadata(List<InternetRadioStation> internetRadioStations) {
|
||||||
long timestamp = System.currentTimeMillis();
|
long timestamp = System.currentTimeMillis();
|
||||||
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
ArrayList<SessionMediaItem> sessionMediaItems = new ArrayList<>();
|
||||||
|
|
@ -736,11 +905,11 @@ public class AutomotiveRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
private static class GetMediaItemsThreadSafe implements Runnable {
|
private static class GetMediaItemsThreadSafe implements Runnable {
|
||||||
private final SessionMediaItemDao sessionMediaItemDao;
|
private final SessionMediaItemDao sessionMediaItemDao;
|
||||||
private final Long timestamp;
|
private final Long timestamp;
|
||||||
|
private final List<MediaItem> mediaItems = new ArrayList<>();
|
||||||
private List<MediaItem> mediaItems = new ArrayList<>();
|
|
||||||
|
|
||||||
public GetMediaItemsThreadSafe(SessionMediaItemDao sessionMediaItemDao, Long timestamp) {
|
public GetMediaItemsThreadSafe(SessionMediaItemDao sessionMediaItemDao, Long timestamp) {
|
||||||
this.sessionMediaItemDao = sessionMediaItemDao;
|
this.sessionMediaItemDao = sessionMediaItemDao;
|
||||||
|
|
@ -758,43 +927,6 @@ public class AutomotiveRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* private static class InsertThreadSafe implements Runnable {
|
|
||||||
private final SessionMediaItemDao sessionMediaItemDao;
|
|
||||||
private final List<Child> children;
|
|
||||||
private final List<PodcastEpisode> podcastEpisodes;
|
|
||||||
private final List<InternetRadioStation> internetRadioStations;
|
|
||||||
private final long timestamp;
|
|
||||||
|
|
||||||
public InsertThreadSafe(SessionMediaItemDao sessionMediaItemDao, List<Child> children, List<PodcastEpisode> podcastEpisodes, List<InternetRadioStation> internetRadioStations, long timestamp) {
|
|
||||||
this.sessionMediaItemDao = sessionMediaItemDao;
|
|
||||||
this.children = children;
|
|
||||||
this.podcastEpisodes = podcastEpisodes;
|
|
||||||
this.internetRadioStations = internetRadioStations;
|
|
||||||
this.timestamp = timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (children != null) {
|
|
||||||
SessionMediaItem sessionMediaItem = new SessionMediaItem(children);
|
|
||||||
sessionMediaItem.setTimestamp(timestamp);
|
|
||||||
sessionMediaItemDao.insert(sessionMediaItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (podcastEpisodes != null) {
|
|
||||||
SessionMediaItem sessionMediaItem = new SessionMediaItem(podcastEpisodes);
|
|
||||||
sessionMediaItem.setTimestamp(timestamp);
|
|
||||||
sessionMediaItemDao.insert(sessionMediaItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internetRadioStations != null) {
|
|
||||||
SessionMediaItem sessionMediaItem = new SessionMediaItem(internetRadioStations);
|
|
||||||
sessionMediaItem.setTimestamp(timestamp);
|
|
||||||
sessionMediaItemDao.insert(sessionMediaItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
private static class InsertAllThreadSafe implements Runnable {
|
private static class InsertAllThreadSafe implements Runnable {
|
||||||
private final SessionMediaItemDao sessionMediaItemDao;
|
private final SessionMediaItemDao sessionMediaItemDao;
|
||||||
private final List<SessionMediaItem> sessionMediaItems;
|
private final List<SessionMediaItem> sessionMediaItems;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
package com.cappielloantonio.tempo.service
|
package com.cappielloantonio.tempo.service
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MediaItem.SubtitleConfiguration
|
import androidx.media3.common.MediaItem.SubtitleConfiguration
|
||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.session.LibraryResult
|
import androidx.media3.session.LibraryResult
|
||||||
import androidx.media3.session.MediaLibraryService
|
|
||||||
import com.cappielloantonio.tempo.model.SessionMediaItem
|
|
||||||
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
object MediaBrowserTree {
|
object MediaBrowserTree {
|
||||||
|
|
||||||
|
|
@ -36,7 +31,6 @@ object MediaBrowserTree {
|
||||||
private const val MOST_PLAYED_ID = "[mostPlayedID]"
|
private const val MOST_PLAYED_ID = "[mostPlayedID]"
|
||||||
private const val LAST_PLAYED_ID = "[lastPlayedID]"
|
private const val LAST_PLAYED_ID = "[lastPlayedID]"
|
||||||
private const val RECENTLY_ADDED_ID = "[recentlyAddedID]"
|
private const val RECENTLY_ADDED_ID = "[recentlyAddedID]"
|
||||||
private const val BEST_OF_ID = "[bestOfID]"
|
|
||||||
private const val MADE_FOR_YOU_ID = "[madeForYouID]"
|
private const val MADE_FOR_YOU_ID = "[madeForYouID]"
|
||||||
private const val STARRED_TRACKS_ID = "[starredTracksID]"
|
private const val STARRED_TRACKS_ID = "[starredTracksID]"
|
||||||
private const val STARRED_ALBUMS_ID = "[starredAlbumsID]"
|
private const val STARRED_ALBUMS_ID = "[starredAlbumsID]"
|
||||||
|
|
@ -51,7 +45,9 @@ object MediaBrowserTree {
|
||||||
// Second level OTHER_ID
|
// Second level OTHER_ID
|
||||||
private const val PODCAST_ID = "[podcastID]"
|
private const val PODCAST_ID = "[podcastID]"
|
||||||
private const val RADIO_ID = "[radioID]"
|
private const val RADIO_ID = "[radioID]"
|
||||||
private const val DOWNLOAD_ID = "[downloadID]"
|
|
||||||
|
private const val ALBUM_ID = "[albumID]"
|
||||||
|
private const val ARTIST_ID = "[artistID]"
|
||||||
|
|
||||||
private class MediaItemNode(val item: MediaItem) {
|
private class MediaItemNode(val item: MediaItem) {
|
||||||
private val children: MutableList<MediaItem> = ArrayList()
|
private val children: MutableList<MediaItem> = ArrayList()
|
||||||
|
|
@ -103,7 +99,7 @@ object MediaBrowserTree {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initialize(context: Context, automotiveRepository: AutomotiveRepository) {
|
fun initialize(automotiveRepository: AutomotiveRepository) {
|
||||||
this.automotiveRepository = automotiveRepository
|
this.automotiveRepository = automotiveRepository
|
||||||
|
|
||||||
if (isInitialized) return
|
if (isInitialized) return
|
||||||
|
|
@ -197,17 +193,6 @@ object MediaBrowserTree {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
treeNodes[BEST_OF_ID] =
|
|
||||||
MediaItemNode(
|
|
||||||
buildMediaItem(
|
|
||||||
title = "Best of",
|
|
||||||
mediaId = BEST_OF_ID,
|
|
||||||
isPlayable = false,
|
|
||||||
isBrowsable = true,
|
|
||||||
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
treeNodes[MADE_FOR_YOU_ID] =
|
treeNodes[MADE_FOR_YOU_ID] =
|
||||||
MediaItemNode(
|
MediaItemNode(
|
||||||
buildMediaItem(
|
buildMediaItem(
|
||||||
|
|
@ -255,11 +240,10 @@ object MediaBrowserTree {
|
||||||
treeNodes[HOME_ID]!!.addChild(MOST_PLAYED_ID)
|
treeNodes[HOME_ID]!!.addChild(MOST_PLAYED_ID)
|
||||||
treeNodes[HOME_ID]!!.addChild(LAST_PLAYED_ID)
|
treeNodes[HOME_ID]!!.addChild(LAST_PLAYED_ID)
|
||||||
treeNodes[HOME_ID]!!.addChild(RECENTLY_ADDED_ID)
|
treeNodes[HOME_ID]!!.addChild(RECENTLY_ADDED_ID)
|
||||||
treeNodes[HOME_ID]!!.addChild(BEST_OF_ID)
|
|
||||||
treeNodes[HOME_ID]!!.addChild(MADE_FOR_YOU_ID)
|
treeNodes[HOME_ID]!!.addChild(MADE_FOR_YOU_ID)
|
||||||
// treeNodes[HOME_ID]!!.addChild(STARRED_TRACKS_ID)
|
treeNodes[HOME_ID]!!.addChild(STARRED_TRACKS_ID)
|
||||||
// treeNodes[HOME_ID]!!.addChild(STARRED_ALBUMS_ID)
|
treeNodes[HOME_ID]!!.addChild(STARRED_ALBUMS_ID)
|
||||||
// treeNodes[HOME_ID]!!.addChild(STARRED_ARTISTS_ID)
|
treeNodes[HOME_ID]!!.addChild(STARRED_ARTISTS_ID)
|
||||||
|
|
||||||
// Second level LIBRARY_ID
|
// Second level LIBRARY_ID
|
||||||
|
|
||||||
|
|
@ -312,20 +296,8 @@ object MediaBrowserTree {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
treeNodes[DOWNLOAD_ID] =
|
|
||||||
MediaItemNode(
|
|
||||||
buildMediaItem(
|
|
||||||
title = "Downloads",
|
|
||||||
mediaId = DOWNLOAD_ID,
|
|
||||||
isPlayable = false,
|
|
||||||
isBrowsable = true,
|
|
||||||
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_RADIO_STATIONS
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
treeNodes[OTHER_ID]!!.addChild(PODCAST_ID)
|
treeNodes[OTHER_ID]!!.addChild(PODCAST_ID)
|
||||||
treeNodes[OTHER_ID]!!.addChild(RADIO_ID)
|
treeNodes[OTHER_ID]!!.addChild(RADIO_ID)
|
||||||
// treeNodes[OTHER_ID]!!.addChild(DOWNLOAD_ID)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRootItem(): MediaItem {
|
fun getRootItem(): MediaItem {
|
||||||
|
|
@ -333,8 +305,7 @@ object MediaBrowserTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getChildren(
|
fun getChildren(
|
||||||
id: String,
|
id: String
|
||||||
params: MediaLibraryService.LibraryParams?
|
|
||||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
return when (id) {
|
return when (id) {
|
||||||
ROOT_ID -> treeNodes[ROOT_ID]?.getChildren()!!
|
ROOT_ID -> treeNodes[ROOT_ID]?.getChildren()!!
|
||||||
|
|
@ -345,17 +316,14 @@ object MediaBrowserTree {
|
||||||
MOST_PLAYED_ID -> automotiveRepository.getAlbums(id, "frequent", 100)
|
MOST_PLAYED_ID -> automotiveRepository.getAlbums(id, "frequent", 100)
|
||||||
LAST_PLAYED_ID -> automotiveRepository.getAlbums(id, "recent", 100)
|
LAST_PLAYED_ID -> automotiveRepository.getAlbums(id, "recent", 100)
|
||||||
RECENTLY_ADDED_ID -> automotiveRepository.getAlbums(id, "newest", 100)
|
RECENTLY_ADDED_ID -> automotiveRepository.getAlbums(id, "newest", 100)
|
||||||
BEST_OF_ID -> automotiveRepository.getStarredArtists(id, true)
|
MADE_FOR_YOU_ID -> automotiveRepository.getStarredArtists(id)
|
||||||
MADE_FOR_YOU_ID -> automotiveRepository.getStarredArtists(id, true)
|
|
||||||
STARRED_TRACKS_ID -> automotiveRepository.starredSongs
|
STARRED_TRACKS_ID -> automotiveRepository.starredSongs
|
||||||
STARRED_ALBUMS_ID -> automotiveRepository.getStarredAlbums(id)
|
STARRED_ALBUMS_ID -> automotiveRepository.getStarredAlbums(id)
|
||||||
STARRED_ARTISTS_ID -> automotiveRepository.getStarredArtists(id, false)
|
STARRED_ARTISTS_ID -> automotiveRepository.getStarredArtists(id)
|
||||||
FOLDER_ID -> automotiveRepository.getMusicFolders(id)
|
FOLDER_ID -> automotiveRepository.getMusicFolders(id)
|
||||||
PLAYLIST_ID -> automotiveRepository.getPlaylists(id)
|
PLAYLIST_ID -> automotiveRepository.getPlaylists(id)
|
||||||
PODCAST_ID -> automotiveRepository.getNewestPodcastEpisodes(id, 100)
|
PODCAST_ID -> automotiveRepository.getNewestPodcastEpisodes(100)
|
||||||
RADIO_ID -> automotiveRepository.getInternetRadioStations(id)
|
RADIO_ID -> automotiveRepository.internetRadioStations
|
||||||
|
|
||||||
DOWNLOAD_ID -> Futures.immediateFuture(null)
|
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (id.startsWith(MOST_PLAYED_ID)) {
|
if (id.startsWith(MOST_PLAYED_ID)) {
|
||||||
|
|
@ -382,12 +350,30 @@ object MediaBrowserTree {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id.startsWith(BEST_OF_ID)) {
|
if (id.startsWith(MADE_FOR_YOU_ID)) {
|
||||||
|
return automotiveRepository.getMadeForYou(
|
||||||
|
id.removePrefix(
|
||||||
|
MADE_FOR_YOU_ID
|
||||||
|
),
|
||||||
|
20
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id.startsWith(MADE_FOR_YOU_ID)) {
|
if (id.startsWith(STARRED_ALBUMS_ID)) {
|
||||||
|
return automotiveRepository.getAlbumTracks(
|
||||||
|
id.removePrefix(
|
||||||
|
STARRED_ALBUMS_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.startsWith(STARRED_ARTISTS_ID)) {
|
||||||
|
return automotiveRepository.getArtistAlbum(
|
||||||
|
STARRED_ALBUMS_ID,
|
||||||
|
id.removePrefix(
|
||||||
|
STARRED_ARTISTS_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id.startsWith(FOLDER_ID)) {
|
if (id.startsWith(FOLDER_ID)) {
|
||||||
|
|
@ -425,12 +411,21 @@ object MediaBrowserTree {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id.startsWith(PODCAST_ID)) {
|
if (id.startsWith(ALBUM_ID)) {
|
||||||
|
return automotiveRepository.getAlbumTracks(
|
||||||
|
id.removePrefix(
|
||||||
|
ALBUM_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id.startsWith(RADIO_ID)) {
|
if (id.startsWith(ARTIST_ID)) {
|
||||||
|
return automotiveRepository.getArtistAlbum(
|
||||||
|
ALBUM_ID,
|
||||||
|
id.removePrefix(
|
||||||
|
ARTIST_ID
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
||||||
|
|
@ -461,4 +456,12 @@ object MediaBrowserTree {
|
||||||
|
|
||||||
return updatedMediaItems
|
return updatedMediaItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun search(query: String): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
|
return automotiveRepository.search(
|
||||||
|
query,
|
||||||
|
ALBUM_ID,
|
||||||
|
ARTIST_ID
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,9 @@ open class MediaLibrarySessionCallback(
|
||||||
automotiveRepository: AutomotiveRepository
|
automotiveRepository: AutomotiveRepository
|
||||||
) :
|
) :
|
||||||
MediaLibraryService.MediaLibrarySession.Callback {
|
MediaLibraryService.MediaLibrarySession.Callback {
|
||||||
private val TAG = "MediaLibraryServiceCall"
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
MediaBrowserTree.initialize(context, automotiveRepository)
|
MediaBrowserTree.initialize(automotiveRepository)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val customLayoutCommandButtons: List<CommandButton> = listOf(
|
private val customLayoutCommandButtons: List<CommandButton> = listOf(
|
||||||
|
|
@ -118,7 +117,7 @@ open class MediaLibrarySessionCallback(
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
params: MediaLibraryService.LibraryParams?
|
params: MediaLibraryService.LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
return MediaBrowserTree.getChildren(parentId, params)
|
return MediaBrowserTree.getChildren(parentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAddMediaItems(
|
override fun onAddMediaItems(
|
||||||
|
|
@ -133,17 +132,17 @@ open class MediaLibrarySessionCallback(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* override fun onSearch(
|
override fun onSearch(
|
||||||
session: MediaLibraryService.MediaLibrarySession,
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: MediaSession.ControllerInfo,
|
||||||
query: String,
|
query: String,
|
||||||
params: MediaLibraryService.LibraryParams?
|
params: MediaLibraryService.LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<Void>> {
|
): ListenableFuture<LibraryResult<Void>> {
|
||||||
session.notifySearchResultChanged(browser, query, MediaBrowserTree.search(query).size, params)
|
session.notifySearchResultChanged(browser, query, 60, params)
|
||||||
return Futures.immediateFuture(LibraryResult.ofVoid())
|
return Futures.immediateFuture(LibraryResult.ofVoid())
|
||||||
} */
|
}
|
||||||
|
|
||||||
/* override fun onGetSearchResult(
|
override fun onGetSearchResult(
|
||||||
session: MediaLibraryService.MediaLibrarySession,
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: MediaSession.ControllerInfo,
|
||||||
query: String,
|
query: String,
|
||||||
|
|
@ -151,12 +150,8 @@ open class MediaLibrarySessionCallback(
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
params: MediaLibraryService.LibraryParams?
|
params: MediaLibraryService.LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
return Futures.immediateFuture(
|
return MediaBrowserTree.search(query)
|
||||||
LibraryResult.ofItemList(
|
}
|
||||||
MediaBrowserTree.search(query), params
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} */
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue