Add newpipe

pull/585/head
FongMi 12 months ago
parent a37ca80dd9
commit 89bd56881c
  1. 3
      app/build.gradle
  2. 7
      app/proguard-rules.pro
  3. 69
      app/src/main/java/com/fongmi/android/tv/impl/NewPipeImpl.java
  4. 3
      app/src/main/java/com/fongmi/android/tv/player/Source.java
  5. 142
      app/src/main/java/com/fongmi/android/tv/player/extractor/Youtube.java

@ -81,6 +81,7 @@ android {
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
@ -101,6 +102,7 @@ dependencies {
implementation 'com.github.bumptech.glide:okhttp3-integration:4.16.0'
implementation 'com.github.jahirfiquitiva:TextDrawable:1.0.3'
implementation 'com.github.thegrizzlylabs:sardine-android:0.9'
implementation 'com.github.teamnewpipe:NewPipeExtractor:v0.24.5'
implementation 'com.google.android.material:material:1.12.0'
implementation 'com.google.zxing:core:3.3.0'
implementation 'com.guolindev.permissionx:permissionx:1.8.0'
@ -123,4 +125,5 @@ dependencies {
annotationProcessor 'androidx.room:room-compiler:2.6.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.4'
}

@ -54,6 +54,13 @@
# Nano
-keep class fi.iki.elonen.** { *; }
# NewPipeExtractor
-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; }
-keep class org.mozilla.javascript.** { *; }
-keep class org.mozilla.classfile.ClassFileWriter
-dontwarn org.mozilla.javascript.tools.**
-dontwarn java.beans.**
# QuickJS
-keep class com.fongmi.quickjs.method.** { *; }

@ -0,0 +1,69 @@
package com.fongmi.android.tv.impl;
import androidx.annotation.NonNull;
import com.github.catvod.net.OkHttp;
import com.github.catvod.utils.Util;
import org.schabi.newpipe.extractor.downloader.Downloader;
import org.schabi.newpipe.extractor.downloader.Request;
import org.schabi.newpipe.extractor.downloader.Response;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
public final class NewPipeImpl extends Downloader {
private static class Loader {
static volatile NewPipeImpl INSTANCE = new NewPipeImpl();
}
public static NewPipeImpl get() {
return Loader.INSTANCE;
}
@Override
public Response execute(@NonNull Request request) throws IOException, ReCaptchaException {
String httpMethod = request.httpMethod();
String url = request.url();
Map<String, List<String>> headers = request.headers();
byte[] dataToSend = request.dataToSend();
RequestBody requestBody = null;
if (dataToSend != null) {
requestBody = RequestBody.create(null, dataToSend);
}
okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder().method(httpMethod, requestBody).url(url).addHeader("User-Agent", Util.CHROME);
for (Map.Entry<String, List<String>> pair : headers.entrySet()) {
String headerName = pair.getKey();
List<String> headerValueList = pair.getValue();
if (headerValueList.size() > 1) {
requestBuilder.removeHeader(headerName);
for (String headerValue : headerValueList) {
requestBuilder.addHeader(headerName, headerValue);
}
} else if (headerValueList.size() == 1) {
requestBuilder.header(headerName, headerValueList.get(0));
}
}
okhttp3.Response response = OkHttp.client().newCall(requestBuilder.build()).execute();
if (response.code() == 429) {
response.close();
throw new ReCaptchaException("reCaptcha Challenge requested", url);
}
ResponseBody body = response.body();
String responseBodyToReturn = body.string();
String latestUrl = response.request().url().toString();
return new Response(response.code(), response.message(), response.headers().toMultimap(), responseBodyToReturn, latestUrl);
}
}

@ -60,6 +60,9 @@ public class Source {
if (Thunder.Parser.match(url)) {
items.add(Thunder.Parser.get(url));
iterator.remove();
} else if (Youtube.Parser.match(url)) {
items.add(Youtube.Parser.get(url));
iterator.remove();
}
}

@ -1,20 +1,43 @@
package com.fongmi.android.tv.player.extractor;
import android.util.Base64;
import com.fongmi.android.tv.bean.Episode;
import com.fongmi.android.tv.impl.NewPipeImpl;
import com.fongmi.android.tv.player.Source;
import com.github.catvod.net.OkHttp;
import com.github.catvod.utils.Json;
import com.github.catvod.utils.Util;
import com.google.common.net.HttpHeaders;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.Headers;
import org.schabi.newpipe.extractor.ListExtractor;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.localization.Localization;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubePlaylistExtractor;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubePlaylistLinkHandlerFactory;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
public class Youtube implements Source.Extractor {
private static final String MPD = "<MPD xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns='urn:mpeg:dash:schema:mpd:2011' xsi:schemaLocation='urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd' type='static' mediaPresentationDuration='PT%sS' minBufferTime='PT1.500S' profiles='urn:mpeg:dash:profile:isoff-on-demand:2011'>\n" + "<Period duration='PT%sS' start='PT0S'>\n" + "%s\n" + "%s\n" + "</Period>\n" + "</MPD>";
private static final String ADAPT = "<AdaptationSet lang='chi'>\n" + "<ContentComponent contentType='%s'/>\n" + "<Representation id='%d' bandwidth='%d' codecs='%s' mimeType='%s' %s>\n" + "<BaseURL>%s</BaseURL>\n" + "<SegmentBase indexRange='%s'>\n" + "<Initialization range='%s'/>\n" + "</SegmentBase>\n" + "</Representation>\n" + "</AdaptationSet>";
private static final Pattern PATTERN_LIST = Pattern.compile("(youtube\\.com|youtu\\.be).*list=");
public Youtube() {
NewPipe.init(NewPipeImpl.get(), Localization.fromLocale(Locale.getDefault()));
}
@Override
public boolean match(String scheme, String host) {
return host.contains("youtube.com") || host.contains("youtu.be");
@ -22,17 +45,52 @@ public class Youtube implements Source.Extractor {
@Override
public String fetch(String url) throws Exception {
String html = OkHttp.newCall(url, Headers.of(HttpHeaders.USER_AGENT, Util.CHROME)).execute().body().string();
Matcher matcher = Pattern.compile("var ytInitialPlayerResponse =(.*?\\});").matcher(html);
if (matcher.find()) return getHlsManifestUrl(matcher);
return "";
LinkHandler handler = YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(url);
YoutubeStreamExtractor extractor = new YoutubeStreamExtractor(ServiceList.YouTube, handler);
extractor.forceLocalization(NewPipe.getPreferredLocalization());
extractor.fetchPage();
return StreamType.LIVE_STREAM.equals(extractor.getStreamType()) ? extractor.getHlsUrl() : getMpd(extractor);
}
private String getMpd(YoutubeStreamExtractor extractor) throws Exception {
StringBuilder video = new StringBuilder();
StringBuilder audio = new StringBuilder();
List<AudioStream> audioFormats = extractor.getAudioStreams();
List<VideoStream> videoFormats = extractor.getVideoOnlyStreams();
for (AudioStream format : audioFormats) audio.append(getAdaptationSet(format, getAudioParam(format)));
for (VideoStream format : videoFormats) video.append(getAdaptationSet(format, getVideoParam(format)));
String mpd = String.format(Locale.getDefault(), MPD, extractor.getLength(), extractor.getLength(), video, audio);
return "data:application/dash+xml;base64," + Base64.encodeToString(mpd.getBytes(), Base64.DEFAULT);
}
private String getHlsManifestUrl(Matcher matcher) {
JsonObject object = Json.parse(matcher.group(1)).getAsJsonObject();
JsonElement hlsManifestUrl = object.get("streamingData").getAsJsonObject().get("hlsManifestUrl");
if (hlsManifestUrl.isJsonArray()) return hlsManifestUrl.getAsJsonArray().get(0).getAsString();
return hlsManifestUrl.getAsString();
private String getVideoParam(VideoStream format) {
return String.format(Locale.getDefault(), "height='%d' width='%d' frameRate='%d' maxPlayoutRate='1' startWithSAP='1'", format.getHeight(), format.getWidth(), format.getFps());
}
private String getAudioParam(AudioStream format) {
return String.format(Locale.getDefault(), "subsegmentAlignment='true' audioSamplingRate='%d'", format.getItagItem().getSampleRate());
}
private String getAdaptationSet(VideoStream format, String param) {
int iTag = format.getItag();
int bitrate = format.getBitrate();
String codecs = format.getCodec();
String mimeType = format.getFormat().getMimeType();
String url = format.getContent().replace("&", "&amp;");
String initRange = format.getInitStart() + "-" + format.getInitEnd();
String indexRange = format.getIndexStart() + "-" + format.getIndexEnd();
return String.format(Locale.getDefault(), ADAPT, "video", iTag, bitrate, codecs, mimeType, param, url, indexRange, initRange);
}
private String getAdaptationSet(AudioStream format, String param) {
int iTag = format.getItag();
int bitrate = format.getBitrate();
String codecs = format.getCodec();
String mimeType = format.getFormat().getMimeType();
String url = format.getContent().replace("&", "&amp;");
String initRange = format.getInitStart() + "-" + format.getInitEnd();
String indexRange = format.getIndexStart() + "-" + format.getIndexEnd();
return String.format(Locale.getDefault(), ADAPT, "audio", iTag, bitrate, codecs, mimeType, param, url, indexRange, initRange);
}
@Override
@ -42,4 +100,50 @@ public class Youtube implements Source.Extractor {
@Override
public void exit() {
}
public static class Parser implements Callable<List<Episode>> {
private YoutubePlaylistExtractor extractor;
private final String url;
public static boolean match(String url) {
return PATTERN_LIST.matcher(url).find();
}
public static Parser get(String url) {
return new Parser(url);
}
public Parser(String url) {
this.url = url;
}
@Override
public List<Episode> call() {
try {
ListLinkHandler handler = YoutubePlaylistLinkHandlerFactory.getInstance().fromUrl(url);
extractor = new YoutubePlaylistExtractor(ServiceList.YouTube, handler);
extractor.forceLocalization(NewPipe.getPreferredLocalization());
extractor.fetchPage();
List<Episode> episodes = new ArrayList<>();
add(episodes, extractor.getInitialPage());
return episodes;
} catch (Exception e) {
return Collections.emptyList();
}
}
private void add(List<Episode> episodes, ListExtractor.InfoItemsPage<StreamInfoItem> page) {
for (StreamInfoItem item : page.getItems()) {
episodes.add(Episode.create(item.getName(), item.getUrl()));
}
if (page.hasNextPage()) {
try {
add(episodes, extractor.getPage(page.getNextPage()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}

Loading…
Cancel
Save