Skip to content

Commit 650f092

Browse files
authored
Merge pull request #465 from XiangRongLin/playlist_continuation
[YouTube] Fix playlist continuations extraction
2 parents 334e1e9 + 5bceff0 commit 650f092

2 files changed

Lines changed: 70 additions & 16 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubePlaylistExtractor.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,10 @@ public InfoItemsPage<StreamInfoItem> getInitialPage() {
190190
return new InfoItemsPage<>(collector, null);
191191
} else if (contents.getObject(0).has("playlistVideoListRenderer")) {
192192
final JsonObject videos = contents.getObject(0).getObject("playlistVideoListRenderer");
193-
collectStreamsFrom(collector, videos.getArray("contents"));
193+
final JsonArray videosArray = videos.getArray("contents");
194+
collectStreamsFrom(collector, videosArray);
194195

195-
nextPage = getNextPageFrom(videos.getArray("continuations"));
196+
nextPage = getNextPageFrom(videosArray);
196197
}
197198

198199
return new InfoItemsPage<>(collector, nextPage);
@@ -207,24 +208,34 @@ public InfoItemsPage<StreamInfoItem> getPage(final Page page) throws IOException
207208
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
208209
final JsonArray ajaxJson = getJsonResponse(page.getUrl(), getExtractorLocalization());
209210

210-
final JsonObject sectionListContinuation = ajaxJson.getObject(1).getObject("response")
211-
.getObject("continuationContents").getObject("playlistVideoListContinuation");
211+
final JsonArray continuation = ajaxJson.getObject(1)
212+
.getObject("response")
213+
.getArray("onResponseReceivedActions")
214+
.getObject(0)
215+
.getObject("appendContinuationItemsAction")
216+
.getArray("continuationItems");
212217

213-
collectStreamsFrom(collector, sectionListContinuation.getArray("contents"));
218+
collectStreamsFrom(collector, continuation);
214219

215-
return new InfoItemsPage<>(collector, getNextPageFrom(sectionListContinuation.getArray("continuations")));
220+
return new InfoItemsPage<>(collector, getNextPageFrom(continuation));
216221
}
217222

