Merge pull request #165 from CatVodTVOfficial/dev

Add magnet support.
pull/1/head
小黄瓜 4 years ago committed by GitHub
commit 6b71a63b25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      app/build.gradle
  2. BIN
      app/libs/thunder.jar
  3. 23
      app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java
  4. 333
      app/src/main/java/com/github/tvbox/osc/util/thunder/Thunder.java
  5. 81
      app/src/main/java/com/github/tvbox/osc/viewmodel/SourceViewModel.java
  6. BIN
      player/src/main/jniLibs/armeabi-v7a/libxl_stat.so
  7. BIN
      player/src/main/jniLibs/armeabi-v7a/libxl_thunder_sdk.so

@ -68,6 +68,7 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation files('libs\\thunder.jar')
annotationProcessor 'androidx.room:room-compiler:2.3.0'
implementation 'androidx.room:room-runtime:2.3.0'

Binary file not shown.

@ -53,6 +53,7 @@ import com.github.tvbox.osc.util.LOG;
import com.github.tvbox.osc.util.MD5;
import com.github.tvbox.osc.util.PlayerHelper;
import com.github.tvbox.osc.util.XWalkUtils;
import com.github.tvbox.osc.util.thunder.Thunder;
import com.github.tvbox.osc.viewmodel.SourceViewModel;
import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.AbsCallback;
@ -462,6 +463,28 @@ public class PlayActivity extends BaseActivity {
playUrl(null, null);
String progressKey = mVodInfo.sourceKey + mVodInfo.id + mVodInfo.playFlag + mVodInfo.playIndex;
if (Thunder.play(vs.url, new Thunder.ThunderCallback() {
@Override
public void status(int code, String info) {
if (code < 0) {
setTip(info, false, true);
} else {
setTip(info, true, false);
}
}
@Override
public void list(String playList) {
}
@Override
public void play(String url) {
playUrl(url, null);
}
})) {
mController.showParse(false);
return;
}
sourceViewModel.getPlay(sourceKey, mVodInfo.playFlag, progressKey, vs.url);
}

