From c594e3d7cc5b7a852091be3cbd11710486566244 Mon Sep 17 00:00:00 2001 From: Haggai Eran Date: Sun, 15 Jan 2023 01:11:34 +0200 Subject: [PATCH] Media browser interface to show playlists on Android Auto --- .../mediabrowser/MediaBrowserConnector.java | 165 +++++++++++++++++- 1 file changed, 160 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserConnector.java b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserConnector.java index b1f4e8ba15c..a61679bd96c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserConnector.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediabrowser/MediaBrowserConnector.java @@ -1,33 +1,106 @@ package org.schabi.newpipe.player.mediabrowser; +import android.net.Uri; import android.os.Bundle; -import android.support.v4.media.MediaBrowserCompat; +import android.support.v4.media.MediaBrowserCompat.MediaItem; +import android.support.v4.media.MediaDescriptionCompat; +import android.support.v4.media.session.MediaSessionCompat; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.media.MediaBrowserServiceCompat; +import androidx.media.MediaBrowserServiceCompat.Result; +import androidx.media.utils.MediaConstants; +import org.schabi.newpipe.NewPipeDatabase; +import org.schabi.newpipe.R; +import org.schabi.newpipe.database.AppDatabase; +import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; +import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; +import org.schabi.newpipe.local.playlist.LocalPlaylistManager; import org.schabi.newpipe.player.PlayerService; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.SinglePlayQueue; +import org.schabi.newpipe.util.NavigationHelper; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; -public class MediaBrowserConnector { +public class MediaBrowserConnector extends MediaSessionCompat.Callback { private static final String TAG = MediaBrowserConnector.class.getSimpleName(); private final PlayerService playerService; + private AppDatabase database; + private LocalPlaylistManager localPlaylistManager; public MediaBrowserConnector(@NonNull final PlayerService playerService) { this.playerService = playerService; playerService.setSessionToken(playerService.getMediaSession().getSessionToken()); + playerService.getMediaSession().setCallback(this); } @NonNull private static final String MY_MEDIA_ROOT_ID = "media_root_id"; + @NonNull + private static final String MY_MEDIA_BOOKMARKS_ID = "media_playlists"; + @NonNull + private static final String MY_MEDIA_HISTORY_ID = "media_history"; + @NonNull + private static final String MY_MEDIA_BOOKMARKS_PREFIX = "media_playlist_"; + @NonNull + private static final String MY_MEDIA_ITEM_PREFIX = "media_item_"; + + private MediaItem createRootMediaItem(final String mediaId, final String folderName) { + final var builder = new MediaDescriptionCompat.Builder(); + builder.setMediaId(mediaId); + builder.setTitle(folderName); + + final var extras = new Bundle(); + extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, + "NewPipe"); + builder.setExtras(extras); + return new MediaItem(builder.build(), MediaItem.FLAG_BROWSABLE); + } + + private MediaItem createPlaylistMediaItem(final PlaylistMetadataEntry playlist) { + final var builder = new MediaDescriptionCompat.Builder(); + builder.setMediaId(createMediaIdForPlaylist(playlist.uid)) + .setTitle(playlist.name) + .setIconUri(Uri.parse(playlist.thumbnailUrl)); + + final var extras = new Bundle(); + extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, + playerService.getResources().getString(R.string.tab_bookmarks)); + builder.setExtras(extras); + return new MediaItem(builder.build(), MediaItem.FLAG_BROWSABLE); + } + + private String createMediaIdForPlaylist(final long playlistId) { + return MY_MEDIA_BOOKMARKS_PREFIX + playlistId; + } + + private MediaItem createPlaylistStreamMediaItem(final PlaylistMetadataEntry playlist, + final PlaylistStreamEntry item, + final int index) { + final var builder = new MediaDescriptionCompat.Builder(); + builder.setMediaId(createMediaIdForItem(playlist.uid, index)) + .setTitle(item.getStreamEntity().getTitle()) + .setIconUri(Uri.parse(item.getStreamEntity().getThumbnailUrl())); + + return new MediaItem(builder.build(), MediaItem.FLAG_PLAYABLE); + } + + private String createMediaIdForItem(final long playlistId, final int index) { + return MY_MEDIA_ITEM_PREFIX + playlistId + "_" + index; + } @Nullable - public MediaBrowserServiceCompat.BrowserRoot onGetRoot(@NonNull final String clientPackageName, final int clientUid, + public MediaBrowserServiceCompat.BrowserRoot onGetRoot(@NonNull final String clientPackageName, + final int clientUid, @Nullable final Bundle rootHints) { Log.d(TAG, String.format("MediaBrowserService.onGetRoot(%s, %s, %s)", clientPackageName, clientUid, rootHints)); @@ -36,11 +109,93 @@ public MediaBrowserServiceCompat.BrowserRoot onGetRoot(@NonNull final String cli } public void onLoadChildren(@NonNull final String parentId, - @NonNull final MediaBrowserServiceCompat.Result> result) { + @NonNull final Result> result) { Log.d(TAG, String.format("MediaBrowserService.onLoadChildren(%s)", parentId)); - final List mediaItems = new ArrayList<>(); + final List mediaItems = new ArrayList<>(); + if (parentId.equals(MY_MEDIA_ROOT_ID)) { + mediaItems.add( + createRootMediaItem(MY_MEDIA_BOOKMARKS_ID, + playerService.getResources().getString(R.string.tab_bookmarks))); + mediaItems.add( + createRootMediaItem(MY_MEDIA_HISTORY_ID, + playerService.getResources().getString(R.string.action_history))); + } else if (parentId.startsWith(MY_MEDIA_BOOKMARKS_ID)) { + populateBookmarks(mediaItems); + } else if (parentId.startsWith(MY_MEDIA_BOOKMARKS_PREFIX)) { + populatePlaylist(extractPlaylistId(parentId), mediaItems); + } result.sendResult(mediaItems); } + + private Long extractPlaylistId(final String mediaId) { + return Long.valueOf(mediaId.substring(MY_MEDIA_BOOKMARKS_PREFIX.length())); + } + + private LocalPlaylistManager getPlaylistManager() { + if (database == null) { + database = NewPipeDatabase.getInstance(playerService); + } + if (localPlaylistManager == null) { + localPlaylistManager = new LocalPlaylistManager(database); + } + return localPlaylistManager; + } + + private void populateBookmarks(final List mediaItems) { + // TODO async + final var playlists = getPlaylistManager().getPlaylists().blockingFirst(); + mediaItems.addAll(playlists.stream().map(it -> createPlaylistMediaItem(it)) + .collect(Collectors.toList())); + } + + private void populatePlaylist(final Long playlistId, final List mediaItems) { + // TODO async + getPlaylistManager().getPlaylists().blockingFirst() + .stream().filter(it -> it.uid == playlistId).findFirst() + .ifPresent(playlist -> { + final var items = getPlaylistManager().getPlaylistStreams(playlist.uid) + .blockingFirst(); + int index = 0; + for (final var item : items) { + mediaItems.add(createPlaylistStreamMediaItem(playlist, item, index)); + ++index; + } + }); + } + + @Override + public void onPlay() { + super.onPlay(); + NavigationHelper.playOnBackgroundPlayer(playerService, null, true); + } + + @Override + public void onPlayFromMediaId(final String mediaId, final Bundle extras) { + super.onPlayFromMediaId(mediaId, extras); + final var playQueue = extractPlayQueueFromMediaId(mediaId); + NavigationHelper.playOnBackgroundPlayer(playerService, playQueue, true); + } + + Pattern playlistMediaIdRe = Pattern.compile(MY_MEDIA_ITEM_PREFIX + "(\\d+)_(\\d+)"); + private PlayQueue extractPlayQueueFromMediaId(final String mediaId) { + final Matcher m = playlistMediaIdRe.matcher(mediaId); + if (!m.matches()) { + return null; + } + final Long playlistId = Long.valueOf(m.group(1)); + final int index = Integer.valueOf(m.group(2)); + + final var items = getPlaylistManager().getPlaylists().blockingFirst() + .stream().filter(it -> it.uid == playlistId).findFirst() + .map(playlist -> { + return getPlaylistManager().getPlaylistStreams(playlist.uid) + .blockingFirst().stream() + .map(it -> it.toStreamInfoItem()) + .collect(Collectors.toList()); + }).orElse(new ArrayList<>()); + + return new SinglePlayQueue(items, index); + } }