diff --git a/app/build.gradle b/app/build.gradle index 3e132dc8..88954611 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/leanback/res/layout/dialog_buffer.xml b/app/src/leanback/res/layout/dialog_buffer.xml index d2e15589..146ebf0c 100644 --- a/app/src/leanback/res/layout/dialog_buffer.xml +++ b/app/src/leanback/res/layout/dialog_buffer.xml @@ -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" /> \ No newline at end of file diff --git a/app/src/main/assets/jar/custom_spider.jar b/app/src/main/assets/jar/custom_spider.jar new file mode 100644 index 00000000..58a7aa89 Binary files /dev/null and b/app/src/main/assets/jar/custom_spider.jar differ diff --git a/app/src/main/java/com/fongmi/android/tv/api/AdBlocker.java b/app/src/main/java/com/fongmi/android/tv/api/AdBlocker.java new file mode 100644 index 00000000..7cbf588f --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/AdBlocker.java @@ -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 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 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 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 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 MALICIOUS_ADS = Arrays.asList( + ".*\\.17un\\.com", + ".*\\.baidustatic\\.com", + ".*\\.cnzz\\.com", + ".*\\.duomeng\\.cn", + ".*\\.shuzilm\\.cn", + ".*\\.haoyuemh\\.com", + ".*\\.571xz\\.com", + ".*\\.madthumbs\\.com" + ); + + /** + * 跟踪统计域名 + */ + private static final List 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 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 getGamblingAdHosts() { + return GAMBLING_ADS; + } + + /** + * 获取通用广告联盟域名 + */ + public static List getGeneralAdHosts() { + return GENERAL_ADS; + } + + /** + * 获取视频平台广告域名 + */ + public static List getVideoAdHosts() { + return VIDEO_ADS; + } + + /** + * 获取弹窗广告域名 + */ + public static List getPopupAdHosts() { + return POPUP_ADS; + } + + /** + * 获取恶意网站域名 + */ + public static List getMaliciousAdHosts() { + return MALICIOUS_ADS; + } + + /** + * 获取跟踪统计域名 + */ + public static List 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 list) { + for (String pattern : list) { + if (host.matches(pattern.replace("*", ".*"))) { + return true; + } + if (host.contains(pattern.replace(".*", ""))) { + return true; + } + } + return false; + } +} + diff --git a/app/src/main/java/com/fongmi/android/tv/api/config/VodConfig.java b/app/src/main/java/com/fongmi/android/tv/api/config/VodConfig.java index 71654780..2ab3d336 100644 --- a/app/src/main/java/com/fongmi/android/tv/api/config/VodConfig.java +++ b/app/src/main/java/com/fongmi/android/tv/api/config/VodConfig.java @@ -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())) { diff --git a/app/src/main/java/com/fongmi/android/tv/api/loader/JarLoader.java b/app/src/main/java/com/fongmi/android/tv/api/loader/JarLoader.java index 490ef702..66f2f0f8 100644 --- a/app/src/main/java/com/fongmi/android/tv/api/loader/JarLoader.java +++ b/app/src/main/java/com/fongmi/android/tv/api/loader/JarLoader.java @@ -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(); } } diff --git a/app/src/main/java/com/fongmi/android/tv/ui/custom/CustomWebView.java b/app/src/main/java/com/fongmi/android/tv/ui/custom/CustomWebView.java index d18ca48d..b503c433 100644 --- a/app/src/main/java/com/fongmi/android/tv/ui/custom/CustomWebView.java +++ b/app/src/main/java/com/fongmi/android/tv/ui/custom/CustomWebView.java @@ -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; } diff --git a/app/src/main/res/drawable/shape_vod_year.xml b/app/src/main/res/drawable/shape_vod_year.xml index c63ae778..6e0874eb 100644 --- a/app/src/main/res/drawable/shape_vod_year.xml +++ b/app/src/main/res/drawable/shape_vod_year.xml @@ -7,7 +7,7 @@ android:topLeftRadius="8dp" android:topRightRadius="8dp" /> - + - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 3332a313..5adf8ca2 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,9 +1,17 @@ - + + + - \ No newline at end of file + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9c38416d..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..d5d8d1f9 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index c03df1ed..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 00000000..739fc212 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 4f1da6ef..00000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..d5d8d1f9 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-ldpi/ic_launcher.png b/app/src/main/res/mipmap-ldpi/ic_launcher.png deleted file mode 100644 index 06c857e8..00000000 Binary files a/app/src/main/res/mipmap-ldpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-ldpi/ic_launcher.webp b/app/src/main/res/mipmap-ldpi/ic_launcher.webp new file mode 100644 index 00000000..e7cf685b Binary files /dev/null and b/app/src/main/res/mipmap-ldpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png deleted file mode 100644 index 88dc1cb2..00000000 Binary files a/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.webp new file mode 100644 index 00000000..5a729a93 Binary files /dev/null and b/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-ldpi/ic_launcher_round.png b/app/src/main/res/mipmap-ldpi/ic_launcher_round.png deleted file mode 100644 index 388da5ff..00000000 Binary files a/app/src/main/res/mipmap-ldpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-ldpi/ic_launcher_round.webp b/app/src/main/res/mipmap-ldpi/ic_launcher_round.webp new file mode 100644 index 00000000..e7cf685b Binary files /dev/null and b/app/src/main/res/mipmap-ldpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 6a31bc32..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..e7cf685b Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index d9ab4ae8..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 00000000..5a729a93 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 0260d97c..00000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..e7cf685b Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 087e60bd..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..0b5cafa9 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index fbfc10ed..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 00000000..3ac5fb9e Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 31f02496..00000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..0b5cafa9 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index f0bc944f..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..4b9415aa Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index ee032716..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp new file mode 100644 index 00000000..9c224121 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index f66d0e63..00000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..4b9415aa Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 8d037a6e..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..3925de3a Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 57472c25..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 00000000..0993491e Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index ad29c87d..00000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..3925de3a Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 00000000..63747f0a --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,7 @@ + + + + #222222 + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 1a7ea447..e8bb1b71 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -91,7 +91,7 @@ 应用设置 网络设置 数据管理 - v3.0.3 + v3.0.9 在GitHub上查看 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 5c3caacf..ee72c949 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -89,7 +89,7 @@ 應用設置 網絡設置 數據管理 - v3.0.3 + v3.0.9 在GitHub上查看 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index a61a3134..0d95d657 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -27,5 +27,8 @@ #E6FFFFFF #FFEB3B #FFEB3B + + + #FFFFFF \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd31d3d8..2fb77317 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -92,7 +92,7 @@ Choose Off On - v3.0.6 + v3.0.9 View on GitHub @@ -152,7 +152,7 @@ Device authorization limit reached This subscription has no live content Current source has no live content - 这里撒子内容都没得~ + 空谷待音~ 老表~没得收藏哈 搜索无结果,换个关键词试试 No play data @@ -238,8 +238,8 @@ Target size Scan result - No video sources added yet\nClick the button below to add - Add Source + 空谷无音,待君添源 + 添加源 XMBOX软件许可协议 diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java index cf20c3b3..7a704b48 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java @@ -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(); } diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java index d797e5f3..f7d0b65d 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java @@ -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(); } diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java index d32f3e33..4dc1201c 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java @@ -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(() -> { diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ControlDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ControlDialog.java index 31fc31ad..b830f9a8 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ControlDialog.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ControlDialog.java @@ -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); diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/VodFragment.java b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/VodFragment.java index ef7e837d..21618787 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/VodFragment.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/VodFragment.java @@ -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(); } } diff --git a/app/src/mobile/res/layout/dialog_buffer.xml b/app/src/mobile/res/layout/dialog_buffer.xml index 0cdb1d7a..c6d004c0 100644 --- a/app/src/mobile/res/layout/dialog_buffer.xml +++ b/app/src/mobile/res/layout/dialog_buffer.xml @@ -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" /> \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_control.xml b/app/src/mobile/res/layout/dialog_control.xml index f7f1e4e1..1621217d 100644 --- a/app/src/mobile/res/layout/dialog_control.xml +++ b/app/src/mobile/res/layout/dialog_control.xml @@ -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" /> + app:trackColorInactive="@color/white_30" /> \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_timer.xml b/app/src/mobile/res/layout/dialog_timer.xml index 64ea3814..d6f4ed94 100644 --- a/app/src/mobile/res/layout/dialog_timer.xml +++ b/app/src/mobile/res/layout/dialog_timer.xml @@ -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" diff --git a/build_debug.sh b/build_debug.sh new file mode 100644 index 00000000..3a35c221 --- /dev/null +++ b/build_debug.sh @@ -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 diff --git a/build_release.sh b/build_release.sh new file mode 100644 index 00000000..dd6fb169 --- /dev/null +++ b/build_release.sh @@ -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 + diff --git a/build_test.sh b/build_test.sh new file mode 100644 index 00000000..30d77b62 --- /dev/null +++ b/build_test.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd /Users/chen/Desktop/XMBOX +./gradlew clean assembleMobileArm64_v8aDebug diff --git a/change_webp_color.sh b/change_webp_color.sh new file mode 100644 index 00000000..5ec1c36c --- /dev/null +++ b/change_webp_color.sh @@ -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" + diff --git a/check_logs.sh b/check_logs.sh new file mode 100644 index 00000000..922a6e28 --- /dev/null +++ b/check_logs.sh @@ -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 + diff --git a/docs/AD_BLOCKING.md b/docs/AD_BLOCKING.md new file mode 100644 index 00000000..2d095f2f --- /dev/null +++ b/docs/AD_BLOCKING.md @@ -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 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. 🛡️ **片头片尾跳过** - 处理嵌入式广告 + +让您享受**纯净、流畅、安全**的视频观看体验! + diff --git a/install_and_run.sh b/install_and_run.sh new file mode 100644 index 00000000..b5fd8fd5 --- /dev/null +++ b/install_and_run.sh @@ -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 + diff --git a/other/image/icon.png b/other/image/icon.png index ebdba05f..fc9e1f78 100644 Binary files a/other/image/icon.png and b/other/image/icon.png differ diff --git a/other/image/logo-1.png b/other/image/logo-1.png index 8537b34c..becdb7fe 100644 Binary files a/other/image/logo-1.png and b/other/image/logo-1.png differ diff --git a/other/image/logo-2.png b/other/image/logo-2.png index 3a1cbd45..ee040446 100644 Binary files a/other/image/logo-2.png and b/other/image/logo-2.png differ diff --git a/other/sample/ad_block_example.json b/other/sample/ad_block_example.json new file mode 100644 index 00000000..4626b205 --- /dev/null +++ b/other/sample/ad_block_example.json @@ -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解析时生效", + "隐私保护": "所有拦截在本地进行,不上传任何数据" + } +} + diff --git a/replace_jar.sh b/replace_jar.sh new file mode 100644 index 00000000..e98200d5 --- /dev/null +++ b/replace_jar.sh @@ -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 "=== 完成! ===" +