diff --git a/build.gradle b/build.gradle
index 0bd985d15..17896966b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,5 +10,8 @@ dependencies {
implementation 'org.mozilla:rhino:1.7.7.1'
testImplementation 'junit:junit:4.12'
+
+ sourceCompatibility = 1.7
+ targetCompatibility = 1.7
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/Extractor.java b/src/main/java/org/schabi/newpipe/extractor/Extractor.java
index c704e6f56..39a39ae05 100644
--- a/src/main/java/org/schabi/newpipe/extractor/Extractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/Extractor.java
@@ -1,35 +1,73 @@
package org.schabi.newpipe.extractor;
-import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import java.io.Serializable;
+import java.io.IOException;
-public abstract class Extractor implements Serializable {
- private final int serviceId;
- private final String url;
- private final UrlIdHandler urlIdHandler;
- private final StreamInfoItemCollector previewInfoCollector;
+public abstract class Extractor {
+ /**
+ * {@link StreamingService} currently related to this extractor.
+ * Useful for getting other things from a service (like the url handlers for cleaning/accepting/get id from urls).
+ */
+ private final StreamingService service;
- public Extractor(UrlIdHandler urlIdHandler, int serviceId, String url) {
- this.urlIdHandler = urlIdHandler;
- this.serviceId = serviceId;
- this.url = url;
- this.previewInfoCollector = new StreamInfoItemCollector(serviceId);
+ /**
+ * Dirty/original url that was passed in the constructor.
+ *
+ * What makes a url "dirty" or not is, for example, the additional parameters
+ * (not important as—in this case—the id):
+ *
+ * https://www.youtube.com/watch?v=a9Zf_258aTI&t=4s → &t=4s
+ *
+ * But as you can imagine, the time parameter is very important when calling, for example, {@link org.schabi.newpipe.extractor.stream.StreamExtractor#getTimeStamp()}.
+ */
+ private final String originalUrl;
+
+ /**
+ * The cleaned url, result of passing the {@link #originalUrl} to the associated urlIdHandler ({@link #getUrlIdHandler()}).
+ *
+ * Is lazily-cleaned by calling {@link #getCleanUrl()}
+ */
+ private String cleanUrl;
+
+ public Extractor(StreamingService service, String url) throws ExtractionException {
+ this.service = service;
+ this.originalUrl = url;
}
- public String getUrl() {
- return url;
+ /**
+ * @return a {@link UrlIdHandler} of the current extractor type (e.g. a ChannelExtractor should return a channel url handler).
+ */
+ protected abstract UrlIdHandler getUrlIdHandler() throws ParsingException;
+
+ /**
+ * Fetch the current page.
+ */
+ public abstract void fetchPage() throws IOException, ExtractionException;
+
+ public String getOriginalUrl() {
+ return originalUrl;
}
- public UrlIdHandler getUrlIdHandler() {
- return urlIdHandler;
+ public String getCleanUrl() {
+ if (cleanUrl != null && !cleanUrl.isEmpty()) return cleanUrl;
+
+ try {
+ cleanUrl = getUrlIdHandler().cleanUrl(originalUrl);
+ } catch (Exception e) {
+ cleanUrl = null;
+ // Fallback to the original url
+ return originalUrl;
+ }
+ return cleanUrl;
+ }
+
+ public StreamingService getService() {
+ return service;
}
public int getServiceId() {
- return serviceId;
- }
-
- protected StreamInfoItemCollector getStreamPreviewInfoCollector() {
- return previewInfoCollector;
+ return service.getServiceId();
}
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/Info.java b/src/main/java/org/schabi/newpipe/extractor/Info.java
index f55a5740d..6443b3d57 100644
--- a/src/main/java/org/schabi/newpipe/extractor/Info.java
+++ b/src/main/java/org/schabi/newpipe/extractor/Info.java
@@ -1,8 +1,8 @@
package org.schabi.newpipe.extractor;
import java.io.Serializable;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Vector;
public abstract class Info implements Serializable {
@@ -15,5 +15,5 @@ public abstract class Info implements Serializable {
public String url;
public String name;
- public List errors = new Vector<>();
+ public List errors = new ArrayList<>();
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/InfoItemCollector.java b/src/main/java/org/schabi/newpipe/extractor/InfoItemCollector.java
index b55deda04..f65372224 100644
--- a/src/main/java/org/schabi/newpipe/extractor/InfoItemCollector.java
+++ b/src/main/java/org/schabi/newpipe/extractor/InfoItemCollector.java
@@ -2,8 +2,8 @@ package org.schabi.newpipe.extractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Vector;
/*
* Created by Christian Schabesberger on 12.02.17.
@@ -26,8 +26,8 @@ import java.util.Vector;
*/
public abstract class InfoItemCollector {
- private List itemList = new Vector<>();
- private List errors = new Vector<>();
+ private List itemList = new ArrayList<>();
+ private List errors = new ArrayList<>();
private int serviceId = -1;
public InfoItemCollector(int serviceId) {
@@ -46,7 +46,7 @@ public abstract class InfoItemCollector {
if (serviceId != otherC.serviceId) {
throw new ExtractionException("Service Id does not equal: "
+ NewPipe.getNameOfService(serviceId)
- + " and " + NewPipe.getNameOfService(otherC.serviceId));
+ + " and " + NewPipe.getNameOfService((otherC.serviceId)));
}
errors.addAll(otherC.errors);
itemList.addAll(otherC.itemList);
diff --git a/src/main/java/org/schabi/newpipe/extractor/ListExtractor.java b/src/main/java/org/schabi/newpipe/extractor/ListExtractor.java
index 318cde7d5..7af2f75e8 100644
--- a/src/main/java/org/schabi/newpipe/extractor/ListExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/ListExtractor.java
@@ -4,6 +4,7 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
import java.io.IOException;
+import java.util.List;
/**
* Base class to extractors that have a list (e.g. playlists, channels).
@@ -11,16 +12,40 @@ import java.io.IOException;
public abstract class ListExtractor extends Extractor {
protected String nextStreamsUrl;
- public ListExtractor(UrlIdHandler urlIdHandler, int serviceId, String url) {
- super(urlIdHandler, serviceId, url);
+ /**
+ * Get a new ListExtractor with the given nextStreamsUrl set.
+ *
+ * The extractor WILL fetch the page if {@link #fetchPageUponCreation()} return true, otherwise, it will NOT.
+ *
+ * You can call {@link #fetchPage()} later, but this is mainly used just to get more items, so we don't waste bandwidth
+ * downloading the whole page, but if the service that is being implemented need it, just do its own logic in {@link #fetchPageUponCreation()}.
+ */
+ public ListExtractor(StreamingService service, String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ super(service, url);
+ setNextStreamsUrl(nextStreamsUrl);
+
+ if (fetchPageUponCreation()) {
+ fetchPage();
+ }
}
- public boolean hasMoreStreams(){
+ /**
+ * Decide if the page will be fetched upon creation.
+ *
+ * The default implementation checks if the nextStreamsUrl is null or empty (indication that the caller
+ * don't need or know what is the next page, thus, fetch the page).
+ */
+ protected boolean fetchPageUponCreation() {
+ return nextStreamsUrl == null || nextStreamsUrl.isEmpty();
+ }
+
+ public abstract StreamInfoItemCollector getStreams() throws IOException, ExtractionException;
+ public abstract NextItemsResult getNextStreams() throws IOException, ExtractionException;
+
+ public boolean hasMoreStreams() {
return nextStreamsUrl != null && !nextStreamsUrl.isEmpty();
}
- public abstract StreamInfoItemCollector getNextStreams() throws ExtractionException, IOException;
-
public String getNextStreamsUrl() {
return nextStreamsUrl;
}
@@ -29,4 +54,29 @@ public abstract class ListExtractor extends Extractor {
this.nextStreamsUrl = nextStreamsUrl;
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Inner
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static class NextItemsResult {
+ /**
+ * The current list of items to this result
+ */
+ public final List nextItemsList;
+
+ /**
+ * Next url to fetch more items
+ */
+ public final String nextItemsUrl;
+
+ public NextItemsResult(List nextItemsList, String nextItemsUrl) {
+ this.nextItemsList = nextItemsList;
+ this.nextItemsUrl = nextItemsUrl;
+ }
+
+ public boolean hasMoreStreams() {
+ return nextItemsUrl != null && !nextItemsUrl.isEmpty();
+ }
+ }
+
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/ListInfo.java b/src/main/java/org/schabi/newpipe/extractor/ListInfo.java
new file mode 100644
index 000000000..d625c14e8
--- /dev/null
+++ b/src/main/java/org/schabi/newpipe/extractor/ListInfo.java
@@ -0,0 +1,9 @@
+package org.schabi.newpipe.extractor;
+
+import java.util.List;
+
+public abstract class ListInfo extends Info {
+ public List related_streams;
+ public boolean has_more_streams;
+ public String next_streams_url;
+}
diff --git a/src/main/java/org/schabi/newpipe/extractor/NewPipe.java b/src/main/java/org/schabi/newpipe/extractor/NewPipe.java
index 044c85967..7b4057d94 100644
--- a/src/main/java/org/schabi/newpipe/extractor/NewPipe.java
+++ b/src/main/java/org/schabi/newpipe/extractor/NewPipe.java
@@ -1,7 +1,5 @@
package org.schabi.newpipe.extractor;
-import org.schabi.newpipe.extractor.exceptions.ExtractionException;
-
/*
* Created by Christian Schabesberger on 23.08.15.
*
@@ -22,54 +20,16 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
* along with NewPipe. If not, see .
*/
-/**
- * Provides access to the video streaming services supported by NewPipe.
- * Currently only Youtube until the API becomes more stable.
- */
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
-@SuppressWarnings("ALL")
+/**
+ * Provides access to streaming services supported by NewPipe.
+ */
public class NewPipe {
private static final String TAG = NewPipe.class.toString();
-
- private NewPipe() {
- }
-
private static Downloader downloader = null;
- public static StreamingService[] getServices() {
- return ServiceList.serviceList;
- }
-
- public static StreamingService getService(int serviceId) throws ExtractionException {
- for (StreamingService s : ServiceList.serviceList) {
- if (s.getServiceId() == serviceId) {
- return s;
- }
- }
- return null;
- }
-
- public static StreamingService getService(String serviceName) throws ExtractionException {
- return ServiceList.serviceList[getIdOfService(serviceName)];
- }
-
- public static String getNameOfService(int id) {
- try {
- return getService(id).getServiceInfo().name;
- } catch (Exception e) {
- System.err.println("Service id not known");
- e.printStackTrace();
- return "";
- }
- }
-
- public static int getIdOfService(String serviceName) {
- for (int i = 0; i < ServiceList.serviceList.length; i++) {
- if (ServiceList.serviceList[i].getServiceInfo().name.equals(serviceName)) {
- return i;
- }
- }
- return -1;
+ private NewPipe() {
}
public static void init(Downloader d) {
@@ -80,12 +40,63 @@ public class NewPipe {
return downloader;
}
- public static StreamingService getServiceByUrl(String url) {
- for (StreamingService s : ServiceList.serviceList) {
- if (s.getLinkTypeByUrl(url) != StreamingService.LinkType.NONE) {
- return s;
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static StreamingService[] getServices() {
+ final ServiceList[] values = ServiceList.values();
+ final StreamingService[] streamingServices = new StreamingService[values.length];
+
+ for (int i = 0; i < values.length; i++) streamingServices[i] = values[i].getService();
+
+ return streamingServices;
+ }
+
+ public static StreamingService getService(int serviceId) throws ExtractionException {
+ for (ServiceList item : ServiceList.values()) {
+ if (item.getService().getServiceId() == serviceId) {
+ return item.getService();
}
}
- return null;
+ throw new ExtractionException("There's no service with the id = \"" + serviceId + "\"");
+ }
+
+ public static StreamingService getService(String serviceName) throws ExtractionException {
+ for (ServiceList item : ServiceList.values()) {
+ if (item.getService().getServiceInfo().name.equals(serviceName)) {
+ return item.getService();
+ }
+ }
+ throw new ExtractionException("There's no service with the name = \"" + serviceName + "\"");
+ }
+
+ public static StreamingService getServiceByUrl(String url) throws ExtractionException {
+ for (ServiceList item : ServiceList.values()) {
+ if (item.getService().getLinkTypeByUrl(url) != StreamingService.LinkType.NONE) {
+ return item.getService();
+ }
+ }
+ throw new ExtractionException("No service can handle the url = \"" + url + "\"");
+ }
+
+ public static int getIdOfService(String serviceName) {
+ try {
+ //noinspection ConstantConditions
+ return getService(serviceName).getServiceId();
+ } catch (ExtractionException ignored) {
+ return -1;
+ }
+ }
+
+ public static String getNameOfService(int id) {
+ try {
+ //noinspection ConstantConditions
+ return getService(id).getServiceInfo().name;
+ } catch (Exception e) {
+ System.err.println("Service id not known");
+ e.printStackTrace();
+ return "";
+ }
}
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/ServiceList.java b/src/main/java/org/schabi/newpipe/extractor/ServiceList.java
index 26a748368..64f79ff25 100644
--- a/src/main/java/org/schabi/newpipe/extractor/ServiceList.java
+++ b/src/main/java/org/schabi/newpipe/extractor/ServiceList.java
@@ -1,15 +1,36 @@
package org.schabi.newpipe.extractor;
-import org.schabi.newpipe.extractor.services.youtube.YoutubeService;
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudService;
+import org.schabi.newpipe.extractor.services.youtube.YoutubeService;
-/*
- * Created by the-scrabi on 18.02.17.
+/**
+ * A list of supported services.
*/
+public enum ServiceList {
+ Youtube(new YoutubeService(0, "Youtube")),
+ SoundCloud(new SoundcloudService(1, "SoundCloud"));
+// DailyMotion(new DailyMotionService(2, "DailyMotion"));
-class ServiceList {
- public static final StreamingService[] serviceList = {
- new YoutubeService(0),
- new SoundcloudService(1)
- };
+ private final StreamingService service;
+
+ ServiceList(StreamingService service) {
+ this.service = service;
+ }
+
+ public StreamingService getService() {
+ return service;
+ }
+
+ public StreamingService.ServiceInfo getServiceInfo() {
+ return service.getServiceInfo();
+ }
+
+ public int getId() {
+ return service.getServiceId();
+ }
+
+ @Override
+ public String toString() {
+ return service.getServiceInfo().name;
+ }
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/StreamingService.java b/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
index 8d5b87ffa..ae2241f30 100644
--- a/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
+++ b/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
@@ -10,7 +10,11 @@ import java.io.IOException;
public abstract class StreamingService {
public class ServiceInfo {
- public String name = "";
+ public final String name;
+
+ public ServiceInfo(String name) {
+ this.name = name;
+ }
}
public enum LinkType {
@@ -20,35 +24,46 @@ public abstract class StreamingService {
PLAYLIST
}
- private int serviceId;
+ private final int serviceId;
+ private final ServiceInfo serviceInfo;
- public StreamingService(int id) {
- serviceId = id;
+ public StreamingService(int id, String name) {
+ this.serviceId = id;
+ this.serviceInfo = new ServiceInfo(name);
}
- public abstract ServiceInfo getServiceInfo();
-
- public abstract UrlIdHandler getStreamUrlIdHandlerInstance();
- public abstract UrlIdHandler getChannelUrlIdHandlerInstance();
- public abstract UrlIdHandler getPlaylistUrlIdHandlerInstance();
- public abstract SearchEngine getSearchEngineInstance();
- public abstract SuggestionExtractor getSuggestionExtractorInstance();
- public abstract StreamExtractor getStreamExtractorInstance(String url) throws IOException, ExtractionException;
- public abstract ChannelExtractor getChannelExtractorInstance(String url) throws ExtractionException, IOException;
- public abstract PlaylistExtractor getPlaylistExtractorInstance(String url) throws ExtractionException, IOException;
-
-
public final int getServiceId() {
return serviceId;
}
+ public ServiceInfo getServiceInfo() {
+ return serviceInfo;
+ }
+
+ public abstract UrlIdHandler getStreamUrlIdHandler();
+ public abstract UrlIdHandler getChannelUrlIdHandler();
+ public abstract UrlIdHandler getPlaylistUrlIdHandler();
+ public abstract SearchEngine getSearchEngine();
+ public abstract SuggestionExtractor getSuggestionExtractor();
+ public abstract StreamExtractor getStreamExtractor(String url) throws IOException, ExtractionException;
+ public abstract ChannelExtractor getChannelExtractor(String url, String nextStreamsUrl) throws IOException, ExtractionException;
+ public abstract PlaylistExtractor getPlaylistExtractor(String url, String nextStreamsUrl) throws IOException, ExtractionException;
+
+ public ChannelExtractor getChannelExtractor(String url) throws IOException, ExtractionException {
+ return getChannelExtractor(url, null);
+ }
+
+ public PlaylistExtractor getPlaylistExtractor(String url) throws IOException, ExtractionException {
+ return getPlaylistExtractor(url, null);
+ }
+
/**
* figure out where the link is pointing to (a channel, video, playlist, etc.)
*/
public final LinkType getLinkTypeByUrl(String url) {
- UrlIdHandler sH = getStreamUrlIdHandlerInstance();
- UrlIdHandler cH = getChannelUrlIdHandlerInstance();
- UrlIdHandler pH = getPlaylistUrlIdHandlerInstance();
+ UrlIdHandler sH = getStreamUrlIdHandler();
+ UrlIdHandler cH = getChannelUrlIdHandler();
+ UrlIdHandler pH = getPlaylistUrlIdHandler();
if (sH.acceptUrl(url)) {
return LinkType.STREAM;
diff --git a/src/main/java/org/schabi/newpipe/extractor/SuggestionExtractor.java b/src/main/java/org/schabi/newpipe/extractor/SuggestionExtractor.java
index 8750c73d8..983adc07e 100644
--- a/src/main/java/org/schabi/newpipe/extractor/SuggestionExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/SuggestionExtractor.java
@@ -33,8 +33,7 @@ public abstract class SuggestionExtractor {
this.serviceId = serviceId;
}
- public abstract List suggestionList(String query, String contentCountry)
- throws ExtractionException, IOException;
+ public abstract List suggestionList(String query, String contentCountry) throws IOException, ExtractionException;
public int getServiceId() {
return serviceId;
diff --git a/src/main/java/org/schabi/newpipe/extractor/UrlIdHandler.java b/src/main/java/org/schabi/newpipe/extractor/UrlIdHandler.java
index 9e5739e33..84a80b9da 100644
--- a/src/main/java/org/schabi/newpipe/extractor/UrlIdHandler.java
+++ b/src/main/java/org/schabi/newpipe/extractor/UrlIdHandler.java
@@ -1,9 +1,6 @@
package org.schabi.newpipe.extractor;
-import java.io.IOException;
-
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
/*
* Created by Christian Schabesberger on 26.07.16.
diff --git a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java
index 64311ef06..bdcad4527 100644
--- a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelExtractor.java
@@ -1,11 +1,10 @@
package org.schabi.newpipe.extractor.channel;
import org.schabi.newpipe.extractor.ListExtractor;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
-import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
import java.io.IOException;
@@ -31,8 +30,13 @@ import java.io.IOException;
public abstract class ChannelExtractor extends ListExtractor {
- public ChannelExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) throws ExtractionException, IOException {
- super(urlIdHandler, serviceId, url);
+ public ChannelExtractor(StreamingService service, String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ super(service, url, nextStreamsUrl);
+ }
+
+ @Override
+ protected UrlIdHandler getUrlIdHandler() throws ParsingException {
+ return getService().getChannelUrlIdHandler();
}
public abstract String getChannelId() throws ParsingException;
@@ -40,7 +44,6 @@ public abstract class ChannelExtractor extends ListExtractor {
public abstract String getAvatarUrl() throws ParsingException;
public abstract String getBannerUrl() throws ParsingException;
public abstract String getFeedUrl() throws ParsingException;
- public abstract StreamInfoItemCollector getStreams() throws ParsingException, ReCaptchaException, IOException;
public abstract long getSubscriberCount() throws ParsingException;
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java
index 6ccff7814..47473de2d 100644
--- a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java
+++ b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfo.java
@@ -1,11 +1,16 @@
package org.schabi.newpipe.extractor.channel;
-import org.schabi.newpipe.extractor.Info;
-import org.schabi.newpipe.extractor.InfoItem;
+import org.schabi.newpipe.extractor.ListExtractor.NextItemsResult;
+import org.schabi.newpipe.extractor.ListInfo;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
-import java.util.List;
+import java.io.IOException;
+import java.util.ArrayList;
/*
* Created by Christian Schabesberger on 31.07.16.
@@ -27,17 +32,36 @@ import java.util.List;
* along with NewPipe. If not, see .
*/
-public class ChannelInfo extends Info {
+public class ChannelInfo extends ListInfo {
+
+ public static NextItemsResult getMoreItems(ServiceList serviceItem, String nextStreamsUrl) throws IOException, ExtractionException {
+ return getMoreItems(serviceItem.getService(), nextStreamsUrl);
+ }
+
+ public static NextItemsResult getMoreItems(StreamingService service, String nextStreamsUrl) throws IOException, ExtractionException {
+ return service.getChannelExtractor(null, nextStreamsUrl).getNextStreams();
+ }
+
+ public static ChannelInfo getInfo(String url) throws IOException, ExtractionException {
+ return getInfo(NewPipe.getServiceByUrl(url), url);
+ }
+
+ public static ChannelInfo getInfo(ServiceList serviceItem, String url) throws IOException, ExtractionException {
+ return getInfo(serviceItem.getService(), url);
+ }
+
+ public static ChannelInfo getInfo(StreamingService service, String url) throws IOException, ExtractionException {
+ return getInfo(service.getChannelExtractor(url));
+ }
public static ChannelInfo getInfo(ChannelExtractor extractor) throws ParsingException {
ChannelInfo info = new ChannelInfo();
// important data
info.service_id = extractor.getServiceId();
- info.url = extractor.getUrl();
+ info.url = extractor.getCleanUrl();
info.id = extractor.getChannelId();
info.name = extractor.getChannelName();
- info.has_more_streams = extractor.hasMoreStreams();
try {
info.avatar_url = extractor.getAvatarUrl();
@@ -67,13 +91,16 @@ public class ChannelInfo extends Info {
info.errors.add(e);
}
+ // Lists can be null if a exception was thrown during extraction
+ if (info.related_streams == null) info.related_streams = new ArrayList<>();
+
+ info.has_more_streams = extractor.hasMoreStreams();
+ info.next_streams_url = extractor.getNextStreamsUrl();
return info;
}
public String avatar_url;
public String banner_url;
public String feed_url;
- public List related_streams;
public long subscriber_count = -1;
- public boolean has_more_streams = false;
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItem.java b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItem.java
index 367c1183e..e0ff5a4d3 100644
--- a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItem.java
+++ b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItem.java
@@ -27,7 +27,7 @@ public class ChannelInfoItem extends InfoItem {
public String thumbnail_url;
public String description;
public long subscriber_count = -1;
- public long view_count = -1;
+ public long stream_count = -1;
public ChannelInfoItem() {
super(InfoType.CHANNEL);
diff --git a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemCollector.java b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemCollector.java
index feca36503..8e8c45d2a 100644
--- a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemCollector.java
+++ b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemCollector.java
@@ -43,7 +43,7 @@ public class ChannelInfoItemCollector extends InfoItemCollector {
addError(e);
}
try {
- resultItem.view_count = extractor.getViewCount();
+ resultItem.stream_count = extractor.getStreamCount();
} catch (Exception e) {
addError(e);
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemExtractor.java b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemExtractor.java
index c8c08e641..5f22ca7aa 100644
--- a/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/channel/ChannelInfoItemExtractor.java
@@ -28,5 +28,5 @@ public interface ChannelInfoItemExtractor {
String getWebPageUrl() throws ParsingException;
String getDescription() throws ParsingException;
long getSubscriberCount() throws ParsingException;
- long getViewCount() throws ParsingException;
+ long getStreamCount() throws ParsingException;
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistExtractor.java b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistExtractor.java
index 514c10ece..f9f62e9cc 100644
--- a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistExtractor.java
@@ -1,18 +1,22 @@
package org.schabi.newpipe.extractor.playlist;
import org.schabi.newpipe.extractor.ListExtractor;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
-import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
import java.io.IOException;
public abstract class PlaylistExtractor extends ListExtractor {
- public PlaylistExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) throws ExtractionException, IOException {
- super(urlIdHandler, serviceId, url);
+ public PlaylistExtractor(StreamingService service, String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ super(service, url, nextStreamsUrl);
+ }
+
+ @Override
+ protected UrlIdHandler getUrlIdHandler() throws ParsingException {
+ return getService().getPlaylistUrlIdHandler();
}
public abstract String getPlaylistId() throws ParsingException;
@@ -22,6 +26,5 @@ public abstract class PlaylistExtractor extends ListExtractor {
public abstract String getUploaderUrl() throws ParsingException;
public abstract String getUploaderName() throws ParsingException;
public abstract String getUploaderAvatarUrl() throws ParsingException;
- public abstract StreamInfoItemCollector getStreams() throws ParsingException, ReCaptchaException, IOException;
- public abstract long getStreamsCount() throws ParsingException;
+ public abstract long getStreamCount() throws ParsingException;
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfo.java b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfo.java
index 18cb8a595..6e296dd76 100644
--- a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfo.java
+++ b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfo.java
@@ -1,25 +1,49 @@
package org.schabi.newpipe.extractor.playlist;
-import org.schabi.newpipe.extractor.Info;
-import org.schabi.newpipe.extractor.InfoItem;
+import org.schabi.newpipe.extractor.ListExtractor.NextItemsResult;
+import org.schabi.newpipe.extractor.ListInfo;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
-import java.util.List;
+import java.io.IOException;
+import java.util.ArrayList;
-public class PlaylistInfo extends Info {
+public class PlaylistInfo extends ListInfo {
+
+ public static NextItemsResult getMoreItems(ServiceList serviceItem, String nextStreamsUrl) throws IOException, ExtractionException {
+ return getMoreItems(serviceItem.getService(), nextStreamsUrl);
+ }
+
+ public static NextItemsResult getMoreItems(StreamingService service, String nextStreamsUrl) throws IOException, ExtractionException {
+ return service.getPlaylistExtractor(null, nextStreamsUrl).getNextStreams();
+ }
+
+ public static PlaylistInfo getInfo(String url) throws IOException, ExtractionException {
+ return getInfo(NewPipe.getServiceByUrl(url), url);
+ }
+
+ public static PlaylistInfo getInfo(ServiceList serviceItem, String url) throws IOException, ExtractionException {
+ return getInfo(serviceItem.getService(), url);
+ }
+
+ public static PlaylistInfo getInfo(StreamingService service, String url) throws IOException, ExtractionException {
+ return getInfo(service.getPlaylistExtractor(url));
+ }
public static PlaylistInfo getInfo(PlaylistExtractor extractor) throws ParsingException {
PlaylistInfo info = new PlaylistInfo();
info.service_id = extractor.getServiceId();
- info.url = extractor.getUrl();
+ info.url = extractor.getCleanUrl();
info.id = extractor.getPlaylistId();
info.name = extractor.getPlaylistName();
- info.has_more_streams = extractor.hasMoreStreams();
try {
- info.streams_count = extractor.getStreamsCount();
+ info.stream_count = extractor.getStreamCount();
} catch (Exception e) {
info.errors.add(e);
}
@@ -56,6 +80,11 @@ public class PlaylistInfo extends Info {
info.errors.add(e);
}
+ // Lists can be null if a exception was thrown during extraction
+ if (info.related_streams == null) info.related_streams = new ArrayList<>();
+
+ info.has_more_streams = extractor.hasMoreStreams();
+ info.next_streams_url = extractor.getNextStreamsUrl();
return info;
}
@@ -64,7 +93,5 @@ public class PlaylistInfo extends Info {
public String uploader_url;
public String uploader_name;
public String uploader_avatar_url;
- public long streams_count = 0;
- public List related_streams;
- public boolean has_more_streams;
+ public long stream_count = 0;
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItem.java b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItem.java
index 6a4eafcc2..d49521586 100644
--- a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItem.java
+++ b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItem.java
@@ -8,7 +8,7 @@ public class PlaylistInfoItem extends InfoItem {
/**
* How many streams this playlist have
*/
- public long streams_count = 0;
+ public long stream_count = 0;
public PlaylistInfoItem() {
super(InfoType.PLAYLIST);
diff --git a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItemCollector.java b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItemCollector.java
index e3e764c4e..6ad2e1509 100644
--- a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItemCollector.java
+++ b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItemCollector.java
@@ -1,7 +1,6 @@
package org.schabi.newpipe.extractor.playlist;
import org.schabi.newpipe.extractor.InfoItemCollector;
-import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
public class PlaylistInfoItemCollector extends InfoItemCollector {
@@ -22,7 +21,7 @@ public class PlaylistInfoItemCollector extends InfoItemCollector {
addError(e);
}
try {
- resultItem.streams_count = extractor.getStreamsCount();
+ resultItem.stream_count = extractor.getStreamCount();
} catch (Exception e) {
addError(e);
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItemExtractor.java b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItemExtractor.java
index 66ea009ae..a97a1a33f 100644
--- a/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItemExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/playlist/PlaylistInfoItemExtractor.java
@@ -6,5 +6,5 @@ public interface PlaylistInfoItemExtractor {
String getThumbnailUrl() throws ParsingException;
String getPlaylistName() throws ParsingException;
String getWebPageUrl() throws ParsingException;
- long getStreamsCount() throws ParsingException;
+ long getStreamCount() throws ParsingException;
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/search/InfoItemSearchCollector.java b/src/main/java/org/schabi/newpipe/extractor/search/InfoItemSearchCollector.java
index c33ddefe0..9dc647988 100644
--- a/src/main/java/org/schabi/newpipe/extractor/search/InfoItemSearchCollector.java
+++ b/src/main/java/org/schabi/newpipe/extractor/search/InfoItemSearchCollector.java
@@ -59,7 +59,7 @@ public class InfoItemSearchCollector extends InfoItemCollector {
try {
result.resultList.add(streamCollector.extract(extractor));
} catch (FoundAdException ae) {
- System.err.println("Found add");
+ System.err.println("Found ad");
} catch (Exception e) {
addError(e);
}
@@ -69,7 +69,7 @@ public class InfoItemSearchCollector extends InfoItemCollector {
try {
result.resultList.add(channelCollector.extract(extractor));
} catch (FoundAdException ae) {
- System.err.println("Found add");
+ System.err.println("Found ad");
} catch (Exception e) {
addError(e);
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/search/SearchEngine.java b/src/main/java/org/schabi/newpipe/extractor/search/SearchEngine.java
index d77812c7b..83308136b 100644
--- a/src/main/java/org/schabi/newpipe/extractor/search/SearchEngine.java
+++ b/src/main/java/org/schabi/newpipe/extractor/search/SearchEngine.java
@@ -49,5 +49,5 @@ public abstract class SearchEngine {
//Result search(String query, int page);
public abstract InfoItemSearchCollector search(
String query, int page, String contentCountry, EnumSet filter)
- throws ExtractionException, IOException;
+ throws IOException, ExtractionException;
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/search/SearchResult.java b/src/main/java/org/schabi/newpipe/extractor/search/SearchResult.java
index 0783d3d6a..11c9850ba 100644
--- a/src/main/java/org/schabi/newpipe/extractor/search/SearchResult.java
+++ b/src/main/java/org/schabi/newpipe/extractor/search/SearchResult.java
@@ -4,9 +4,9 @@ import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
-import java.util.Vector;
/*
* Created by Christian Schabesberger on 29.02.16.
@@ -31,7 +31,7 @@ import java.util.Vector;
public class SearchResult {
public static SearchResult getSearchResult(SearchEngine engine, String query,
int page, String languageCode, EnumSet filter)
- throws ExtractionException, IOException {
+ throws IOException, ExtractionException {
SearchResult result = engine
.search(query, page, languageCode, filter)
@@ -50,6 +50,6 @@ public class SearchResult {
}
public String suggestion;
- public List resultList = new Vector<>();
- public List errors = new Vector<>();
+ public List resultList = new ArrayList<>();
+ public List errors = new ArrayList<>();
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelExtractor.java
index 7c1f0be6e..e68d312f2 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelExtractor.java
@@ -1,37 +1,46 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import java.io.IOException;
-
-import org.json.JSONArray;
import org.json.JSONObject;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.UrlIdHandler;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
+import java.io.IOException;
+
@SuppressWarnings("WeakerAccess")
public class SoundcloudChannelExtractor extends ChannelExtractor {
private String channelId;
private JSONObject channel;
- private String nextUrl;
- public SoundcloudChannelExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) throws ExtractionException, IOException {
- super(urlIdHandler, url, serviceId);
+ public SoundcloudChannelExtractor(StreamingService service, String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ super(service, url, nextStreamsUrl);
+ }
+ @Override
+ public void fetchPage() throws IOException, ExtractionException {
Downloader dl = NewPipe.getDownloader();
- channelId = urlIdHandler.getId(url);
- String apiUrl = "https://api-v2.soundcloud.com/users/" + channelId
- + "?client_id=" + SoundcloudParsingHelper.clientId();
+ channelId = getUrlIdHandler().getId(getOriginalUrl());
+ String apiUrl = "https://api.soundcloud.com/users/" + channelId +
+ "?client_id=" + SoundcloudParsingHelper.clientId();
String response = dl.download(apiUrl);
channel = new JSONObject(response);
}
+ @Override
+ public String getCleanUrl() {
+ try {
+ return channel.getString("permalink_url");
+ } catch (Exception e) {
+ return getOriginalUrl();
+ }
+ }
+
@Override
public String getChannelId() {
return channelId;
@@ -56,32 +65,6 @@ public class SoundcloudChannelExtractor extends ChannelExtractor {
}
}
- @Override
- public StreamInfoItemCollector getStreams() throws ReCaptchaException, IOException, ParsingException {
- StreamInfoItemCollector collector = getStreamPreviewInfoCollector();
- Downloader dl = NewPipe.getDownloader();
-
- String apiUrl = "https://api-v2.soundcloud.com/users/" + channelId + "/tracks"
- + "?client_id=" + SoundcloudParsingHelper.clientId()
- + "&limit=10"
- + "&offset=0"
- + "&linked_partitioning=1";
-
- String response = dl.download(apiUrl);
- JSONObject responseObject = new JSONObject(response);
-
- nextUrl = responseObject.getString("next_href")
- + "&client_id=" + SoundcloudParsingHelper.clientId()
- + "&linked_partitioning=1";
-
- JSONArray responseCollection = responseObject.getJSONArray("collection");
- for (int i = 0; i < responseCollection.length(); i++) {
- JSONObject track = responseCollection.getJSONObject(i);
- collector.commit(new SoundcloudStreamInfoItemExtractor(track));
- }
- return collector;
- }
-
@Override
public long getSubscriberCount() {
return channel.getLong("followers_count");
@@ -93,26 +76,27 @@ public class SoundcloudChannelExtractor extends ChannelExtractor {
}
@Override
- public StreamInfoItemCollector getNextStreams() throws ExtractionException, IOException {
- if (nextUrl.equals("")) {
+ public StreamInfoItemCollector getStreams() throws IOException, ExtractionException {
+ StreamInfoItemCollector collector = new StreamInfoItemCollector(getServiceId());
+
+ String apiUrl = "https://api-v2.soundcloud.com/users/" + getChannelId() + "/tracks"
+ + "?client_id=" + SoundcloudParsingHelper.clientId()
+ + "&limit=20"
+ + "&linked_partitioning=1";
+
+ nextStreamsUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, collector, apiUrl);
+ return collector;
+ }
+
+ @Override
+ public NextItemsResult getNextStreams() throws IOException, ExtractionException {
+ if (!hasMoreStreams()) {
throw new ExtractionException("Channel doesn't have more streams");
}
- StreamInfoItemCollector collector = getStreamPreviewInfoCollector();
- Downloader dl = NewPipe.getDownloader();
+ StreamInfoItemCollector collector = new StreamInfoItemCollector(getServiceId());
+ nextStreamsUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, collector, nextStreamsUrl);
- String response = dl.download(nextUrl);
- JSONObject responseObject = new JSONObject(response);
-
- nextUrl = responseObject.getString("next_href")
- + "&client_id=" + SoundcloudParsingHelper.clientId()
- + "&linked_partitioning=1";
-
- JSONArray responseCollection = responseObject.getJSONArray("collection");
- for (int i = 0; i < responseCollection.length(); i++) {
- JSONObject track = responseCollection.getJSONObject(i);
- collector.commit(new SoundcloudStreamInfoItemExtractor(track));
- }
- return collector;
+ return new NextItemsResult(collector.getItemList(), nextStreamsUrl);
}
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelInfoItemExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelInfoItemExtractor.java
index 160de129a..b676eb65f 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelInfoItemExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelInfoItemExtractor.java
@@ -31,7 +31,7 @@ public class SoundcloudChannelInfoItemExtractor implements ChannelInfoItemExtrac
}
@Override
- public long getViewCount() {
+ public long getStreamCount() {
return searchResult.getLong("track_count");
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelUrlIdHandler.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelUrlIdHandler.java
index be6c20754..4c96079c6 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelUrlIdHandler.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudChannelUrlIdHandler.java
@@ -1,10 +1,7 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import org.json.JSONObject;
import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
-import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
@@ -21,47 +18,28 @@ public class SoundcloudChannelUrlIdHandler implements UrlIdHandler {
@Override
public String getUrl(String channelId) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
-
- String response = dl.download("https://api-v2.soundcloud.com/user/" + channelId
- + "?client_id=" + SoundcloudParsingHelper.clientId());
- JSONObject responseObject = new JSONObject(response);
-
- return responseObject.getString("permalink_url");
+ return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer("https://api.soundcloud.com/users/" + channelId);
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
}
@Override
- public String getId(String siteUrl) throws ParsingException {
+ public String getId(String url) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
-
- String response = dl.download(siteUrl);
- Document doc = Jsoup.parse(response);
-
- Element androidElement = doc.select("meta[property=al:android:url]").first();
- String id = androidElement.attr("content").substring(19);
-
- return id;
+ return SoundcloudParsingHelper.resolveIdWithEmbedPlayer(url);
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
}
@Override
- public String cleanUrl(String siteUrl) throws ParsingException {
+ public String cleanUrl(String complexUrl) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
+ Element ogElement = Jsoup.parse(NewPipe.getDownloader().download(complexUrl))
+ .select("meta[property=og:url]").first();
- String response = dl.download(siteUrl);
- Document doc = Jsoup.parse(response);
-
- Element ogElement = doc.select("meta[property=og:url]").first();
- String url = ogElement.attr("content");
-
- return url;
+ return ogElement.attr("content");
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
@@ -71,6 +49,5 @@ public class SoundcloudChannelUrlIdHandler implements UrlIdHandler {
public boolean acceptUrl(String channelUrl) {
String regex = "^https?://(www\\.)?soundcloud.com/[0-9a-z_-]+(/((tracks|albums|sets|reposts|followers|following)/?)?)?([#?].*)?$";
return Parser.isMatch(regex, channelUrl.toLowerCase());
-
}
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java
index 45af5c29e..e78aad961 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudParsingHelper.java
@@ -1,13 +1,7 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import java.io.IOException;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
+import org.json.JSONArray;
+import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
@@ -15,61 +9,51 @@ import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
+import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
import org.schabi.newpipe.extractor.utils.Parser;
import org.schabi.newpipe.extractor.utils.Parser.RegexException;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
public class SoundcloudParsingHelper {
+ private static String clientId;
+
private SoundcloudParsingHelper() {
}
- public static final String clientId() throws ReCaptchaException, IOException, RegexException {
+ public static String clientId() throws ReCaptchaException, IOException, RegexException {
+ if (clientId != null && !clientId.isEmpty()) return clientId;
+
Downloader dl = NewPipe.getDownloader();
String response = dl.download("https://soundcloud.com");
Document doc = Jsoup.parse(response);
+ // TODO: Find a less heavy way to get the client_id
+ // Currently we are downloading a 1MB file (!) just to get the client_id,
+ // youtube-dl don't have a way too, they are just hardcoding and updating it when it becomes invalid.
+ // The embed mode has a way to get it, but we still have to download a heavy file (~800KB).
Element jsElement = doc.select("script[src^=https://a-v2.sndcdn.com/assets/app]").first();
String js = dl.download(jsElement.attr("src"));
- String clientId = Parser.matchGroup1(",client_id:\"(.*?)\"", js);
+ clientId = Parser.matchGroup1(",client_id:\"(.*?)\"", js);
return clientId;
}
- public static String toTimeAgoString(String time) throws ParsingException {
- try {
- List times = Arrays.asList(TimeUnit.DAYS.toMillis(365), TimeUnit.DAYS.toMillis(30),
- TimeUnit.DAYS.toMillis(7), TimeUnit.HOURS.toMillis(1), TimeUnit.MINUTES.toMillis(1),
- TimeUnit.SECONDS.toMillis(1));
- List timesString = Arrays.asList("year", "month", "week", "day", "hour", "minute", "second");
-
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
-
- long timeAgo = System.currentTimeMillis() - dateFormat.parse(time).getTime();
-
- StringBuilder timeAgoString = new StringBuilder();
-
- for (int i = 0; i < times.size(); i++) {
- Long current = times.get(i);
- long currentAmount = timeAgo / current;
- if (currentAmount > 0) {
- timeAgoString.append(currentAmount).append(" ").append(timesString.get(i))
- .append(currentAmount != 1 ? "s ago" : " ago");
- break;
- }
- }
- if (timeAgoString.toString().equals("")) {
- timeAgoString.append("Just now");
- }
- return timeAgoString.toString();
- } catch (ParseException e) {
- throw new ParsingException(e.getMessage(), e);
- }
- }
-
public static String toDateString(String time) throws ParsingException {
try {
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
- Date date = dateFormat.parse(time);
+ Date date;
+ // Have two date formats, one for the 'api.soundc...' and the other 'api-v2.soundc...'.
+ try {
+ date = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(time);
+ } catch (Exception e) {
+ date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss +0000").parse(time);
+ }
+
SimpleDateFormat newDateFormat = new SimpleDateFormat("yyyy-MM-dd");
return newDateFormat.format(date);
} catch (ParseException e) {
@@ -77,4 +61,83 @@ public class SoundcloudParsingHelper {
}
}
+ /**
+ * Call the endpoint "/resolve" of the api.
+ * See https://developers.soundcloud.com/docs/api/reference#resolve
+ */
+ public static JSONObject resolveFor(String url) throws IOException, ReCaptchaException, ParsingException {
+ String apiUrl = "https://api.soundcloud.com/resolve"
+ + "?url=" + URLEncoder.encode(url, "UTF-8")
+ + "&client_id=" + clientId();
+
+ return new JSONObject(NewPipe.getDownloader().download(apiUrl));
+ }
+
+ /**
+ * Fetch the embed player with the apiUrl and return the canonical url (like the permalink_url from the json api).
+ *
+ * @return the url resolved
+ */
+ public static String resolveUrlWithEmbedPlayer(String apiUrl) throws IOException, ReCaptchaException, ParsingException {
+
+ String response = NewPipe.getDownloader().download("https://w.soundcloud.com/player/?url="
+ + URLEncoder.encode(apiUrl, "UTF-8"));
+
+ return Jsoup.parse(response).select("link[rel=\"canonical\"]").first().attr("abs:href");
+ }
+
+ /**
+ * Fetch the embed player with the url and return the id (like the id from the json api).
+ *
+ * @return the id resolved
+ */
+ public static String resolveIdWithEmbedPlayer(String url) throws IOException, ReCaptchaException, ParsingException {
+
+ String response = NewPipe.getDownloader().download("https://w.soundcloud.com/player/?url="
+ + URLEncoder.encode(url, "UTF-8"));
+ return Parser.matchGroup1(",\"id\":(.*?),", response);
+ }
+
+ /**
+ * Fetch the streams from the given api and commit each of them to the collector.
+ *
+ * This differ from {@link #getStreamsFromApi(StreamInfoItemCollector, String)} in the sense that they will always
+ * get MIN_ITEMS or more items.
+ *
+ * @param minItems the method will return only when it have extracted that many items (equal or more)
+ */
+ public static String getStreamsFromApiMinItems(int minItems, StreamInfoItemCollector collector, String apiUrl) throws IOException, ReCaptchaException, ParsingException {
+ String nextStreamsUrl = SoundcloudParsingHelper.getStreamsFromApi(collector, apiUrl);
+
+ while (!nextStreamsUrl.isEmpty() && collector.getItemList().size() < minItems) {
+ nextStreamsUrl = SoundcloudParsingHelper.getStreamsFromApi(collector, nextStreamsUrl);
+ }
+
+ return nextStreamsUrl;
+ }
+
+ /**
+ * Fetch the streams from the given api and commit each of them to the collector.
+ *
+ * @return the next streams url, empty if don't have
+ */
+ public static String getStreamsFromApi(StreamInfoItemCollector collector, String apiUrl) throws IOException, ReCaptchaException, ParsingException {
+ String response = NewPipe.getDownloader().download(apiUrl);
+ JSONObject responseObject = new JSONObject(response);
+
+ JSONArray responseCollection = responseObject.getJSONArray("collection");
+ for (int i = 0; i < responseCollection.length(); i++) {
+ collector.commit(new SoundcloudStreamInfoItemExtractor(responseCollection.getJSONObject(i)));
+ }
+
+ String nextStreamsUrl;
+ try {
+ nextStreamsUrl = responseObject.getString("next_href");
+ if (!nextStreamsUrl.contains("client_id=")) nextStreamsUrl += "&client_id=" + SoundcloudParsingHelper.clientId();
+ } catch (Exception ignored) {
+ nextStreamsUrl = "";
+ }
+
+ return nextStreamsUrl;
+ }
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractor.java
index 01a0bee4e..08538a0d1 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractor.java
@@ -1,38 +1,46 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import java.io.IOException;
-import java.util.List;
-
-import org.json.JSONArray;
import org.json.JSONObject;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.UrlIdHandler;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
-import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
+import java.io.IOException;
+
@SuppressWarnings("WeakerAccess")
public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
private String playlistId;
private JSONObject playlist;
- private List nextTracks;
- public SoundcloudPlaylistExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) throws IOException, ExtractionException {
- super(urlIdHandler, url, serviceId);
+ public SoundcloudPlaylistExtractor(StreamingService service, String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ super(service, url, nextStreamsUrl);
+ }
+ @Override
+ public void fetchPage() throws IOException, ExtractionException {
Downloader dl = NewPipe.getDownloader();
- playlistId = urlIdHandler.getId(url);
- String apiUrl = "https://api-v2.soundcloud.com/users/" + playlistId
- + "?client_id=" + SoundcloudParsingHelper.clientId();
+ playlistId = getUrlIdHandler().getId(getOriginalUrl());
+ String apiUrl = "https://api.soundcloud.com/playlists/" + playlistId +
+ "?client_id=" + SoundcloudParsingHelper.clientId() +
+ "&representation=compact";
String response = dl.download(apiUrl);
playlist = new JSONObject(response);
}
+ @Override
+ public String getCleanUrl() {
+ try {
+ return playlist.getString("permalink_url");
+ } catch (Exception e) {
+ return getOriginalUrl();
+ }
+ }
+
@Override
public String getPlaylistId() {
return playlistId;
@@ -69,61 +77,33 @@ public class SoundcloudPlaylistExtractor extends PlaylistExtractor {
}
@Override
- public long getStreamsCount() {
+ public long getStreamCount() {
return playlist.getLong("track_count");
}
@Override
- public StreamInfoItemCollector getStreams() throws ParsingException, ReCaptchaException, IOException {
- StreamInfoItemCollector collector = getStreamPreviewInfoCollector();
- Downloader dl = NewPipe.getDownloader();
+ public StreamInfoItemCollector getStreams() throws IOException, ExtractionException {
+ StreamInfoItemCollector collector = new StreamInfoItemCollector(getServiceId());
- String apiUrl = "https://api-v2.soundcloud.com/playlists/" + playlistId
- + "?client_id=" + SoundcloudParsingHelper.clientId();
+ // Note the "api", NOT "api-v2"
+ String apiUrl = "https://api.soundcloud.com/playlists/" + getPlaylistId() + "/tracks"
+ + "?client_id=" + SoundcloudParsingHelper.clientId()
+ + "&limit=20"
+ + "&linked_partitioning=1";
- String response = dl.download(apiUrl);
- JSONObject responseObject = new JSONObject(response);
- JSONArray responseCollection = responseObject.getJSONArray("collection");
-
- for (int i = 0; i < responseCollection.length(); i++) {
- JSONObject track = responseCollection.getJSONObject(i);
- try {
- collector.commit(new SoundcloudStreamInfoItemExtractor(track));
- } catch (Exception e) {
- nextTracks.add(track.getString("id"));
- }
- }
+ nextStreamsUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, collector, apiUrl);
return collector;
}
@Override
- public StreamInfoItemCollector getNextStreams() throws ReCaptchaException, IOException, ParsingException {
- if (nextTracks.equals(null)) {
- return null;
+ public NextItemsResult getNextStreams() throws IOException, ExtractionException {
+ if (!hasMoreStreams()) {
+ throw new ExtractionException("Playlist doesn't have more streams");
}
- StreamInfoItemCollector collector = getStreamPreviewInfoCollector();
- Downloader dl = NewPipe.getDownloader();
+ StreamInfoItemCollector collector = new StreamInfoItemCollector(getServiceId());
+ nextStreamsUrl = SoundcloudParsingHelper.getStreamsFromApiMinItems(15, collector, nextStreamsUrl);
- // TODO: Do this per 10 tracks, instead of all tracks at once
- String apiUrl = "https://api-v2.soundcloud.com/tracks?ids=";
- for (String id : nextTracks) {
- apiUrl += id;
- if (!id.equals(nextTracks.get(nextTracks.size() - 1))) {
- apiUrl += ",";
- }
- }
- apiUrl += "&client_id=" + SoundcloudParsingHelper.clientId();
-
- String response = dl.download(apiUrl);
- JSONObject responseObject = new JSONObject(response);
- JSONArray responseCollection = responseObject.getJSONArray("collection");
-
- for (int i = 0; i < responseCollection.length(); i++) {
- JSONObject track = responseCollection.getJSONObject(i);
- collector.commit(new SoundcloudStreamInfoItemExtractor(track));
- }
- nextTracks = null;
- return collector;
+ return new NextItemsResult(collector.getItemList(), nextStreamsUrl);
}
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistUrlIdHandler.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistUrlIdHandler.java
index 8244a8146..4b5b7ee3e 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistUrlIdHandler.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistUrlIdHandler.java
@@ -1,10 +1,7 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import org.json.JSONObject;
import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
-import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
@@ -21,13 +18,7 @@ public class SoundcloudPlaylistUrlIdHandler implements UrlIdHandler {
@Override
public String getUrl(String listId) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
-
- String response = dl.download("https://api-v2.soundcloud.com/playlists/" + listId
- + "?client_id=" + SoundcloudParsingHelper.clientId());
- JSONObject responseObject = new JSONObject(response);
-
- return responseObject.getString("permalink_url");
+ return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer("https://api.soundcloud.com/playlists/" + listId);
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
@@ -36,15 +27,7 @@ public class SoundcloudPlaylistUrlIdHandler implements UrlIdHandler {
@Override
public String getId(String url) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
-
- String response = dl.download(url);
- Document doc = Jsoup.parse(response);
-
- Element androidElement = doc.select("meta[property=al:android:url]").first();
- String id = androidElement.attr("content").substring(23);
-
- return id;
+ return SoundcloudParsingHelper.resolveIdWithEmbedPlayer(url);
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
@@ -53,15 +36,10 @@ public class SoundcloudPlaylistUrlIdHandler implements UrlIdHandler {
@Override
public String cleanUrl(String complexUrl) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
+ Element ogElement = Jsoup.parse(NewPipe.getDownloader().download(complexUrl))
+ .select("meta[property=og:url]").first();
- String response = dl.download(complexUrl);
- Document doc = Jsoup.parse(response);
-
- Element ogElement = doc.select("meta[property=og:url]").first();
- String url = ogElement.attr("content");
-
- return url;
+ return ogElement.attr("content");
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudSearchEngine.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudSearchEngine.java
index 8837b933b..757ff51b8 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudSearchEngine.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudSearchEngine.java
@@ -1,9 +1,5 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.EnumSet;
-
import org.json.JSONArray;
import org.json.JSONObject;
import org.schabi.newpipe.extractor.Downloader;
@@ -12,6 +8,10 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.search.InfoItemSearchCollector;
import org.schabi.newpipe.extractor.search.SearchEngine;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.EnumSet;
+
public class SoundcloudSearchEngine extends SearchEngine {
public static final String CHARSET_UTF_8 = "UTF-8";
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudService.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudService.java
index 9a0ba4311..80c565ac8 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudService.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudService.java
@@ -13,61 +13,48 @@ import java.io.IOException;
public class SoundcloudService extends StreamingService {
- public SoundcloudService(int id) {
- super(id);
+ public SoundcloudService(int id, String name) {
+ super(id, name);
}
@Override
- public ServiceInfo getServiceInfo() {
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.name = "Soundcloud";
- return serviceInfo;
- }
-
- @Override
- public StreamExtractor getStreamExtractorInstance(String url)
- throws ExtractionException, IOException {
- UrlIdHandler urlIdHandler = SoundcloudStreamUrlIdHandler.getInstance();
- if (urlIdHandler.acceptUrl(url)) {
- return new SoundcloudStreamExtractor(urlIdHandler, url, getServiceId());
- } else {
- throw new IllegalArgumentException("supplied String is not a valid Soundcloud URL");
- }
- }
-
- @Override
- public SearchEngine getSearchEngineInstance() {
+ public SearchEngine getSearchEngine() {
return new SoundcloudSearchEngine(getServiceId());
}
@Override
- public UrlIdHandler getStreamUrlIdHandlerInstance() {
+ public UrlIdHandler getStreamUrlIdHandler() {
return SoundcloudStreamUrlIdHandler.getInstance();
}
@Override
- public UrlIdHandler getChannelUrlIdHandlerInstance() {
+ public UrlIdHandler getChannelUrlIdHandler() {
return SoundcloudChannelUrlIdHandler.getInstance();
}
-
@Override
- public UrlIdHandler getPlaylistUrlIdHandlerInstance() {
+ public UrlIdHandler getPlaylistUrlIdHandler() {
return SoundcloudPlaylistUrlIdHandler.getInstance();
}
+
@Override
- public ChannelExtractor getChannelExtractorInstance(String url) throws ExtractionException, IOException {
- return new SoundcloudChannelExtractor(getChannelUrlIdHandlerInstance(), url, getServiceId());
+ public StreamExtractor getStreamExtractor(String url) throws IOException, ExtractionException {
+ return new SoundcloudStreamExtractor(this, url);
}
@Override
- public PlaylistExtractor getPlaylistExtractorInstance(String url) throws ExtractionException, IOException {
- return new SoundcloudPlaylistExtractor(getPlaylistUrlIdHandlerInstance(), url, getServiceId());
+ public ChannelExtractor getChannelExtractor(String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ return new SoundcloudChannelExtractor(this, url, nextStreamsUrl);
}
@Override
- public SuggestionExtractor getSuggestionExtractorInstance() {
+ public PlaylistExtractor getPlaylistExtractor(String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ return new SoundcloudPlaylistExtractor(this, url, nextStreamsUrl);
+ }
+
+ @Override
+ public SuggestionExtractor getSuggestionExtractor() {
return new SoundcloudSuggestionExtractor(getServiceId());
}
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractor.java
index 61eaa5c53..f75ec06db 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamExtractor.java
@@ -1,19 +1,14 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import java.io.IOException;
-import java.util.List;
-import java.util.Vector;
-
import org.json.JSONArray;
import org.json.JSONObject;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.UrlIdHandler;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
@@ -21,33 +16,39 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.utils.Parser;
-import org.schabi.newpipe.extractor.utils.Parser.RegexException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
public class SoundcloudStreamExtractor extends StreamExtractor {
- private String pageUrl;
- private String trackId;
private JSONObject track;
- public SoundcloudStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl, int serviceId) throws ExtractionException, IOException {
- super(urlIdHandler, pageUrl, serviceId);
+ public SoundcloudStreamExtractor(StreamingService service, String url) throws IOException, ExtractionException {
+ super(service, url);
+ }
- Downloader dl = NewPipe.getDownloader();
-
- trackId = urlIdHandler.getId(pageUrl);
- String apiUrl = "https://api-v2.soundcloud.com/tracks/" + trackId
- + "?client_id=" + SoundcloudParsingHelper.clientId();
-
- String response = dl.download(apiUrl);
- track = new JSONObject(response);
+ @Override
+ public void fetchPage() throws IOException, ExtractionException {
+ track = SoundcloudParsingHelper.resolveFor(getOriginalUrl());
if (!track.getString("policy").equals("ALLOW") && !track.getString("policy").equals("MONETIZE")) {
throw new ContentNotAvailableException("Content not available: policy " + track.getString("policy"));
}
}
+ @Override
+ public String getCleanUrl() {
+ try {
+ return track.getString("permalink_url");
+ } catch (Exception e) {
+ return getOriginalUrl();
+ }
+ }
+
@Override
public String getId() {
- return trackId;
+ return track.getInt("id") + "";
}
@Override
@@ -96,11 +97,11 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
}
@Override
- public List getAudioStreams() throws ReCaptchaException, IOException, RegexException {
- Vector audioStreams = new Vector<>();
+ public List getAudioStreams() throws IOException, ExtractionException {
+ List audioStreams = new ArrayList<>();
Downloader dl = NewPipe.getDownloader();
- String apiUrl = "https://api.soundcloud.com/i1/tracks/" + trackId + "/streams"
+ String apiUrl = "https://api.soundcloud.com/i1/tracks/" + getId() + "/streams"
+ "?client_id=" + SoundcloudParsingHelper.clientId();
String response = dl.download(apiUrl);
@@ -113,20 +114,20 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
}
@Override
- public List getVideoStreams() {
+ public List getVideoStreams() throws IOException, ExtractionException {
return null;
}
@Override
- public List getVideoOnlyStreams() {
- return null;
+ public List getVideoOnlyStreams() throws IOException, ExtractionException {
+ return null;
}
@Override
public int getTimeStamp() throws ParsingException {
String timeStamp;
try {
- timeStamp = Parser.matchGroup1("(#t=\\d{0,3}h?\\d{0,3}m?\\d{1,3}s?)", pageUrl);
+ timeStamp = Parser.matchGroup1("(#t=\\d{0,3}h?\\d{0,3}m?\\d{1,3}s?)", getOriginalUrl());
} catch (Parser.RegexException e) {
// catch this instantly since an url does not necessarily have to have a time stamp
@@ -190,16 +191,16 @@ public class SoundcloudStreamExtractor extends StreamExtractor {
}
@Override
- public StreamInfoItemExtractor getNextVideo() {
+ public StreamInfoItemExtractor getNextVideo() throws IOException, ExtractionException {
return null;
}
@Override
- public StreamInfoItemCollector getRelatedVideos() throws ReCaptchaException, IOException, ParsingException {
- StreamInfoItemCollector collector = getStreamPreviewInfoCollector();
+ public StreamInfoItemCollector getRelatedVideos() throws IOException, ExtractionException {
+ StreamInfoItemCollector collector = new StreamInfoItemCollector(getServiceId());
Downloader dl = NewPipe.getDownloader();
- String apiUrl = "https://api-v2.soundcloud.com/tracks/" + trackId + "/related"
+ String apiUrl = "https://api-v2.soundcloud.com/tracks/" + getId() + "/related"
+ "?client_id=" + SoundcloudParsingHelper.clientId();
String response = dl.download(apiUrl);
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamInfoItemExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamInfoItemExtractor.java
index 001315c09..3b768808c 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamInfoItemExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamInfoItemExtractor.java
@@ -35,7 +35,7 @@ public class SoundcloudStreamInfoItemExtractor implements StreamInfoItemExtracto
@Override
public String getUploadDate() throws ParsingException {
- return SoundcloudParsingHelper.toTimeAgoString(searchResult.getString("created_at"));
+ return SoundcloudParsingHelper.toDateString(searchResult.getString("created_at"));
}
@Override
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamUrlIdHandler.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamUrlIdHandler.java
index 2badb8ee5..0baf47c38 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamUrlIdHandler.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudStreamUrlIdHandler.java
@@ -1,10 +1,7 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import org.json.JSONObject;
import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
-import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
@@ -13,6 +10,7 @@ import org.schabi.newpipe.extractor.utils.Parser;
public class SoundcloudStreamUrlIdHandler implements UrlIdHandler {
private static final SoundcloudStreamUrlIdHandler instance = new SoundcloudStreamUrlIdHandler();
+
private SoundcloudStreamUrlIdHandler() {
}
@@ -23,13 +21,7 @@ public class SoundcloudStreamUrlIdHandler implements UrlIdHandler {
@Override
public String getUrl(String videoId) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
-
- String response = dl.download("https://api-v2.soundcloud.com/tracks/" + videoId
- + "?client_id=" + SoundcloudParsingHelper.clientId());
- JSONObject responseObject = new JSONObject(response);
-
- return responseObject.getString("permalink_url");
+ return SoundcloudParsingHelper.resolveUrlWithEmbedPlayer("https://api.soundcloud.com/tracks/" + videoId);
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
@@ -38,15 +30,7 @@ public class SoundcloudStreamUrlIdHandler implements UrlIdHandler {
@Override
public String getId(String url) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
-
- String response = dl.download(url);
- Document doc = Jsoup.parse(response);
-
- Element androidElement = doc.select("meta[property=al:android:url]").first();
- String id = androidElement.attr("content").substring(20);
-
- return id;
+ return SoundcloudParsingHelper.resolveIdWithEmbedPlayer(url);
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
@@ -55,15 +39,10 @@ public class SoundcloudStreamUrlIdHandler implements UrlIdHandler {
@Override
public String cleanUrl(String complexUrl) throws ParsingException {
try {
- Downloader dl = NewPipe.getDownloader();
+ Element ogElement = Jsoup.parse(NewPipe.getDownloader().download(complexUrl))
+ .select("meta[property=og:url]").first();
- String response = dl.download(complexUrl);
- Document doc = Jsoup.parse(response);
-
- Element ogElement = doc.select("meta[property=og:url]").first();
- String url = ogElement.attr("content");
-
- return url;
+ return ogElement.attr("content");
} catch (Exception e) {
throw new ParsingException(e.getMessage(), e);
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudSuggestionExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudSuggestionExtractor.java
index 6af0fdbdd..1dbee93ce 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudSuggestionExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudSuggestionExtractor.java
@@ -1,10 +1,5 @@
package org.schabi.newpipe.extractor.services.soundcloud;
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-
import org.json.JSONArray;
import org.json.JSONObject;
import org.schabi.newpipe.extractor.Downloader;
@@ -13,6 +8,11 @@ import org.schabi.newpipe.extractor.SuggestionExtractor;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.utils.Parser.RegexException;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.List;
+
public class SoundcloudSuggestionExtractor extends SuggestionExtractor {
public static final String CHARSET_UTF_8 = "UTF-8";
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java b/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java
index 1e1cc5552..8a68f2aaa 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/youtube/ItagItem.java
@@ -43,7 +43,7 @@ public class ItagItem {
// Disable Opus codec as it's not well supported in older devices
// new ItagItem(249, AUDIO, WEBMA, 50),
// new ItagItem(250, AUDIO, WEBMA, 70),
-// new ItagItem(251, AUDIO, WEBMA, 16),
+// new ItagItem(251, AUDIO, WEBMA, 160),
new ItagItem(171, AUDIO, WEBMA, 128),
new ItagItem(172, AUDIO, WEBMA, 256),
new ItagItem(139, AUDIO, M4A, 48),
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java
index 56cad383d..29f711727 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java
@@ -8,7 +8,7 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.UrlIdHandler;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
@@ -44,6 +44,7 @@ import java.io.IOException;
@SuppressWarnings("WeakerAccess")
public class YoutubeChannelExtractor extends ChannelExtractor {
private static final String CHANNEL_FEED_BASE = "https://www.youtube.com/feeds/videos.xml?channel_id=";
+ private static final String CHANNEL_URL_PARAMETERS = "/videos?view=0&flow=list&sort=dd&live_view=10000";
private Document doc;
/**
@@ -51,29 +52,26 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
*/
private Document nextStreamsAjax;
- /*//////////////////////////////////////////////////////////////////////////
- // Variables for cache purposes (not "select" the current document all over again)
- //////////////////////////////////////////////////////////////////////////*/
- private String channelId;
- private String channelName;
- private String avatarUrl;
- private String bannerUrl;
- private String feedUrl;
- private long subscriberCount = -1;
+ public YoutubeChannelExtractor(StreamingService service, String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ super(service, url, nextStreamsUrl);
+ }
- public YoutubeChannelExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) throws ExtractionException, IOException {
- super(urlIdHandler, urlIdHandler.cleanUrl(url), serviceId);
- fetchDocument();
+ @Override
+ public void fetchPage() throws IOException, ExtractionException {
+ Downloader downloader = NewPipe.getDownloader();
+
+ String userUrl = getCleanUrl() + CHANNEL_URL_PARAMETERS;
+ String pageContent = downloader.download(userUrl);
+ doc = Jsoup.parse(pageContent, userUrl);
+
+ nextStreamsUrl = getNextStreamsUrlFrom(doc);
+ nextStreamsAjax = null;
}
@Override
public String getChannelId() throws ParsingException {
try {
- if (channelId == null) {
- channelId = getUrlIdHandler().getId(getUrl());
- }
-
- return channelId;
+ return getUrlIdHandler().getId(getCleanUrl());
} catch (Exception e) {
throw new ParsingException("Could not get channel id");
}
@@ -82,11 +80,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
@Override
public String getChannelName() throws ParsingException {
try {
- if (channelName == null) {
- channelName = doc.select("span[class=\"qualified-channel-title-text\"]").first().select("a").first().text();
- }
-
- return channelName;
+ return doc.select("span[class=\"qualified-channel-title-text\"]").first().select("a").first().text();
} catch (Exception e) {
throw new ParsingException("Could not get channel name");
}
@@ -95,11 +89,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
@Override
public String getAvatarUrl() throws ParsingException {
try {
- if (avatarUrl == null) {
- avatarUrl = doc.select("img[class=\"channel-header-profile-image\"]").first().attr("abs:src");
- }
-
- return avatarUrl;
+ return doc.select("img[class=\"channel-header-profile-image\"]").first().attr("abs:src");
} catch (Exception e) {
throw new ParsingException("Could not get avatar", e);
}
@@ -108,59 +98,47 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
@Override
public String getBannerUrl() throws ParsingException {
try {
- if (bannerUrl == null) {
- Element el = doc.select("div[id=\"gh-banner\"]").first().select("style").first();
- String cssContent = el.html();
- String url = "https:" + Parser.matchGroup1("url\\(([^)]+)\\)", cssContent);
+ Element el = doc.select("div[id=\"gh-banner\"]").first().select("style").first();
+ String cssContent = el.html();
+ String url = "https:" + Parser.matchGroup1("url\\(([^)]+)\\)", cssContent);
- bannerUrl = url.contains("s.ytimg.com") || url.contains("default_banner") ? null : url;
- }
-
- return bannerUrl;
+ return url.contains("s.ytimg.com") || url.contains("default_banner") ? null : url;
} catch (Exception e) {
throw new ParsingException("Could not get Banner", e);
}
}
- @Override
- public StreamInfoItemCollector getStreams() throws ParsingException {
- StreamInfoItemCollector collector = getStreamPreviewInfoCollector();
- Element ul = doc.select("ul[id=\"browse-items-primary\"]").first();
- collectStreamsFrom(collector, ul);
- return collector;
- }
@Override
public long getSubscriberCount() throws ParsingException {
-
- if (subscriberCount == -1) {
- Element el = doc.select("span[class*=\"yt-subscription-button-subscriber-count\"]").first();
- if (el != null) {
- subscriberCount = Long.parseLong(Utils.removeNonDigitCharacters(el.text()));
- } else {
- throw new ParsingException("Could not get subscriber count");
- }
+ Element el = doc.select("span[class*=\"yt-subscription-button-subscriber-count\"]").first();
+ if (el != null) {
+ return Long.parseLong(Utils.removeNonDigitCharacters(el.text()));
+ } else {
+ throw new ParsingException("Could not get subscriber count");
}
-
- return subscriberCount;
}
@Override
public String getFeedUrl() throws ParsingException {
try {
- if (feedUrl == null) {
- String channelId = doc.getElementsByClass("yt-uix-subscription-button").first().attr("data-channel-external-id");
- feedUrl = channelId == null ? "" : CHANNEL_FEED_BASE + channelId;
- }
-
- return feedUrl;
+ String channelId = doc.getElementsByClass("yt-uix-subscription-button").first().attr("data-channel-external-id");
+ return channelId == null ? "" : CHANNEL_FEED_BASE + channelId;
} catch (Exception e) {
throw new ParsingException("Could not get feed url", e);
}
}
@Override
- public StreamInfoItemCollector getNextStreams() throws ExtractionException, IOException {
+ public StreamInfoItemCollector getStreams() throws IOException, ExtractionException {
+ StreamInfoItemCollector collector = new StreamInfoItemCollector(getServiceId());
+ Element ul = doc.select("ul[id=\"browse-items-primary\"]").first();
+ collectStreamsFrom(collector, ul);
+ return collector;
+ }
+
+ @Override
+ public NextItemsResult getNextStreams() throws IOException, ExtractionException {
if (!hasMoreStreams()) {
throw new ExtractionException("Channel doesn't have more streams");
}
@@ -169,7 +147,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
setupNextStreamsAjax(NewPipe.getDownloader());
collectStreamsFrom(collector, nextStreamsAjax.select("body").first());
- return collector;
+ return new NextItemsResult(collector.getItemList(), nextStreamsUrl);
}
private void setupNextStreamsAjax(Downloader downloader) throws IOException, ReCaptchaException, ParsingException {
@@ -182,8 +160,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
String nextStreamsHtmlDataRaw = ajaxData.getString("load_more_widget_html");
if (!nextStreamsHtmlDataRaw.isEmpty()) {
- Document nextStreamsData = Jsoup.parse(nextStreamsHtmlDataRaw, nextStreamsUrl);
- nextStreamsUrl = getNextStreamsUrl(nextStreamsData);
+ nextStreamsUrl = getNextStreamsUrlFrom(Jsoup.parse(nextStreamsHtmlDataRaw, nextStreamsUrl));
} else {
nextStreamsUrl = "";
}
@@ -192,7 +169,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
}
}
- private String getNextStreamsUrl(Document d) throws ParsingException {
+ private String getNextStreamsUrlFrom(Document d) throws ParsingException {
try {
Element button = d.select("button[class*=\"yt-uix-load-more\"]").first();
if (button != null) {
@@ -206,17 +183,6 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
}
}
- private void fetchDocument() throws IOException, ReCaptchaException, ParsingException {
- Downloader downloader = NewPipe.getDownloader();
-
- String userUrl = getUrl() + "/videos?view=0&flow=list&sort=dd&live_view=10000";
- String pageContent = downloader.download(userUrl);
- doc = Jsoup.parse(pageContent, userUrl);
-
- nextStreamsUrl = getNextStreamsUrl(doc);
- nextStreamsAjax = null;
- }
-
private void collectStreamsFrom(StreamInfoItemCollector collector, Element element) throws ParsingException {
collector.getItemList().clear();
@@ -230,7 +196,8 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
@Override
public boolean isAd() throws ParsingException {
- return !li.select("span[class*=\"icon-not-available\"]").isEmpty();
+ return !li.select("span[class*=\"icon-not-available\"]").isEmpty() ||
+ !li.select("span[class*=\"yt-badge-ad\"]").isEmpty();
}
@Override
@@ -259,7 +226,7 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
public int getDuration() throws ParsingException {
try {
return YoutubeParsingHelper.parseDurationString(
- li.select("span[class=\"video-time\"]").first().text());
+ li.select("span[class*=\"video-time\"]").first().text());
} catch (Exception e) {
if (isLiveStream(li)) {
// -1 for no duration
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelInfoItemExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelInfoItemExtractor.java
index 3caff88b0..7151a33b3 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelInfoItemExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelInfoItemExtractor.java
@@ -68,7 +68,7 @@ public class YoutubeChannelInfoItemExtractor implements ChannelInfoItemExtractor
}
@Override
- public long getViewCount() throws ParsingException {
+ public long getStreamCount() throws ParsingException {
Element metaEl = el.select("ul[class*=\"yt-lockup-meta-info\"]").first();
if (metaEl == null) {
return 0;
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractor.java
index 5e8813992..36fe614c7 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractor.java
@@ -7,6 +7,7 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
@@ -23,39 +24,31 @@ import java.io.IOException;
@SuppressWarnings("WeakerAccess")
public class YoutubePlaylistExtractor extends PlaylistExtractor {
- private Document doc = null;
+ private Document doc;
/**
* It's lazily initialized (when getNextStreams is called)
*/
- private Document nextStreamsAjax = null;
+ private Document nextStreamsAjax;
- /*//////////////////////////////////////////////////////////////////////////
- // Variables for cache purposes (not "select" the current document all over again)
- //////////////////////////////////////////////////////////////////////////*/
- private String playlistId;
- private String playlistName;
- private String avatarUrl;
- private String bannerUrl;
+ public YoutubePlaylistExtractor(StreamingService service, String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ super(service, url, nextStreamsUrl);
+ }
- private long streamsCount;
+ @Override
+ public void fetchPage() throws IOException, ExtractionException {
+ Downloader downloader = NewPipe.getDownloader();
- private String uploaderUrl;
- private String uploaderName;
- private String uploaderAvatarUrl;
+ String pageContent = downloader.download(getCleanUrl());
+ doc = Jsoup.parse(pageContent, getCleanUrl());
- public YoutubePlaylistExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) throws IOException, ExtractionException {
- super(urlIdHandler, urlIdHandler.cleanUrl(url), serviceId);
- fetchDocument();
+ nextStreamsUrl = getNextStreamsUrlFrom(doc);
+ nextStreamsAjax = null;
}
@Override
public String getPlaylistId() throws ParsingException {
try {
- if (playlistId == null) {
- playlistId = getUrlIdHandler().getId(getUrl());
- }
-
- return playlistId;
+ return getUrlIdHandler().getId(getCleanUrl());
} catch (Exception e) {
throw new ParsingException("Could not get playlist id");
}
@@ -64,11 +57,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
@Override
public String getPlaylistName() throws ParsingException {
try {
- if (playlistName == null) {
- playlistName = doc.select("div[id=pl-header] h1[class=pl-header-title]").first().text();
- }
-
- return playlistName;
+ return doc.select("div[id=pl-header] h1[class=pl-header-title]").first().text();
} catch (Exception e) {
throw new ParsingException("Could not get playlist name");
}
@@ -77,11 +66,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
@Override
public String getAvatarUrl() throws ParsingException {
try {
- if (avatarUrl == null) {
- avatarUrl = doc.select("div[id=pl-header] div[class=pl-header-thumb] img").first().attr("abs:src");
- }
-
- return avatarUrl;
+ return doc.select("div[id=pl-header] div[class=pl-header-thumb] img").first().attr("abs:src");
} catch (Exception e) {
throw new ParsingException("Could not get playlist avatar");
}
@@ -90,18 +75,16 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
@Override
public String getBannerUrl() throws ParsingException {
try {
- if (bannerUrl == null) {
- Element el = doc.select("div[id=\"gh-banner\"] style").first();
- String cssContent = el.html();
- String url = "https:" + Parser.matchGroup1("url\\((.*)\\)", cssContent);
- if (url.contains("s.ytimg.com")) {
- bannerUrl = null;
- } else {
- bannerUrl = url.substring(0, url.indexOf(");"));
- }
+ Element el = doc.select("div[id=\"gh-banner\"] style").first();
+ String cssContent = el.html();
+ String url = "https:" + Parser.matchGroup1("url\\((.*)\\)", cssContent);
+ if (url.contains("s.ytimg.com")) {
+ return null;
+ } else {
+ return url.substring(0, url.indexOf(");"));
}
- return bannerUrl;
+
} catch (Exception e) {
throw new ParsingException("Could not get playlist Banner");
}
@@ -110,11 +93,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
@Override
public String getUploaderUrl() throws ParsingException {
try {
- if (uploaderUrl == null) {
- uploaderUrl = doc.select("ul[class=\"pl-header-details\"] li").first().select("a").first().attr("abs:href");
- }
-
- return uploaderUrl;
+ return doc.select("ul[class=\"pl-header-details\"] li").first().select("a").first().attr("abs:href");
} catch (Exception e) {
throw new ParsingException("Could not get playlist uploader name");
}
@@ -123,11 +102,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
@Override
public String getUploaderName() throws ParsingException {
try {
- if (uploaderName == null) {
- uploaderName = doc.select("span[class=\"qualified-channel-title-text\"]").first().select("a").first().text();
- }
-
- return uploaderName;
+ return doc.select("span[class=\"qualified-channel-title-text\"]").first().select("a").first().text();
} catch (Exception e) {
throw new ParsingException("Could not get playlist uploader name");
}
@@ -136,54 +111,46 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
@Override
public String getUploaderAvatarUrl() throws ParsingException {
try {
- if (uploaderAvatarUrl == null) {
- uploaderAvatarUrl = doc.select("div[id=gh-banner] img[class=channel-header-profile-image]").first().attr("abs:src");
- }
-
- return uploaderAvatarUrl;
+ return doc.select("div[id=gh-banner] img[class=channel-header-profile-image]").first().attr("abs:src");
} catch (Exception e) {
throw new ParsingException("Could not get playlist uploader avatar");
}
}
@Override
- public long getStreamsCount() throws ParsingException {
- if (streamsCount <= 0) {
- String input;
+ public long getStreamCount() throws ParsingException {
+ String input;
- try {
- input = doc.select("ul[class=\"pl-header-details\"] li").get(1).text();
- } catch (IndexOutOfBoundsException e) {
- throw new ParsingException("Could not get video count from playlist", e);
- }
-
- try {
- streamsCount = Long.parseLong(Utils.removeNonDigitCharacters(input));
- } catch (NumberFormatException e) {
- // When there's no videos in a playlist, there's no number in the "innerHtml",
- // all characters that is not a number is removed, so we try to parse a empty string
- if (!input.isEmpty()) {
- streamsCount = 0;
- } else {
- throw new ParsingException("Could not handle input: " + input, e);
- }
- }
+ try {
+ input = doc.select("ul[class=\"pl-header-details\"] li").get(1).text();
+ } catch (IndexOutOfBoundsException e) {
+ throw new ParsingException("Could not get video count from playlist", e);
}
- return streamsCount;
+ try {
+ return Long.parseLong(Utils.removeNonDigitCharacters(input));
+ } catch (NumberFormatException e) {
+ // When there's no videos in a playlist, there's no number in the "innerHtml",
+ // all characters that is not a number is removed, so we try to parse a empty string
+ if (!input.isEmpty()) {
+ return 0;
+ } else {
+ throw new ParsingException("Could not handle input: " + input, e);
+ }
+ }
}
@Override
- public StreamInfoItemCollector getStreams() throws ParsingException {
- StreamInfoItemCollector collector = getStreamPreviewInfoCollector();
+ public StreamInfoItemCollector getStreams() throws IOException, ExtractionException {
+ StreamInfoItemCollector collector = new StreamInfoItemCollector(getServiceId());
Element tbody = doc.select("tbody[id=\"pl-load-more-destination\"]").first();
collectStreamsFrom(collector, tbody);
return collector;
}
@Override
- public StreamInfoItemCollector getNextStreams() throws ExtractionException, IOException {
- if (!hasMoreStreams()){
+ public NextItemsResult getNextStreams() throws IOException, ExtractionException {
+ if (!hasMoreStreams()) {
throw new ExtractionException("Playlist doesn't have more streams");
}
@@ -191,7 +158,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
setupNextStreamsAjax(NewPipe.getDownloader());
collectStreamsFrom(collector, nextStreamsAjax.select("tbody[id=\"pl-load-more-destination\"]").first());
- return collector;
+ return new NextItemsResult(collector.getItemList(), nextStreamsUrl);
}
private void setupNextStreamsAjax(Downloader downloader) throws IOException, ReCaptchaException, ParsingException {
@@ -204,8 +171,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
String nextStreamsHtmlDataRaw = ajaxData.getString("load_more_widget_html");
if (!nextStreamsHtmlDataRaw.isEmpty()) {
- final Document nextStreamsData = Jsoup.parse(nextStreamsHtmlDataRaw, nextStreamsUrl);
- nextStreamsUrl = getNextStreamsUrl(nextStreamsData);
+ nextStreamsUrl = getNextStreamsUrlFrom(Jsoup.parse(nextStreamsHtmlDataRaw, nextStreamsUrl));
} else {
nextStreamsUrl = "";
}
@@ -214,17 +180,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
}
}
- private void fetchDocument() throws IOException, ReCaptchaException, ParsingException {
- Downloader downloader = NewPipe.getDownloader();
-
- String pageContent = downloader.download(getUrl());
- doc = Jsoup.parse(pageContent, getUrl());
-
- nextStreamsUrl = getNextStreamsUrl(doc);
- nextStreamsAjax = null;
- }
-
- private String getNextStreamsUrl(Document d) throws ParsingException {
+ private String getNextStreamsUrlFrom(Document d) throws ParsingException {
try {
Element button = d.select("button[class*=\"yt-uix-load-more\"]").first();
if (button != null) {
@@ -241,7 +197,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
private void collectStreamsFrom(StreamInfoItemCollector collector, Element element) throws ParsingException {
collector.getItemList().clear();
- final YoutubeStreamUrlIdHandler youtubeStreamUrlIdHandler = YoutubeStreamUrlIdHandler.getInstance();
+ final UrlIdHandler streamUrlIdHandler = getService().getStreamUrlIdHandler();
for (final Element li : element.children()) {
collector.commit(new StreamInfoItemExtractor() {
@Override
@@ -252,7 +208,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
@Override
public String getWebPageUrl() throws ParsingException {
try {
- return youtubeStreamUrlIdHandler.getUrl(li.attr("data-video-id"));
+ return streamUrlIdHandler.getUrl(li.attr("data-video-id"));
} catch (Exception e) {
throw new ParsingException("Could not get web page url for the video", e);
}
@@ -300,7 +256,7 @@ public class YoutubePlaylistExtractor extends PlaylistExtractor {
@Override
public String getThumbnailUrl() throws ParsingException {
try {
- return "https://i.ytimg.com/vi/" + youtubeStreamUrlIdHandler.getId(getWebPageUrl()) + "/hqdefault.jpg";
+ return "https://i.ytimg.com/vi/" + streamUrlIdHandler.getId(getWebPageUrl()) + "/hqdefault.jpg";
} catch (Exception e) {
throw new ParsingException("Could not get thumbnail url", e);
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java
index 082a1c6da..eb8926024 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java
@@ -34,61 +34,48 @@ import java.io.IOException;
public class YoutubeService extends StreamingService {
- public YoutubeService(int id) {
- super(id);
+ public YoutubeService(int id, String name) {
+ super(id, name);
}
@Override
- public ServiceInfo getServiceInfo() {
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.name = "Youtube";
- return serviceInfo;
- }
-
- @Override
- public StreamExtractor getStreamExtractorInstance(String url)
- throws ExtractionException, IOException {
- UrlIdHandler urlIdHandler = YoutubeStreamUrlIdHandler.getInstance();
- if (urlIdHandler.acceptUrl(url)) {
- return new YoutubeStreamExtractor(urlIdHandler, url, getServiceId());
- } else {
- throw new IllegalArgumentException("supplied String is not a valid Youtube URL");
- }
- }
-
- @Override
- public SearchEngine getSearchEngineInstance() {
+ public SearchEngine getSearchEngine() {
return new YoutubeSearchEngine(getServiceId());
}
@Override
- public UrlIdHandler getStreamUrlIdHandlerInstance() {
+ public UrlIdHandler getStreamUrlIdHandler() {
return YoutubeStreamUrlIdHandler.getInstance();
}
@Override
- public UrlIdHandler getChannelUrlIdHandlerInstance() {
+ public UrlIdHandler getChannelUrlIdHandler() {
return YoutubeChannelUrlIdHandler.getInstance();
}
-
@Override
- public UrlIdHandler getPlaylistUrlIdHandlerInstance() {
+ public UrlIdHandler getPlaylistUrlIdHandler() {
return YoutubePlaylistUrlIdHandler.getInstance();
}
+
@Override
- public ChannelExtractor getChannelExtractorInstance(String url) throws ExtractionException, IOException {
- return new YoutubeChannelExtractor(getChannelUrlIdHandlerInstance(), url, getServiceId());
+ public StreamExtractor getStreamExtractor(String url) throws IOException, ExtractionException {
+ return new YoutubeStreamExtractor(this, url);
}
@Override
- public PlaylistExtractor getPlaylistExtractorInstance(String url) throws ExtractionException, IOException {
- return new YoutubePlaylistExtractor(getPlaylistUrlIdHandlerInstance(), url, getServiceId());
+ public ChannelExtractor getChannelExtractor(String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ return new YoutubeChannelExtractor(this, url, nextStreamsUrl);
}
@Override
- public SuggestionExtractor getSuggestionExtractorInstance() {
+ public PlaylistExtractor getPlaylistExtractor(String url, String nextStreamsUrl) throws IOException, ExtractionException {
+ return new YoutubePlaylistExtractor(this, url, nextStreamsUrl);
+ }
+
+ @Override
+ public SuggestionExtractor getSuggestionExtractor() {
return new YoutubeSuggestionExtractor(getServiceId());
}
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java
index 197e9bfe1..7fe23053b 100644
--- a/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java
@@ -10,7 +10,7 @@ import org.mozilla.javascript.Function;
import org.mozilla.javascript.ScriptableObject;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.UrlIdHandler;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
@@ -26,9 +26,9 @@ import org.schabi.newpipe.extractor.utils.Parser;
import org.schabi.newpipe.extractor.utils.Utils;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -80,12 +80,9 @@ public class YoutubeStreamExtractor extends StreamExtractor {
/*//////////////////////////////////////////////////////////////////////////*/
private Document doc;
- private final String dirtyUrl;
- public YoutubeStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl, int serviceId) throws ExtractionException, IOException {
- super(urlIdHandler, urlIdHandler.cleanUrl(pageUrl), serviceId);
- dirtyUrl = pageUrl;
- fetchDocument();
+ public YoutubeStreamExtractor(StreamingService service, String url) throws IOException, ExtractionException {
+ super(service, url);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -95,7 +92,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
@Override
public String getId() throws ParsingException {
try {
- return getUrlIdHandler().getId(getUrl());
+ return getUrlIdHandler().getId(getCleanUrl());
} catch (Exception e) {
throw new ParsingException("Could not get stream id");
}
@@ -238,8 +235,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
@Override
- public List getAudioStreams() throws ParsingException {
- Vector audioStreams = new Vector<>();
+ public List getAudioStreams() throws IOException, ExtractionException {
+ List audioStreams = new ArrayList<>();
try {
String encodedUrlMap;
// playerArgs could be null if the video is age restricted
@@ -288,8 +285,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
@Override
- public List getVideoStreams() throws ParsingException {
- Vector videoStreams = new Vector<>();
+ public List getVideoStreams() throws IOException, ExtractionException {
+ List videoStreams = new ArrayList<>();
try {
String encodedUrlMap;
@@ -342,8 +339,8 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
@Override
- public List getVideoOnlyStreams() throws ParsingException {
- Vector videoOnlyStreams = new Vector<>();
+ public List getVideoOnlyStreams() throws IOException, ExtractionException {
+ List videoOnlyStreams = new ArrayList<>();
try {
String encodedUrlMap;
@@ -405,7 +402,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
public int getTimeStamp() throws ParsingException {
String timeStamp;
try {
- timeStamp = Parser.matchGroup1("((#|&|\\?)t=\\d{0,3}h?\\d{0,3}m?\\d{1,3}s?)", dirtyUrl);
+ timeStamp = Parser.matchGroup1("((#|&|\\?)t=\\d{0,3}h?\\d{0,3}m?\\d{1,3}s?)", getOriginalUrl());
} catch (Parser.RegexException e) {
// catch this instantly since an url does not necessarily have to have a time stamp
@@ -516,7 +513,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
@Override
- public StreamInfoItemExtractor getNextVideo() throws ParsingException {
+ public StreamInfoItemExtractor getNextVideo() throws IOException, ExtractionException {
try {
return extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first()
.select("li").first());
@@ -526,9 +523,9 @@ public class YoutubeStreamExtractor extends StreamExtractor {
}
@Override
- public StreamInfoItemCollector getRelatedVideos() throws ParsingException {
+ public StreamInfoItemCollector getRelatedVideos() throws IOException, ExtractionException {
try {
- StreamInfoItemCollector collector = getStreamPreviewInfoCollector();
+ StreamInfoItemCollector collector = new StreamInfoItemCollector(getServiceId());
Element ul = doc.select("ul[id=\"watch-related\"]").first();
if (ul != null) {
for (Element li : ul.children()) {
@@ -617,11 +614,12 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// cached values
private static volatile String decryptionCode = "";
- private void fetchDocument() throws IOException, ReCaptchaException, ParsingException {
+ @Override
+ public void fetchPage() throws IOException, ExtractionException {
Downloader downloader = NewPipe.getDownloader();
- String pageContent = downloader.download(getUrl());
- doc = Jsoup.parse(pageContent, getUrl());
+ String pageContent = downloader.download(getCleanUrl());
+ doc = Jsoup.parse(pageContent, getCleanUrl());
JSONObject ytPlayerConfig;
String playerUrl;
@@ -632,7 +630,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// Check if the video is age restricted
if (pageContent.contains(" suggestionList(
- String query, String contentCountry)
- throws ExtractionException, IOException {
+ public List suggestionList(String query, String contentCountry) throws IOException, ExtractionException {
+ Downloader dl = NewPipe.getDownloader();
List suggestions = new ArrayList<>();
- Downloader dl = NewPipe.getDownloader();
-
String url = "https://suggestqueries.google.com/complete/search"
- + "?client=" + ""
- + "&output=" + "toolbar"
+ + "?client=" + "firefox" // 'toolbar' for xml
+ "&ds=" + "yt"
+ "&hl=" + URLEncoder.encode(contentCountry, CHARSET_UTF_8)
+ "&q=" + URLEncoder.encode(query, CHARSET_UTF_8);
-
String response = dl.download(url);
-
- //TODO: Parse xml data using Jsoup not done
- DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
- DocumentBuilder dBuilder;
- org.w3c.dom.Document doc = null;
-
try {
- dBuilder = dbFactory.newDocumentBuilder();
- doc = dBuilder.parse(new InputSource(
- new ByteArrayInputStream(response.getBytes(CHARSET_UTF_8))));
- doc.getDocumentElement().normalize();
- } catch (ParserConfigurationException | SAXException | IOException e) {
- throw new ParsingException("Could not parse document.");
- }
-
- try {
- NodeList nList = doc.getElementsByTagName("CompleteSuggestion");
- for (int temp = 0; temp < nList.getLength(); temp++) {
-
- NodeList nList1 = doc.getElementsByTagName("suggestion");
- Node nNode1 = nList1.item(temp);
- if (nNode1.getNodeType() == Node.ELEMENT_NODE) {
- org.w3c.dom.Element eElement = (org.w3c.dom.Element) nNode1;
- suggestions.add(eElement.getAttribute("data"));
- }
- }
- return suggestions;
+ JSONArray suggestionsArray = new JSONArray(response).getJSONArray(1);
+ for (Object suggestion : suggestionsArray) suggestions.add(suggestion.toString());
} catch (Exception e) {
- throw new ParsingException("Could not get suggestions form document.", e);
+ throw new ParsingException("Could not parse suggestions response.", e);
}
+
+ return suggestions;
}
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/stream/Stream.java b/src/main/java/org/schabi/newpipe/extractor/stream/Stream.java
index c7606cee0..d14e56519 100644
--- a/src/main/java/org/schabi/newpipe/extractor/stream/Stream.java
+++ b/src/main/java/org/schabi/newpipe/extractor/stream/Stream.java
@@ -13,7 +13,7 @@ public abstract class Stream implements Serializable {
}
/**
- * Reveals whether two streams are the same, but have different urls
+ * Reveals whether two streams have the same stats (format and bitrate, for example)
*/
public boolean equalStats(Stream cmp) {
return cmp != null && format == cmp.format;
diff --git a/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java b/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java
index 1cf4c1fe5..c70ad36d7 100644
--- a/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java
+++ b/src/main/java/org/schabi/newpipe/extractor/stream/StreamExtractor.java
@@ -21,9 +21,10 @@ package org.schabi.newpipe.extractor.stream;
*/
import org.schabi.newpipe.extractor.Extractor;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.UrlIdHandler;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import java.io.IOException;
import java.util.List;
@@ -33,8 +34,14 @@ import java.util.List;
*/
public abstract class StreamExtractor extends Extractor {
- public StreamExtractor(UrlIdHandler urlIdHandler, String url, int serviceId) {
- super(urlIdHandler, serviceId, url);
+ public StreamExtractor(StreamingService service, String url) throws IOException, ExtractionException {
+ super(service, url);
+ fetchPage();
+ }
+
+ @Override
+ protected UrlIdHandler getUrlIdHandler() throws ParsingException {
+ return getService().getStreamUrlIdHandler();
}
public abstract String getId() throws ParsingException;
@@ -48,22 +55,22 @@ public abstract class StreamExtractor extends Extractor {
public abstract String getUploadDate() throws ParsingException;
public abstract String getThumbnailUrl() throws ParsingException;
public abstract String getUploaderThumbnailUrl() throws ParsingException;
- public abstract List getAudioStreams() throws ParsingException, ReCaptchaException, IOException;
- public abstract List getVideoStreams() throws ParsingException;
- public abstract List getVideoOnlyStreams() throws ParsingException;
+ public abstract List getAudioStreams() throws IOException, ExtractionException;
+ public abstract List getVideoStreams() throws IOException, ExtractionException;
+ public abstract List getVideoOnlyStreams() throws IOException, ExtractionException;
public abstract String getDashMpdUrl() throws ParsingException;
public abstract int getAgeLimit() throws ParsingException;
public abstract String getAverageRating() throws ParsingException;
public abstract int getLikeCount() throws ParsingException;
public abstract int getDislikeCount() throws ParsingException;
- public abstract StreamInfoItemExtractor getNextVideo() throws ParsingException;
- public abstract StreamInfoItemCollector getRelatedVideos() throws ParsingException, ReCaptchaException, IOException;
+ public abstract StreamInfoItemExtractor getNextVideo() throws IOException, ExtractionException;
+ public abstract StreamInfoItemCollector getRelatedVideos() throws IOException, ExtractionException;
public abstract StreamType getStreamType() throws ParsingException;
/**
* Analyses the webpage's document and extracts any error message there might be.
*
- * @return Error message; null if there is no error message.
+ * @return Error message; null if there is no error message.
*/
public abstract String getErrorMessage();
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java b/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java
index 6168ae26e..2e0242ea1 100644
--- a/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java
+++ b/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java
@@ -2,14 +2,18 @@ package org.schabi.newpipe.extractor.stream;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.InfoItem;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.utils.DashMpdParser;
import org.schabi.newpipe.extractor.utils.Utils;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Vector;
/*
* Created by Christian Schabesberger on 26.08.15.
@@ -46,11 +50,23 @@ public class StreamInfo extends Info {
public StreamInfo() {
}
+ public static StreamInfo getInfo(String url) throws IOException, ExtractionException {
+ return getInfo(NewPipe.getServiceByUrl(url), url);
+ }
+
+ public static StreamInfo getInfo(ServiceList serviceItem, String url) throws IOException, ExtractionException {
+ return getInfo(serviceItem.getService(), url);
+ }
+
+ public static StreamInfo getInfo(StreamingService service, String url) throws IOException, ExtractionException {
+ return getInfo(service.getStreamExtractor(url));
+ }
+
/**
* Fills out the video info fields which are common to all services.
* Probably needs to be overridden by subclasses
*/
- public static StreamInfo getVideoInfo(StreamExtractor extractor) throws ExtractionException {
+ public static StreamInfo getInfo(StreamExtractor extractor) throws ExtractionException {
StreamInfo streamInfo = new StreamInfo();
try {
@@ -80,7 +96,7 @@ public class StreamInfo extends Info {
// if one of these is not available an exception is meant to be thrown directly into the frontend.
streamInfo.service_id = extractor.getServiceId();
- streamInfo.url = extractor.getUrl();
+ streamInfo.url = extractor.getCleanUrl();
streamInfo.stream_type = extractor.getStreamType();
streamInfo.id = extractor.getId();
streamInfo.name = extractor.getTitle();
@@ -128,15 +144,12 @@ public class StreamInfo extends Info {
}
// Lists can be null if a exception was thrown during extraction
- if (streamInfo.video_streams == null) streamInfo.video_streams = new Vector<>();
- if (streamInfo.video_only_streams == null) streamInfo.video_only_streams = new Vector<>();
- if (streamInfo.audio_streams == null) streamInfo.audio_streams = new Vector<>();
+ if (streamInfo.video_streams == null) streamInfo.video_streams = new ArrayList<>();
+ if (streamInfo.video_only_streams == null) streamInfo.video_only_streams = new ArrayList<>();
+ if (streamInfo.audio_streams == null) streamInfo.audio_streams = new ArrayList<>();
if (streamInfo.dashMpdUrl != null && !streamInfo.dashMpdUrl.isEmpty()) {
try {
- // Will try to find in the dash manifest for any stream that the ItagItem has (by the id),
- // it has video, video only and audio streams and will only add to the list if it don't
- // find a similar stream in the respective lists (calling Stream#equalStats).
DashMpdParser.getStreams(streamInfo);
} catch (Exception e) {
// Sometimes we receive 403 (forbidden) error when trying to download the manifest,
@@ -246,6 +259,8 @@ public class StreamInfo extends Info {
streamInfo.addException(e);
}
+ if (streamInfo.related_streams == null) streamInfo.related_streams = new ArrayList<>();
+
return streamInfo;
}
@@ -278,7 +293,7 @@ public class StreamInfo extends Info {
public int dislike_count = -1;
public String average_rating;
public StreamInfoItem next_video;
- public List related_streams = new Vector<>();
+ public List related_streams;
//in seconds. some metadata is not passed using a StreamInfo object!
public int start_position = 0;
}
diff --git a/src/main/java/org/schabi/newpipe/extractor/utils/DashMpdParser.java b/src/main/java/org/schabi/newpipe/extractor/utils/DashMpdParser.java
index 2fa6b249a..27b625168 100644
--- a/src/main/java/org/schabi/newpipe/extractor/utils/DashMpdParser.java
+++ b/src/main/java/org/schabi/newpipe/extractor/utils/DashMpdParser.java
@@ -53,7 +53,13 @@ public class DashMpdParser {
}
/**
- * Download manifest and return nodelist with elements of tag "AdaptationSet"
+ * Will try to download (using {@link StreamInfo#dashMpdUrl}) and parse the dash manifest,
+ * then it will search for any stream that the ItagItem has (by the id).
+ *
+ * It has video, video only and audio streams and will only add to the list if it don't
+ * find a similar stream in the respective lists (calling {@link Stream#equalStats}).
+ *
+ * @param streamInfo where the parsed streams will be added
*/
public static void getStreams(StreamInfo streamInfo) throws DashMpdParsingException, ReCaptchaException {
String dashDoc;
diff --git a/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java b/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java
index ff32c88b6..d81b7f8d9 100644
--- a/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java
+++ b/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java
@@ -9,7 +9,7 @@ public class Utils {
* Remove all non-digit characters from a string.
* Examples:
*
- 1 234 567 views -> 1234567
- * - $ 31,133.124 -> 31133124
+ * $31,133.124 -> 31133124
*
* @param toRemove string to remove non-digit chars
* @return a string that contains only digits
@@ -21,15 +21,23 @@ public class Utils {
/**
* Check if throwable have the cause
*/
- public static boolean hasCauseThrowable(Throwable throwable, Class> causeToCheck) {
+ public static boolean hasCauseThrowable(Throwable throwable, Class>... causesToCheck) {
// Check if getCause is not the same as cause (the getCause is already the root),
// as it will cause a infinite loop if it is
- Throwable cause, getCause = throwable;
+ Throwable cause, getCause = throwable;
+
+ for (Class> causesEl : causesToCheck) {
+ if (throwable.getClass().isAssignableFrom(causesEl)) {
+ return true;
+ }
+ }
while ((cause = throwable.getCause()) != null && getCause != cause) {
getCause = cause;
- if (cause.getClass().isAssignableFrom(causeToCheck)) {
- return true;
+ for (Class> causesEl : causesToCheck) {
+ if (cause.getClass().isAssignableFrom(causesEl)) {
+ return true;
+ }
}
}
return false;
diff --git a/src/test/java/org/schabi/newpipe/Downloader.java b/src/test/java/org/schabi/newpipe/Downloader.java
index 870c17005..e3e08cc5e 100644
--- a/src/test/java/org/schabi/newpipe/Downloader.java
+++ b/src/test/java/org/schabi/newpipe/Downloader.java
@@ -14,7 +14,7 @@ import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
-/**
+/*
* Created by Christian Schabesberger on 28.01.16.
*
* Copyright (C) Christian Schabesberger 2016
@@ -35,16 +35,17 @@ import javax.net.ssl.HttpsURLConnection;
*/
public class Downloader implements org.schabi.newpipe.extractor.Downloader {
-
+
private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0";
private static String mCookies = "";
private static Downloader instance = null;
- private Downloader() {}
+ private Downloader() {
+ }
public static Downloader getInstance() {
- if(instance == null) {
+ if (instance == null) {
synchronized (Downloader.class) {
if (instance == null) {
instance = new Downloader();
@@ -62,11 +63,14 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
return Downloader.mCookies;
}
- /**Download the text file at the supplied URL as in download(String),
+ /**
+ * Download the text file at the supplied URL as in download(String),
* but set the HTTP header field "Accept-Language" to the supplied string.
- * @param siteUrl the URL of the text file to return the contents of
+ *
+ * @param siteUrl the URL of the text file to return the contents of
* @param language the language (usually a 2-character code) to set as the preferred language
- * @return the contents of the specified text file*/
+ * @return the contents of the specified text file
+ */
public String download(String siteUrl, String language) throws IOException, ReCaptchaException {
Map requestProperties = new HashMap<>();
requestProperties.put("Accept-Language", language);
@@ -74,29 +78,35 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
}
- /**Download the text file at the supplied URL as in download(String),
+ /**
+ * Download the text file at the supplied URL as in download(String),
* but set the HTTP header field "Accept-Language" to the supplied string.
- * @param siteUrl the URL of the text file to return the contents of
+ *
+ * @param siteUrl the URL of the text file to return the contents of
* @param customProperties set request header properties
* @return the contents of the specified text file
- * @throws IOException*/
+ * @throws IOException
+ */
public String download(String siteUrl, Map customProperties) throws IOException, ReCaptchaException {
URL url = new URL(siteUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
Iterator it = customProperties.entrySet().iterator();
- while(it.hasNext()) {
- Map.Entry pair = (Map.Entry)it.next();
- con.setRequestProperty((String)pair.getKey(), (String)pair.getValue());
+ while (it.hasNext()) {
+ Map.Entry pair = (Map.Entry) it.next();
+ con.setRequestProperty((String) pair.getKey(), (String) pair.getValue());
}
return dl(con);
}
- /**Common functionality between download(String url) and download(String url, String language)*/
+ /**
+ * Common functionality between download(String url) and download(String url, String language)
+ */
private static String dl(HttpsURLConnection con) throws IOException, ReCaptchaException {
StringBuilder response = new StringBuilder();
BufferedReader in = null;
try {
+ con.setReadTimeout(30 * 1000);// 30s
con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", USER_AGENT);
@@ -108,13 +118,13 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
new InputStreamReader(con.getInputStream()));
String inputLine;
- while((inputLine = in.readLine()) != null) {
+ while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
- } catch(UnknownHostException uhe) {//thrown when there's no internet connection
+ } catch (UnknownHostException uhe) {//thrown when there's no internet connection
throw new IOException("unknown host or no network", uhe);
//Toast.makeText(getActivity(), uhe.getMessage(), Toast.LENGTH_LONG).show();
- } catch(Exception e) {
+ } catch (Exception e) {
/*
* HTTP 429 == Too Many Request
* Receive from Youtube.com = ReCaptcha challenge request
@@ -123,9 +133,10 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
if (con.getResponseCode() == 429) {
throw new ReCaptchaException("reCaptcha Challenge requested");
}
- throw new IOException(e);
+
+ throw new IOException(con.getResponseCode() + " " + con.getResponseMessage(), e);
} finally {
- if(in != null) {
+ if (in != null) {
in.close();
}
}
@@ -133,10 +144,13 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
return response.toString();
}
- /**Download (via HTTP) the text file located at the supplied URL, and return its contents.
+ /**
+ * Download (via HTTP) the text file located at the supplied URL, and return its contents.
* Primarily intended for downloading web pages.
+ *
* @param siteUrl the URL of the text file to download
- * @return the contents of the specified text file*/
+ * @return the contents of the specified text file
+ */
public String download(String siteUrl) throws IOException, ReCaptchaException {
URL url = new URL(siteUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
diff --git a/src/test/java/org/schabi/newpipe/extractor/NewPipeTest.java b/src/test/java/org/schabi/newpipe/extractor/NewPipeTest.java
new file mode 100644
index 000000000..1c59e76f0
--- /dev/null
+++ b/src/test/java/org/schabi/newpipe/extractor/NewPipeTest.java
@@ -0,0 +1,74 @@
+package org.schabi.newpipe.extractor;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
+import static org.schabi.newpipe.extractor.NewPipe.getServiceByUrl;
+
+public class NewPipeTest {
+ @Test
+ public void getAllServicesTest() throws Exception {
+ assertEquals(NewPipe.getServices().length, ServiceList.values().length);
+ }
+
+ @Test
+ public void testAllServicesHaveDifferentId() throws Exception {
+ HashSet servicesId = new HashSet<>();
+ for (StreamingService streamingService : NewPipe.getServices()) {
+ String errorMsg = "There are services with the same id = " + streamingService.getServiceId() + " (current service > " + streamingService.getServiceInfo().name + ")";
+
+ assertTrue(errorMsg, servicesId.add(streamingService.getServiceId()));
+ }
+ }
+
+ @Test
+ public void getServiceWithId() throws Exception {
+ assertEquals(NewPipe.getService(Youtube.getId()), Youtube.getService());
+ assertEquals(NewPipe.getService(SoundCloud.getId()), SoundCloud.getService());
+
+ assertNotEquals(NewPipe.getService(SoundCloud.getId()), Youtube.getService());
+ }
+
+ @Test
+ public void getServiceWithName() throws Exception {
+ assertEquals(NewPipe.getService(Youtube.getServiceInfo().name), Youtube.getService());
+ assertEquals(NewPipe.getService(SoundCloud.getServiceInfo().name), SoundCloud.getService());
+
+ assertNotEquals(NewPipe.getService(Youtube.getServiceInfo().name), SoundCloud.getService());
+ }
+
+ @Test
+ public void getServiceWithUrl() throws Exception {
+ assertEquals(getServiceByUrl("https://www.youtube.com/watch?v=_r6CgaFNAGg"), Youtube.getService());
+ assertEquals(getServiceByUrl("https://www.youtube.com/channel/UCi2bIyFtz-JdI-ou8kaqsqg"), Youtube.getService());
+ assertEquals(getServiceByUrl("https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH"), Youtube.getService());
+ assertEquals(getServiceByUrl("https://soundcloud.com/shupemoosic/pegboard-nerds-try-this"), SoundCloud.getService());
+ assertEquals(getServiceByUrl("https://soundcloud.com/deluxe314/sets/pegboard-nerds"), SoundCloud.getService());
+ assertEquals(getServiceByUrl("https://soundcloud.com/pegboardnerds"), SoundCloud.getService());
+
+ assertNotEquals(getServiceByUrl("https://soundcloud.com/pegboardnerds"), Youtube.getService());
+ assertNotEquals(getServiceByUrl("https://www.youtube.com/playlist?list=PLRqwX-V7Uu6ZiZxtDDRCi6uhfTH4FilpH"), SoundCloud.getService());
+ }
+
+ @Test
+ public void getIdWithServiceName() throws Exception {
+ assertEquals(NewPipe.getIdOfService(Youtube.getServiceInfo().name), Youtube.getId());
+ assertEquals(NewPipe.getIdOfService(SoundCloud.getServiceInfo().name), SoundCloud.getId());
+
+ assertNotEquals(NewPipe.getIdOfService(SoundCloud.getServiceInfo().name), Youtube.getId());
+ }
+
+ @Test
+ public void getServiceNameWithId() throws Exception {
+ assertEquals(NewPipe.getNameOfService(Youtube.getId()), Youtube.getServiceInfo().name);
+ assertEquals(NewPipe.getNameOfService(SoundCloud.getId()), SoundCloud.getServiceInfo().name);
+
+ assertNotEquals(NewPipe.getNameOfService(Youtube.getId()), SoundCloud.getServiceInfo().name);
+ }
+}
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java
index 0338bb768..3c98a5a2a 100644
--- a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractorTest.java
@@ -1,16 +1,17 @@
package org.schabi.newpipe.extractor.services.youtube;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static junit.framework.Assert.assertTrue;
-
import org.junit.Before;
import org.junit.Test;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
-/**
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
+
+/*
* Created by Christian Schabesberger on 12.09.16.
*
* Copyright (C) Christian Schabesberger 2015
@@ -41,8 +42,8 @@ public class YoutubeChannelExtractorTest {
@Before
public void setUp() throws Exception {
NewPipe.init(Downloader.getInstance());
- extractor = NewPipe.getService("Youtube")
- .getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw");
+ extractor = Youtube.getService()
+ .getChannelExtractor("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw");
}
@Test
@@ -61,7 +62,7 @@ public class YoutubeChannelExtractorTest {
}
@Test
- public void testGetBannerurl() throws Exception {
+ public void testGetBannerUrl() throws Exception {
assertTrue(extractor.getBannerUrl(), extractor.getBannerUrl().contains("yt3"));
}
@@ -81,9 +82,10 @@ public class YoutubeChannelExtractorTest {
}
@Test
- public void testHasNextPage() throws Exception {
- // this particular example (link) has a next page !!!
- assertTrue("no next page link found", extractor.hasMoreStreams());
+ public void testHasMoreStreams() throws Exception {
+ // Setup the streams
+ extractor.getStreams();
+ assertTrue("don't have more streams", extractor.hasMoreStreams());
}
@Test
@@ -92,16 +94,11 @@ public class YoutubeChannelExtractorTest {
}
@Test
- public void testGetNextPage() throws Exception {
- extractor = NewPipe.getService("Youtube")
- .getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw");
- assertTrue("next page didn't have content", !extractor.getStreams().getItemList().isEmpty());
+ public void testGetNextStreams() throws Exception {
+ // Setup the streams
+ extractor.getStreams();
+ assertTrue("extractor didn't have next streams", !extractor.getNextStreams().nextItemsList.isEmpty());
+ assertTrue("extractor didn't have more streams after getNextStreams", extractor.hasMoreStreams());
}
- @Test
- public void testGetNextNextPageUrl() throws Exception {
- extractor = NewPipe.getService("Youtube")
- .getChannelExtractorInstance("https://www.youtube.com/channel/UCYJ61XIK64sp6ZFFS8sctxw");
- assertTrue("next page didn't have content", extractor.hasMoreStreams());
- }
}
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractorTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractorTest.java
new file mode 100644
index 000000000..45fc46d1b
--- /dev/null
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubePlaylistExtractorTest.java
@@ -0,0 +1,98 @@
+package org.schabi.newpipe.extractor.services.youtube;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.schabi.newpipe.Downloader;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
+
+/**
+ * Test for {@link PlaylistExtractor}
+ */
+
+public class YoutubePlaylistExtractorTest {
+ private PlaylistExtractor extractor;
+
+ @Before
+ public void setUp() throws Exception {
+ NewPipe.init(Downloader.getInstance());
+ extractor = Youtube.getService()
+ .getPlaylistExtractor("https://www.youtube.com/playlist?list=PL7XlqX4npddfrdpMCxBnNZXg2GFll7t5y");
+ }
+
+ @Test
+ public void testGetDownloader() throws Exception {
+ assertNotNull(NewPipe.getDownloader());
+ }
+
+ @Test
+ public void testGetId() throws Exception {
+ assertEquals(extractor.getPlaylistId(), "PL7XlqX4npddfrdpMCxBnNZXg2GFll7t5y");
+ }
+
+ @Test
+ public void testGetName() throws Exception {
+ assertEquals(extractor.getPlaylistName(), "important videos");
+ }
+
+ @Test
+ public void testGetAvatarUrl() throws Exception {
+ assertTrue(extractor.getAvatarUrl(), extractor.getAvatarUrl().contains("yt"));
+ }
+
+ @Test
+ public void testGetBannerUrl() throws Exception {
+ assertTrue(extractor.getBannerUrl(), extractor.getBannerUrl().contains("yt"));
+ }
+
+ @Test
+ public void testGetUploaderUrl() throws Exception {
+ assertTrue(extractor.getUploaderUrl(), extractor.getUploaderUrl().contains("youtube.com"));
+ }
+
+ @Test
+ public void testGetUploaderName() throws Exception {
+ assertTrue(extractor.getUploaderName(), !extractor.getUploaderName().isEmpty());
+ }
+
+ @Test
+ public void testGetUploaderAvatarUrl() throws Exception {
+ assertTrue(extractor.getUploaderAvatarUrl(), extractor.getUploaderAvatarUrl().contains("yt"));
+ }
+
+ @Test
+ public void testGetStreamsCount() throws Exception {
+ assertTrue("error in the streams count", extractor.getStreamCount() > 100);
+ }
+
+ @Test
+ public void testGetStreams() throws Exception {
+ assertTrue("no streams are received", !extractor.getStreams().getItemList().isEmpty());
+ }
+
+ @Test
+ public void testGetStreamsErrors() throws Exception {
+ assertTrue("errors during stream list extraction", extractor.getStreams().getErrors().isEmpty());
+ }
+
+ @Test
+ public void testHasMoreStreams() throws Exception {
+ // Setup the streams
+ extractor.getStreams();
+ assertTrue("extractor didn't have more streams", extractor.hasMoreStreams());
+ }
+
+ @Test
+ public void testGetNextStreams() throws Exception {
+ // Setup the streams
+ extractor.getStreams();
+ assertTrue("extractor didn't have next streams", !extractor.getNextStreams().nextItemsList.isEmpty());
+ assertTrue("extractor didn't have more streams after getNextStreams", extractor.hasMoreStreams());
+ }
+
+}
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineAllTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineAllTest.java
index ca01d2295..e18c0830c 100644
--- a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineAllTest.java
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineAllTest.java
@@ -9,11 +9,12 @@ import org.schabi.newpipe.extractor.search.SearchResult;
import java.util.EnumSet;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
-/**
+/*
* Created by Christian Schabesberger on 29.12.15.
*
* Copyright (C) Christian Schabesberger 2015
@@ -42,7 +43,7 @@ public class YoutubeSearchEngineAllTest {
@Before
public void setUp() throws Exception {
NewPipe.init(Downloader.getInstance());
- SearchEngine engine = NewPipe.getService("Youtube").getSearchEngineInstance();
+ SearchEngine engine = Youtube.getService().getSearchEngine();
// Youtube will suggest "asdf" instead of "asdgff"
// keep in mind that the suggestions can change by country (the parameter "de")
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineChannelTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineChannelTest.java
index df0d0bb03..868b2b83c 100644
--- a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineChannelTest.java
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineChannelTest.java
@@ -10,12 +10,13 @@ import org.schabi.newpipe.extractor.search.SearchResult;
import java.util.EnumSet;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
-/**
+/*
* Created by Christian Schabesberger on 29.12.15.
*
* Copyright (C) Christian Schabesberger 2015
@@ -44,7 +45,7 @@ public class YoutubeSearchEngineChannelTest {
@Before
public void setUp() throws Exception {
NewPipe.init(Downloader.getInstance());
- SearchEngine engine = NewPipe.getService("Youtube").getSearchEngineInstance();
+ SearchEngine engine = Youtube.getService().getSearchEngine();
// Youtube will suggest "gronkh" instead of "grrunkh"
// keep in mind that the suggestions can change by country (the parameter "de")
@@ -59,7 +60,9 @@ public class YoutubeSearchEngineChannelTest {
@Test
public void testChannelItemType() {
- assertEquals(result.resultList.get(0).info_type, InfoItem.InfoType.CHANNEL);
+ for (InfoItem infoItem : result.resultList) {
+ assertEquals(InfoItem.InfoType.CHANNEL, infoItem.info_type);
+ }
}
@Test
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineStreamTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineStreamTest.java
index 74334a3e5..da67ea766 100644
--- a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineStreamTest.java
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngineStreamTest.java
@@ -2,6 +2,7 @@ package org.schabi.newpipe.extractor.services.youtube;
import org.junit.Before;
import org.junit.Test;
+
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.NewPipe;
@@ -10,12 +11,13 @@ import org.schabi.newpipe.extractor.search.SearchResult;
import java.util.EnumSet;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
-/**
+/*
* Created by Christian Schabesberger on 29.12.15.
*
* Copyright (C) Christian Schabesberger 2015
@@ -44,7 +46,7 @@ public class YoutubeSearchEngineStreamTest {
@Before
public void setUp() throws Exception {
NewPipe.init(Downloader.getInstance());
- SearchEngine engine = NewPipe.getService("Youtube").getSearchEngineInstance();
+ SearchEngine engine = Youtube.getService().getSearchEngine();
// Youtube will suggest "results" instead of "rsults",
// keep in mind that the suggestions can change by country (the parameter "de")
@@ -58,8 +60,10 @@ public class YoutubeSearchEngineStreamTest {
}
@Test
- public void testChannelItemType() {
- assertEquals(result.resultList.get(0).info_type, InfoItem.InfoType.STREAM);
+ public void testStreamItemType() {
+ for (InfoItem infoItem : result.resultList) {
+ assertEquals(InfoItem.InfoType.STREAM, infoItem.info_type);
+ }
}
@Test
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java
index 8e2c7be78..8c3976e3d 100644
--- a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java
@@ -1,21 +1,23 @@
package org.schabi.newpipe.extractor.services.youtube;
-import static junit.framework.Assert.assertTrue;
-
-import java.io.IOException;
-
import org.junit.Before;
import org.junit.Test;
import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.stream.StreamExtractor;
+import org.schabi.newpipe.extractor.stream.StreamInfoItemCollector;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
-/**
+import java.io.IOException;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
+
+/*
* Created by Christian Schabesberger on 30.12.15.
*
* Copyright (C) Christian Schabesberger 2015
@@ -45,8 +47,7 @@ public class YoutubeStreamExtractorDefaultTest {
@Before
public void setUp() throws Exception {
NewPipe.init(Downloader.getInstance());
- extractor = NewPipe.getService("Youtube")
- .getStreamExtractorInstance("https://www.youtube.com/watch?v=YQHsXMglC9A");
+ extractor = Youtube.getService().getStreamExtractor("https://www.youtube.com/watch?v=YQHsXMglC9A");
}
@Test
@@ -56,10 +57,8 @@ public class YoutubeStreamExtractorDefaultTest {
}
@Test
- public void testGetValidTimeStamp() throws ExtractionException, IOException {
- StreamExtractor extractor =
- NewPipe.getService("Youtube")
- .getStreamExtractorInstance("https://youtu.be/FmG385_uUys?t=174");
+ public void testGetValidTimeStamp() throws IOException, ExtractionException {
+ StreamExtractor extractor = Youtube.getService().getStreamExtractor("https://youtu.be/FmG385_uUys?t=174");
assertTrue(Integer.toString(extractor.getTimeStamp()),
extractor.getTimeStamp() == 174);
}
@@ -113,12 +112,12 @@ public class YoutubeStreamExtractorDefaultTest {
}
@Test
- public void testGetAudioStreams() throws ParsingException, ReCaptchaException, IOException {
+ public void testGetAudioStreams() throws IOException, ExtractionException {
assertTrue(!extractor.getAudioStreams().isEmpty());
}
@Test
- public void testGetVideoStreams() throws ParsingException {
+ public void testGetVideoStreams() throws IOException, ExtractionException {
for(VideoStream s : extractor.getVideoStreams()) {
assertTrue(s.url,
s.url.contains(HTTPS));
@@ -138,4 +137,11 @@ public class YoutubeStreamExtractorDefaultTest {
assertTrue(extractor.getDashMpdUrl(),
extractor.getDashMpdUrl() != null || !extractor.getDashMpdUrl().isEmpty());
}
+
+ @Test
+ public void testGetRelatedVideos() throws ExtractionException, IOException {
+ StreamInfoItemCollector relatedVideos = extractor.getRelatedVideos();
+ assertFalse(relatedVideos.getItemList().isEmpty());
+ assertTrue(relatedVideos.getErrors().isEmpty());
+ }
}
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorGemaTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorGemaTest.java
new file mode 100644
index 000000000..ba164e800
--- /dev/null
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorGemaTest.java
@@ -0,0 +1,53 @@
+package org.schabi.newpipe.extractor.services.youtube;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.schabi.newpipe.Downloader;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+
+import java.io.IOException;
+
+import static org.junit.Assert.fail;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
+
+/*
+ * Created by Christian Schabesberger on 30.12.15.
+ *
+ * Copyright (C) Christian Schabesberger 2015
+ * YoutubeVideoExtractorGema.java is part of NewPipe.
+ *
+ * NewPipe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * NewPipe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPipe. If not, see .
+ */
+
+/**
+ * This exception is only thrown in Germany.
+ *
+ * WARNING: Deactivate this Test Case before uploading it to Github, otherwise CI will fail.
+ */
+@Ignore
+public class YoutubeStreamExtractorGemaTest {
+
+ @Test
+ public void testGemaError() throws IOException, ExtractionException {
+ try {
+ NewPipe.init(Downloader.getInstance());
+ Youtube.getService().getStreamExtractor("https://www.youtube.com/watch?v=3O1_3zBUKM8");
+
+ fail("GemaException should be thrown");
+ } catch (YoutubeStreamExtractor.GemaException ignored) {
+ // Exception was thrown, Gema error detection is working.
+ }
+ }
+}
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorRestrictedTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorRestrictedTest.java
new file mode 100644
index 000000000..862eaa486
--- /dev/null
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorRestrictedTest.java
@@ -0,0 +1,108 @@
+package org.schabi.newpipe.extractor.services.youtube;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.schabi.newpipe.Downloader;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.exceptions.ParsingException;
+import org.schabi.newpipe.extractor.stream.StreamExtractor;
+import org.schabi.newpipe.extractor.stream.VideoStream;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertTrue;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
+
+/**
+ * Test for {@link YoutubeStreamUrlIdHandler}
+ */
+public class YoutubeStreamExtractorRestrictedTest {
+ public static final String HTTPS = "https://";
+ private StreamExtractor extractor;
+
+ @Before
+ public void setUp() throws Exception {
+ NewPipe.init(Downloader.getInstance());
+ extractor = Youtube.getService()
+ .getStreamExtractor("https://www.youtube.com/watch?v=i6JTvzrpBy0");
+ }
+
+ @Test
+ public void testGetInvalidTimeStamp() throws ParsingException {
+ assertTrue(Integer.toString(extractor.getTimeStamp()),
+ extractor.getTimeStamp() <= 0);
+ }
+
+ @Test
+ public void testGetValidTimeStamp() throws IOException, ExtractionException {
+ StreamExtractor extractor= Youtube.getService()
+ .getStreamExtractor("https://youtu.be/FmG385_uUys?t=174");
+ assertTrue(Integer.toString(extractor.getTimeStamp()),
+ extractor.getTimeStamp() == 174);
+ }
+
+ @Test
+ public void testGetAgeLimit() throws ParsingException {
+ assertTrue(extractor.getAgeLimit() == 18);
+ }
+
+ @Test
+ public void testGetTitle() throws ParsingException {
+ assertTrue(!extractor.getTitle().isEmpty());
+ }
+
+ @Test
+ public void testGetDescription() throws ParsingException {
+ assertTrue(extractor.getDescription() != null);
+ }
+
+ @Test
+ public void testGetUploader() throws ParsingException {
+ assertTrue(!extractor.getUploader().isEmpty());
+ }
+
+ @Test
+ public void testGetLength() throws ParsingException {
+ assertTrue(extractor.getLength() > 0);
+ }
+
+ @Test
+ public void testGetViews() throws ParsingException {
+ assertTrue(extractor.getLength() > 0);
+ }
+
+ @Test
+ public void testGetUploadDate() throws ParsingException {
+ assertTrue(extractor.getUploadDate().length() > 0);
+ }
+
+ @Test
+ public void testGetThumbnailUrl() throws ParsingException {
+ assertTrue(extractor.getThumbnailUrl(),
+ extractor.getThumbnailUrl().contains(HTTPS));
+ }
+
+ @Test
+ public void testGetUploaderThumbnailUrl() throws ParsingException {
+ assertTrue(extractor.getUploaderThumbnailUrl(),
+ extractor.getUploaderThumbnailUrl().contains(HTTPS));
+ }
+
+ @Test
+ public void testGetAudioStreams() throws IOException, ExtractionException {
+ // audiostream not always necessary
+ assertTrue(!extractor.getAudioStreams().isEmpty());
+ }
+
+ @Test
+ public void testGetVideoStreams() throws IOException, ExtractionException {
+ for(VideoStream s : extractor.getVideoStreams()) {
+ assertTrue(s.url,
+ s.url.contains(HTTPS));
+ assertTrue(s.resolution.length() > 0);
+ assertTrue(Integer.toString(s.format),
+ 0 <= s.format && s.format <= 4);
+ }
+ }
+}
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandlerTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandlerTest.java
new file mode 100644
index 000000000..c0c77714c
--- /dev/null
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandlerTest.java
@@ -0,0 +1,117 @@
+package org.schabi.newpipe.extractor.services.youtube;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.schabi.newpipe.Downloader;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.exceptions.FoundAdException;
+import org.schabi.newpipe.extractor.exceptions.ParsingException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for {@link YoutubeStreamUrlIdHandler}
+ */
+public class YoutubeStreamUrlIdHandlerTest {
+ private static String AD_URL = "https://googleads.g.doubleclick.net/aclk?sa=l&ai=C-2IPgeVTWPf4GcOStgfOnIOADf78n61GvKmmobYDrgIQASDj-5MDKAJg9ZXOgeAEoAGgy_T-A8gBAakC2gkpmquIsT6oAwGqBJMBT9BgD5kVgbN0dX602bFFaDw9vsxq-We-S8VkrXVBi6W_e7brZ36GCz1WO3EPEeklYuJjXLUowwCOKsd-8xr1UlS_tusuFJv9iX35xoBHKTRvs8-0aDbfEIm6in37QDfFuZjqgEMB8-tg0Jn_Pf1RU5OzbuU40B4Gy25NUTnOxhDKthOhKBUSZEksCEerUV8GMu10iAXCxquwApIFBggDEAEYAaAGGsgGlIjthrUDgAfItIsBqAemvhvYBwHSCAUIgGEQAbgT6AE&num=1&sig=AOD64_1DybDd4qAm5O7o9UAbTNRdqXXHFQ&ctype=21&video_id=dMO_IXYPZew&client=ca-pub-6219811747049371&adurl=http://www.youtube.com/watch%3Fv%3DdMO_IXYPZew";
+ private YoutubeStreamUrlIdHandler urlIdHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ urlIdHandler = YoutubeStreamUrlIdHandler.getInstance();
+ NewPipe.init(Downloader.getInstance());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void getIdWithNullAsUrl() throws ParsingException {
+ urlIdHandler.getId(null);
+ }
+
+ @Test(expected = FoundAdException.class)
+ public void getIdForAd() throws ParsingException {
+ urlIdHandler.getId(AD_URL);
+ }
+
+ @Test
+ public void getIdForInvalidUrls() throws ParsingException {
+ List invalidUrls = new ArrayList<>(50);
+ invalidUrls.add("https://www.youtube.com/watch?v=jZViOEv90d");
+ invalidUrls.add("https://www.youtube.com/watchjZViOEv90d");
+ invalidUrls.add("https://www.youtube.com/");
+ for(String invalidUrl: invalidUrls) {
+ Throwable exception = null;
+ try {
+ urlIdHandler.getId(invalidUrl);
+ } catch (ParsingException e) {
+ exception = e;
+ }
+ if(exception == null) {
+ fail("Expected ParsingException for url: " + invalidUrl);
+ }
+ }
+ }
+ @Test
+ public void getId() throws Exception {
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("https://www.youtube.com/watch?v=jZViOEv90dI"));
+ assertEquals("W-fFHeTX70Q", urlIdHandler.getId("https://www.youtube.com/watch?v=W-fFHeTX70Q"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("https://www.youtube.com/watch?v=jZViOEv90dI?t=100"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("https://WWW.YouTube.com/watch?v=jZViOEv90dI?t=100"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("HTTPS://www.youtube.com/watch?v=jZViOEv90dI?t=100"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("https://youtu.be/jZViOEv90dI?t=9s"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("HTTPS://Youtu.be/jZViOEv90dI?t=9s"));
+ assertEquals("uEJuoEs1UxY", urlIdHandler.getId("http://www.youtube.com/watch_popup?v=uEJuoEs1UxY"));
+ assertEquals("uEJuoEs1UxY", urlIdHandler.getId("http://www.Youtube.com/watch_popup?v=uEJuoEs1UxY"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("https://www.youtube.com/embed/jZViOEv90dI"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("https://www.youtube-nocookie.com/embed/jZViOEv90dI"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("http://www.youtube.com/watch?v=jZViOEv90dI"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("http://youtube.com/watch?v=jZViOEv90dI"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("http://youtu.be/jZViOEv90dI?t=9s"));
+ assertEquals("7_WWz2DSnT8", urlIdHandler.getId("https://youtu.be/7_WWz2DSnT8"));
+ assertEquals("oy6NvWeVruY", urlIdHandler.getId("https://m.youtube.com/watch?v=oy6NvWeVruY"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("http://www.youtube.com/embed/jZViOEv90dI"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("http://www.Youtube.com/embed/jZViOEv90dI"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("http://www.youtube-nocookie.com/embed/jZViOEv90dI"));
+ assertEquals("EhxJLojIE_o", urlIdHandler.getId("http://www.youtube.com/attribution_link?a=JdfC0C9V6ZI&u=%2Fwatch%3Fv%3DEhxJLojIE_o%26feature%3Dshare"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("vnd.youtube://www.youtube.com/watch?v=jZViOEv90dI"));
+ assertEquals("jZViOEv90dI", urlIdHandler.getId("vnd.youtube:jZViOEv90dI"));
+
+ // Shared links
+ String sharedId = "7JIArTByb3E";
+ String realId = "Q7JsK50NGaA";
+ assertEquals(realId, urlIdHandler.getId("vnd.youtube://www.YouTube.com/shared?ci=" + sharedId + "&feature=twitter-deep-link"));
+ assertEquals(realId, urlIdHandler.getId("vnd.youtube://www.youtube.com/shared?ci=" + sharedId ));
+ assertEquals(realId, urlIdHandler.getId("https://www.youtube.com/shared?ci=" + sharedId));
+ }
+
+
+ @Test
+ public void testAcceptUrl() {
+ assertTrue(urlIdHandler.acceptUrl("https://www.youtube.com/watch?v=jZViOEv90dI"));
+ assertTrue(urlIdHandler.acceptUrl("https://www.youtube.com/watch?v=jZViOEv90dI?t=100"));
+ assertTrue(urlIdHandler.acceptUrl("https://WWW.YouTube.com/watch?v=jZViOEv90dI?t=100"));
+ assertTrue(urlIdHandler.acceptUrl("HTTPS://www.youtube.com/watch?v=jZViOEv90dI?t=100"));
+ assertTrue(urlIdHandler.acceptUrl("https://youtu.be/jZViOEv90dI?t=9s"));
+ //assertTrue(urlIdHandler.acceptUrl("https://www.youtube.com/watch/jZViOEv90dI"));
+ assertTrue(urlIdHandler.acceptUrl("https://www.youtube.com/embed/jZViOEv90dI"));
+ assertTrue(urlIdHandler.acceptUrl("https://www.youtube-nocookie.com/embed/jZViOEv90dI"));
+ assertTrue(urlIdHandler.acceptUrl("http://www.youtube.com/watch?v=jZViOEv90dI"));
+ assertTrue(urlIdHandler.acceptUrl("http://youtu.be/jZViOEv90dI?t=9s"));
+ assertTrue(urlIdHandler.acceptUrl("http://www.youtube.com/embed/jZViOEv90dI"));
+ assertTrue(urlIdHandler.acceptUrl("http://www.youtube-nocookie.com/embed/jZViOEv90dI"));
+ assertTrue(urlIdHandler.acceptUrl("http://www.youtube.com/attribution_link?a=JdfC0C9V6ZI&u=%2Fwatch%3Fv%3DEhxJLojIE_o%26feature%3Dshare"));
+ assertTrue(urlIdHandler.acceptUrl("vnd.youtube://www.youtube.com/watch?v=jZViOEv90dI"));
+ assertTrue(urlIdHandler.acceptUrl("vnd.youtube:jZViOEv90dI"));
+
+ assertTrue(urlIdHandler.acceptUrl("vnd.youtube:jZViOEv90dI"));
+
+ String sharedId = "8A940MXKFmQ";
+ assertTrue(urlIdHandler.acceptUrl("vnd.youtube://www.youtube.com/shared?ci=" + sharedId + "&feature=twitter-deep-link"));
+ assertTrue(urlIdHandler.acceptUrl("vnd.youtube://www.youtube.com/shared?ci=" + sharedId ));
+ assertTrue(urlIdHandler.acceptUrl("https://www.youtube.com/shared?ci=" + sharedId));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSuggestionExtractorTest.java b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSuggestionExtractorTest.java
new file mode 100644
index 000000000..c47828ab4
--- /dev/null
+++ b/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSuggestionExtractorTest.java
@@ -0,0 +1,51 @@
+package org.schabi.newpipe.extractor.services.youtube;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.schabi.newpipe.Downloader;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.SuggestionExtractor;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertFalse;
+import static org.schabi.newpipe.extractor.ServiceList.Youtube;
+
+/*
+ * Created by Christian Schabesberger on 18.11.16.
+ *
+ * Copyright (C) Christian Schabesberger 2016
+ * YoutubeSuggestionExtractorTest.java is part of NewPipe.
+ *
+ * NewPipe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * NewPipe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPipe. If not, see .
+ */
+
+/**
+ * Test for {@link SuggestionExtractor}
+ */
+public class YoutubeSuggestionExtractorTest {
+ private SuggestionExtractor suggestionExtractor;
+
+ @Before
+ public void setUp() throws Exception {
+ NewPipe.init(Downloader.getInstance());
+ suggestionExtractor = Youtube.getService().getSuggestionExtractor();
+ }
+
+ @Test
+ public void testIfSuggestions() throws IOException, ExtractionException {
+ assertFalse(suggestionExtractor.suggestionList("hello", "de").isEmpty());
+ }
+}