Skip to content

Commit

Permalink
Media browser interface to show playlists on Android Auto
Browse files Browse the repository at this point in the history
  • Loading branch information
haggaie committed Jan 14, 2023
1 parent a3e9df2 commit c594e3d
Showing 1 changed file with 160 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -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));
Expand All @@ -36,11 +109,93 @@ public MediaBrowserServiceCompat.BrowserRoot onGetRoot(@NonNull final String cli
}

public void onLoadChildren(@NonNull final String parentId,
@NonNull final MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>> result) {
@NonNull final Result<List<MediaItem>> result) {
Log.d(TAG, String.format("MediaBrowserService.onLoadChildren(%s)", parentId));

final List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>();
final List<MediaItem> 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<MediaItem> 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<MediaItem> 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);
}
}

0 comments on commit c594e3d

Please sign in to comment.