创建于: 2025-12-08 11:22:39 | 更新于: 2025-12-08 11:22:39
分享9种日常实用绳结。 分享9种日常实用绳结。 http://xhslink.com/o/9lbemqjIWOv
复制后打开【小红书】查看笔记!
创建于: 2025-12-06 11:33:13 | 更新于: 2025-12-06 11:33:13
C:\Users\Administrator\Desktop\utao\android\x5\app\src\main\java\tv\utao\x5\impl\WebViewClientImpl.java
创建于: 2025-10-29 21:12:34 | 更新于: 2025-10-29 21:12:34
https://mesh.if.iqiyi.com/tvg/v2/lw/base_info?entity_id=5521006720234400&device_id=d918879b3a193c2ef3699d1ebf934b49&auth_cookie=&user_id=0&vip_type=-1&vip_status=0&conduit_id=&pcv=13.103.23529&app_version=13.103.23529&ext=&app_mode=standard&scale=100×tamp=1761629910516&src=pca_tvg&os=&ad_ext=%7B%22r%22%3A%222.5.0-ares6-pure%22%7D&sign=63EC185760584346E891A06794350DB0
以上是爱奇艺播放某剧抓包得到的api网址,打开可以得到包含某剧剧集的相关信息@mesh.txt,其它剧只要修改相关参数即可得到,其中“entity_id=5521006720234400”可在加载海报信息的api地址https://pub.m.iqiyi.com/h5/main/recVideos/lib/?page_id=1&mode=24&channel_id=2&post=list&from=mobile_videolib&is_unified_interface=1&version=1.0.0&play_platform=H5_QIYI里获得,如@pub.txt。 sign 是动态生成,用下方式测试成功得到sign=63EC185760584346E891A06794350DB0
const crypto = require('crypto');
const rawString = [
'ad_ext={"r":"2.5.0-ares6-pure"}',
'app_mode=standard',
'app_version=13.103.23529',
'auth_cookie=',
'conduit_id=',
'device_id=d918879b3a193c2ef3699d1ebf934b49',
'entity_id=5521006720234400',
'ext=',
'os=',
'pcv=13.103.23529',
'scale=100',
'src=pca_tvg',
'timestamp=1761629910516',
'user_id=0',
'vip_status=0',
'vip_type=-1',
'secret_key=howcuteitis'
].join('&');
const sign = crypto.createHash('md5').update(rawString).digest('hex').toUpperCase();
console.log('生成的 sign:', sign);
创建于: 2025-10-26 02:12:24 | 更新于: 2025-10-26 02:12:24
第一步:检查是否符合已知的“通用模式”
第一步:检查是否符合已知的“通用模式”
在添加一个新频道时,首先看它的URL或页面结构是否符合我们已经归纳好的通用模式。
是否为 cloudlive 类型?
是否为 iapp 类型?
是否为 sdpd 类型?
第二步:尝试简单的“通用全屏”
如果新频道不属于上面任何一种已知模式,那它可能是一个简单的、没有复杂逻辑的普通网页。
首选:尝试指向 tv/common
次选:尝试调用 fullscreenW
第三步:创建专属脚本(作为最后手段)
如果以上所有通用方法都失败了,那么说明这个网站确实比较“特殊”,需要专门为它编写定制的脚本。
创建于: 2025-10-23 18:16:21 | 更新于: 2025-10-23 18:16:21
这个问题确实非常顽固。在修改了原生Java代码后依然无效,说明 WebView
的事件传递机制比我们想象的还要特殊,标准的 super.dispatchKeyEvent
调用在您的这个环境中被某种方式忽略或阻止了。
这个问题确实非常顽固。在修改了原生Java代码后依然无效,说明 WebView
的事件传递机制比我们想象的还要特殊,标准的 super.dispatchKeyEvent
调用在您的这个环境中被某种方式忽略或阻止了。
我现在准备采取最后、也是最强硬的手段。我们不再尝试让安卓系统‘传递’按键事件,而是直接在Java代码里捕获按键,
然后手动生成对应的JavaScript按键事件,并将其‘注入’到网页中执行。
这种方法相当于用Java代码‘命令’网页里的JavaScript去执行操作。它绕过了所有复杂的系统事件分发流程,直达目标。
如果这个方法再无效,那问题就可能超出了代码层面(例如,特定的ROM或 WebView 版本自身的bug)。
我将再次修改 MainActivity.java。请稍候。
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ ✓ WriteFile Writing to ...x5\java\tv\utao\x5\MainActivity.java │
│ │
│ 44 } │
│ 45 │
│ 46 public boolean dispatchKeyEvent(KeyEvent event) { │
│ 47 - // 获取当前URL,用于判断是否在播放器页面 │
│ 47 String currentUrl = (mWebView != null) ? mWebView.getUrl() : ""; │
│ 48 │
│ 50 - // 如果是播放器页面,则不执行任何原生按键拦截,将事件完全交由WebView内部的JS处理 │
│ 49 + // 终极方案:对播放器页面,在Java层捕获按键,并手动注入JS事件 │
│ 50 if (currentUrl != null && currentUrl.contains("player.html")) { │
│ 51 + if (event.getAction() == KeyEvent.ACTION_DOWN) { //只处理按下事件 │
│ 52 + int keyCode = event.getKeyCode(); │
│ 53 + String jsToExecute = null; │
│ 54 + String scriptPattern = │
│ "document.getElementById('control-overlay').dispatchEvent(new KeyboardEvent('keydown', │
│ {'keyCode': %d, 'bubbles': true}));"; │
│ 55 + │
│ 56 + switch (keyCode) { │
│ 57 + case KeyEvent.KEYCODE_DPAD_CENTER: │
│ 58 + case KeyEvent.KEYCODE_ENTER: │
│ 59 + jsToExecute = String.format(scriptPattern, 13); │
│ 60 + break; │
│ 61 + case KeyEvent.KEYCODE_DPAD_UP: │
│ 62 + jsToExecute = String.format(scriptPattern, 38); │
│ 63 + break; │
│ 64 + case KeyEvent.KEYCODE_DPAD_DOWN: │
│ 65 + jsToExecute = String.format(scriptPattern, 40); │
│ 66 + break; │
│ 67 + case KeyEvent.KEYCODE_BACK: │
│ 68 + jsToExecute = String.format(scriptPattern, 27); │
│ 69 + break; │
│ 70 + } │
│ 71 + │
│ 72 + if (jsToExecute != null) { │
│ 73 + mWebView.evaluateJavascript(jsToExecute, null); │
│ 74 + return true; // 事件已处理,在此消费掉 │
│ 75 + } │
│ 76 + } │
│ 77 + // 对于ACTION_UP或其他未处理的按键,还是可以传递下去 │
│ 78 return super.dispatchKeyEvent(event); │
│ 79 } │
│ 80 │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 99 │
│ 100 boolean isExternalPlayer = currentUrl != null && currentUrl.contains("xukzhao168"); │
│ 101 │
│ 76 - // Only intercept DPAD keys for internal pages. │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 102 if (!isExternalPlayer) { │
│ 103 if(keyCode==KeyEvent.KEYCODE_DPAD_CENTER||keyCode==KeyEvent.KEYCODE_ENTER){ │
│ 104 if(openOkMenu()&&!WebViewClientImpl.currentUrlIsHome()){ │
│ 105 return ctrl("menu"); │
│ 106 } │
│ 107 return ctrl("ok"); │
│ 83 - // return super.dispatchKeyEvent(event); │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 108 } │
│ 109 if(keyCode==KeyEvent.KEYCODE_MENU||keyCode==KeyEvent.KEYCODE_TAB){ │
│ 110 return ctrl("menu"); │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 127 ||keyCode==KeyEvent.KEYCODE_VOLUME_MUTE){ │
│ 128 return super.dispatchKeyEvent(event); │
│ 129 } │
│ 106 - // return ctrl("menu"); │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 130 return super.dispatchKeyEvent(event); │
│ 131 } │
│ 132 private boolean keyBack(){ │
│ 133 String url = WebViewClientImpl.backUrl(); │
│ 134 LogUtil.i("keyBack","keyBack "+url); │
│ 112 - //NextPlusNavigationDelegate.backUrl(); │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 135 if(null!=url&&null!=mWebView){ │
│ 136 mWebView.loadUrl(url); │
│ 137 return true; │
│ 138 } │
│ 139 long currentTime = System.currentTimeMillis(); │
│ 140 if (currentTime - mClickBackTime < 3000) { │
│ 119 - //killAppProcess(); │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 141 finish(); │
│ 121 - //super.onBackPressed(); │
│ 122 - //System.exit(0); │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 142 } else { │
│ 143 ToastUtils.show(this, "再按一次返回键退出", Toast.LENGTH_SHORT); │
│ 144 mClickBackTime = currentTime; │
│ 145 } │
│ 127 - //detail-> home-> index │
│ ═════════════════════════════════════════════════════════════════════════════════════════════ │
│ 146 return true; │
│ 147 } │
│ 148 │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ 我已经将最终的“手动注入”方案代码写入了 MainActivity.java。
这已经是我们能从代码层面采取的最直接、最可靠的手段了。它不再依赖安卓系统的事件分发,而是用Java代码强制命令网
页执行相应的按键操作。
创建于: 2025-10-16 20:47:15 | 更新于: 2025-10-16 20:47:15
电视剧
https://m.douban.cmliussss.net/rexxar/api/v2/subject/recent_hot/tv?start=0&limit=25&category=tv&type=tv
电影
https://m.douban.cmliussss.net/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=25…
电视剧
https://m.douban.cmliussss.net/rexxar/api/v2/subject/recent_hot/tv?start=0&limit=25&category=tv&type=tv
电影
https://m.douban.cmliussss.net/rexxar/api/v2/subject/recent_hot/movie?start=0&limit=25&category=热门&type=全部
综艺
https://m.douban.cmliussss.net/rexxar/api/v2/subject/recent_hot/tv?start=0&limit=25&category=show&type=show
电视剧
https://tv.xukzhao168.dpdns.org/api/douban/categories?kind=tv&category=tv&type=tv&limit=25&start=0
创建于: 2025-10-15 11:41:02 | 更新于: 2025-10-15 11:41:02
x5\x5\impl\WebViewClientImpl.java │
╰────────────────────────────────────────────────╯
✦ 找到了!
x5\x5\impl\WebViewClientImpl.java │
╰────────────────────────────────────────────────╯
✦ 找到了!
我终于在 WebViewClientImpl.java 中找到了问题的根源。我的猜测完全错了,事实比想象的要简单得多,也更出乎意料。
请看 shouldInterceptRequest 方法中的这段代码:
1 // ...
2 int index= url.indexOf("tv-web");
3 if(index<0){
4 // ... (省略了处理外部网站的代码)
5 return super.shouldInterceptRequest(webView, webResourceRequest);
6 }
7
8 if(url.endsWith("tvImg=1")){
9 if(index>0) {
10 String fileName = url.substring(index,url.indexOf("?"));
11 LogUtil.i(TAG, "fileName image " + fileName);
12 return new WebResourceResponse("image/jpeg",
13 ConstantMy.UTF8, FileUtil.readExtIn(MyApplication.getAppContext(),fileName));
14 }
15 }
16 // ...
代码逻辑分析:
tv-web,它会继续检查 URL 是否以 tvImg=1 结尾。然后,它会从 URL 中截取文件名(例如 tv-web/img/utao.jpg),并使用 FileUtil.readExtIn() 从 App 的 assets
目录中读取这个图片文件,然后返回给 WebView。
结论:
tvImg=1 这个参数的真正作用,根本不是用来触发添加 Referer 的,而是用来加载 App 本地 assets 目录下的图片!
这就是为什么你的 moontv 页面图片出不来的原因:
tv-web,于是就直接让系统去请求这个网络地址了。而 douban/home.js 能正常显示图片,是因为它请求的 API 返回的图片 URL 本身就是豆瓣的,而 _apiX.getJson 在请求
API 时就已经伪造了 Referer,所以能拿到数据。但是,当把这个豆瓣图片 URL 放到 <img>
标签里时,同样会遇到防盗链问题。
真正的解决方案:
我们需要修改 WebViewClientImpl.java,让它在拦截到豆瓣图片请求时,主动为它加上 Referer 头。
我将修改 shouldInterceptRequest 方法,在处理外部网站请求的部分,增加一个针对豆瓣图片的特殊逻辑。
创建于: 2025-10-09 00:16:41 | 更新于: 2025-10-09 01:12:50
搜索结果证实了我们的猜想。在 app/src/main/assets/tv-web/js/load_detail.js
文件中,我发现了专门针对芒果TV(mgtv)链接的逻辑。
搜索结果证实了我们的猜想。在 app/src/main/assets/tv-web/js/load_detail.js
文件中,我发现了专门针对芒果TV(mgtv)链接的逻辑。
特别是这一行代码:
if(url.startsWith("https://www.mgtv.com/live"))
这表明当应用检测到以 https://www.mgtv.com/live
开头的URL时,会触发一段特殊的处理代码,而不是直接在WebView中打开它。
此外,我还找到了 mgtv/home.js 和 mgtv/detail.js 等文件,这些文件会向芒果TV的API(pianku.api.mgtv.com 和
pcweb.api.mgtv.com)发送请求,以获取视频信息。
这整套机制说明,应用需要先通过API获取到真实的视频流地址,然后才能播放。
为了完全搞清楚处理流程,我现在将读取并分析 load_detail.js 文件的具体内容
分析 load_detail.js 文件后,流程已经非常清晰了:
结论是:
您最初的判断完全正确。应用并非直接播放 https://www.mgtv.com/live 的URL。而是通过 load_detail.js
识别这类链接,然后调用 tv/hntv/detail.js
这个专门的脚本来处理。这个脚本里应该包含了从芒果直播页面中提取真实视频流地址并进行播放的复杂逻辑。
为了彻底弄清具体的实现方法,我将继续查看 js/tv/hntv/detail.js 文件的内容
对 tv/hntv/detail.js 文件的分析揭示了最后的操作步骤:
这个脚本的作用是在芒果TV的直播页面加载后,执行一系列自动化操作:
最终结论
您的判断完全正确。
应用处理 https://www.mgtv.com/live 链接的完整流程是:
x5\x5\impl\ WebViewClientImpl.java 文件中的 onPageFinished 方法,会根据一个名为 type 的变量来决定加载哪个JS文件:
* 当 type 为 1 时(在 LiveActivity 中,也就是直播页面),它会固定加载 js/load_detail_tv.js。
* 在其他情况下,它会加载 js/load_detail_video.js。
x5\assets\tv-web\js\load_detail_tv.js 的代码,终于找到了问题的根源。
这个文件里的 loadDetailByUrl 函数,包含了一系列 if
语句来匹配不同的电视台网址,但其中完全缺失了对芒果TV(mgtv.com)直播链接的判断逻辑。
代码里有 cctv.com、jstv.com 等等,唯独没有下面这关键的一行:
1 if(url.startsWith("https://www.mgtv.com/live")){
2 return "tv/hntv";
3 }
正是因为缺少这个判断,所有芒果TV的直播链接都被当成了“未知”链接,导致系统最终加载了通用的 tv/common/detail.js
脚本,问题由此产生。
解决方案:
我只需要在 load_detail_tv.js 文件中,把这缺失的 if 判断加回去即可。