@ -0,0 +1,333 @@
package com.github.tvbox.osc.util.thunder;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.text.TextUtils;
import com.xunlei.downloadlib.XLDownloadManager;
import com.xunlei.downloadlib.XLTaskHelper;
import com.xunlei.downloadlib.android.XLUtil;
import com.xunlei.downloadlib.parameter.TorrentFileInfo;
import com.xunlei.downloadlib.parameter.TorrentInfo;
import com.xunlei.downloadlib.parameter.XLTaskInfo;
import java.io.File;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Thunder {
private static String cacheRoot = "";
private static long currentTask = 0L;
private static ArrayList<TorrentFileInfo> torrentFileInfoArrayList = null;
private static ExecutorService threadPool = null;
private static void init(Context context) {
// fake deviceId and Mac
SharedPreferences sharedPreferences = context.getSharedPreferences("rand_thunder_id", Context.MODE_PRIVATE);
String imei = sharedPreferences.getString("imei", null);
String mac = sharedPreferences.getString("mac", null);
if (imei == null) {
imei = randomImei();
sharedPreferences.edit().putString("imei", imei).commit();
}
if (mac == null) {
mac = randomMac();
sharedPreferences.edit().putString("mac", mac).commit();
}
XLUtil.mIMEI = imei;
XLUtil.isGetIMEI = true;
XLUtil.mMAC = mac;
XLUtil.isGetMAC = true;
String cd3 = "cee25055f125a2fde0";
String base64Decode = "axzNjAwMQ^^yb==0^852^083dbcff^";
String substring = base64Decode.substring(1);
String substring2 = cd3.substring(0, cd3.length() - 1);
String cd = substring + substring2;
XLTaskHelper.init(context, cd, "21.01.07.800002");
cacheRoot = context.getCacheDir().getAbsolutePath() + File.separator + "thunder";
}
public static void stop() {
if (currentTask > 0) {
XLTaskHelper.instance().stopTask(currentTask);
currentTask = 0L;
}
torrentFileInfoArrayList = null;
// del cache file
File cache = new File(cacheRoot);
recursiveDelete(cache);
if (!cache.exists())
cache.mkdirs();
if (threadPool != null) {
try {
threadPool.shutdownNow();
threadPool = null;
} catch (Throwable th) {
}
}
}
public interface ThunderCallback {
void status(int code, String info);
void list(String playList);
void play(String url);
}
public static void parse(Context context, String url, ThunderCallback callback) {
init(context);
stop();
threadPool = Executors.newSingleThreadExecutor();
if (isMagnet(url) || isThunder(url)) {
String link = isThunder(url) ? XLDownloadManager.getInstance().parserThunderUrl(url) : url;
Uri p = Uri.parse(link);
if (p == null) {
callback.status(-1, "链接错误");
return;
}
String fileName = XLTaskHelper.instance().getFileName(link);
File cache = new File(cacheRoot + File.separator + fileName);
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
currentTask = isMagnet(url) ?
XLTaskHelper.instance().addMagentTask(url, cacheRoot, fileName) :
XLTaskHelper.instance().addThunderTask(url, cacheRoot, fileName);
} catch (Exception exception) {
exception.printStackTrace();
currentTask = 0;
}
if (currentTask <= 0) {
callback.status(-1, "链接错误");
return;
}
int count = 30;
while (true) {
count--;
if (count <= 0) {
callback.status(-1, "解析超时");
break;
}
XLTaskInfo taskInfo = XLTaskHelper.instance().getTaskInfo(currentTask);
switch (taskInfo.mTaskStatus) {
case 2: {
callback.status(0, "正在获取文件列表...");
try {
TorrentInfo torrentInfo = XLTaskHelper.instance().getTorrentInfo(cache.getAbsolutePath());
if (torrentInfo == null || TextUtils.isEmpty(torrentInfo.mInfoHash)) {
callback.status(-1, "解析失败");
} else {
TorrentFileInfo[] mSubFileInfo = torrentInfo.mSubFileInfo;
ArrayList<String> playList = new ArrayList<>();
ArrayList<TorrentFileInfo> list = new ArrayList<>();
if (mSubFileInfo != null && mSubFileInfo.length >= 0) {
for (TorrentFileInfo sub : mSubFileInfo) {
if (isMedia(sub.mFileName)) {
sub.torrentPath = cache.getAbsolutePath();
playList.add(sub.mFileName + "$tvbox-torrent:" + list.size());
list.add(sub);
}
}
}
if (list.size() > 0) {
torrentFileInfoArrayList = list;
callback.list(TextUtils.join("#", playList));
} else {
callback.status(-1, "文件列表为空!");
}
}
} catch (Throwable throwable) {
throwable.printStackTrace();
callback.status(-1, "解析失败");
}
return;
}
case 3: {
callback.status(-1, "解析失败");
return;
}
default: {
callback.status(0, "解析中...");
break;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
}
}
public static boolean play(String url, ThunderCallback callback) {
if (url.startsWith("tvbox-torrent:")) {
int idx = Integer.parseInt(url.substring(14));
TorrentFileInfo info = torrentFileInfoArrayList.get(idx);
if (currentTask > 0) {
XLTaskHelper.instance().stopTask(currentTask);
currentTask = 0L;
}
threadPool.execute(new Runnable() {
@Override
public void run() {
String torrentName = new File(info.torrentPath).getName();
String cache = cacheRoot + File.separator + torrentName.substring(0, torrentName.lastIndexOf("."));
currentTask = XLTaskHelper.instance().addTorrentTask(info.torrentPath, cache, info.mFileIndex);
if (currentTask < 0)
callback.status(-1, "下载出错");
int count = 30;
while (true) {
count--;
if (count <= 0) {
callback.status(-1, "解析下载超时");
break;
}
XLTaskInfo taskInfo = XLTaskHelper.instance().getBtSubTaskInfo(currentTask, info.mFileIndex).mTaskInfo;
switch (taskInfo.mTaskStatus) {
case 3: {
callback.status(-1, errorInfo(taskInfo.mErrorCode));
return;
}
case 1:
case 4: // 下载中
case 2: { // 下载完成
String pUrl = XLTaskHelper.instance().getLoclUrl(cache + File.separator + info.mFileName);
callback.play(pUrl);
return;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
return true;
}
return false;
}
private static String errorInfo(int code) {
switch (code) {
case 9125:
return "文件名太长";
case 111120:
return "文件路径太长";
case 111142:
return "文件太小";
case 111085:
return "磁盘空间不足";
case 111171:
return "拒绝的网络连接";
case 9301:
return "缓冲区不足";
case 114001:
case 114004:
case 114005:
case 114006:
case 114007:
case 114011:
case 9304:
case 111154:
return "版权限制:无权下载";
case 114101:
return "无效链接";
default:
return "ErrorCode=" + code;
}
}
public static boolean isSupportUrl(String url) {
return isMagnet(url) || isThunder(url)/* || isTorrent(url) || isEd2k(url)*/;
}
private static boolean isMagnet(String url) {
return url.toLowerCase().startsWith("magnet:");
}
private static boolean isThunder(String url) {
return url.toLowerCase().startsWith("thunder");
}
private static boolean isTorrent(String url) {
return url.toLowerCase().split(";")[0].endsWith(".torrent");
}
private static boolean isEd2k(String url) {
return url.toLowerCase().startsWith("ed2k:");
}
static void recursiveDelete(File file) {
if (!file.exists())
return;
if (file.isDirectory()) {
for (File f : file.listFiles()) {
recursiveDelete(f);
}
}
file.delete();
}
static ArrayList<String> formats = new ArrayList<>();
static boolean isMedia(String name) {
if (formats.size() == 0) {
formats.add(".rmvb");
formats.add(".avi");
formats.add(".mkv");
formats.add(".flv");
formats.add(".mp4");
formats.add(".rm");
formats.add(".vob");
formats.add(".wmv");
formats.add(".mov");
formats.add(".3gp");
formats.add(".asf");
formats.add("mpg");
formats.add("mpeg");
formats.add("mpe");
}
for (String f : formats) {
if (name.toLowerCase().endsWith(f))
return true;
}
return false;
}
static String randomImei() {
return randomString("0123456", 15);
}
static String randomMac() {
return randomString("ABCDEF0123456", 12).toUpperCase();
}
static String randomString(String base, int length) {
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}
}

@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel;
import com.github.catvod.crawler.Spider;
import com.github.tvbox.osc.api.ApiConfig;
import com.github.tvbox.osc.base.App;
import com.github.tvbox.osc.bean.AbsJson;
import com.github.tvbox.osc.bean.AbsSortJson;
import com.github.tvbox.osc.bean.AbsSortXml;
@ -17,6 +18,8 @@ import com.github.tvbox.osc.bean.SourceBean;
import com.github.tvbox.osc.event.RefreshEvent;
import com.github.tvbox.osc.util.DefaultConfig;
import com.github.tvbox.osc.util.HawkConfig;
import com.github.tvbox.osc.util.LOG;
import com.github.tvbox.osc.util.thunder.Thunder;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@ -618,6 +621,55 @@ public class SourceViewModel extends ViewModel {
}
}
private void checkThunder(AbsXml data) {
boolean thunderParse = false;
if (data.movie != null && data.movie.videoList != null && data.movie.videoList.size() == 1) {
Movie.Video video = data.movie.videoList.get(0);
if (video != null && video.urlBean != null && video.urlBean.infoList != null && video.urlBean.infoList.size() == 1) {
Movie.Video.UrlBean.UrlInfo urlInfo = video.urlBean.infoList.get(0);
if (urlInfo != null && urlInfo.beanList.size() == 1 && Thunder.isSupportUrl(urlInfo.beanList.get(0).url)) {
thunderParse = true;
Thunder.parse(App.getInstance(), urlInfo.beanList.get(0).url, new Thunder.ThunderCallback() {
@Override
public void status(int code, String info) {
if (code >= 0) {
LOG.i(info);
} else {
urlInfo.beanList.get(0).name = info;
detailResult.postValue(data);
}
}
@Override
public void list(String playList) {
urlInfo.urls = playList;
String[] str = playList.split("#");
List<Movie.Video.UrlBean.UrlInfo.InfoBean> infoBeanList = new ArrayList<>();
for (String s : str) {
if (s.contains("$")) {
String[] ss = s.split("\\$");
if (ss.length >= 2) {
infoBeanList.add(new Movie.Video.UrlBean.UrlInfo.InfoBean(ss[0], ss[1]));
}
}
}
urlInfo.beanList = infoBeanList;
detailResult.postValue(data);
}
@Override
public void play(String url) {
}
});
}
}
}
if (!thunderParse) {
detailResult.postValue(data);
}
}
private AbsXml xml(MutableLiveData<AbsXml> result, String xml, String sourceKey) {
try {
XStream xstream = new XStream(new DomDriver());//创建Xstram对象
@ -637,7 +689,11 @@ public class SourceViewModel extends ViewModel {
} else if (quickSearchResult == result) {
EventBus.getDefault().post(new RefreshEvent(RefreshEvent.TYPE_QUICK_SEARCH_RESULT, data));
} else if (result != null) {
result.postValue(data);
if (result == detailResult) {
checkThunder(data);
} else {
result.postValue(data);
}
}
return data;
} catch (Exception e) {
@ -654,6 +710,23 @@ public class SourceViewModel extends ViewModel {
private AbsXml json(MutableLiveData<AbsXml> result, String json, String sourceKey) {
try {
// 测试数据
/*json = "{\n" +
"\t\"list\": [{\n" +
"\t\t\"vod_id\": \"137133\",\n" +
"\t\t\"vod_name\": \"磁力测试\",\n" +
"\t\t\"vod_pic\": \"https:/img9.doubanio.com/view/photo/s_ratio_poster/public/p2656327176.webp\",\n" +
"\t\t\"type_name\": \"剧情 / 爱情 / 古装\",\n" +
"\t\t\"vod_year\": \"2022\",\n" +
"\t\t\"vod_area\": \"中国大陆\",\n" +
"\t\t\"vod_remarks\": \"40集全\",\n" +
"\t\t\"vod_actor\": \"刘亦菲\",\n" +
"\t\t\"vod_director\": \"杨阳\",\n" +
"\t\t\"vod_content\": \"  在钱塘开茶铺的赵盼儿(刘亦菲 饰)惊闻未婚夫、新科探花欧阳旭(徐海乔 饰)要另娶当朝高官之女,不甘命运的她誓要上京讨个公道。在途中她遇到了出自权门但生性正直的皇城司指挥顾千帆(陈晓 饰),并卷入江南一场大案,两人不打不相识从而结缘。赵盼儿凭借智慧解救了被骗婚而惨遭虐待的“江南第一琵琶高手”宋引章(林允 饰)与被苛刻家人逼得离家出走的豪爽厨娘孙三娘(柳岩 饰),三位姐妹从此结伴同行,终抵汴京,见识世间繁华。为了不被另攀高枝的欧阳旭从东京赶走,赵盼儿与宋引章、孙三娘一起历经艰辛,将小小茶坊一步步发展为汴京最大的酒楼,揭露了负心人的真面目,收获了各自的真挚感情和人生感悟,也为无数平凡女子推开了一扇平等救赎之门。\",\n" +
"\t\t\"vod_play_from\": \"磁力测试\",\n" +
"\t\t\"vod_play_url\": \"0$magnet:?xt=urn:btih:9e9358b946c427962533472efdd2efd9e9e38c67&dn=%e9%98%b3%e5%85%89%e7%94%b5%e5%bd%b1www.ygdy8.com.%e7%83%ad%e8%a1%80.2022.BD.1080P.%e9%9f%a9%e8%af%ad%e4%b8%ad%e8%8b%b1%e5%8f%8c%e5%ad%97.mkv&tr=udp%3a%2f%2ftracker.opentrackr.org%3a1337%2fannounce&tr=udp%3a%2f%2fexodus.desync.com%3a6969%2fannounce\"\n" +
"\t}]\n" +
"}";*/
AbsJson absJson = new Gson().fromJson(json, new TypeToken<AbsJson>() {
}.getType());
AbsXml data = absJson.toAbsXml();
@ -663,7 +736,11 @@ public class SourceViewModel extends ViewModel {
} else if (quickSearchResult == result) {
EventBus.getDefault().post(new RefreshEvent(RefreshEvent.TYPE_QUICK_SEARCH_RESULT, data));
} else if (result != null) {
result.postValue(data);
if (result == detailResult) {
checkThunder(data);
} else {
result.postValue(data);
}
}
return data;
} catch (Exception e) {

Loading…
Cancel
Save