4k
This commit is contained in:
@@ -31,6 +31,7 @@
|
||||
<activity
|
||||
android:name=".BrowserActivity"
|
||||
android:hardwareAccelerated="true"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
|
||||
@@ -339,6 +339,8 @@ class BrowserActivity : AppCompatActivity(), RemoteControlCallback {
|
||||
Log.d("TVBrowser.Filter", "Blocked script: $reqUrl")
|
||||
return ContentFilterHelper.createEmptyScriptResponse()
|
||||
}
|
||||
// 1.3 兼容旧版 WebView:拦截并替换含高版本语法(如 ES2021 `??=`)而导致播放器崩溃的插件脚本
|
||||
VideoSnifferHelper.createCompatScriptResponse(reqUrl)?.let { return it }
|
||||
// 2. 视频流嗅探拦截(.m3u8, .mp4 等)
|
||||
if (VideoSnifferHelper.isVideoStreamUrl(reqUrl)) {
|
||||
onVideoSniffed(reqUrl, currentPageTitle, "net")
|
||||
@@ -378,6 +380,8 @@ class BrowserActivity : AppCompatActivity(), RemoteControlCallback {
|
||||
|
||||
override fun onPageCommitVisible(view: WebView?, url: String?) {
|
||||
super.onPageCommitVisible(view, url)
|
||||
// 新文档 window 刚提交时立即注入嗅探 Hook(onPageStarted 时新 window 可能尚未提交)
|
||||
if (!hasDocStartHook) view?.evaluateJavascript(VideoSnifferHelper.JS_DOCUMENT_START_HOOK, null)
|
||||
injectDesktopModeFallback(view)
|
||||
// 新页面首帧可见时再注入一次,尽量在广告图显示前隐藏
|
||||
if (contentFilterActive) view?.evaluateJavascript(ContentFilterHelper.JS_CONTENT_FILTER, null)
|
||||
@@ -448,6 +452,7 @@ class BrowserActivity : AppCompatActivity(), RemoteControlCallback {
|
||||
webView.webChromeClient = object : WebChromeClient() {
|
||||
override fun onProgressChanged(view: WebView?, newProgress: Int) {
|
||||
progressBar.progress = newProgress
|
||||
if (!hasDocStartHook) view?.evaluateJavascript(VideoSnifferHelper.JS_DOCUMENT_START_HOOK, null)
|
||||
if (newProgress >= 100) progressBar.visibility = View.GONE
|
||||
}
|
||||
|
||||
@@ -586,6 +591,14 @@ class BrowserActivity : AppCompatActivity(), RemoteControlCallback {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
setIntent(intent)
|
||||
if (intent?.action == Intent.ACTION_VIEW && intent.data != null && ::webView.isInitialized) {
|
||||
loadUrl(intent.data!!.toString())
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否为 Android TV 设备 */
|
||||
private fun isTelevisionDevice(): Boolean {
|
||||
val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as? android.app.UiModeManager
|
||||
|
||||
@@ -38,7 +38,7 @@ class TVBrowserApplication : Application() {
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US)
|
||||
val fileName = "crash_${dateFormat.format(Date())}.txt"
|
||||
val file = File(dir, fileName)
|
||||
FileWriter(file, Charsets.UTF_8).use { writer ->
|
||||
file.bufferedWriter(Charsets.UTF_8).use { writer ->
|
||||
val pw = PrintWriter(writer)
|
||||
pw.println("=== 崩溃日志 ${SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())} ===")
|
||||
pw.println()
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebSettings
|
||||
@@ -120,6 +121,7 @@ class VideoResolver(
|
||||
if (AdBlockHelper.isEnabled(context) && AdBlockHelper.shouldBlockUrl(url)) {
|
||||
return AdBlockHelper.createEmptyResponse()
|
||||
}
|
||||
VideoSnifferHelper.createCompatScriptResponse(url)?.let { return it }
|
||||
if (VideoSnifferHelper.isVideoStreamUrl(url)) {
|
||||
handler.post { onCandidate(gen, url, "net", null, "") }
|
||||
}
|
||||
@@ -134,6 +136,10 @@ class VideoResolver(
|
||||
if (!hasDocStartHook) view?.evaluateJavascript(VideoSnifferHelper.JS_DOCUMENT_START_HOOK, null)
|
||||
}
|
||||
|
||||
override fun onPageCommitVisible(view: WebView?, url: String?) {
|
||||
if (!hasDocStartHook) view?.evaluateJavascript(VideoSnifferHelper.JS_DOCUMENT_START_HOOK, null)
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
if (!hasDocStartHook) view?.evaluateJavascript(VideoSnifferHelper.JS_DOCUMENT_START_HOOK, null)
|
||||
view?.evaluateJavascript(VideoSnifferHelper.JS_SITE_ADAPTER_MACCMS, null)
|
||||
@@ -144,6 +150,11 @@ class VideoResolver(
|
||||
handler?.proceed() // 与浏览器主界面一致:电视系统时间不准时放行
|
||||
}
|
||||
}
|
||||
wv.webChromeClient = object : WebChromeClient() {
|
||||
override fun onProgressChanged(view: WebView?, newProgress: Int) {
|
||||
if (!hasDocStartHook) view?.evaluateJavascript(VideoSnifferHelper.JS_DOCUMENT_START_HOOK, null)
|
||||
}
|
||||
}
|
||||
// 1x1 挂到窗口上:部分内核对未挂载的 WebView 会暂停计时器,导致播放器脚本不执行
|
||||
container.addView(wv, FrameLayout.LayoutParams(1, 1))
|
||||
return wv
|
||||
|
||||
@@ -78,6 +78,27 @@ object VideoSnifferHelper {
|
||||
fun createRejectedResponse(): WebResourceResponse =
|
||||
WebResourceResponse("text/plain", "utf-8", 403, "Blocked", emptyMap(), ByteArrayInputStream(ByteArray(0)))
|
||||
|
||||
/**
|
||||
* 针对旧版系统 WebView(Chromium < 85,不支持 `??=` / `||=` 等 ES2021 语法)的播放器插件兼容替换:
|
||||
* 例如 4K影视(4kvm.tv)引入的 `artplayer-plugin-danmaku*.js` 含有 `??=`,在电视旧版 WebView 中会报
|
||||
* `Uncaught SyntaxError: Unexpected token '='`,进而导致主脚本调用 `artplayerPluginDanmaku(...)` 时抛
|
||||
* `ReferenceError: artplayerPluginDanmaku is not defined`,在创建 Artplayer / hls.js 前直接中断。
|
||||
* 返回无语法兼容问题且免除弹幕 DOM 开销的同名空插件,确保播放器与 m3u8 加载正常执行。
|
||||
*/
|
||||
private const val JS_DANMAKU_COMPAT_STUB =
|
||||
"window.artplayerPluginDanmaku=function(){return function(){var n=function(){};return{name:'artplayerPluginDanmaku',send:n,load:n,clear:n,toggle:n,setOpacity:n,destroy:n,push:n,manager:null,container:null}}};"
|
||||
|
||||
fun createCompatScriptResponse(url: String): WebResourceResponse? {
|
||||
if (url.contains("artplayer-plugin-danmaku", ignoreCase = true)) {
|
||||
return WebResourceResponse(
|
||||
"application/javascript",
|
||||
"utf-8",
|
||||
ByteArrayInputStream(JS_DANMAKU_COMPAT_STUB.toByteArray(Charsets.UTF_8))
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/** 原生播放器无法播放时给用户的提示 */
|
||||
const val WEB_ONLY_MESSAGE = "央视网视频经过加密,原生播放器会花屏,请直接在网页中播放"
|
||||
|
||||
@@ -212,6 +233,14 @@ object VideoSnifferHelper {
|
||||
*/
|
||||
val JS_DOCUMENT_START_HOOK = """
|
||||
(function() {
|
||||
if (typeof window.artplayerPluginDanmaku !== 'function') {
|
||||
window.artplayerPluginDanmaku = function() {
|
||||
return function() {
|
||||
var n = function() {};
|
||||
return { name: 'artplayerPluginDanmaku', send: n, load: n, clear: n, toggle: n, setOpacity: n, destroy: n, push: n, manager: null, container: null };
|
||||
};
|
||||
};
|
||||
}
|
||||
if (window.__tvSniffInstalled) return;
|
||||
window.__tvSniffInstalled = true;
|
||||
var reported = {};
|
||||
|
||||
Reference in New Issue
Block a user