11package org .schabi .newpipe .extractor .services .youtube .extractors ;
22
33import static org .schabi .newpipe .extractor .services .youtube .linkHandler .YoutubeParsingHelper .getJsonResponse ;
4+ import static org .schabi .newpipe .extractor .services .youtube .linkHandler .YoutubeParsingHelper .getUrlFromNavigationEndpoint ;
45
56import com .grack .nanojson .JsonArray ;
67import com .grack .nanojson .JsonObject ;
2526 */
2627public class YoutubeMixPlaylistExtractor extends PlaylistExtractor {
2728
28-
29- private final static String CONTENTS = "contents" ;
30- private final static String RESPONSE = "response" ;
31- private final static String PLAYLIST = "playlist" ;
32- private final static String TWO_COLUMN_WATCH_NEXT_RESULTS = "twoColumnWatchNextResults" ;
33- private final static String PLAYLIST_PANEL_VIDEO_RENDERER = "playlistPanelVideoRenderer" ;
34-
29+ private JsonObject initialData ;
3530 private JsonObject playlistData ;
3631
3732 public YoutubeMixPlaylistExtractor (StreamingService service , ListLinkHandler linkHandler ) {
@@ -43,9 +38,9 @@ public void onFetchPage(@Nonnull Downloader downloader)
4338 throws IOException , ExtractionException {
4439 final String url = getUrl () + "&pbj=1" ;
4540 final JsonArray ajaxJson = getJsonResponse (url , getExtractorLocalization ());
46- JsonObject initialData = ajaxJson .getObject (3 ).getObject (RESPONSE );
47- playlistData = initialData .getObject (CONTENTS ).getObject (TWO_COLUMN_WATCH_NEXT_RESULTS )
48- .getObject (PLAYLIST ).getObject (PLAYLIST );
41+ initialData = ajaxJson .getObject (3 ).getObject ("response" );
42+ playlistData = initialData .getObject ("contents" ).getObject ("twoColumnWatchNextResults" )
43+ .getObject ("playlist" ).getObject ("playlist" );
4944 }
5045
5146 @ Nonnull
@@ -62,7 +57,14 @@ public String getName() throws ParsingException {
6257 public String getThumbnailUrl () throws ParsingException {
6358 try {
6459 final String playlistId = playlistData .getString ("playlistId" );
65- return getThumbnailUrlFromId (playlistId );
60+ try {
61+ return getThumbnailUrlFromPlaylistId (playlistId );
62+ } catch (ParsingException e ) {
63+ //fallback to thumbnail of current video. Always the case for channel mix
64+ return getThumbnailUrlFromVideoId (
65+ initialData .getObject ("currentVideoEndpoint" ).getObject ("watchEndpoint" )
66+ .getString ("videoId" ));
67+ }
6668 } catch (Exception e ) {
6769 throw new ParsingException ("Could not get playlist thumbnail" , e );
6870 }
@@ -101,20 +103,26 @@ public long getStreamCount() {
101103 @ Override
102104 public InfoItemsPage <StreamInfoItem > getInitialPage () throws ExtractionException {
103105 StreamInfoItemsCollector collector = new StreamInfoItemsCollector (getServiceId ());
104- collectStreamsFrom (collector , playlistData .getArray (CONTENTS ));
106+ collectStreamsFrom (collector , playlistData .getArray ("contents" ));
105107 return new InfoItemsPage <>(collector , getNextPageUrl ());
106108 }
107109
108110 @ Override
109111 public String getNextPageUrl () throws ExtractionException {
110- final JsonObject lastStream = ((JsonObject ) playlistData .getArray (CONTENTS )
111- .get (playlistData .getArray (CONTENTS ).size () - 1 ));
112- if (lastStream == null || lastStream .getObject (PLAYLIST_PANEL_VIDEO_RENDERER ) == null ) {
112+ return getNextPageUrlFrom (playlistData );
113+ }
114+
115+ private String getNextPageUrlFrom (JsonObject playlistData ) throws ExtractionException {
116+ final JsonObject lastStream = ((JsonObject ) playlistData .getArray ("contents" )
117+ .get (playlistData .getArray ("contents" ).size () - 1 ));
118+ if (lastStream == null || lastStream .getObject ("playlistPanelVideoRenderer" ) == null ) {
113119 throw new ExtractionException ("Could not extract next page url" );
114120 }
115- return "https://youtube.com" + lastStream .getObject (PLAYLIST_PANEL_VIDEO_RENDERER )
116- .getObject ("navigationEndpoint" ).getObject ("commandMetadata" )
117- .getObject ("webCommandMetadata" ).getString ("url" ) + "&pbj=1" ;
121+ //Index of video in mix is missing, but adding it doesn't appear to have any effect.
122+ //And since the index needs to be tracked by us, it is left out
123+ return getUrlFromNavigationEndpoint (
124+ lastStream .getObject ("playlistPanelVideoRenderer" ).getObject ("navigationEndpoint" ))
125+ + "&pbj=1" ;
118126 }
119127
120128 @ Override
@@ -127,21 +135,20 @@ public InfoItemsPage<StreamInfoItem> getPage(final String pageUrl)
127135
128136 StreamInfoItemsCollector collector = new StreamInfoItemsCollector (getServiceId ());
129137 final JsonArray ajaxJson = getJsonResponse (pageUrl , getExtractorLocalization ());
130- playlistData =
131- ajaxJson .getObject (3 ).getObject (RESPONSE ).getObject (CONTENTS )
132- .getObject (TWO_COLUMN_WATCH_NEXT_RESULTS ).getObject (PLAYLIST )
133- .getObject (PLAYLIST );
134- final JsonArray streams = playlistData .getArray (CONTENTS );
138+ JsonObject playlistData =
139+ ajaxJson .getObject (3 ).getObject ("response" ).getObject ("contents" )
140+ .getObject ("twoColumnWatchNextResults" ).getObject ("playlist" )
141+ .getObject ("playlist" );
142+ final JsonArray streams = playlistData .getArray ("contents" );
135143 //Because continuation requests are created with the last video of previous request as start
136144 streams .remove (0 );
137145 collectStreamsFrom (collector , streams );
138- return new InfoItemsPage <>(collector , getNextPageUrl ( ));
146+ return new InfoItemsPage <>(collector , getNextPageUrlFrom ( playlistData ));
139147 }
140148
141149 private void collectStreamsFrom (
142150 @ Nonnull StreamInfoItemsCollector collector ,
143151 @ Nullable JsonArray streams ) {
144- collector .reset ();
145152
146153 if (streams == null ) {
147154 return ;
@@ -152,24 +159,30 @@ private void collectStreamsFrom(
152159 for (Object stream : streams ) {
153160 if (stream instanceof JsonObject ) {
154161 JsonObject streamInfo = ((JsonObject ) stream )
155- .getObject (PLAYLIST_PANEL_VIDEO_RENDERER );
162+ .getObject ("playlistPanelVideoRenderer" );
156163 if (streamInfo != null ) {
157164 collector .commit (new YoutubeStreamInfoItemExtractor (streamInfo , timeAgoParser ));
158165 }
159166 }
160167 }
161168 }
162169
163- private String getThumbnailUrlFromId (String playlistId ) throws ParsingException {
170+ private String getThumbnailUrlFromPlaylistId (String playlistId ) throws ParsingException {
164171 final String videoId ;
165172 if (playlistId .startsWith ("RDMM" )) {
166173 videoId = playlistId .substring (4 );
174+ } else if (playlistId .startsWith ("RDCMUC" )) {
175+ throw new ParsingException ("is channel mix" );
167176 } else {
168177 videoId = playlistId .substring (2 );
169178 }
170179 if (videoId .isEmpty ()) {
171180 throw new ParsingException ("videoId is empty" );
172181 }
182+ return getThumbnailUrlFromVideoId (videoId );
183+ }
184+
185+ private String getThumbnailUrlFromVideoId (String videoId ) {
173186 return "https://i.ytimg.com/vi/" + videoId + "/hqdefault.jpg" ;
174187 }
175188}
0 commit comments