Skip to content

Releases: zhaoruizhe81/AIIvideo

2.0.8

Choose a tag to compare

@zhaoruizhe81 zhaoruizhe81 released this 20 Oct 15:32

2.0.8

代理页这一块重做:订阅和免费节点分成两张卡,订阅去重、国家标注,测速那几条陈年 bug 一起修。

订阅和免费节点分开

以前测速池是订阅 + 内置免费节点混在一起,按延迟排一张榜。问题是这两类东西性质完全不同:一份是你付了钱的线路,一份是公开共享、随时会挂、流量过陌生人服务器的节点。免费节点延迟低就会排在付费线路前面,点错一下出口就被换掉;「别用来登录重要账号」那行提示挂在整块底下,看着对全部有效,其实只对免费有效。

现在两张卡各测各的、各显示各的,speed?source=subscription|builtin。两边同时跑会踩坏后端的累积表,所以互斥,一边在跑另一边按钮是灰的。

订阅去重与公告识别

拿真实订阅验的:10 条「节点」去掉名字后 md5 完全一致——同一台机器复制五份,再加「剩余流量:50 GB」「套餐到期」「拆盲盒」这类公告。以前全进测速池,列表上是六条一模一样的 host:port。

现在解析时按「协议+主机+端口+凭据+传输层参数」去重,公告条目直接忽略,摘要写「1 个节点,4 条重复已合并,5 条公告已忽略」。名字也保住了(以前 # 后面的备注被丢掉),而且如果公告排在真节点前面,用真名字覆盖,不会出现「节点叫剩余流量」这种事。

节点显示国家

离线查表,不联网:DB-IP 免费库(CC BY 4.0),只取 IPv4,362,108 段、245 个国家,增量编码后 1.8MB(压进 APK 约 680KB)。列表显示「🇺🇸 美国 · 🐾美国随缘喵 1」,名字优先,没有名字才退回 host:port。国家名靠系统 Locale 拿中文,不自带 245 条映射表。

写的时候踩到:IPv4 是 32 位无符号数,200.x 以上在 Java 的 int 里是负数,直接二分会让 8.8.8.8 查到数组末尾那个负数段。改用 Integer.compareUnsigned,也省了换 long[] 的 2.9MB 堆。

测速全部「不通」的真凶

Libv2ray.measureOutboundDelay 必须先 ensureEnv()(go.Seq.setContext + initCoreEnv),否则每个节点都在几毫秒内返回 -1。以前这步只在「保存并启动」里做,所以冷启动后的新进程里、没点过启动时,7 条免费节点 10 毫秒内全报不通,而同一批节点在核心起过之后再测是 56~64ms。现在 ProxyCore.probe() 自己确保环境,HuangGuoApp 也在启动时后台预热那 3 秒。

「拉订阅 timeout」

三处叠加:机场订阅 TTFB 实测 9.76 秒;每批测速都重新下载一次订阅(12 条 4 批 = 4 次下载,第三次被限速就整轮断);下载走的还是直连——因为当前源的 net.proxy 是空数组,NetRoute.directOnly() 对任何域名都返回 true,把订阅也摘掉了代理。

现在:订阅/节点池下载走 getOnceRouted(),固定先走代理、失败再退回直连;拉取成功即写缓存,测速直接复用,只有点「更新订阅」才重新下载;整条请求加 callTimeout 防慢慢吐字节拖死;失败自动重试一次;桥上限 30 → 45 秒。错误文案也会区分「核心没在跑」和「代理在跑但这次没通」。

顺手修的 UI

重画不再冲掉输入框里正在粘的地址;顶部那行优先显示节点名字;测速列表标出「使用中」;失败提示走红色并给「重试」按钮;订阅地址放行 http://(明文早在 2.0.4 就放开了,表单还在拦);「换一批」改成「更新」;「改回免费」加确认;播放器锁改成连点两次解锁,锁定态留一枚「已锁定」角标;底部七格的字号和图标槽收紧,长标签不会挤到邻格。

2.0.6

Choose a tag to compare

@zhaoruizhe81 zhaoruizhe81 released this 20 Oct 12:48

2.0.6

启动时静默查一次更新,发现新版本提醒一次。顺带修掉自动检查失败后会空等 12 小时的问题。

之前

2.0.5 及更早,启动时根本不查更新。那段代码 2026-10-16 被删了,理由是当年的清单指向 github.com/MrSsg/HuangguoAPP-for-Andriod(已 404),每次冷启动白打一次外网。通道后来换成自己的 android-update.json,但启动检查一直没接回来——装 2.0.4 的人不看设置页就永远不知道 2.0.5 已经能装。

现在

启动 4 秒后走 android-update.json 查一次,manual:false,命中原生 12 小时节流时直接回放缓存、不打网络。

发现新版本弹一句「发现新版本 vX · 设置 → 版本 里更新」,同一个版本号只弹一次(hgupd:prompted)。之后只在设置页里留着那个「更新到 vX」按钮。不弹对话框、不拦启动、不自动安装。查不到或没网一律静默。

修掉的一个窗口问题

last_auto_check 是在打网络之前写的(这样离线反复冷启动不会每次去撞墙),代价是一次失败就把整个 12 小时窗口用掉了。现在失败时把时间戳往回拨到「12 小时前 + 30 分钟」,下一次冷启动半小时后再试。

一直重启会怎样

12 小时内重启多少次都只回放缓存,零请求。cached() 会拿缓存清单的 versionCode 和当前安装的比,装上新版之后返回 none,不会一直提示。

手点「检查」是另一条通道:manual:true,一小时最多五次,超过提示「检查太频繁了」——以前这种情况会显示「已是最新版本」,用户越急越点、越点越看到「已是最新」。

2.0.5

Choose a tag to compare

@zhaoruizhe81 zhaoruizhe81 released this 20 Oct 12:43

2.0.5

播放器三件事:防误触锁、相邻页预热、后台回来的黑屏。

防误触锁

左边缘竖直居中一个锁,控制栏显示时才出现。锁上之后上下两条栏和玻璃渐变一起收掉,屏幕上只剩那个锁。

拦的不只是控件:单击只弹提示、横划不拖进度、竖划不切集(onInterceptTouchEvent 直接吃掉整段手势,翻页器根本没机会响应)、系统返回键不退出。只有点那个锁才解开。

藏控件是不够的——藏了控件手势还在,兜里蹭一下照样跳进度、切到下一集。

相邻页预热

两页固定落在不同的 player 上(奇偶绑定),另一个槽位正好空着。用它把用户最可能滑向的那一页先起播、静音、停在第一帧上:stop 旧流 → 挂 TextureView → setMedia → 音量 0 → play(),等 VLC 报 Vout(视频输出就绪)再 setTime + pause()。

滑过去时只走 play() + 抬音量,demuxer、硬解、缓冲、关键帧全是现成的。方向按 onScrolled 的 dy 判断,只预一条——上下两个邻居奇偶相同、抢同一个槽位,同时预两条就得塞第三个 player,多一份硬解实例不值得。

几个防坑:只认 Vout 不认 Playing(后者在 play() 之后立刻就来,一帧都没解);900ms 防抖,别去抢正在建的第一份硬解实例;槽位本来就停着目标页时不重启(那份比新起的更暖,重启反而把进度和缓冲全扔掉);6 秒等不到 Vout 就撤销标记,别把某页永久占住。

后台回来也是有声黑屏

onStart() 原来是先 play() 再 150ms 后 attachVout —— 和起播同一个顺序问题。后台期间 TextureView 的 buffer 基本都被系统回收,回来时 surface 要重建:音频立刻响,画面等 surface 恢复 + vout 重挂 + 第一个关键帧。

现在先挂 surface 再出声,静音起播等画面。兜底只给 600ms,不像起播那样给 3 秒——数据本来就在缓冲区里,而且 VLC 可能认得出是同一条流、不重发 Vout。

起播顺序整体对齐

playPage 曾经是 setMedia → play() → attachVout。attachVout 也改成幂等(IdentityHashMap 记住每个 player 挂在哪块 TextureView 上),同一块 view 已经挂着就只更新窗口尺寸。之前滑一页能触发两三次重建,每次都是一段黑。

2.0.4

Choose a tag to compare

@zhaoruizhe81 zhaoruizhe81 released this 20 Oct 10:33

2.0.4

明文 HTTP 的订阅地址不再被拦。

之前的表现

订阅地址填 http://64.64.224.121:8099/p/xxxx,「拉取节点」直接报:

CLEARTEXT communication to 64.64.224.121 not permitted by network security policy

res/xml/network_security_config.xml 里的 base-config 是 cleartextTrafficPermitted="false",只给抖音的三条 CDN 域名开了白名单(douyinvod.com / byteicdn.com / byteimg.com,视频播放要用)。但订阅地址是用户自己填的任意 URL——自建服务和不少机场只给 http://IP:port/p/xxxx,这种地址永远进不了白名单,所以这条路一直是死的。

改法

整个 App 放开明文:base-config 改成 cleartextTrafficPermitted="true",三条域名的白名单就没用了,一起删掉。

取舍写在配置文件的注释里:这东西本来就是满世界抓 http 资源的内容客户端,白名单拦不住的东西比它拦住的多。HTTPS 仍然照常走 TLS,节点本身的流量走 Xray 出站,不经这套策略。

验证

装到真机(Redmi,Android 16)上,用同一条 http://64.64.224.121:8099/... 订阅实测:

[source] 路由 64.64.224.121 → 代理
[source] 订阅 http://64.64.224.121:8099/p/qpUzrtTx9SSsxfOrvSAQDh → 1 个节点

报错消失,节点解析出来了(一条 vless reality)。

2.0.3

Choose a tag to compare

@zhaoruizhe81 zhaoruizhe81 released this 20 Oct 10:19

2.0.3

Clash 订阅里的 vmess 和 vless 现在能认了。顺手修掉一个一直存在的 vmess 解析 bug。

Clash YAML 只转了一半

「订阅地址」支持三种内容:裸节点列表、base64、Clash YAML。第三种以前只转 http / https / socks5 / ss / trojan 五种,vmess 和 vless 直接丢进「不支持」计数。而 Clash Verge、CMFA、各家机场后台导出的配置里,这两个占绝大多数——一份正常的订阅导进来会显示「0 个节点」,用户只会以为订阅坏了。

现在补上转换,字段名逐个对齐:

Clash 字段 分享链接参数
network type
servername / sni sni
client-fingerprint fp
flow flow
ws-opts.path / ws-opts.headers.Host path / host
grpc-service-name serviceName
reality-opts.public-key / short-id pbk / sid
cipher(vmess) scy

vmess 那条拼成 base64 JSON 交给同一个解析器,vless 拼成标准 query——两条最终都走 XrayConfig.parseShareLink,没有第二套解析逻辑。只认 proxies: 那一段,不引 YAML 库。

vmess 的 #备注 把整条链接搞挂

parseVmess 之前把 # 之后的内容也算进 base64 载荷。真实链接几乎都带节点名,解码器在补的 = 后面撞上一堆字母就抛 Input byte array has incorrect ending byte,整条链接被记成「看不懂」。现在先切 # 再解码。

验证

新增一条测试覆盖 Clash 里的 vless + reality 和 vmess + ws,断言解析出来的 tls=reality / flow / host / fingerprint / publicKey / shortId 都对,并且能生成出 Xray 配置。全套 203 个单测通过。

之前那条 YAML能转成链接 的断言也跟着改了:缺 uuid 的 vmess 现在记进「看不懂被跳过」,不再算「协议不支持」。

2.0.2

Choose a tag to compare

@zhaoruizhe81 zhaoruizhe81 released this 20 Oct 10:10

2.0.2

点过「停止」之后,内置核心不再自己活过来。

两处绕过用户意图的自动拉起

点「停止」时 proxyStop 故意不改模式——用户按这个键多半只是想临时断一下,模式留在「内置核心」,节点和订阅都留着。但那之后有两条路会把它重新点着:

  1. 切模式:proxyMode 切回 builtin 的规则是「盘上有链接就立刻拉起」。「直连 → 内置核心」拨一下,等于又启动了一次。
  2. 换源:换源走 restartAfterSource,Java 重建 Activity、WebView 整页重载,启动链再跑一遍 bootProxyCore → proxyBoot,那里只判「模式是 builtin + 核心没在跑 + 有链接」。

改法

加一个落盘的 userStopped:proxyStop 置位,proxyStart 成功处清除(「保存并启动」和从测速结果里连节点都走这条),proxyMode 与 proxyBoot 都在自动拉起之前看它。

停止就是真停——换模式、换源、杀进程重开都不会再拉,直到你自己点「保存并启动」或者从测速结果里挑一个节点连。

改动只碰 ProxyConsole.java,安装包大小和 2.0.1 一致(42,170,428 字节)。

2.0.1 —— 裁掉安装包里的色情域名库

Choose a tag to compare

@zhaoruizhe81 zhaoruizhe81 released this 20 Oct 03:35

2.0.1

安装包瘦身,功能一行没改。

装了什么不该装的东西

libv2ray.aar 自带两个 v2ray 数据文件,之前全打进了 APK:geosite.dat 11MB、geoip.dat 16.9MB,合计占了安装包一半以上。

而代码里只写了一条分流规则 geosite:cn。geosite.dat 里另外 1546 个分类一次都用不上,其中有个叫 CATEGORY-PORN 的,装着 6660 个色情域名(pornhub 出现 54 次、xvideos 114 次)。geoip.dat 更彻底 —— domainStrategy 是 AsIs,分流只按域名匹配、不做 IP 判断,这 16.9MB 从来没被引用过。

裁法

新增 tools/geocut,两步走:

  1. 按 country_code 只留 CN;
  2. 拿 CATEGORY-PORN 的域名全集当黑名单,回头剔 CN 列表。

第二步少不得。光裁分类删不干净 —— 上游把 51cg.com 收进了 cn,得靠黑名单才剔得掉。

结果

之前 之后
geosite.dat 11,097,262 1,967,446
geoip.dat 16,907,278 139,034
安装包 48,997,412 42,170,300

另外清掉了几个 assets 里 js 注释中的站名,node --check 通过。

说清楚一件事

这不会让已经装了 1.0.x 的手机上那个「包含病毒 a.gray.sex...」的提示消失。安全引擎的判定按包名 + 签名缓存,com.huangguo.mobile 这张证书已经判过了,同包名覆盖安装会直接沿用结论、不重新扫描。

改动的作用是:以后不再因为「APK 里躺着一份色情域名表」而命中的。装的时候如果弹窗还在,点右上角 ⋮ 里的「单次安装授权」即可继续。

SHA-256 5cb6cd813f419c50330b023355edfe904497d6334aed5b1c5664bf11ae6dbd72

AIIvideo 1.0.31

Choose a tag to compare

@zhaoruizhe81 zhaoruizhe81 released this 18 Oct 10:50

新增 MissAV 源。

整站(含 /api/*、封面域 fourhoi.com、视频 CDN surrit.com)都挂在 Cloudflare 后面,cf_clearance 绑 TLS 指纹 —— okhttp 带什么头都是 403。所以抓取全部改走隐藏 WebView 过盾后在页面里发同源 fetch;播放则借同一个 WebView 逐块取 m3u8 与分片,改写成中继地址后交给 VLC。真机实测 2:18:10 完整时长、可拖进度、四档码率、取流失败 0 次。

顺带修掉五个「接口 200、界面空」的静默失败:

  • 分类只有第一页(前端读 pages,我们只给了 hasMore)
  • 点分类 0 部作品(分类地址三种形态拼错)
  • 全部类型空白(要分组信封,不是平铺数组)
  • 集数 undefined(别名表缺 episodeNumber / source)
  • 类型胶囊写成 dm96/高清(同上,缺 tagName)

导航按站点自己的两级结构做:「无码影片」下 12 个子站(FC2 / HEYZO / 东京热 / 一本道 / Caribbeancom / Gachinco / 人妻斩…),「素人」5 个。类型和女优这类索引页不再混进作品列表 tab。

另外这批一起进来的:DirectHosts 显式直连表(修代理拖垮直连源)、桥调用 30 秒超时(修详情页永久 loading)、hgBack 四个设置子页返回、VLC 第 N 集有声无画。

全量 275 用例,含 MissavConfigTest(整份加源配置形状)与 VlcRelayTest(m3u8 改写)。