11package org .schabi .newpipe .extractor .services .soundcloud ;
22
3+ import com .grack .nanojson .JsonArray ;
34import com .grack .nanojson .JsonObject ;
45import com .grack .nanojson .JsonParser ;
56import com .grack .nanojson .JsonParserException ;
7+
8+ import org .schabi .newpipe .extractor .NewPipe ;
69import org .schabi .newpipe .extractor .StreamingService ;
710import org .schabi .newpipe .extractor .downloader .Downloader ;
811import org .schabi .newpipe .extractor .exceptions .ExtractionException ;
1215import org .schabi .newpipe .extractor .stream .StreamInfoItem ;
1316import org .schabi .newpipe .extractor .stream .StreamInfoItemsCollector ;
1417
15- import javax .annotation .Nonnull ;
1618import java .io .IOException ;
1719
20+ import javax .annotation .Nonnull ;
21+ import javax .annotation .Nullable ;
22+
1823@ SuppressWarnings ("WeakerAccess" )
1924public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
25+ private static final int streamsPerRequestedPage = 15 ;
26+
2027 private String playlistId ;
2128 private JsonObject playlist ;
2229
23- private StreamInfoItemsCollector streamInfoItemsCollector = null ;
24- private String nextPageUrl = null ;
30+ private StreamInfoItemsCollector streamInfoItemsCollector ;
31+ private String nextPageUrl ;
2532
2633 public SoundcloudPlaylistExtractor (StreamingService service , ListLinkHandler linkHandler ) {
2734 super (service , linkHandler );
@@ -31,7 +38,7 @@ public SoundcloudPlaylistExtractor(StreamingService service, ListLinkHandler lin
3138 public void onFetchPage (@ Nonnull Downloader downloader ) throws IOException , ExtractionException {
3239
3340 playlistId = getLinkHandler ().getId ();
34- String apiUrl = "https://api.soundcloud.com/playlists/" + playlistId +
41+ String apiUrl = "https://api-v2 .soundcloud.com/playlists/" + playlistId +
3542 "?client_id=" + SoundcloudParsingHelper .clientId () +
3643 "&representation=compact" ;
3744
@@ -55,6 +62,7 @@ public String getName() {
5562 return playlist .getString ("title" );
5663 }
5764
65+ @ Nullable
5866 @ Override
5967 public String getThumbnailUrl () {
6068 String artworkUrl = playlist .getString ("artwork_url" );
@@ -64,21 +72,20 @@ public String getThumbnailUrl() {
6472 // if it also fails, return null
6573 try {
6674 final InfoItemsPage <StreamInfoItem > infoItems = getInitialPage ();
67- if (infoItems .getItems ().isEmpty ()) return null ;
6875
6976 for (StreamInfoItem item : infoItems .getItems ()) {
70- final String thumbnailUrl = item .getThumbnailUrl ();
71- if (thumbnailUrl == null || thumbnailUrl .isEmpty ()) continue ;
72-
73- String thumbnailUrlBetterResolution = thumbnailUrl .replace ("large.jpg" , "crop.jpg" );
74- return thumbnailUrlBetterResolution ;
77+ artworkUrl = item .getThumbnailUrl ();
78+ if (artworkUrl != null && !artworkUrl .isEmpty ()) break ;
7579 }
7680 } catch (Exception ignored ) {
7781 }
82+
83+ if (artworkUrl == null ) {
84+ return null ;
85+ }
7886 }
7987
80- String artworkUrlBetterResolution = artworkUrl .replace ("large.jpg" , "crop.jpg" );
81- return artworkUrlBetterResolution ;
88+ return artworkUrl .replace ("large.jpg" , "crop.jpg" );
8289 }
8390
8491 @ Override
@@ -110,27 +117,42 @@ public long getStreamCount() {
110117 @ Override
111118 public InfoItemsPage <StreamInfoItem > getInitialPage () throws IOException , ExtractionException {
112119 if (streamInfoItemsCollector == null ) {
113- computeStreamsAndNextPageUrl ();
120+ computeInitialTracksAndNextPageUrl ();
114121 }
115- return new InfoItemsPage <>(streamInfoItemsCollector , getNextPageUrl () );
122+ return new InfoItemsPage <>(streamInfoItemsCollector , nextPageUrl );
116123 }
117124
118- private void computeStreamsAndNextPageUrl () throws ExtractionException , IOException {
125+ private void computeInitialTracksAndNextPageUrl () throws IOException , ExtractionException {
119126 streamInfoItemsCollector = new StreamInfoItemsCollector (getServiceId ());
127+ StringBuilder nextPageUrlBuilder = new StringBuilder ("https://api-v2.soundcloud.com/tracks?client_id=" );
128+ nextPageUrlBuilder .append (SoundcloudParsingHelper .clientId ());
129+ nextPageUrlBuilder .append ("&ids=" );
130+
131+ JsonArray tracks = playlist .getArray ("tracks" );
132+ for (Object o : tracks ) {
133+ if (o instanceof JsonObject ) {
134+ JsonObject track = (JsonObject ) o ;
135+ if (track .has ("title" )) { // i.e. if full info is available
136+ streamInfoItemsCollector .commit (new SoundcloudStreamInfoItemExtractor (track ));
137+ } else {
138+ // %09d would be enough, but a 0 before the number does not create problems, so let's be sure
139+ nextPageUrlBuilder .append (String .format ("%010d," , track .getInt ("id" )));
140+ }
141+ }
142+ }
120143
121- // Note the "api", NOT "api-v2"
122- String apiUrl = "https://api.soundcloud.com/playlists/" + getId () + "/tracks"
123- + "?client_id=" + SoundcloudParsingHelper .clientId ()
124- + "&limit=20"
125- + "&linked_partitioning=1" ;
126-
127- nextPageUrl = SoundcloudParsingHelper .getStreamsFromApiMinItems (15 , streamInfoItemsCollector , apiUrl );
144+ nextPageUrlBuilder .setLength (nextPageUrlBuilder .length () - 1 ); // remove trailing ,
145+ nextPageUrl = nextPageUrlBuilder .toString ();
146+ if (nextPageUrl .endsWith ("&ids" )) {
147+ // there are no other videos
148+ nextPageUrl = "" ;
149+ }
128150 }
129151
130152 @ Override
131153 public String getNextPageUrl () throws IOException , ExtractionException {
132154 if (nextPageUrl == null ) {
133- computeStreamsAndNextPageUrl ();
155+ computeInitialTracksAndNextPageUrl ();
134156 }
135157 return nextPageUrl ;
136158 }
@@ -141,9 +163,36 @@ public InfoItemsPage<StreamInfoItem> getPage(String pageUrl) throws IOException,
141163 throw new ExtractionException (new IllegalArgumentException ("Page url is empty or null" ));
142164 }
143165
166+ // see computeInitialTracksAndNextPageUrl
167+ final int lengthFirstPartOfUrl = ("https://api-v2.soundcloud.com/tracks?client_id="
168+ + SoundcloudParsingHelper .clientId ()
169+ + "&ids=" ).length ();
170+ final int lengthOfEveryStream = 11 ;
171+
172+ String currentPageUrl , nextUrl ;
173+ int lengthMaxStreams = lengthFirstPartOfUrl + lengthOfEveryStream * streamsPerRequestedPage ;
174+ if (pageUrl .length () <= lengthMaxStreams ) {
175+ currentPageUrl = pageUrl ; // fetch every remaining video, there are less than the max
176+ nextUrl = "" ; // afterwards the list is complete
177+ } else {
178+ currentPageUrl = pageUrl .substring (0 , lengthMaxStreams );
179+ nextUrl = pageUrl .substring (0 , lengthFirstPartOfUrl ) + pageUrl .substring (lengthMaxStreams );
180+ }
181+
144182 StreamInfoItemsCollector collector = new StreamInfoItemsCollector (getServiceId ());
145- String nextPageUrl = SoundcloudParsingHelper .getStreamsFromApiMinItems (15 , collector , pageUrl );
183+ String response = NewPipe .getDownloader ().get (currentPageUrl , getExtractorLocalization ()).responseBody ();
184+
185+ try {
186+ JsonArray tracks = JsonParser .array ().from (response );
187+ for (Object track : tracks ) {
188+ if (track instanceof JsonObject ) {
189+ collector .commit (new SoundcloudStreamInfoItemExtractor ((JsonObject ) track ));
190+ }
191+ }
192+ } catch (JsonParserException e ) {
193+ throw new ParsingException ("Could not parse json response" , e );
194+ }
146195
147- return new InfoItemsPage <>(collector , nextPageUrl );
196+ return new InfoItemsPage <>(collector , nextUrl );
148197 }
149198}
0 commit comments