11package org .schabi .newpipe .player .mediabrowser ;
22
3+ import android .net .Uri ;
34import android .os .Bundle ;
4- import android .support . v4 . media . MediaBrowserCompat ;
5+ import android .os . ResultReceiver ;
56import android .support .v4 .media .MediaBrowserCompat .MediaItem ;
7+ import android .support .v4 .media .MediaDescriptionCompat ;
68import android .support .v4 .media .session .MediaSessionCompat ;
9+ import android .support .v4 .media .session .PlaybackStateCompat ;
710import android .util .Log ;
811
912import androidx .annotation .NonNull ;
1013import androidx .annotation .Nullable ;
14+ import androidx .annotation .StringRes ;
1115import androidx .media .MediaBrowserServiceCompat ;
1216import androidx .media .MediaBrowserServiceCompat .Result ;
17+ import androidx .media .utils .MediaConstants ;
1318
19+ import com .google .android .exoplayer2 .Player ;
1420import com .google .android .exoplayer2 .ext .mediasession .MediaSessionConnector ;
1521
22+ import org .schabi .newpipe .NewPipeDatabase ;
23+ import org .schabi .newpipe .R ;
24+ import org .schabi .newpipe .database .AppDatabase ;
25+ import org .schabi .newpipe .database .playlist .PlaylistMetadataEntry ;
26+ import org .schabi .newpipe .database .playlist .PlaylistStreamEntry ;
27+ import org .schabi .newpipe .local .playlist .LocalPlaylistManager ;
1628import org .schabi .newpipe .player .PlayerService ;
29+ import org .schabi .newpipe .player .playqueue .PlayQueue ;
30+ import org .schabi .newpipe .player .playqueue .SinglePlayQueue ;
31+ import org .schabi .newpipe .util .NavigationHelper ;
1732
1833import java .util .ArrayList ;
1934import java .util .List ;
35+ import java .util .stream .Collectors ;
2036
21- public class MediaBrowserConnector {
37+ public class MediaBrowserConnector implements MediaSessionConnector . PlaybackPreparer {
2238 private static final String TAG = MediaBrowserConnector .class .getSimpleName ();
2339
2440 private final PlayerService playerService ;
2541 private final @ NonNull MediaSessionConnector sessionConnector ;
2642 private final @ NonNull MediaSessionCompat mediaSession ;
2743
44+ private AppDatabase database ;
45+ private LocalPlaylistManager localPlaylistManager ;
2846 public MediaBrowserConnector (@ NonNull final PlayerService playerService ) {
2947 this .playerService = playerService ;
3048 mediaSession = new MediaSessionCompat (playerService , TAG );
3149 sessionConnector = new MediaSessionConnector (mediaSession );
3250 sessionConnector .setMetadataDeduplicationEnabled (true );
51+ sessionConnector .setPlaybackPreparer (this );
3352 playerService .setSessionToken (mediaSession .getSessionToken ());
3453 }
3554
@@ -42,7 +61,53 @@ public void release() {
4261 }
4362
4463 @ NonNull
45- private static final String MY_MEDIA_ROOT_ID = "media_root_id" ;
64+ private static final String ID_ROOT = "//${BuildConfig.APPLICATION_ID}/r" ;
65+ @ NonNull
66+ private static final String ID_BOOKMARKS = ID_ROOT + "/playlists" ;
67+
68+ private MediaItem createRootMediaItem (final String mediaId , final String folderName ) {
69+ final var builder = new MediaDescriptionCompat .Builder ();
70+ builder .setMediaId (mediaId );
71+ builder .setTitle (folderName );
72+
73+ final var extras = new Bundle ();
74+ extras .putString (MediaConstants .DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE ,
75+ "NewPipe" );
76+ builder .setExtras (extras );
77+ return new MediaItem (builder .build (), MediaItem .FLAG_BROWSABLE );
78+ }
79+
80+ private MediaItem createPlaylistMediaItem (final PlaylistMetadataEntry playlist ) {
81+ final var builder = new MediaDescriptionCompat .Builder ();
82+ builder .setMediaId (createMediaIdForPlaylist (playlist .uid ))
83+ .setTitle (playlist .name )
84+ .setIconUri (Uri .parse (playlist .thumbnailUrl ));
85+
86+ final var extras = new Bundle ();
87+ extras .putString (MediaConstants .DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE ,
88+ playerService .getResources ().getString (R .string .tab_bookmarks ));
89+ builder .setExtras (extras );
90+ return new MediaItem (builder .build (), MediaItem .FLAG_BROWSABLE );
91+ }
92+
93+ private String createMediaIdForPlaylist (final long playlistId ) {
94+ return ID_BOOKMARKS + '/' + playlistId ;
95+ }
96+
97+ private MediaItem createPlaylistStreamMediaItem (final PlaylistMetadataEntry playlist ,
98+ final PlaylistStreamEntry item ,
99+ final int index ) {
100+ final var builder = new MediaDescriptionCompat .Builder ();
101+ builder .setMediaId (createMediaIdForPlaylistIndex (playlist .uid , index ))
102+ .setTitle (item .getStreamEntity ().getTitle ())
103+ .setIconUri (Uri .parse (item .getStreamEntity ().getThumbnailUrl ()));
104+
105+ return new MediaItem (builder .build (), MediaItem .FLAG_PLAYABLE );
106+ }
107+
108+ private String createMediaIdForPlaylistIndex (final long playlistId , final int index ) {
109+ return createMediaIdForPlaylist (playlistId ) + '/' + index ;
110+ }
46111
47112 @ Nullable
48113 public MediaBrowserServiceCompat .BrowserRoot onGetRoot (@ NonNull final String clientPackageName ,
@@ -51,15 +116,138 @@ public MediaBrowserServiceCompat.BrowserRoot onGetRoot(@NonNull final String cli
51116 Log .d (TAG , String .format ("MediaBrowserService.onGetRoot(%s, %s, %s)" ,
52117 clientPackageName , clientUid , rootHints ));
53118
54- return new MediaBrowserServiceCompat .BrowserRoot (MY_MEDIA_ROOT_ID , null );
119+ return new MediaBrowserServiceCompat .BrowserRoot (ID_ROOT , null );
55120 }
56121
57122 public void onLoadChildren (@ NonNull final String parentId ,
58123 @ NonNull final Result <List <MediaItem >> result ) {
59124 Log .d (TAG , String .format ("MediaBrowserService.onLoadChildren(%s)" , parentId ));
60125
61- final List <MediaBrowserCompat .MediaItem > mediaItems = new ArrayList <>();
62-
126+ final List <MediaItem > mediaItems = new ArrayList <>();
127+ final var parentIdUri = Uri .parse (parentId );
128+
129+ if (parentId .equals (ID_ROOT )) {
130+ mediaItems .add (
131+ createRootMediaItem (ID_BOOKMARKS ,
132+ playerService .getResources ().getString (R .string .tab_bookmarks )));
133+
134+ } else if (parentId .startsWith (ID_BOOKMARKS )) {
135+ final var path = parentIdUri .getPathSegments ();
136+ if (path .size () == 2 ) {
137+ populateBookmarks (mediaItems );
138+ } else if (path .size () == 3 ) {
139+ final var playlistId = Long .valueOf (path .get (2 ));
140+ populatePlaylist (playlistId , mediaItems );
141+ } else {
142+ Log .w (TAG , "Unknown playlist uri " + parentId );
143+ }
144+ }
63145 result .sendResult (mediaItems );
64146 }
147+
148+ private LocalPlaylistManager getPlaylistManager () {
149+ if (database == null ) {
150+ database = NewPipeDatabase .getInstance (playerService );
151+ }
152+ if (localPlaylistManager == null ) {
153+ localPlaylistManager = new LocalPlaylistManager (database );
154+ }
155+ return localPlaylistManager ;
156+ }
157+
158+ private void populateBookmarks (final List <MediaItem > mediaItems ) {
159+ // TODO async
160+ final var playlists = getPlaylistManager ().getPlaylists ().blockingFirst ();
161+ mediaItems .addAll (playlists .stream ().map (this ::createPlaylistMediaItem )
162+ .collect (Collectors .toList ()));
163+ }
164+
165+ private void populatePlaylist (final Long playlistId , final List <MediaItem > mediaItems ) {
166+ // TODO async
167+ getPlaylistManager ().getPlaylists ().blockingFirst ()
168+ .stream ().filter (it -> it .uid == playlistId ).findFirst ()
169+ .ifPresent (playlist -> {
170+ final var items = getPlaylistManager ().getPlaylistStreams (playlist .uid )
171+ .blockingFirst ();
172+ int index = 0 ;
173+ for (final var item : items ) {
174+ mediaItems .add (createPlaylistStreamMediaItem (playlist , item , index ));
175+ ++index ;
176+ }
177+ });
178+ }
179+
180+ private void playbackError (@ StringRes final int resId , final int code ) {
181+ sessionConnector .setCustomErrorMessage (playerService .getString (resId ), code );
182+ }
183+
184+ private PlayQueue extractPlayQueueFromMediaId (final String mediaId ) {
185+ final Uri mediaIdUri = Uri .parse (mediaId );
186+ if (mediaIdUri == null ) {
187+ // TODO handle errors
188+ return null ;
189+ }
190+ if (mediaId .startsWith (ID_BOOKMARKS )) {
191+ final var path = mediaIdUri .getPathSegments ();
192+ if (path .size () == 4 ) {
193+ final long playlistId = Long .parseLong (path .get (2 ));
194+ final int index = Integer .parseInt (path .get (3 ));
195+
196+ final var items = getPlaylistManager ().getPlaylists ().blockingFirst ()
197+ .stream ().filter (it -> it .uid == playlistId ).findFirst ()
198+ .map (playlist -> getPlaylistManager ().getPlaylistStreams (playlist .uid )
199+ .blockingFirst ().stream ()
200+ .map (PlaylistStreamEntry ::toStreamInfoItem )
201+ .collect (Collectors .toList ())).orElse (new ArrayList <>());
202+
203+ return new SinglePlayQueue (items , index );
204+ }
205+ }
206+
207+ playbackError (R .string .error_http_not_found ,
208+ PlaybackStateCompat .ERROR_CODE_NOT_SUPPORTED );
209+ return null ;
210+ }
211+
212+ @ Override
213+ public long getSupportedPrepareActions () {
214+ return PlaybackStateCompat .ACTION_PLAY_FROM_MEDIA_ID ;
215+ }
216+
217+ @ Override
218+ public void onPrepare (final boolean playWhenReady ) {
219+ // No need to prepare
220+ }
221+
222+ @ Override
223+ public void onPrepareFromMediaId (@ NonNull final String mediaId , final boolean playWhenReady ,
224+ @ Nullable final Bundle extras ) {
225+ Log .d (TAG , String .format ("MediaBrowserConnector.onPrepareFromMediaId(%s, %s, %s)" ,
226+ mediaId , playWhenReady , extras ));
227+
228+ if (playWhenReady ) {
229+ final var playQueue = extractPlayQueueFromMediaId (mediaId );
230+ if (playQueue != null ) {
231+ NavigationHelper .playOnBackgroundPlayer (playerService , playQueue , true );
232+ }
233+ }
234+ }
235+
236+ @ Override
237+ public void onPrepareFromSearch (@ NonNull final String query , final boolean playWhenReady ,
238+ @ Nullable final Bundle extras ) {
239+ playbackError (R .string .content_not_supported , PlaybackStateCompat .ERROR_CODE_NOT_SUPPORTED );
240+ }
241+
242+ @ Override
243+ public void onPrepareFromUri (@ NonNull final Uri uri , final boolean playWhenReady ,
244+ @ Nullable final Bundle extras ) {
245+ playbackError (R .string .content_not_supported , PlaybackStateCompat .ERROR_CODE_NOT_SUPPORTED );
246+ }
247+
248+ @ Override
249+ public boolean onCommand (@ NonNull final Player player , @ NonNull final String command ,
250+ @ Nullable final Bundle extras , @ Nullable final ResultReceiver cb ) {
251+ return false ;
252+ }
65253}
0 commit comments