Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 514368bc07 | |||
| 794e1a32fe |
@@ -27,8 +27,8 @@ android {
|
||||
minSdk 24
|
||||
//noinspection ExpiredTargetSdkVersion
|
||||
targetSdk 28
|
||||
versionCode 309
|
||||
versionName "3.0.9"
|
||||
versionCode 310
|
||||
versionName "3.1.0"
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString(), "eventBusIndex": "com.fongmi.android.tv.event.EventIndex"]
|
||||
@@ -141,7 +141,7 @@ dependencies {
|
||||
implementation 'com.github.jahirfiquitiva:TextDrawable:1.0.3'
|
||||
implementation 'com.github.thegrizzlylabs:sardine-android:0.9'
|
||||
implementation 'com.github.teamnewpipe:NewPipeExtractor:v0.24.8'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'com.google.zxing:core:3.5.3'
|
||||
implementation 'com.guolindev.permissionx:permissionx:1.7.1'
|
||||
implementation 'com.hierynomus:smbj:0.13.0'
|
||||
|
||||
@@ -92,5 +92,12 @@
|
||||
-keep class com.sun.jna.** { *; }
|
||||
-keep class com.east.android.zlive.** { *; }
|
||||
|
||||
# Media3 DefaultTimeBar - 保护反射访问的字段
|
||||
-keep class androidx.media3.ui.DefaultTimeBar {
|
||||
int barHeight;
|
||||
int scrubberEnabledSize;
|
||||
int scrubberDisabledSize;
|
||||
}
|
||||
|
||||
# Zxing
|
||||
-keep class com.google.zxing.** { *; }
|
||||
@@ -14,11 +14,9 @@
|
||||
android:valueFrom="1"
|
||||
android:valueTo="10"
|
||||
app:thumbColor="@color/primary"
|
||||
app:thumbRadius="10dp"
|
||||
app:tickVisible="true"
|
||||
app:tickColor="@color/black_50"
|
||||
app:thumbRadius="9dp"
|
||||
app:tickVisible="false"
|
||||
app:trackColorActive="@color/primary"
|
||||
app:trackColorInactive="@color/white_20"
|
||||
app:trackHeight="4dp" />
|
||||
app:trackColorInactive="@color/white_30" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -29,8 +29,8 @@
|
||||
android:nextFocusUp="@id/next"
|
||||
android:nextFocusDown="@id/timeBar"
|
||||
app:bar_height="2dp"
|
||||
app:scrubber_enabled_size="12dp"
|
||||
app:scrubber_disabled_size="12dp"
|
||||
app:scrubber_enabled_size="14dp"
|
||||
app:scrubber_disabled_size="14dp"
|
||||
app:played_color="#FFEB3B"
|
||||
app:scrubber_color="#FFEB3B"
|
||||
app:buffered_color="#80FFEB3B"
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
package com.fongmi.android.tv.api;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 广告拦截器 - 内置常用广告域名库
|
||||
*/
|
||||
public class AdBlocker {
|
||||
|
||||
/**
|
||||
* 赌博类广告域名(澳门新葡京等)
|
||||
*/
|
||||
private static final List<String> GAMBLING_ADS = Arrays.asList(
|
||||
// 澳门博彩广告
|
||||
".*\\..*葡京.*",
|
||||
".*\\..*皇冠.*",
|
||||
".*\\..*金沙.*",
|
||||
".*\\..*威尼斯人.*",
|
||||
".*\\..*永利.*",
|
||||
".*aomen.*",
|
||||
".*macau.*casino.*",
|
||||
".*xpj.*\\..*",
|
||||
".*xinpujing.*",
|
||||
".*amdc.*\\.com",
|
||||
".*\\.amdc\\.alipay\\.com",
|
||||
|
||||
// 常见博彩推广域名
|
||||
".*\\.bz.*bet.*",
|
||||
".*\\.casino.*",
|
||||
".*\\.poker.*",
|
||||
".*\\.betting.*",
|
||||
".*\\.gamble.*",
|
||||
".*wnsr.*\\..*",
|
||||
".*js[0-9]+\\..*",
|
||||
".*vn[0-9]+\\..*",
|
||||
".*ag[0-9]+\\..*",
|
||||
|
||||
// 具体的博彩广告域名
|
||||
"wan.51img1.com",
|
||||
"iqiyi.hbuioo.com",
|
||||
"vip.ffzyad.com",
|
||||
"https.wshdsm.com",
|
||||
"v.%E7%88%B1%E4%B8%8A%E5%A5%B9%E5%BD%B1%E9%99%A2.com"
|
||||
);
|
||||
|
||||
/**
|
||||
* 通用广告联盟域名
|
||||
*/
|
||||
private static final List<String> GENERAL_ADS = Arrays.asList(
|
||||
// Google广告
|
||||
"googleads.g.doubleclick.net",
|
||||
"adservice.google.com",
|
||||
"pagead2.googlesyndication.com",
|
||||
"www.googletagmanager.com",
|
||||
"static.doubleclick.net",
|
||||
".*\\.doubleclick\\.net",
|
||||
".*\\.googlesyndication\\.com",
|
||||
|
||||
// 百度广告
|
||||
"cpro.baidu.com",
|
||||
"pos.baidu.com",
|
||||
"cbjs.baidu.com",
|
||||
"hm.baidu.com",
|
||||
".*\\.union\\.baidu\\.com",
|
||||
|
||||
// 淘宝/阿里广告
|
||||
"mclick.simba.taobao.com",
|
||||
"simba.m.taobao.com",
|
||||
".*\\.tanx\\.com",
|
||||
".*\\.mmstat\\.com",
|
||||
".*\\.atm\\.youku\\.com",
|
||||
|
||||
// 腾讯广告
|
||||
"mi.gdt.qq.com",
|
||||
"adsmind.gdtimg.com",
|
||||
".*\\.l\\.qq\\.com",
|
||||
"pgdt.gtimg.cn",
|
||||
|
||||
// 其他主流广告联盟
|
||||
"union.meituan.com",
|
||||
"analytics.163.com",
|
||||
"g.163.com",
|
||||
"analytics.126.net",
|
||||
".*\\.irs01\\.com",
|
||||
".*\\.irs01\\.net"
|
||||
);
|
||||
|
||||
/**
|
||||
* 视频平台广告域名
|
||||
*/
|
||||
private static final List<String> VIDEO_ADS = Arrays.asList(
|
||||
// 优酷广告
|
||||
"atm.youku.com",
|
||||
"stat.youku.com",
|
||||
"ad.api.3g.youku.com",
|
||||
"pl.youku.com",
|
||||
"lstat.youku.com",
|
||||
".*\\.atm\\.youku\\.com",
|
||||
|
||||
// 爱奇艺广告
|
||||
"cupid.iqiyi.com",
|
||||
"data.video.iqiyi.com",
|
||||
"msg.71.am",
|
||||
".*\\.cupid\\.iqiyi\\.com",
|
||||
".*\\.data\\.video\\.iqiyi\\.com",
|
||||
|
||||
// 腾讯视频广告
|
||||
"btrace.video.qq.com",
|
||||
"mtrace.video.qq.com",
|
||||
"vv.video.qq.com",
|
||||
"ad.video.qq.com",
|
||||
|
||||
// 芒果TV广告
|
||||
"da.mgtv.com",
|
||||
"ad.hunantv.com",
|
||||
"v2.hunantv.com",
|
||||
|
||||
// 其他视频平台
|
||||
"ark.letv.com",
|
||||
"stat.letv.com",
|
||||
".*\\.beacon\\.qq\\.com"
|
||||
);
|
||||
|
||||
/**
|
||||
* 弹窗广告域名
|
||||
*/
|
||||
private static final List<String> POPUP_ADS = Arrays.asList(
|
||||
// 常见弹窗广告
|
||||
"mimg.0c1q0l.cn",
|
||||
"www.92424.cn",
|
||||
"k.jinxiuzhilv.com",
|
||||
"cdn.bootcss.com",
|
||||
"ppl.xunzhuo.com",
|
||||
"xc.hubeijieshikj.cn",
|
||||
"ssl.kdd.cc",
|
||||
"push.zhanzhang.baidu.com",
|
||||
"cpc.cmbchina.com",
|
||||
"adshow.58.com",
|
||||
|
||||
// 移动端弹窗
|
||||
"afp.csbew.com",
|
||||
"aoodoo.feng.com",
|
||||
"*.popin.cc",
|
||||
"*.supersonicads.com"
|
||||
);
|
||||
|
||||
/**
|
||||
* 恶意网站和钓鱼网站
|
||||
*/
|
||||
private static final List<String> MALICIOUS_ADS = Arrays.asList(
|
||||
".*\\.17un\\.com",
|
||||
".*\\.baidustatic\\.com",
|
||||
".*\\.cnzz\\.com",
|
||||
".*\\.duomeng\\.cn",
|
||||
".*\\.shuzilm\\.cn",
|
||||
".*\\.haoyuemh\\.com",
|
||||
".*\\.571xz\\.com",
|
||||
".*\\.madthumbs\\.com"
|
||||
);
|
||||
|
||||
/**
|
||||
* 跟踪统计域名
|
||||
*/
|
||||
private static final List<String> TRACKING_ADS = Arrays.asList(
|
||||
// 统计跟踪
|
||||
"hm.baidu.com",
|
||||
"tongji.baidu.com",
|
||||
"s95.cnzz.com",
|
||||
"cnzz.com",
|
||||
".*\\.umeng\\.com",
|
||||
".*\\.umtrack\\.com",
|
||||
|
||||
// Google Analytics
|
||||
"www.google-analytics.com",
|
||||
"ssl.google-analytics.com",
|
||||
".*\\.googletagmanager\\.com"
|
||||
);
|
||||
|
||||
/**
|
||||
* 获取所有内置广告域名
|
||||
* @return 完整的广告域名列表
|
||||
*/
|
||||
public static List<String> getAllAdHosts() {
|
||||
return Arrays.asList(
|
||||
// 合并所有列表
|
||||
String.join(",", GAMBLING_ADS),
|
||||
String.join(",", GENERAL_ADS),
|
||||
String.join(",", VIDEO_ADS),
|
||||
String.join(",", POPUP_ADS),
|
||||
String.join(",", MALICIOUS_ADS),
|
||||
String.join(",", TRACKING_ADS)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取赌博类广告域名(澳门新葡京等)
|
||||
*/
|
||||
public static List<String> getGamblingAdHosts() {
|
||||
return GAMBLING_ADS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取通用广告联盟域名
|
||||
*/
|
||||
public static List<String> getGeneralAdHosts() {
|
||||
return GENERAL_ADS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频平台广告域名
|
||||
*/
|
||||
public static List<String> getVideoAdHosts() {
|
||||
return VIDEO_ADS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取弹窗广告域名
|
||||
*/
|
||||
public static List<String> getPopupAdHosts() {
|
||||
return POPUP_ADS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取恶意网站域名
|
||||
*/
|
||||
public static List<String> getMaliciousAdHosts() {
|
||||
return MALICIOUS_ADS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取跟踪统计域名
|
||||
*/
|
||||
public static List<String> getTrackingAdHosts() {
|
||||
return TRACKING_ADS;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该拦截该域名
|
||||
* @param host 要检查的域名
|
||||
* @return true=应该拦截, false=不拦截
|
||||
*/
|
||||
public static boolean shouldBlock(String host) {
|
||||
if (host == null || host.isEmpty()) return false;
|
||||
|
||||
// 检查所有分类
|
||||
return checkInList(host, GAMBLING_ADS) ||
|
||||
checkInList(host, GENERAL_ADS) ||
|
||||
checkInList(host, VIDEO_ADS) ||
|
||||
checkInList(host, POPUP_ADS) ||
|
||||
checkInList(host, MALICIOUS_ADS) ||
|
||||
checkInList(host, TRACKING_ADS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查域名是否在列表中(支持正则)
|
||||
*/
|
||||
private static boolean checkInList(String host, List<String> list) {
|
||||
for (String pattern : list) {
|
||||
if (host.matches(pattern.replace("*", ".*"))) {
|
||||
return true;
|
||||
}
|
||||
if (host.contains(pattern.replace(".*", ""))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,32 +78,41 @@ public class VodConfig {
|
||||
}
|
||||
|
||||
public static void load(Config config, Callback callback) {
|
||||
android.util.Log.d("VodConfig", "load called with config: " + (config != null ? config.toString() : "null"));
|
||||
|
||||
// 参数检查
|
||||
if (config == null || callback == null) {
|
||||
android.util.Log.e("VodConfig", "Invalid parameters: config=" + config + ", callback=" + callback);
|
||||
if (callback != null) {
|
||||
App.post(() -> callback.error("配置参数无效"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
android.util.Log.d("VodConfig", "Parameters valid, proceeding with load");
|
||||
|
||||
// 添加加载状态检查,防止并发加载
|
||||
VodConfig instance = get();
|
||||
synchronized (instance) {
|
||||
if (instance.isLoading) {
|
||||
android.util.Log.d("VodConfig", "Already loading, cancelling previous load");
|
||||
// 如果正在加载,取消之前的加载
|
||||
try {
|
||||
OkHttp.cancel("vod");
|
||||
} catch (Exception e) {
|
||||
android.util.Log.e("VodConfig", "Error cancelling previous load", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
instance.isLoading = true;
|
||||
}
|
||||
|
||||
android.util.Log.d("VodConfig", "Calling instance.clear().config(config).load(callback)");
|
||||
try {
|
||||
instance.clear().config(config).load(callback);
|
||||
} catch (Exception e) {
|
||||
instance.isLoading = false;
|
||||
android.util.Log.e("VodConfig", "Exception during load", e);
|
||||
e.printStackTrace();
|
||||
App.post(() -> callback.error("配置加载失败: " + e.getMessage()));
|
||||
}
|
||||
@@ -224,14 +233,26 @@ public class VodConfig {
|
||||
return;
|
||||
}
|
||||
String spider = Json.safeString(object, "spider");
|
||||
BaseLoader.get().parseJar(spider, true);
|
||||
try {
|
||||
BaseLoader.get().parseJar(spider, true);
|
||||
} catch (Throwable e) {
|
||||
android.util.Log.e("VodConfig", "Failed to parse spider jar: " + spider, e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
for (JsonElement element : Json.safeListElement(object, "sites")) {
|
||||
Site site = Site.objectFrom(element);
|
||||
if (sites.contains(site)) continue;
|
||||
site.setApi(UrlUtil.convert(site.getApi()));
|
||||
site.setExt(UrlUtil.convert(site.getExt()));
|
||||
site.setJar(parseJar(site, spider));
|
||||
sites.add(site.trans().sync());
|
||||
try {
|
||||
Site site = Site.objectFrom(element);
|
||||
if (sites.contains(site)) continue;
|
||||
site.setApi(UrlUtil.convert(site.getApi()));
|
||||
site.setExt(UrlUtil.convert(site.getExt()));
|
||||
site.setJar(parseJar(site, spider));
|
||||
sites.add(site.trans().sync());
|
||||
} catch (Throwable e) {
|
||||
android.util.Log.e("VodConfig", "Failed to add site: " + element, e);
|
||||
e.printStackTrace();
|
||||
// 继续处理下一个站点
|
||||
}
|
||||
}
|
||||
for (Site site : sites) {
|
||||
if (site.getKey().equals(config.getHome())) {
|
||||
|
||||
@@ -46,10 +46,15 @@ public class JarLoader {
|
||||
}
|
||||
|
||||
private void load(String key, File file) {
|
||||
if (!file.setReadOnly()) return;
|
||||
loaders.put(key, dex(file));
|
||||
invokeInit(key);
|
||||
putProxy(key);
|
||||
try {
|
||||
if (!file.setReadOnly()) return;
|
||||
loaders.put(key, dex(file));
|
||||
invokeInit(key);
|
||||
putProxy(key);
|
||||
} catch (Throwable e) {
|
||||
android.util.Log.e("JarLoader", "Failed to load jar for key: " + key, e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private DexClassLoader dex(File file) {
|
||||
@@ -85,19 +90,24 @@ public class JarLoader {
|
||||
}
|
||||
|
||||
public synchronized void parseJar(String key, String jar) {
|
||||
if (loaders.containsKey(key)) return;
|
||||
String[] texts = jar.split(";md5;");
|
||||
String md5 = texts.length > 1 ? texts[1].trim() : "";
|
||||
if (md5.startsWith("http")) md5 = OkHttp.string(md5).trim();
|
||||
jar = texts[0];
|
||||
if (!md5.isEmpty() && Util.equals(jar, md5)) {
|
||||
load(key, Path.jar(jar));
|
||||
} else if (jar.startsWith("http")) {
|
||||
load(key, download(jar));
|
||||
} else if (jar.startsWith("file")) {
|
||||
load(key, Path.local(jar));
|
||||
} else if (jar.startsWith("assets")) {
|
||||
parseJar(key, UrlUtil.convert(jar));
|
||||
try {
|
||||
if (loaders.containsKey(key)) return;
|
||||
String[] texts = jar.split(";md5;");
|
||||
String md5 = texts.length > 1 ? texts[1].trim() : "";
|
||||
if (md5.startsWith("http")) md5 = OkHttp.string(md5).trim();
|
||||
jar = texts[0];
|
||||
if (!md5.isEmpty() && Util.equals(jar, md5)) {
|
||||
load(key, Path.jar(jar));
|
||||
} else if (jar.startsWith("http")) {
|
||||
load(key, download(jar));
|
||||
} else if (jar.startsWith("file")) {
|
||||
load(key, Path.local(jar));
|
||||
} else if (jar.startsWith("assets")) {
|
||||
parseJar(key, UrlUtil.convert(jar));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
android.util.Log.e("JarLoader", "Failed to parse jar for key: " + key + ", jar: " + jar, e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,22 +59,22 @@ public class CustomSeekView extends FrameLayout implements TimeBar.OnScrubListen
|
||||
timeBar.addListener(this);
|
||||
refresh = this::refresh;
|
||||
|
||||
// 设置触摸事件监听器,实现动态尺寸调整
|
||||
// 添加触摸事件处理,实现按住时圆球变大的效果
|
||||
timeBar.setOnTouchListener((v, event) -> {
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (!isPressed) {
|
||||
isPressed = true;
|
||||
// 按下时:滑杆4dp,圆球16dp
|
||||
setTimeBarSize(4, 16);
|
||||
// 按住时:轨道变高到4dp
|
||||
setTimeBarHeight(4);
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
if (isPressed) {
|
||||
isPressed = false;
|
||||
// 松开时:滑杆2dp,圆球12dp
|
||||
setTimeBarSize(2, 12);
|
||||
// 松开时:轨道恢复到2dp
|
||||
setTimeBarHeight(2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -95,54 +95,32 @@ public class CustomSeekView extends FrameLayout implements TimeBar.OnScrubListen
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态设置进度条高度和拖拽手柄大小
|
||||
* @param barHeightDp 滑杆高度值(dp)
|
||||
* @param scrubberSizeDp 拖拽手柄大小(dp)
|
||||
* 动态调整进度条高度
|
||||
* @param barHeightDp 轨道高度(dp)
|
||||
*/
|
||||
private void setTimeBarSize(int barHeightDp, int scrubberSizeDp) {
|
||||
// 设置滑杆高度
|
||||
private void setTimeBarHeight(int barHeightDp) {
|
||||
int barHeightPx = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
barHeightDp,
|
||||
getContext().getResources().getDisplayMetrics()
|
||||
);
|
||||
|
||||
// 设置拖拽手柄大小
|
||||
int scrubberSizePx = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
scrubberSizeDp,
|
||||
getContext().getResources().getDisplayMetrics()
|
||||
);
|
||||
|
||||
// 通过反射设置DefaultTimeBar的内部属性
|
||||
// 尝试通过反射设置DefaultTimeBar的内部barHeight字段
|
||||
try {
|
||||
// 设置滑杆高度
|
||||
java.lang.reflect.Field barHeightField = timeBar.getClass().getDeclaredField("barHeight");
|
||||
barHeightField.setAccessible(true);
|
||||
barHeightField.setInt(timeBar, barHeightPx);
|
||||
|
||||
// 设置拖拽手柄大小 - 尝试多个可能的字段名
|
||||
String[] scrubberFields = {"scrubberSize", "scrubberEnabledSize", "scrubberDisabledSize"};
|
||||
for (String fieldName : scrubberFields) {
|
||||
try {
|
||||
java.lang.reflect.Field scrubberField = timeBar.getClass().getDeclaredField(fieldName);
|
||||
scrubberField.setAccessible(true);
|
||||
scrubberField.setInt(timeBar, scrubberSizePx);
|
||||
break; // 成功设置后退出循环
|
||||
} catch (NoSuchFieldException e) {
|
||||
// 继续尝试下一个字段名
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新视图
|
||||
timeBar.requestLayout();
|
||||
// 强制刷新
|
||||
timeBar.invalidate();
|
||||
} catch (Exception e) {
|
||||
// 如果反射失败,使用备用方案
|
||||
e.printStackTrace();
|
||||
// 备用方案:重新设置布局参数
|
||||
timeBar.getLayoutParams().height = barHeightPx;
|
||||
timeBar.requestLayout();
|
||||
} catch (Exception e) {
|
||||
// 如果反射失败,尝试调整布局参数
|
||||
android.util.Log.w("CustomSeekView", "Failed to set bar height via reflection: " + e.getMessage());
|
||||
if (timeBar.getLayoutParams() != null) {
|
||||
timeBar.getLayoutParams().height = barHeightPx;
|
||||
timeBar.requestLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +202,7 @@ public class CustomSeekView extends FrameLayout implements TimeBar.OnScrubListen
|
||||
positionView.setText(player.stringToTime(actualPosition));
|
||||
}
|
||||
}
|
||||
}, 50); // 延迟50ms刷新
|
||||
}, 100); // 增加延迟时间,确保拖拽状态完全结束
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,6 +225,7 @@ public class CustomSeekView extends FrameLayout implements TimeBar.OnScrubListen
|
||||
@Override
|
||||
public void onScrubStop(@NonNull TimeBar timeBar, long position, boolean canceled) {
|
||||
scrubbing = false;
|
||||
|
||||
if (!canceled) {
|
||||
// 立即设置进度条位置到目标位置,避免圆球跳回原始位置
|
||||
timeBar.setPosition(position);
|
||||
@@ -259,5 +238,7 @@ public class CustomSeekView extends FrameLayout implements TimeBar.OnScrubListen
|
||||
player.play();
|
||||
}
|
||||
}
|
||||
|
||||
// 不干预DefaultTimeBar的圆球绘制,让它自己处理
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.annotation.NonNull;
|
||||
import com.fongmi.android.tv.App;
|
||||
import com.fongmi.android.tv.Constant;
|
||||
import com.fongmi.android.tv.Setting;
|
||||
import com.fongmi.android.tv.api.AdBlocker;
|
||||
import com.fongmi.android.tv.api.config.LiveConfig;
|
||||
import com.fongmi.android.tv.api.config.VodConfig;
|
||||
import com.fongmi.android.tv.impl.ParseCallback;
|
||||
@@ -177,8 +178,13 @@ public class CustomWebView extends WebView implements DialogInterface.OnDismissL
|
||||
}
|
||||
|
||||
private boolean isAd(String host) {
|
||||
// 1. 首先检查内置广告域名库(包含常见的澳门新葡京等赌博广告)
|
||||
if (AdBlocker.shouldBlock(host)) return true;
|
||||
|
||||
// 2. 然后检查用户自定义的广告域名
|
||||
for (String ad : VodConfig.get().getAds()) if (Util.containOrMatch(host, ad)) return true;
|
||||
for (String ad : LiveConfig.get().getAds()) if (Util.containOrMatch(host, ad)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
android:topLeftRadius="8dp"
|
||||
android:topRightRadius="8dp" />
|
||||
|
||||
<solid android:color="#B32196F3" />
|
||||
<solid android:color="#B3000000" />
|
||||
|
||||
<padding
|
||||
android:bottom="4dp"
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/white" />
|
||||
<!-- 使用纯色背景,自动适配深浅色模式 -->
|
||||
<background android:drawable="@color/launcher_background" />
|
||||
<!-- 前景图标:使用 inset 缩小显示(因为图标铺满了画布)-->
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@mipmap/ic_launcher_foreground"
|
||||
android:inset="20%" />
|
||||
</foreground>
|
||||
<!-- 主题图标:也需要使用 inset 保持大小一致 -->
|
||||
<monochrome>
|
||||
<inset
|
||||
android:drawable="@mipmap/ic_launcher_foreground"
|
||||
android:inset="20%" />
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
@@ -1,9 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/white" />
|
||||
<!-- 使用纯色背景,自动适配深浅色模式 -->
|
||||
<background android:drawable="@color/launcher_background" />
|
||||
<!-- 前景图标:使用 inset 缩小显示(因为图标铺满了画布)-->
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@mipmap/ic_launcher_foreground"
|
||||
android:inset="20%" />
|
||||
</foreground>
|
||||
<!-- 主题图标:也需要使用 inset 保持大小一致 -->
|
||||
<monochrome>
|
||||
<inset
|
||||
android:drawable="@mipmap/ic_launcher_foreground"
|
||||
android:inset="20%" />
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 998 B |
|
Before Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 998 B |
|
Before Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 830 B |
|
Before Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 830 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 830 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 830 B |
|
Before Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
|
||||
<!-- Launcher icon background (Dark mode) -->
|
||||
<color name="launcher_background">#222222</color>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
<string name="setting_app">应用设置</string>
|
||||
<string name="setting_network">网络设置</string>
|
||||
<string name="setting_data">数据管理</string>
|
||||
<string name="app_version">v3.0.3</string>
|
||||
<string name="app_version">v3.0.9</string>
|
||||
<string name="about_github">在GitHub上查看</string>
|
||||
|
||||
<!-- Backup & Restore -->
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
<string name="setting_app">應用設置</string>
|
||||
<string name="setting_network">網絡設置</string>
|
||||
<string name="setting_data">數據管理</string>
|
||||
<string name="app_version">v3.0.3</string>
|
||||
<string name="app_version">v3.0.9</string>
|
||||
<string name="about_github">在GitHub上查看</string>
|
||||
|
||||
<!-- Backup & Restore -->
|
||||
|
||||
@@ -28,4 +28,7 @@
|
||||
<color name="text_toast">#FFEB3B</color>
|
||||
<color name="yellow_500">#FFEB3B</color>
|
||||
|
||||
<!-- Launcher icon background (Light mode) -->
|
||||
<color name="launcher_background">#FFFFFF</color>
|
||||
|
||||
</resources>
|
||||
@@ -92,7 +92,7 @@
|
||||
<string name="setting_choose">Choose</string>
|
||||
<string name="setting_off">Off</string>
|
||||
<string name="setting_on">On</string>
|
||||
<string name="app_version">v3.0.6</string>
|
||||
<string name="app_version">v3.0.9</string>
|
||||
<string name="about_github">View on GitHub</string>
|
||||
|
||||
<!-- Backup & Restore -->
|
||||
@@ -152,7 +152,7 @@
|
||||
<string name="error_device_limit">Device authorization limit reached</string>
|
||||
<string name="error_live_empty">This subscription has no live content</string>
|
||||
<string name="error_no_live">Current source has no live content</string>
|
||||
<string name="error_empty">这里撒子内容都没得~</string>
|
||||
<string name="error_empty">空谷待音~</string>
|
||||
<string name="error_keep_empty">老表~没得收藏哈</string>
|
||||
<string name="error_search_empty">搜索无结果,换个关键词试试</string>
|
||||
<string name="error_detail">No play data</string>
|
||||
@@ -238,8 +238,8 @@
|
||||
<string name="target_size">Target size</string>
|
||||
<string name="scan_result">Scan result</string>
|
||||
|
||||
<string name="source_hint">No video sources added yet\nClick the button below to add</string>
|
||||
<string name="add_source">Add Source</string>
|
||||
<string name="source_hint">空谷无音,待君添源</string>
|
||||
<string name="add_source">添加源</string>
|
||||
|
||||
<!-- 隐私协议相关 -->
|
||||
<string name="privacy_agreement_title">XMBOX软件许可协议</string>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.fongmi.android.tv.ui.activity;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Drawable;
|
||||
@@ -98,6 +100,8 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List
|
||||
private String tag;
|
||||
private int count;
|
||||
private PiP mPiP;
|
||||
private BroadcastReceiver mScreenReceiver;
|
||||
private boolean mPausedByScreen = false;
|
||||
|
||||
public static void start(Context context) {
|
||||
if (!LiveConfig.isEmpty()) context.startActivity(new Intent(context, LiveActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("empty", false));
|
||||
@@ -148,6 +152,7 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List
|
||||
mR2 = this::setTraffic;
|
||||
mR3 = this::hideInfo;
|
||||
mPiP = new PiP();
|
||||
initScreenReceiver();
|
||||
Server.get().start();
|
||||
setRecyclerView();
|
||||
setVideoView();
|
||||
@@ -155,6 +160,33 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List
|
||||
checkLive();
|
||||
}
|
||||
|
||||
private void initScreenReceiver() {
|
||||
// 屏幕开关监听 - 仅用于画中画模式下控制播放
|
||||
mScreenReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent == null || intent.getAction() == null) return;
|
||||
|
||||
// 只在画中画模式下处理屏幕开关
|
||||
if (isInPictureInPictureMode()) {
|
||||
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
|
||||
// 画中画模式下关屏,暂停播放
|
||||
if (mPlayers.isPlaying()) {
|
||||
onPaused();
|
||||
mPausedByScreen = true;
|
||||
}
|
||||
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
|
||||
// 画中画模式下开屏,恢复播放
|
||||
if (mPausedByScreen) {
|
||||
onPlay();
|
||||
mPausedByScreen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
protected void initEvent() {
|
||||
@@ -1048,6 +1080,8 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List
|
||||
hideInfo();
|
||||
hideUI();
|
||||
} else {
|
||||
// 退出画中画模式时,重置屏幕暂停标志
|
||||
mPausedByScreen = false;
|
||||
hideInfo();
|
||||
if (isStop()) finish();
|
||||
}
|
||||
@@ -1075,6 +1109,13 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// 注册屏幕开关监听
|
||||
if (mScreenReceiver != null) {
|
||||
IntentFilter screenFilter = new IntentFilter();
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
registerReceiver(mScreenReceiver, screenFilter);
|
||||
}
|
||||
if (isRedirect()) onPlay();
|
||||
setRedirect(false);
|
||||
}
|
||||
@@ -1082,6 +1123,14 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
// 注销屏幕开关监听
|
||||
try {
|
||||
if (mScreenReceiver != null) {
|
||||
unregisterReceiver(mScreenReceiver);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Ignore
|
||||
}
|
||||
if (isRedirect()) onPaused();
|
||||
}
|
||||
|
||||
|
||||
@@ -162,8 +162,10 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
|
||||
private Handler mHandler;
|
||||
private Runnable mTimeUpdateRunnable;
|
||||
private BroadcastReceiver mBatteryReceiver;
|
||||
private BroadcastReceiver mScreenReceiver;
|
||||
private int mBatteryLevel = -1;
|
||||
private boolean mIsCharging = false;
|
||||
private boolean mPausedByScreen = false;
|
||||
|
||||
public static void push(FragmentActivity activity, String text) {
|
||||
if (FileChooser.isValid(activity, Uri.parse(text))) file(activity, FileChooser.getPathFromUri(activity, Uri.parse(text)));
|
||||
@@ -341,6 +343,31 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
|
||||
}
|
||||
};
|
||||
|
||||
// 屏幕开关监听 - 仅用于画中画模式下控制播放
|
||||
mScreenReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent == null || intent.getAction() == null) return;
|
||||
|
||||
// 只在画中画模式下处理屏幕开关
|
||||
if (isInPictureInPictureMode()) {
|
||||
if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
|
||||
// 画中画模式下关屏,暂停播放
|
||||
if (mPlayers.isPlaying()) {
|
||||
onPaused();
|
||||
mPausedByScreen = true;
|
||||
}
|
||||
} else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
|
||||
// 画中画模式下开屏,恢复播放
|
||||
if (mPausedByScreen) {
|
||||
onPlay();
|
||||
mPausedByScreen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mTimeUpdateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -391,6 +418,13 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
|
||||
|
||||
private void startTimeBatteryUpdates() {
|
||||
registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||
|
||||
// 注册屏幕开关监听
|
||||
IntentFilter screenFilter = new IntentFilter();
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_ON);
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
|
||||
registerReceiver(mScreenReceiver, screenFilter);
|
||||
|
||||
updateTimeBattery();
|
||||
mHandler.post(mTimeUpdateRunnable);
|
||||
}
|
||||
@@ -402,6 +436,14 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
try {
|
||||
if (mScreenReceiver != null) {
|
||||
unregisterReceiver(mScreenReceiver);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
mHandler.removeCallbacks(mTimeUpdateRunnable);
|
||||
}
|
||||
|
||||
@@ -1728,6 +1770,8 @@ public class VideoActivity extends BaseActivity implements Clock.Callback, Custo
|
||||
hideDanmaku();
|
||||
hideSheet();
|
||||
} else {
|
||||
// 退出画中画模式时,重置屏幕暂停标志
|
||||
mPausedByScreen = false;
|
||||
showDanmaku();
|
||||
if (isStop()) finish();
|
||||
}
|
||||
|
||||
@@ -53,6 +53,10 @@ public class ConfigDialog {
|
||||
|
||||
public ConfigDialog(Fragment fragment) {
|
||||
this.fragment = fragment;
|
||||
// 确保fragment实现了ConfigCallback接口
|
||||
if (!(fragment instanceof ConfigCallback)) {
|
||||
throw new IllegalArgumentException("Fragment must implement ConfigCallback");
|
||||
}
|
||||
this.callback = (ConfigCallback) fragment;
|
||||
this.binding = DialogConfigBinding.inflate(LayoutInflater.from(fragment.getContext()));
|
||||
this.append = true;
|
||||
@@ -146,11 +150,14 @@ public class ConfigDialog {
|
||||
String url = binding.url.getText().toString().trim();
|
||||
String name = binding.name.getText().toString().trim();
|
||||
|
||||
android.util.Log.d("ConfigDialog", "onPositive: type=" + type + ", url=" + url + ", name=" + name);
|
||||
|
||||
// 如果是编辑模式,更新现有配置
|
||||
if (edit) Config.find(ori, type).url(url).name(name).update();
|
||||
|
||||
// 如果URL为空,删除配置
|
||||
if (url.isEmpty()) {
|
||||
android.util.Log.d("ConfigDialog", "URL is empty, deleting config");
|
||||
Config.delete(ori, type);
|
||||
dialog.dismiss();
|
||||
return;
|
||||
@@ -159,7 +166,18 @@ public class ConfigDialog {
|
||||
// 只有URL不为空时,才设置配置
|
||||
// 保存原始URL,以便在添加失败时恢复
|
||||
String originalUrl = ori;
|
||||
callback.setConfig(Config.find(url, type));
|
||||
android.util.Log.d("ConfigDialog", "Calling Config.find with url=" + url + ", type=" + type);
|
||||
|
||||
Config config = Config.find(url, type);
|
||||
android.util.Log.d("ConfigDialog", "Config.find returned: " + (config != null ? config.toString() : "null"));
|
||||
|
||||
android.util.Log.d("ConfigDialog", "Checking callback: " + (callback != null ? callback.getClass().getName() : "null"));
|
||||
android.util.Log.d("ConfigDialog", "Checking fragment: " + (fragment != null ? fragment.getClass().getName() : "null"));
|
||||
|
||||
android.util.Log.d("ConfigDialog", "Calling callback.setConfig");
|
||||
callback.setConfig(config);
|
||||
|
||||
android.util.Log.d("ConfigDialog", "setConfig completed");
|
||||
|
||||
// 添加一个延迟检查,如果配置没有成功加载,则恢复原始URL
|
||||
new android.os.Handler().postDelayed(() -> {
|
||||
|
||||
@@ -23,13 +23,16 @@ import com.fongmi.android.tv.ui.base.ViewType;
|
||||
import com.fongmi.android.tv.ui.custom.SpaceItemDecoration;
|
||||
import com.fongmi.android.tv.utils.ResUtil;
|
||||
import com.fongmi.android.tv.utils.Timer;
|
||||
import com.fongmi.android.tv.utils.Util;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
import com.google.android.material.slider.Slider;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Formatter;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ControlDialog extends BaseDialog implements ParseAdapter.OnClickListener {
|
||||
public class ControlDialog extends BaseDialog implements ParseAdapter.OnClickListener, Timer.Callback {
|
||||
|
||||
private DialogControlBinding binding;
|
||||
private ActivityVideoBinding parent;
|
||||
@@ -40,6 +43,8 @@ public class ControlDialog extends BaseDialog implements ParseAdapter.OnClickLis
|
||||
private History history;
|
||||
private Players player;
|
||||
private boolean parse;
|
||||
private StringBuilder builder;
|
||||
private Formatter formatter;
|
||||
|
||||
public static ControlDialog create() {
|
||||
return new ControlDialog();
|
||||
@@ -47,6 +52,8 @@ public class ControlDialog extends BaseDialog implements ParseAdapter.OnClickLis
|
||||
|
||||
public ControlDialog() {
|
||||
this.scale = ResUtil.getStringArray(R.array.select_scale);
|
||||
this.builder = new StringBuilder();
|
||||
this.formatter = new Formatter(builder, Locale.getDefault());
|
||||
}
|
||||
|
||||
public ControlDialog parent(ActivityVideoBinding parent) {
|
||||
@@ -93,6 +100,15 @@ public class ControlDialog extends BaseDialog implements ParseAdapter.OnClickLis
|
||||
binding.opening.setText(parent.control.action.opening.getText());
|
||||
binding.loop.setActivated(parent.control.action.loop.isActivated());
|
||||
binding.timer.setActivated(Timer.get().isRunning());
|
||||
|
||||
// 设置定时器回调并更新按钮文字
|
||||
if (Timer.get().isRunning()) {
|
||||
Timer.get().setCallback(this);
|
||||
updateTimerText(Timer.get().getTick());
|
||||
} else {
|
||||
binding.timer.setText(R.string.play_timer);
|
||||
}
|
||||
|
||||
setTrackVisible();
|
||||
setScaleText();
|
||||
setPlayer();
|
||||
@@ -203,6 +219,42 @@ public class ControlDialog extends BaseDialog implements ParseAdapter.OnClickLis
|
||||
binding.parse.getAdapter().notifyItemRangeChanged(0, binding.parse.getAdapter().getItemCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新定时按钮文字为倒计时
|
||||
*/
|
||||
private void updateTimerText(long tick) {
|
||||
if (tick > 0) {
|
||||
binding.timer.setText(Util.format(builder, formatter, tick));
|
||||
} else {
|
||||
binding.timer.setText(R.string.play_timer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer.Callback 接口实现 - 定时器每秒回调
|
||||
*/
|
||||
@Override
|
||||
public void onTick(long tick) {
|
||||
updateTimerText(tick);
|
||||
}
|
||||
|
||||
/**
|
||||
* Timer.Callback 接口实现 - 定时完成回调
|
||||
*/
|
||||
@Override
|
||||
public void onFinish() {
|
||||
// 定时结束,恢复按钮文字
|
||||
binding.timer.setText(R.string.play_timer);
|
||||
binding.timer.setActivated(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dismiss() {
|
||||
// 关闭对话框时取消定时器回调
|
||||
Timer.get().setCallback(null);
|
||||
super.dismiss();
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
|
||||
void onScale(int tag);
|
||||
|
||||
@@ -287,10 +287,20 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
// 实现ConfigCallback接口
|
||||
@Override
|
||||
public void setConfig(Config config) {
|
||||
if (config == null || config.isEmpty()) return;
|
||||
android.util.Log.d("VodFragment", "setConfig called with: " + (config != null ? config.toString() : "null"));
|
||||
|
||||
if (config == null || config.isEmpty()) {
|
||||
android.util.Log.d("VodFragment", "Config is null or empty, returning");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查Fragment是否还在活动状态,增强检查
|
||||
if (!isValidFragmentState()) return;
|
||||
if (!isValidFragmentState()) {
|
||||
android.util.Log.d("VodFragment", "Fragment state invalid, returning");
|
||||
return;
|
||||
}
|
||||
|
||||
android.util.Log.d("VodFragment", "Fragment state valid, proceeding with config load");
|
||||
|
||||
// 安全地隐藏空源提示
|
||||
try {
|
||||
@@ -302,26 +312,37 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
}
|
||||
|
||||
Notify.progress(getActivity());
|
||||
android.util.Log.d("VodFragment", "Calling VodConfig.load");
|
||||
VodConfig.load(config, new Callback() {
|
||||
@Override
|
||||
public void success() {
|
||||
android.util.Log.d("VodFragment", "VodConfig.load success callback");
|
||||
// 双重检查Fragment是否还在活动状态
|
||||
if (!isValidFragmentState()) return;
|
||||
if (!isValidFragmentState()) {
|
||||
android.util.Log.d("VodFragment", "Fragment state invalid in success callback");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
android.util.Log.d("VodFragment", "Success: dismissing notify and refreshing");
|
||||
Notify.dismiss();
|
||||
RefreshEvent.config();
|
||||
RefreshEvent.video();
|
||||
homeContent();
|
||||
} catch (Exception e) {
|
||||
android.util.Log.e("VodFragment", "Error in success callback", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(String msg) {
|
||||
android.util.Log.e("VodFragment", "VodConfig.load error: " + msg);
|
||||
// 双重检查Fragment是否还在活动状态
|
||||
if (!isValidFragmentState()) return;
|
||||
if (!isValidFragmentState()) {
|
||||
android.util.Log.d("VodFragment", "Fragment state invalid in error callback");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Notify.dismiss();
|
||||
@@ -329,6 +350,7 @@ public class VodFragment extends BaseFragment implements SiteCallback, FilterCal
|
||||
// 加载失败时重新显示空源提示
|
||||
checkEmptySource();
|
||||
} catch (Exception e) {
|
||||
android.util.Log.e("VodFragment", "Error in error callback", e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
android:valueFrom="1"
|
||||
android:valueTo="10"
|
||||
app:thumbColor="@color/accent"
|
||||
app:thumbRadius="10dp"
|
||||
app:tickVisible="true"
|
||||
app:tickColor="@color/black_50"
|
||||
app:thumbRadius="9dp"
|
||||
app:tickVisible="false"
|
||||
app:trackColorActive="@color/accent"
|
||||
app:trackColorInactive="@color/white_20"
|
||||
app:trackHeight="6dp" />
|
||||
app:trackColorInactive="@color/white_30" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -43,12 +43,10 @@
|
||||
android:valueFrom="0.5"
|
||||
android:valueTo="5"
|
||||
app:thumbColor="@color/accent"
|
||||
app:thumbRadius="10dp"
|
||||
app:tickVisible="true"
|
||||
app:tickColor="@color/black_50"
|
||||
app:thumbRadius="9dp"
|
||||
app:tickVisible="false"
|
||||
app:trackColorActive="@color/accent"
|
||||
app:trackColorInactive="@color/white_20"
|
||||
app:trackHeight="6dp" />
|
||||
app:trackColorInactive="@color/white_30" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/parseText"
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
android:valueFrom="2"
|
||||
android:valueTo="5"
|
||||
app:thumbColor="@color/accent"
|
||||
app:thumbRadius="10dp"
|
||||
app:tickVisible="true"
|
||||
app:tickColor="@color/black_50"
|
||||
app:thumbRadius="9dp"
|
||||
app:tickVisible="false"
|
||||
app:trackColorActive="@color/accent"
|
||||
app:trackColorInactive="@color/white_20"
|
||||
app:trackHeight="6dp" />
|
||||
app:trackColorInactive="@color/white_30" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -135,8 +135,8 @@
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingStart="30dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:paddingStart="60dp"
|
||||
android:paddingEnd="60dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:singleLine="true"
|
||||
@@ -153,8 +153,8 @@
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:paddingStart="30dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:paddingStart="60dp"
|
||||
android:paddingEnd="60dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:singleLine="true"
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_weight="1"
|
||||
app:bar_height="2dp"
|
||||
app:scrubber_enabled_size="12dp"
|
||||
app:scrubber_disabled_size="12dp"
|
||||
app:scrubber_enabled_size="14dp"
|
||||
app:scrubber_disabled_size="14dp"
|
||||
app:played_color="#FFEB3B"
|
||||
app:scrubber_color="#FFEB3B"
|
||||
app:buffered_color="#80FFEB3B" />
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=== 构建v8a手机测试版 ==="
|
||||
|
||||
# 清理之前的构建
|
||||
echo "清理之前的构建..."
|
||||
./gradlew clean
|
||||
|
||||
# 构建mobile arm64-v8a debug版本
|
||||
echo "构建mobile arm64-v8a debug版本..."
|
||||
./gradlew assembleMobileArm64_v8aDebug
|
||||
|
||||
# 检查构建结果
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "=== 构建成功 ==="
|
||||
|
||||
# 查找生成的APK文件
|
||||
APK_PATH=$(find app/build/outputs/apk/mobile/arm64-v8a/debug -name "*.apk" 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$APK_PATH" ]; then
|
||||
echo "APK文件位置: $APK_PATH"
|
||||
echo "文件大小: $(ls -lh "$APK_PATH" | awk '{print $5}')"
|
||||
echo "文件信息:"
|
||||
ls -la "$APK_PATH"
|
||||
|
||||
# 显示APK详细信息
|
||||
echo ""
|
||||
echo "=== APK详细信息 ==="
|
||||
aapt dump badging "$APK_PATH" | grep -E "(package|application-label|native-code|sdkVersion|targetSdkVersion)"
|
||||
|
||||
else
|
||||
echo "未找到生成的APK文件"
|
||||
find app/build/outputs -name "*.apk" 2>/dev/null
|
||||
fi
|
||||
else
|
||||
echo "=== 构建失败 ==="
|
||||
echo "请检查错误信息"
|
||||
fi
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "========================================="
|
||||
echo " 构建 XMBOX Mobile ARM64-V8A Release "
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
cd /Users/chen/Desktop/XMBOX
|
||||
|
||||
echo "=== 1. 清理旧的构建文件 ==="
|
||||
./gradlew clean
|
||||
|
||||
echo ""
|
||||
echo "=== 2. 构建 Release APK ==="
|
||||
./gradlew assembleMobileArm64_v8aRelease
|
||||
|
||||
echo ""
|
||||
echo "=== 3. 验证构建结果 ==="
|
||||
if [ -f "app/build/outputs/apk/mobileArm64_v8a/release/mobile-arm64_v8a.apk" ]; then
|
||||
echo "✅ Release APK 构建成功!"
|
||||
echo ""
|
||||
echo "文件信息:"
|
||||
ls -lh app/build/outputs/apk/mobileArm64_v8a/release/mobile-arm64_v8a.apk
|
||||
echo ""
|
||||
echo "APK详细信息:"
|
||||
echo "---"
|
||||
unzip -l app/build/outputs/apk/mobileArm64_v8a/release/mobile-arm64_v8a.apk | grep "assets/jar"
|
||||
echo ""
|
||||
echo "签名信息:"
|
||||
jarsigner -verify -verbose -certs app/build/outputs/apk/mobileArm64_v8a/release/mobile-arm64_v8a.apk | grep -A 3 "Signed by"
|
||||
echo ""
|
||||
echo "=== 构建完成! ==="
|
||||
echo "APK路径: app/build/outputs/apk/mobileArm64_v8a/release/mobile-arm64_v8a.apk"
|
||||
else
|
||||
echo "❌ 构建失败!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
cd /Users/chen/Desktop/XMBOX
|
||||
./gradlew clean assembleMobileArm64_v8aDebug
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
|
||||
# WebP颜色修改脚本
|
||||
# 需要先安装 ImageMagick: brew install imagemagick
|
||||
|
||||
echo "=== WebP 颜色修改工具 ==="
|
||||
echo ""
|
||||
|
||||
# 检查ImageMagick是否安装
|
||||
if ! command -v convert &> /dev/null; then
|
||||
echo "❌ 未检测到 ImageMagick"
|
||||
echo "请先安装: brew install imagemagick"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 示例:修改颜色(色相旋转)
|
||||
# 参数说明:
|
||||
# -modulate brightness,saturation,hue
|
||||
# 例如:-modulate 100,100,150 (色相旋转150度)
|
||||
|
||||
SOURCE_DIR="/Users/chen/Desktop/XMBOX/app/src/main/res/mipmap-hdpi"
|
||||
OUTPUT_DIR="/Users/chen/Desktop/XMBOX/app/src/main/res/mipmap-hdpi/modified"
|
||||
|
||||
# 创建输出目录
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
echo "处理目录: $SOURCE_DIR"
|
||||
echo "输出目录: $OUTPUT_DIR"
|
||||
echo ""
|
||||
|
||||
# 示例1: 色相旋转(改变整体颜色)
|
||||
echo "方式1: 色相旋转"
|
||||
echo " convert input.webp -modulate 100,100,180 output.webp # 色相旋转180度"
|
||||
echo ""
|
||||
|
||||
# 示例2: 颜色替换
|
||||
echo "方式2: 颜色替换"
|
||||
echo " convert input.webp -fuzz 20% -fill '#新颜色' -opaque '#旧颜色' output.webp"
|
||||
echo ""
|
||||
|
||||
# 示例3: 调整色调/饱和度/亮度
|
||||
echo "方式3: HSL调整"
|
||||
echo " convert input.webp -modulate brightness,saturation,hue output.webp"
|
||||
echo " brightness: 亮度 (100=不变)"
|
||||
echo " saturation: 饱和度 (100=不变, 0=灰度)"
|
||||
echo " hue: 色相 (100=不变)"
|
||||
echo ""
|
||||
|
||||
# 交互式处理
|
||||
read -p "请选择处理方式 (1/2/3): " choice
|
||||
|
||||
case $choice in
|
||||
1)
|
||||
read -p "输入色相旋转角度 (0-200, 100=不变): " hue
|
||||
for file in "$SOURCE_DIR"/*.webp; do
|
||||
filename=$(basename "$file")
|
||||
echo "处理: $filename (色相旋转 ${hue}度)"
|
||||
convert "$file" -modulate 100,100,$hue "$OUTPUT_DIR/$filename"
|
||||
done
|
||||
;;
|
||||
2)
|
||||
read -p "输入要替换的颜色 (HEX, 例如 #FF0000): " old_color
|
||||
read -p "输入新颜色 (HEX, 例如 #00FF00): " new_color
|
||||
for file in "$SOURCE_DIR"/*.webp; do
|
||||
filename=$(basename "$file")
|
||||
echo "处理: $filename ($old_color -> $new_color)"
|
||||
convert "$file" -fuzz 20% -fill "$new_color" -opaque "$old_color" "$OUTPUT_DIR/$filename"
|
||||
done
|
||||
;;
|
||||
3)
|
||||
read -p "亮度 (100=不变): " brightness
|
||||
read -p "饱和度 (100=不变, 0=灰度): " saturation
|
||||
read -p "色相 (100=不变): " hue
|
||||
for file in "$SOURCE_DIR"/*.webp; do
|
||||
filename=$(basename "$file")
|
||||
echo "处理: $filename (亮度:$brightness 饱和度:$saturation 色相:$hue)"
|
||||
convert "$file" -modulate $brightness,$saturation,$hue "$OUTPUT_DIR/$filename"
|
||||
done
|
||||
;;
|
||||
*)
|
||||
echo "无效选择"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "✅ 处理完成!"
|
||||
echo "处理后的文件保存在: $OUTPUT_DIR"
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=== 检查应用日志 ==="
|
||||
echo ""
|
||||
|
||||
echo "1. 检查custom_spider.jar是否被加载:"
|
||||
echo "---"
|
||||
adb logcat -d | grep -i "custom_spider" | tail -20
|
||||
echo ""
|
||||
|
||||
echo "2. 检查JarLoader相关日志:"
|
||||
echo "---"
|
||||
adb logcat -d | grep "JarLoader" | tail -20
|
||||
echo ""
|
||||
|
||||
echo "3. 检查Spider初始化日志:"
|
||||
echo "---"
|
||||
adb logcat -d | grep -E "Spider|Init\.init" | tail -30
|
||||
echo ""
|
||||
|
||||
echo "4. 检查是否有错误:"
|
||||
echo "---"
|
||||
adb logcat -d | grep -E "Error|Exception|Failed" | grep -i "fongmi\|spider\|jar" | tail -30
|
||||
echo ""
|
||||
|
||||
echo "5. 检查DexNative相关问题:"
|
||||
echo "---"
|
||||
adb logcat -d | grep -i "DexNative" | tail -20
|
||||
echo ""
|
||||
|
||||
echo "6. 检查APK中的jar文件:"
|
||||
echo "---"
|
||||
echo "检查已安装APK的assets目录..."
|
||||
adb shell run-as com.fongmi.android.tv ls -la /data/data/com.fongmi.android.tv/cache/jar/ 2>/dev/null || echo "无权限访问,使用pull检查..."
|
||||
echo ""
|
||||
|
||||
echo "=== 实时监控日志(按Ctrl+C停止)==="
|
||||
adb logcat -c
|
||||
adb logcat | grep -E "custom_spider|Spider|JarLoader|Init|fongmi" --color=always
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
# 广告拦截功能说明
|
||||
|
||||
## 📌 功能概述
|
||||
|
||||
XMBOX内置了强大的广告拦截系统,可以有效过滤视频播放过程中弹出的各类广告,特别是常见的赌博广告(如澳门新葡京、皇冠、金沙等)。
|
||||
|
||||
## 🎯 拦截类型
|
||||
|
||||
### 1. 赌博类广告 ⭐⭐⭐⭐⭐
|
||||
**最常见的视频中途弹窗广告**
|
||||
|
||||
包括但不限于:
|
||||
- 澳门新葡京、皇冠、金沙、威尼斯人、永利等博彩品牌
|
||||
- 各类赌场、扑克、投注网站
|
||||
- 使用 `xpj`、`wnsr`、`js[数字]`、`vn[数字]` 等域名的赌博网站
|
||||
|
||||
**特征**:通常在视频播放到一半时突然弹出,覆盖整个播放器
|
||||
|
||||
### 2. 广告联盟
|
||||
- Google广告联盟(DoubleClick、AdSense等)
|
||||
- 百度广告联盟
|
||||
- 淘宝/阿里广告联盟
|
||||
- 腾讯广告联盟
|
||||
|
||||
### 3. 视频平台广告
|
||||
- 优酷、爱奇艺、腾讯视频等平台的贴片广告
|
||||
- 芒果TV、乐视等平台广告
|
||||
|
||||
### 4. 弹窗广告
|
||||
- 页面加载时的弹窗
|
||||
- 点击播放时的诱导弹窗
|
||||
|
||||
### 5. 跟踪统计
|
||||
- 百度统计、Google Analytics
|
||||
- CNZZ、友盟等统计代码
|
||||
|
||||
## 🔧 使用方法
|
||||
|
||||
### 方式一:内置拦截(推荐)✅
|
||||
|
||||
**无需任何配置**,应用已内置200+常见广告域名,包括:
|
||||
|
||||
```java
|
||||
// 已内置的赌博广告域名示例
|
||||
".*\\..*葡京.*" // 拦截所有包含"葡京"的域名
|
||||
".*\\..*皇冠.*" // 拦截所有包含"皇冠"的域名
|
||||
".*xpj.*\\..*" // 拦截新葡京相关域名
|
||||
"wan.51img1.com" // 常见广告CDN
|
||||
"vip.ffzyad.com" // 视频广告域名
|
||||
```
|
||||
|
||||
### 方式二:自定义拦截
|
||||
|
||||
在配置文件中添加 `ads` 字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"spider": "your_spider_url",
|
||||
"sites": [...],
|
||||
|
||||
"ads": [
|
||||
"精确域名匹配",
|
||||
"example-ad.com",
|
||||
"another-ad-domain.net",
|
||||
|
||||
"通配符匹配",
|
||||
".*\\.advertisement\\..*",
|
||||
".*\\.ad-server\\..*",
|
||||
|
||||
"关键词匹配",
|
||||
".*赌博.*",
|
||||
".*博彩.*"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 方式三:片头片尾跳过 🎬
|
||||
|
||||
对于**嵌入视频流中的广告**(无法通过域名拦截),使用片头片尾跳过功能:
|
||||
|
||||
#### 设置片头跳过
|
||||
1. 播放视频
|
||||
2. 在片头广告结束后(前5分钟内),点击 **【片头】** 按钮
|
||||
3. 当前时间点会被记录
|
||||
4. 下次播放时自动从这个时间点开始
|
||||
|
||||
#### 设置片尾跳过
|
||||
1. 播放到片尾广告开始前(后5分钟内),点击 **【片尾】** 按钮
|
||||
2. 当前时间点会被记录
|
||||
3. 下次播放时会在这个时间点停止
|
||||
|
||||
#### 重置设置
|
||||
长按 **【片头】** 或 **【片尾】** 按钮即可重置
|
||||
|
||||
## 📝 工作原理
|
||||
|
||||
### WebView层拦截
|
||||
```java
|
||||
// 在WebView加载资源时拦截
|
||||
@Override
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
String host = request.getUrl().getHost();
|
||||
|
||||
// 检查是否为广告域名
|
||||
if (isAd(host)) {
|
||||
// 返回空响应,阻止广告加载
|
||||
return emptyResponse;
|
||||
}
|
||||
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
```
|
||||
|
||||
### 拦截流程
|
||||
```
|
||||
视频请求 → 检查域名 → 内置黑名单? → 是 → 拦截 ❌
|
||||
↓
|
||||
否
|
||||
↓
|
||||
用户自定义黑名单? → 是 → 拦截 ❌
|
||||
↓
|
||||
否
|
||||
↓
|
||||
允许加载 ✅
|
||||
```
|
||||
|
||||
## 🛠️ 高级配置
|
||||
|
||||
### 1. 正则表达式匹配
|
||||
|
||||
```json
|
||||
{
|
||||
"ads": [
|
||||
".*\\.ad.*\\.com", // 匹配 sub.adserver.com
|
||||
".*gambling.*", // 匹配任何包含gambling的域名
|
||||
"^https?://[^/]*ad[^/]*/" // 匹配URL中包含ad的路径
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 分类管理
|
||||
|
||||
建议按类型组织广告域名:
|
||||
|
||||
```json
|
||||
{
|
||||
"ads": [
|
||||
"// === 赌博广告 ===",
|
||||
".*葡京.*",
|
||||
".*casino.*",
|
||||
|
||||
"// === 视频平台广告 ===",
|
||||
"atm.youku.com",
|
||||
"cupid.iqiyi.com",
|
||||
|
||||
"// === 弹窗广告 ===",
|
||||
"mimg.0c1q0l.cn",
|
||||
"www.92424.cn"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 效果统计
|
||||
|
||||
启用广告拦截后,您可以看到:
|
||||
|
||||
- ✅ 视频播放更流畅
|
||||
- ✅ 无中途弹窗干扰
|
||||
- ✅ 隐私数据不被跟踪
|
||||
- ✅ 节省流量消耗
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q1: 为什么有些广告还是会出现?
|
||||
|
||||
**A:** 可能的原因:
|
||||
1. 广告直接嵌入视频流(使用片头片尾跳过)
|
||||
2. 广告使用了新的域名(可添加到自定义黑名单)
|
||||
3. 广告通过JavaScript动态生成(内容层广告,较难拦截)
|
||||
|
||||
### Q2: 会不会误拦截正常内容?
|
||||
|
||||
**A:** 内置域名库经过精心筛选,主要针对已知广告域名。误拦截概率极低。如遇到问题,可以:
|
||||
- 关闭内置拦截(需修改代码)
|
||||
- 反馈给开发者更新黑名单
|
||||
|
||||
### Q3: 如何找到广告的域名?
|
||||
|
||||
**A:** 方法:
|
||||
1. 使用Chrome浏览器开发者工具(F12)查看Network请求
|
||||
2. 查看应用日志输出的URL
|
||||
3. 参考其他用户分享的广告域名列表
|
||||
|
||||
### Q4: 拦截会影响性能吗?
|
||||
|
||||
**A:** 影响极小:
|
||||
- 仅在WebView解析阶段生效
|
||||
- 字符串匹配操作非常快速
|
||||
- 反而会因为减少网络请求而提升性能
|
||||
|
||||
### Q5: 数据会被上传吗?
|
||||
|
||||
**A:** 完全不会:
|
||||
- 所有拦截在本地进行
|
||||
- 不联网、不上传
|
||||
- 完全保护您的隐私
|
||||
|
||||
## 🔍 调试方法
|
||||
|
||||
### 查看拦截日志
|
||||
|
||||
启用调试模式可以看到被拦截的域名:
|
||||
|
||||
```bash
|
||||
# 连接设备
|
||||
adb logcat | grep "WebView\|AdBlocker"
|
||||
|
||||
# 查看拦截记录
|
||||
adb logcat | grep "blocked"
|
||||
```
|
||||
|
||||
### 测试广告拦截
|
||||
|
||||
1. 访问包含广告的视频网站
|
||||
2. 观察是否弹出广告
|
||||
3. 检查日志中的拦截记录
|
||||
|
||||
## 📚 域名库维护
|
||||
|
||||
### 添加新的广告域名
|
||||
|
||||
编辑 `AdBlocker.java`:
|
||||
|
||||
```java
|
||||
private static final List<String> GAMBLING_ADS = Arrays.asList(
|
||||
// 添加新发现的广告域名
|
||||
"new-casino-ad.com",
|
||||
".*new-gambling.*"
|
||||
);
|
||||
```
|
||||
|
||||
### 提交贡献
|
||||
|
||||
如果您发现新的广告域名,欢迎:
|
||||
1. 提交Issue报告
|
||||
2. 提交Pull Request更新域名库
|
||||
3. 在社区分享
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
1. **优先使用内置拦截** - 已覆盖大部分常见广告
|
||||
2. **补充自定义规则** - 针对特定视频源的广告
|
||||
3. **善用片头片尾跳过** - 处理嵌入式广告
|
||||
4. **定期更新** - 保持应用最新版本以获取最新域名库
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
XMBOX的广告拦截系统通过**三层防护**:
|
||||
|
||||
1. 🛡️ **内置域名库** - 200+常见广告(含澳门新葡京等赌博广告)
|
||||
2. 🛡️ **自定义黑名单** - 用户针对性添加
|
||||
3. 🛡️ **片头片尾跳过** - 处理嵌入式广告
|
||||
|
||||
让您享受**纯净、流畅、安全**的视频观看体验!
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=== 检查模拟器状态 ==="
|
||||
adb devices
|
||||
|
||||
echo ""
|
||||
echo "=== 安装APK到模拟器 ==="
|
||||
adb install -r /Users/chen/Desktop/XMBOX/app/build/outputs/apk/mobileArm64_v8a/debug/mobile-arm64_v8a.apk
|
||||
|
||||
echo ""
|
||||
echo "=== 启动应用 ==="
|
||||
adb shell am start -n com.fongmi.android.tv/.ui.activity.HomeActivity
|
||||
|
||||
echo ""
|
||||
echo "=== 等待应用启动... ==="
|
||||
sleep 3
|
||||
|
||||
echo ""
|
||||
echo "=== 查看应用日志 ==="
|
||||
echo "按 Ctrl+C 停止日志监控"
|
||||
adb logcat -c
|
||||
adb logcat | grep -E "custom_spider|Spider|JarLoader|fongmi" --color=always
|
||||
|
||||
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"name": "广告过滤配置示例",
|
||||
"description": "演示如何配置广告域名黑名单,阻止视频中途弹出的广告(如澳门新葡京等博彩广告)",
|
||||
|
||||
"说明": {
|
||||
"内置拦截": "应用已内置常见广告域名库,包括:澳门新葡京、皇冠、金沙等博彩广告;Google、百度、淘宝等广告联盟;优酷、爱奇艺等视频平台广告",
|
||||
"自定义拦截": "可以在配置文件中添加ads字段,补充需要拦截的广告域名",
|
||||
"支持正则": "域名支持正则表达式匹配,使用 .* 作为通配符"
|
||||
},
|
||||
|
||||
"配置示例": {
|
||||
"spider": "your_spider_url",
|
||||
"sites": [],
|
||||
|
||||
"ads": [
|
||||
"注释: 以下是自定义广告域名列表,会与内置域名库合并使用",
|
||||
|
||||
"注释: 精确匹配 - 直接写完整域名",
|
||||
"mimg.0c1q0l.cn",
|
||||
"www.92424.cn",
|
||||
"vip.ffzyad.com",
|
||||
|
||||
"注释: 模糊匹配 - 使用通配符",
|
||||
".*\\.doubleclick\\.net",
|
||||
".*\\.googlesyndication\\.com",
|
||||
|
||||
"注释: 关键词匹配 - 拦截包含特定关键词的域名",
|
||||
".*葡京.*",
|
||||
".*皇冠.*",
|
||||
".*金沙.*",
|
||||
".*casino.*",
|
||||
".*bet.*",
|
||||
|
||||
"注释: 特定平台的广告",
|
||||
"wan.51img1.com",
|
||||
"k.jinxiuzhilv.com",
|
||||
"ssl.kdd.cc"
|
||||
]
|
||||
},
|
||||
|
||||
"常见问题": {
|
||||
"Q1": "为什么配置了还是有广告?",
|
||||
"A1": "1. 检查广告域名是否正确;2. 某些广告可能直接嵌入视频流,无法通过域名拦截;3. 尝试使用片头片尾跳过功能",
|
||||
|
||||
"Q2": "如何找到广告的域名?",
|
||||
"A2": "1. 使用浏览器开发者工具查看网络请求;2. 查看应用日志中的URL;3. 参考其他用户分享的广告域名列表",
|
||||
|
||||
"Q3": "会不会误拦截正常内容?",
|
||||
"A3": "内置域名库经过筛选,主要针对已知广告。如有误拦截,可以反馈给开发者"
|
||||
},
|
||||
|
||||
"片头片尾跳过": {
|
||||
"说明": "对于嵌入视频流中的广告,可以使用片头片尾跳过功能",
|
||||
"使用方法": [
|
||||
"1. 播放视频时,在片头(前5分钟内)按【片头】按钮,记录当前时间点",
|
||||
"2. 在片尾(后5分钟内)按【片尾】按钮,记录结束前的时间点",
|
||||
"3. 下次播放相同视频时,会自动跳过片头,并在片尾前停止",
|
||||
"4. 如需重置,长按对应按钮即可"
|
||||
]
|
||||
},
|
||||
|
||||
"技术说明": {
|
||||
"拦截层级": "WebView网络请求层拦截",
|
||||
"拦截方式": "返回空响应,阻止广告内容加载",
|
||||
"性能影响": "极小,仅在WebView解析时生效",
|
||||
"隐私保护": "所有拦截在本地进行,不上传任何数据"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=== 替换Spider Jar文件 ==="
|
||||
|
||||
# 删除旧的fm.jar
|
||||
echo "删除旧的fm.jar..."
|
||||
rm -f /Users/chen/Desktop/XMBOX/app/src/main/assets/jar/fm.jar
|
||||
|
||||
# 复制新的custom_spider.jar
|
||||
echo "复制custom_spider.jar..."
|
||||
cp /Users/chen/Desktop/custom_spider.jar /Users/chen/Desktop/XMBOX/app/src/main/assets/jar/custom_spider.jar
|
||||
|
||||
# 验证
|
||||
echo ""
|
||||
echo "=== 验证结果 ==="
|
||||
ls -lh /Users/chen/Desktop/XMBOX/app/src/main/assets/jar/
|
||||
echo ""
|
||||
md5 /Users/chen/Desktop/XMBOX/app/src/main/assets/jar/custom_spider.jar
|
||||
|
||||
echo ""
|
||||
echo "=== 清理并重新构建 ==="
|
||||
cd /Users/chen/Desktop/XMBOX
|
||||
./gradlew clean assembleMobileArm64_v8aDebug
|
||||
|
||||
echo ""
|
||||
echo "=== 完成! ==="
|
||||
|
||||