Skip to content

Commit 3bd08a2

Browse files
committed
Adress requested changes and use final where possible in SoundcloudStreamExtractor
This commit moved the HLS parsing task to a separate method, did little performance improvements and used final where possible in the SoundcloudStreamExtractor file.
1 parent cbacd3c commit 3bd08a2

1 file changed

Lines changed: 51 additions & 30 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudStreamExtractor.java

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
1414
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
1515
import org.schabi.newpipe.extractor.exceptions.ParsingException;
16+
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
1617
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
1718
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
1819
import org.schabi.newpipe.extractor.localization.DateWrapper;
@@ -45,7 +46,7 @@ public SoundcloudStreamExtractor(StreamingService service, LinkHandler linkHandl
4546
public void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException {
4647
track = SoundcloudParsingHelper.resolveFor(downloader, getUrl());
4748

48-
String policy = track.getString("policy", EMPTY_STRING);
49+
final String policy = track.getString("policy", EMPTY_STRING);
4950
if (!policy.equals("ALLOW") && !policy.equals("MONETIZE")) {
5051
if (policy.equals("SNIP")) {
5152
throw new SoundCloudGoPlusContentException();
@@ -194,49 +195,49 @@ public List<AudioStream> getAudioStreams() throws IOException, ExtractionExcepti
194195

195196
try {
196197
final JsonArray transcodings = track.getObject("media").getArray("transcodings");
197-
198198
// Iterate a first time to see if there is a progressive MP3 stream available.
199199
// If yes, the MP3 HLS stream will be not added to audioStreams.
200-
201200
boolean mp3ProgressiveStreamInTranscodings = false;
202201

203202
for (final Object transcoding : transcodings) {
204203
final JsonObject t = (JsonObject) transcoding;
205204
if (t.getString("preset").contains("mp3") &&
206205
t.getObject("format").getString("protocol").equals("progressive")) {
207206
mp3ProgressiveStreamInTranscodings = true;
207+
break;
208208
}
209209
}
210210

211211
// Get information about what stream formats are available
212212
for (final Object transcoding : transcodings) {
213213
final JsonObject t = (JsonObject) transcoding;
214-
String url = t.getString("url");
215214
final String mediaUrl;
215+
final String preset = t.getString("preset");
216+
final String protocol = t.getObject("format").getString("protocol");
217+
String url = t.getString("url");
216218
final MediaFormat mediaFormat;
217219
final int bitrate;
218220

219221
if (!isNullOrEmpty(url)) {
220-
if (t.getString("preset").contains("mp3")) {
222+
if (preset.contains("mp3")) {
221223
// Don't add the MP3 HLS stream if there is a progressive stream present
222224
// because the two have the same bitrate
223-
if (t.getObject("format").getString("protocol").equals("hls") &&
224-
mp3ProgressiveStreamInTranscodings) {
225+
if (mp3ProgressiveStreamInTranscodings && protocol.equals("hls")) {
225226
continue;
226227
}
227-
228228
mediaFormat = MediaFormat.MP3;
229229
bitrate = 128;
230-
} else if (t.getString("preset").contains("opus")) {
230+
} else if (preset.contains("opus")) {
231231
mediaFormat = MediaFormat.OPUS;
232232
bitrate = 64;
233233
} else {
234+
// Unknown format
234235
continue;
235236
}
236237

237238
// TODO: move this to a separate method to generate valid urls when needed (e.g. resuming a paused stream)
238239

239-
if (t.getObject("format").getString("protocol").equals("progressive")) {
240+
if (protocol.equals("progressive")) {
240241
// This url points to the endpoint which generates a unique and short living url to the stream.
241242
url += "?client_id=" + SoundcloudParsingHelper.clientId();
242243
final String res = dl.get(url).responseBody();
@@ -248,38 +249,25 @@ public List<AudioStream> getAudioStreams() throws IOException, ExtractionExcepti
248249
} catch (final JsonParserException e) {
249250
throw new ParsingException("Could not parse streamable url", e);
250251
}
251-
} else if (t.getObject("format").getString("protocol").equals("hls")) {
252-
252+
} else if (protocol.equals("hls")) {
253253
// This url points to the endpoint which generates a unique and short living url to the stream.
254254
url += "?client_id=" + SoundcloudParsingHelper.clientId();
255255
final String res = dl.get(url).responseBody();
256256

257257
try {
258258
final JsonObject mp3HlsUrlObject = JsonParser.object().from(res);
259259
// Links in this file are also only valid for a short period.
260-
// Parsing the HLS manifest to get a single file by requesting a range equal to 0-track_length
261-
final String hlsManifestResponse;
262260
try {
263-
hlsManifestResponse = dl.get(mp3HlsUrlObject.getString("url")).responseBody();
264-
} catch (final IOException e) {
261+
mediaUrl = getSingleUrlFromHlsManifest(mp3HlsUrlObject.getString("url"));
262+
} catch (final ParsingException e) {
263+
// Something went during HLS manifest parsing, don't add this stream to audioStreams
265264
continue;
266265
}
267-
final List<String> hlsRangesList = new ArrayList<>();
268-
final Matcher regex = Pattern.compile("((https?):((//)|(\\\\))+[\\w\\d:#@%/;$()~_?+-=\\\\.&]*)")
269-
.matcher(hlsManifestResponse);
270-
271-
while (regex.find()) {
272-
hlsRangesList.add(hlsManifestResponse.substring(regex.start(0), regex.end(0)));
273-
}
274-
275-
final String hlsLastRangeUrl = hlsRangesList.get(hlsRangesList.size() - 1);
276-
final String[] hlsLastRangeUrlArray = hlsLastRangeUrl.split("/");
277-
278-
mediaUrl = HTTPS + hlsLastRangeUrlArray[2] + "/media/0/" + hlsLastRangeUrlArray[5] + "/" + hlsLastRangeUrlArray[6];
279266
} catch (final JsonParserException e) {
280267
throw new ParsingException("Could not parse streamable url", e);
281268
}
282269
} else {
270+
// Unknown protocol
283271
continue;
284272
}
285273

@@ -294,10 +282,43 @@ public List<AudioStream> getAudioStreams() throws IOException, ExtractionExcepti
294282
return audioStreams;
295283
}
296284

297-
private static String urlEncode(String value) {
285+
private final static Pattern PATTERN_WEB_URLS_IN_HLS_MANIFESTS = Pattern.compile("((http?|https?):((//)|(\\\\))+[\\w\\d:#@%/;$()~_?+-=\\\\.&]*)");
286+
287+
/** Parses a SoundCloud HLS manifest to get a single URL of HLS streams.
288+
* <p>
289+
* This method downloads the provided manifest URL, find all web occurrences using a regex, get
290+
* the last segment URL, changes its segment range to {@code 0/track-length} and return this string.
291+
* @param hlsManifestUrl the URL of the manifest to be parsed
292+
* @return a single URL that contains a range equal to the length of the track
293+
*/
294+
private static String getSingleUrlFromHlsManifest(final String hlsManifestUrl) throws ParsingException {
295+
final Downloader dl = NewPipe.getDownloader();
296+
final String hlsManifestResponse;
297+
298+
try {
299+
hlsManifestResponse = dl.get(hlsManifestUrl).responseBody();
300+
} catch (final IOException | ReCaptchaException e) {
301+
throw new ParsingException("Could not get SoundCloud HLS Manifest");
302+
}
303+
304+
final List<String> hlsRangesList = new ArrayList<>();
305+
final Matcher pattern_matches = PATTERN_WEB_URLS_IN_HLS_MANIFESTS.matcher(hlsManifestResponse);
306+
307+
while (pattern_matches.find()) {
308+
hlsRangesList.add(hlsManifestResponse.substring(pattern_matches.start(0),
309+
pattern_matches.end(0)));
310+
}
311+
312+
final String hlsLastRangeUrl = hlsRangesList.get(hlsRangesList.size() - 1);
313+
final String[] hlsLastRangeUrlArray = hlsLastRangeUrl.split("/");
314+
315+
return HTTPS + hlsLastRangeUrlArray[2] + "/media/0/" + hlsLastRangeUrlArray[5] + "/" + hlsLastRangeUrlArray[6];
316+
}
317+
318+
private static String urlEncode(final String value) {
298319
try {
299320
return URLEncoder.encode(value, UTF_8);
300-
} catch (UnsupportedEncodingException e) {
321+
} catch (final UnsupportedEncodingException e) {
301322
throw new IllegalStateException(e);
302323
}
303324
}

0 commit comments

Comments
 (0)