218-
private Page getNextPageFrom(final JsonArray continuations) {
219-
if (isNullOrEmpty(continuations)) {
223+
private Page getNextPageFrom(final JsonArray contents) {
224+
if (isNullOrEmpty(contents)) {
220225
return null;
221226
}
222227

223-
final JsonObject nextContinuationData = continuations.getObject(0).getObject("nextContinuationData");
224-
final String continuation = nextContinuationData.getString("continuation");
225-
final String clickTrackingParams = nextContinuationData.getString("clickTrackingParams");
226-
return new Page("https://www.youtube.com/browse_ajax?ctoken=" + continuation + "&continuation=" + continuation
227-
+ "&itct=" + clickTrackingParams);
228+
final JsonObject lastElement = contents.getObject(contents.size() - 1);
229+
if (lastElement.has("continuationItemRenderer")) {
230+
final String continuation = lastElement
231+
.getObject("continuationItemRenderer")
232+
.getObject("continuationEndpoint")
233+
.getObject("continuationCommand")
234+
.getString("token");
235+
return new Page("https://www.youtube.com/browse_ajax?continuation=" + continuation);
236+
} else {
237+
return null;
238+
}
228239
}
229240

230241
private void collectStreamsFrom(final StreamInfoItemsCollector collector, final JsonArray videos) {

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractorTest.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
import org.junit.BeforeClass;
44
import org.junit.Ignore;
55
import org.junit.Test;
6+
import org.junit.runner.RunWith;
7+
import org.junit.runners.Suite;
8+
import org.junit.runners.Suite.SuiteClasses;
69
import org.schabi.newpipe.DownloaderTestImpl;
710
import org.schabi.newpipe.extractor.ListExtractor;
811
import org.schabi.newpipe.extractor.NewPipe;
@@ -11,10 +14,17 @@
1114
import org.schabi.newpipe.extractor.exceptions.ParsingException;
1215
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
1316
import org.schabi.newpipe.extractor.services.BasePlaylistExtractorTest;
17+
import org.schabi.newpipe.extractor.services.youtube.YoutubePlaylistExtractorTest.ContinuationsTests;
18+
import org.schabi.newpipe.extractor.services.youtube.YoutubePlaylistExtractorTest.HugePlaylist;
19+
import org.schabi.newpipe.extractor.services.youtube.YoutubePlaylistExtractorTest.LearningPlaylist;
20+
import org.schabi.newpipe.extractor.services.youtube.YoutubePlaylistExtractorTest.NotAvailable;
21+
import org.schabi.newpipe.extractor.services.youtube.YoutubePlaylistExtractorTest.TimelessPopHits;
1422
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubePlaylistExtractor;
1523
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
1624

25+
import static junit.framework.TestCase.assertFalse;
1726
import static org.junit.Assert.assertEquals;
27+
import static org.junit.Assert.assertNull;
1828
import static org.junit.Assert.assertTrue;
1929
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertIsSecureUrl;
2030
import static org.schabi.newpipe.extractor.ServiceList.YouTube;
@@ -23,6 +33,9 @@
2333
/**
2434
* Test for {@link YoutubePlaylistExtractor}
2535
*/
36+
@RunWith(Suite.class)
37+
@SuiteClasses({NotAvailable.class, TimelessPopHits.class, HugePlaylist.class,
38+
LearningPlaylist.class, ContinuationsTests.class})
2639
public class YoutubePlaylistExtractorTest {
2740

2841
public static class NotAvailable {
@@ -114,7 +127,7 @@ public void testThumbnailUrl() throws Exception {
114127

115128
@Ignore
116129
@Test
117-
public void testBannerUrl() throws Exception {
130+
public void testBannerUrl() {
118131
final String bannerUrl = extractor.getBannerUrl();
119132
assertIsSecureUrl(bannerUrl);
120133
assertTrue(bannerUrl, bannerUrl.contains("yt"));
@@ -227,7 +240,7 @@ public void testThumbnailUrl() throws Exception {
227240

228241
@Ignore
229242
@Test
230-
public void testBannerUrl() throws Exception {
243+
public void testBannerUrl() {
231244
final String bannerUrl = extractor.getBannerUrl();
232245
assertIsSecureUrl(bannerUrl);
233246
assertTrue(bannerUrl, bannerUrl.contains("yt"));
@@ -324,7 +337,7 @@ public void testThumbnailUrl() throws Exception {
324337

325338
@Ignore
326339
@Test
327-
public void testBannerUrl() throws Exception {
340+
public void testBannerUrl() {
328341
final String bannerUrl = extractor.getBannerUrl();
329342
assertIsSecureUrl(bannerUrl);
330343
assertTrue(bannerUrl, bannerUrl.contains("yt"));
@@ -352,4 +365,34 @@ public void testStreamCount() throws Exception {
352365
assertTrue("Error in the streams count", extractor.getStreamCount() > 40);
353366
}
354367
}
368+
369+
public static class ContinuationsTests {
370+
371+
@BeforeClass
372+
public static void setUp() {
373+
NewPipe.init(DownloaderTestImpl.getInstance());
374+
}
375+
376+
@Test
377+
public void testNoContinuations() throws Exception {
378+
final YoutubePlaylistExtractor extractor = (YoutubePlaylistExtractor) YouTube
379+
.getPlaylistExtractor(
380+
"https://www.youtube.com/playlist?list=PLXJg25X-OulsVsnvZ7RVtSDW-id9_RzAO");
381+
extractor.fetchPage();
382+
383+
assertNoMoreItems(extractor);
384+
}
385+
386+
@Test
387+
public void testOnlySingleContinuation() throws Exception {
388+
final YoutubePlaylistExtractor extractor = (YoutubePlaylistExtractor) YouTube
389+
.getPlaylistExtractor(
390+
"https://www.youtube.com/playlist?list=PLjgwFL8urN2DFRuRkFTkmtHjyoNWHHdZX");
391+
extractor.fetchPage();
392+
393+
final ListExtractor.InfoItemsPage<StreamInfoItem> page = defaultTestMoreItems(
394+
extractor);
395+
assertFalse("More items available when it shouldn't", page.hasNextPage());
396+
}
397+
}
355398
}

0 commit comments

Comments
 (0)