pull/496/head
FongMi 2 years ago
parent 62c66a814f
commit 4d35ea4ac2
  1. 2
      app/src/main/java/com/fongmi/android/tv/player/Source.java
  2. 35
      app/src/main/java/com/fongmi/android/tv/player/extractor/BiliBili.java
  3. 109
      app/src/main/java/com/fongmi/android/tv/player/extractor/Youtube.java
  4. 7
      app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java

@ -2,7 +2,6 @@ package com.fongmi.android.tv.player;
import com.fongmi.android.tv.bean.Channel;
import com.fongmi.android.tv.bean.Result;
import com.fongmi.android.tv.player.extractor.BiliBili;
import com.fongmi.android.tv.player.extractor.Force;
import com.fongmi.android.tv.player.extractor.JianPian;
import com.fongmi.android.tv.player.extractor.Push;
@ -30,7 +29,6 @@ public class Source {
public Source() {
extractors = new ArrayList<>();
extractors.add(new BiliBili());
extractors.add(new Force());
extractors.add(new JianPian());
extractors.add(new Push());

@ -1,35 +0,0 @@
package com.fongmi.android.tv.player.extractor;
import android.net.Uri;
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 okhttp3.Headers;
public class BiliBili implements Source.Extractor {
@Override
public boolean match(String scheme, String host) {
return "live.bilibili.com".equals(host);
}
@Override
public String fetch(String url) throws Exception {
String room = Uri.parse(url).getPath().replace("/", "");
String api = String.format("https://api.live.bilibili.com/room/v1/Room/playUrl?cid=%s&qn=20000&platform=h5", room);
String result = OkHttp.newCall(api, Headers.of(HttpHeaders.USER_AGENT, Util.CHROME)).execute().body().string();
return Json.parse(result).getAsJsonObject().get("data").getAsJsonObject().get("durl").getAsJsonArray().get(0).getAsJsonObject().get("url").getAsString();
}
@Override
public void stop() {
}
@Override
public void exit() {
}
}

@ -1,21 +1,24 @@
package com.fongmi.android.tv.player.extractor;
import android.text.TextUtils;
import android.util.Base64;
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 org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.services.youtube.YoutubeJavaScriptPlayerManager;
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory;
import org.schabi.newpipe.extractor.stream.VideoStream;
import org.schabi.newpipe.extractor.utils.Parser;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -23,6 +26,9 @@ import okhttp3.Headers;
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='%s' minBufferTime='PT1.500S' profiles='urn:mpeg:dash:profile:isoff-on-demand:2011'>\n" + "<Period duration='%s' start='PT0S'>\n" + "%s\n" + "%s\n" + "</Period>\n" + "</MPD>";
private static final String ADAPTATION_SET = "<AdaptationSet lang='chi'>\n" + "<ContentComponent contentType='%s'/>\n" + "<Representation id='%s' bandwidth='%d' codecs='%s' mimeType='%s' %s maxPlayoutRate='1' startWithSAP='1'>\n" + "<BaseURL>%s</BaseURL>\n" + "<SegmentBase indexRange='%s'>\n" + "<Initialization range='%s'/>\n" + "</SegmentBase>\n" + "</Representation>\n" + "</AdaptationSet>";
@Override
public boolean match(String scheme, String host) {
return host.contains("youtube.com") || host.contains("youtu.be");
@ -34,37 +40,80 @@ public class Youtube implements Source.Extractor {
@Override
public String fetch(String url) throws Exception {
String id = YoutubeStreamLinkHandlerFactory.getInstance().getId(url);
String html = OkHttp.newCall(url, Headers.of(HttpHeaders.USER_AGENT, Util.CHROME)).execute().body().string();
Matcher matcher = Pattern.compile("hlsManifestUrl\\S*?(https\\S*?\\.m3u8)").matcher(html);
if (matcher.find()) {
html = OkHttp.newCall(matcher.group(1), Headers.of(HttpHeaders.USER_AGENT, Util.CHROME)).execute().body().string();
return find(html);
} else {
LinkHandler handler = YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(url);
YoutubeStreamExtractor extractor = new YoutubeStreamExtractor(ServiceList.YouTube, handler);
extractor.fetchPage();
return find(extractor);
Matcher matcher = Pattern.compile("var ytInitialPlayerResponse =(.*?\\});").matcher(html);
if (!matcher.find()) return "";
JsonObject streamingData = Json.parse(matcher.group(1)).getAsJsonObject().get("streamingData").getAsJsonObject();
if (streamingData.has("hlsManifestUrl")) return getHlsManifestUrl(streamingData);
if (streamingData.has("adaptiveFormats")) return getMpdWithBase64(streamingData, id);
return url;
}
private String getHlsManifestUrl(JsonObject streamingData) {
JsonElement hlsManifestUrl = streamingData.get("hlsManifestUrl");
if (hlsManifestUrl.isJsonArray()) return hlsManifestUrl.getAsJsonArray().get(0).getAsString();
return hlsManifestUrl.getAsString();
}
private String getMpdWithBase64(JsonObject streamingData, String videoId) {
String approxDurationMs = "";
StringBuilder video = new StringBuilder();
StringBuilder audio = new StringBuilder();
for (JsonElement element : streamingData.get("adaptiveFormats").getAsJsonArray()) {
JsonObject adaptiveFormat = element.getAsJsonObject();
String mimeType = adaptiveFormat.get("mimeType").getAsString();
if (mimeType.contains("video")) video.append(getAdaptationSet(videoId, adaptiveFormat, "video", mimeType.split(";")));
if (mimeType.contains("audio")) audio.append(getAdaptationSet(videoId, adaptiveFormat, "audio", mimeType.split(";")));
if (TextUtils.isEmpty(approxDurationMs)) approxDurationMs = adaptiveFormat.get("approxDurationMs").getAsString();
}
String duration = String.format(Locale.getDefault(), "PT%.3fS", Integer.parseInt(approxDurationMs) / 1000.0);
String finalMpd = String.format(Locale.getDefault(), MPD, duration, duration, video, audio);
return "data:application/dash+xml;base64," + Base64.encodeToString(finalMpd.getBytes(), 0);
}
private String find(YoutubeStreamExtractor extractor) throws ExtractionException {
VideoStream item = extractor.getVideoStreams().get(0);
for (VideoStream stream : extractor.getVideoStreams()) if (!stream.isVideoOnly() && stream.getHeight() >= item.getHeight()) item = stream;
return item.getContent();
private String getAdaptationSet(String videoId, JsonObject adaptiveFormat, String contentType, String[] split) {
String mediaParam = "";
String mimeType = split[0];
String baseUrl = getBaseUrl(videoId, adaptiveFormat);
String iTag = adaptiveFormat.get("itag").getAsString();
int bitrate = adaptiveFormat.get("bitrate").getAsInt();
String codecs = split[1].split("=")[1].replace("\"", "");
JsonObject initRange = adaptiveFormat.get("initRange").getAsJsonObject();
JsonObject indexRange = adaptiveFormat.get("indexRange").getAsJsonObject();
String initParam = initRange.get("start").getAsString() + "-" + initRange.get("end").getAsString();
String indexParam = indexRange.get("start").getAsString() + "-" + indexRange.get("end").getAsString();
if (mimeType.contains("video")) {
int fps = adaptiveFormat.get("fps").getAsInt();
int width = adaptiveFormat.get("width").getAsInt();
int height = adaptiveFormat.get("height").getAsInt();
mediaParam = String.format(Locale.getDefault(), "height='%d' width='%d' frameRate='%d'", height, width, fps);
}
if (mimeType.contains("audio")) {
int audioSamplingRate = adaptiveFormat.get("audioSampleRate").getAsInt();
mediaParam = String.format(Locale.getDefault(), "subsegmentAlignment='true' audioSamplingRate='%d'", audioSamplingRate);
}
return String.format(Locale.getDefault(), ADAPTATION_SET, contentType, iTag, bitrate, codecs, mimeType, mediaParam, baseUrl, indexParam, initParam);
}
private String find(String html) {
String url = "";
List<String> items = Arrays.asList("301", "300", "96", "95", "94");
for (String item : items) if (!(url = find(html, "https:/.*/" + item + "/.*index.m3u8")).isEmpty()) break;
return url;
private String getBaseUrl(String videoId, JsonObject adaptiveFormat) {
String baseUrl;
if (adaptiveFormat.has("url")) baseUrl = adaptiveFormat.get("url").getAsString();
else baseUrl = decodeCipher(videoId, adaptiveFormat);
return baseUrl.replace("&", "&amp;");
}
private String find(String html, String rule) {
Pattern pattern = Pattern.compile(rule);
Matcher matcher = pattern.matcher(html);
if (matcher.find()) return matcher.group();
return "";
private String decodeCipher(String videoId, JsonObject adaptiveFormat) {
try {
String cipherString = adaptiveFormat.has("cipher") ? adaptiveFormat.get("cipher").getAsString() : adaptiveFormat.get("signatureCipher").getAsString();
Map<String, String> cipher = Parser.compatParseMap(cipherString);
return cipher.get("url") + "&" + cipher.get("sp") + "=" + YoutubeJavaScriptPlayerManager.deobfuscateSignature(videoId, cipher.get("s"));
} catch (Exception e) {
return "";
}
}
@Override

@ -11,7 +11,6 @@ import androidx.viewbinding.ViewBinding;
import com.fongmi.android.tv.App;
import com.fongmi.android.tv.Constant;
import com.fongmi.android.tv.R;
import com.fongmi.android.tv.api.config.VodConfig;
import com.fongmi.android.tv.bean.Config;
import com.fongmi.android.tv.bean.Device;
@ -118,10 +117,6 @@ public class SyncDialog extends BaseDialog implements DeviceAdapter.OnClickListe
dismiss();
}
private void onError() {
Notify.show(R.string.device_offline);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onScanEvent(ScanEvent event) {
ScanTask.create(this).start(event.getAddress());
@ -152,7 +147,7 @@ public class SyncDialog extends BaseDialog implements DeviceAdapter.OnClickListe
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
App.post(() -> onError());
App.post(() -> Notify.show(e.getMessage()));
}
};
}

Loading…
Cancel
Save