Skip to content

Commit f309571

Browse files
committed
[SoundCloud] Use api-v2 in PlaylistExtractor
Rewrote methods to calculate next page url and to get items from it. `api-v2` is different from `api` since the initial playlist page contains (usually) the full info of the first 3 streams and only the id of the other. Then the single tracks can be requested in batch using `/tracks?ids=id1,id2,...`.
1 parent 9eca7df commit f309571

1 file changed

Lines changed: 64 additions & 13 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractor.java

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package org.schabi.newpipe.extractor.services.soundcloud;
22

3+
import com.grack.nanojson.JsonArray;
34
import com.grack.nanojson.JsonObject;
45
import com.grack.nanojson.JsonParser;
56
import com.grack.nanojson.JsonParserException;
7+
8+
import org.schabi.newpipe.extractor.NewPipe;
69
import org.schabi.newpipe.extractor.StreamingService;
710
import org.schabi.newpipe.extractor.downloader.Downloader;
811
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@@ -13,15 +16,23 @@
1316
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
1417

1518
import javax.annotation.Nonnull;
19+
import javax.annotation.Nullable;
20+
1621
import java.io.IOException;
22+
import java.util.ArrayList;
23+
import java.util.List;
1724

1825
@SuppressWarnings("WeakerAccess")
1926
public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
27+
private static final int streamsPerRequestedPage = 15;
28+
2029
private String playlistId;
2130
private JsonObject playlist;
2231

23-
private StreamInfoItemsCollector streamInfoItemsCollector = null;
24-
private String nextPageUrl = null;
32+
private StreamInfoItemsCollector streamInfoItemsCollector;
33+
private List<Integer> nextTrackIds;
34+
private int nextTrackIdsIndex;
35+
private String nextPageUrl;
2536

2637
public SoundcloudPlaylistExtractor(StreamingService service, ListLinkHandler linkHandler) {
2738
super(service, linkHandler);
@@ -31,7 +42,7 @@ public SoundcloudPlaylistExtractor(StreamingService service, ListLinkHandler lin
3142
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
3243

3344
playlistId = getLinkHandler().getId();
34-
String apiUrl = "https://api.soundcloud.com/playlists/" + playlistId +
45+
String apiUrl = "https://api-v2.soundcloud.com/playlists/" + playlistId +
3546
"?client_id=" + SoundcloudParsingHelper.clientId() +
3647
"&representation=compact";
3748

@@ -110,27 +121,55 @@ public long getStreamCount() {
110121
@Override
111122
public InfoItemsPage<StreamInfoItem> getInitialPage() throws IOException, ExtractionException {
112123
if (streamInfoItemsCollector == null) {
113-
computeStreamsAndNextPageUrl();
124+
computeInitialTracksAndNextIds();
114125
}
115126
return new InfoItemsPage<>(streamInfoItemsCollector, getNextPageUrl());
116127
}
117128

118-
private void computeStreamsAndNextPageUrl() throws ExtractionException, IOException {
129+
private void computeInitialTracksAndNextIds() {
119130
streamInfoItemsCollector = new StreamInfoItemsCollector(getServiceId());
131+
nextTrackIds = new ArrayList<>();
132+
nextTrackIdsIndex = 0;
133+
134+
JsonArray tracks = playlist.getArray("tracks");
135+
for (Object o : tracks) {
136+
if (o instanceof JsonObject) {
137+
JsonObject track = (JsonObject) o;
138+
if (track.has("title")) { // i.e. if full info is available
139+
streamInfoItemsCollector.commit(new SoundcloudStreamInfoItemExtractor(track));
140+
} else {
141+
nextTrackIds.add(track.getInt("id"));
142+
}
143+
}
144+
}
145+
}
120146

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";
147+
private void computeAnotherNextPageUrl() throws IOException, ExtractionException {
148+
if (nextTrackIdsIndex >= nextTrackIds.size()) {
149+
nextPageUrl = ""; // there are no more tracks
150+
return;
151+
}
126152

127-
nextPageUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, streamInfoItemsCollector, apiUrl);
153+
StringBuilder urlBuilder = new StringBuilder("https://api-v2.soundcloud.com/tracks?client_id=");
154+
urlBuilder.append(SoundcloudParsingHelper.clientId());
155+
urlBuilder.append("&ids=");
156+
157+
int upperIndex = Math.min(nextTrackIdsIndex + streamsPerRequestedPage, nextTrackIds.size());
158+
for (int i = nextTrackIdsIndex; i < upperIndex; ++i) {
159+
urlBuilder.append(nextTrackIds.get(i));
160+
urlBuilder.append(","); // a , at the end is ok
161+
}
162+
163+
nextPageUrl = urlBuilder.toString();
128164
}
129165

130166
@Override
131167
public String getNextPageUrl() throws IOException, ExtractionException {
132168
if (nextPageUrl == null) {
133-
computeStreamsAndNextPageUrl();
169+
if (nextTrackIds == null) {
170+
computeInitialTracksAndNextIds();
171+
}
172+
computeAnotherNextPageUrl();
134173
}
135174
return nextPageUrl;
136175
}
@@ -142,8 +181,20 @@ public InfoItemsPage<StreamInfoItem> getPage(String pageUrl) throws IOException,
142181
}
143182

144183
StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
145-
String nextPageUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, collector, pageUrl);
184+
String response = NewPipe.getDownloader().get(pageUrl, getExtractorLocalization()).responseBody();
185+
186+
try {
187+
JsonArray tracks = JsonParser.array().from(response);
188+
for (Object track : tracks) {
189+
if (track instanceof JsonObject) {
190+
collector.commit(new SoundcloudStreamInfoItemExtractor((JsonObject) track));
191+
}
192+
}
193+
} catch (JsonParserException e) {
194+
throw new ParsingException("Could not parse json response", e);
195+
}
146196

197+
computeAnotherNextPageUrl();
147198
return new InfoItemsPage<>(collector, nextPageUrl);
148199
}
149200
}

0 commit comments

Comments
 (0)