fix(android): YouTube Error 153 + visible web-frame errors

- YouTube: load the embed via loadDataWithBaseURL with a youtube.com base URL so
  the iframe has a valid origin/referer (a bare loadUrl of /embed/ID gives
  'player misconfigured, Error 153'). Applies to zone + fullscreen YouTube.
- Web frames: shared WebViewSupport.configure() enables mixed-content (self-hosted
  http LAN servers) and pipes WebView load/HTTP/JS-console errors to DebugLog, so a
  failing web frame surfaces the real error in the live panel instead of a black
  broken-page view.
This commit is contained in:
ScreenTinker 2026-06-08 22:42:59 -05:00
parent c184b94602
commit 3510670ce1
3 changed files with 91 additions and 19 deletions

View file

@ -55,13 +55,12 @@ class MediaPlayerManager(
exoPlayer?.stop()
youtubeWebView?.apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.mediaPlaybackRequiresUserGesture = false
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
com.remotedisplay.player.util.WebViewSupport.configure(this, "YouTube")
setBackgroundColor(android.graphics.Color.BLACK)
loadUrl(embedUrl)
// Load via an embed wrapper with a valid youtube.com origin (Error 153 fix).
val html = com.remotedisplay.player.util.WebViewSupport.youtubeEmbedHtml(embedUrl)
if (html != null) loadDataWithBaseURL(com.remotedisplay.player.util.WebViewSupport.YT_BASE, html, "text/html", "UTF-8", null)
else loadUrl(embedUrl)
}
}
@ -78,12 +77,7 @@ class MediaPlayerManager(
exoPlayer?.stop()
youtubeWebView?.apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.mediaPlaybackRequiresUserGesture = false
webViewClient = WebViewClient()
webChromeClient = WebChromeClient()
setBackgroundColor(android.graphics.Color.TRANSPARENT)
com.remotedisplay.player.util.WebViewSupport.configure(this, "Widget")
loadUrl(url)
}
}

View file

@ -155,10 +155,13 @@ class ZoneManager(
container.addView(webView); zoneViews[zone.id] = webView
if (multi) scheduleZoneAdvance(zone.id, durationMs, advance)
}
// YouTube - render in WebView
// YouTube - render via an embed wrapper with a valid origin (Error 153 fix)
mimeType == "video/youtube" && !remoteUrl.isNullOrEmpty() -> {
val webView = createWebView()
webView.loadUrl(remoteUrl); webView.layoutParams = params
val html = com.remotedisplay.player.util.WebViewSupport.youtubeEmbedHtml(remoteUrl)
if (html != null) webView.loadDataWithBaseURL(com.remotedisplay.player.util.WebViewSupport.YT_BASE, html, "text/html", "UTF-8", null)
else webView.loadUrl(remoteUrl)
webView.layoutParams = params
container.addView(webView); zoneViews[zone.id] = webView
if (multi) scheduleZoneAdvance(zone.id, durationMs, advance)
}
@ -245,11 +248,7 @@ class ZoneManager(
private fun createWebView(): WebView {
return WebView(context).apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.mediaPlaybackRequiresUserGesture = false
setBackgroundColor(android.graphics.Color.TRANSPARENT)
webViewClient = WebViewClient()
com.remotedisplay.player.util.WebViewSupport.configure(this, "Zone")
}
}

View file

@ -0,0 +1,79 @@
package com.remotedisplay.player.util
import android.webkit.ConsoleMessage
import android.webkit.WebChromeClient
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
/**
* Shared setup + helpers for the player's WebViews (zone widgets, fullscreen
* widgets, YouTube). Centralizes:
* - JS / DOM storage / autoplay-without-gesture,
* - mixed-content ALLOW (self-hosted servers are often http on the LAN; without
* this an https page embedding http - or vice versa - is silently blocked into
* a black broken-frame),
* - error/console logging piped to DebugLog so a failing web frame shows the
* real reason in the live debug panel instead of just a black broken-page view,
* - a YouTube embed that loads with a valid youtube.com origin (fixes Error 153).
*/
object WebViewSupport {
const val YT_BASE = "https://www.youtube.com"
fun configure(webView: WebView, tag: String) {
webView.settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
mediaPlaybackRequiresUserGesture = false
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
}
webView.setBackgroundColor(android.graphics.Color.TRANSPARENT)
webView.webViewClient = object : WebViewClient() {
override fun onReceivedError(view: WebView?, request: WebResourceRequest?, error: WebResourceError?) {
if (request?.isForMainFrame == true) {
DebugLog.e(tag, "WebView load error ${error?.errorCode} ${error?.description} url=${request.url}")
}
}
override fun onReceivedHttpError(view: WebView?, request: WebResourceRequest?, errorResponse: WebResourceResponse?) {
if (request?.isForMainFrame == true) {
DebugLog.e(tag, "WebView HTTP ${errorResponse?.statusCode} url=${request.url}")
}
}
}
webView.webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(msg: ConsoleMessage?): Boolean {
if (msg?.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
DebugLog.w(tag, "JS error: ${msg.message()} @${msg.sourceId()}:${msg.lineNumber()}")
}
return super.onConsoleMessage(msg)
}
}
}
fun extractYoutubeId(url: String): String? {
val patterns = listOf(
Regex("""embed/([A-Za-z0-9_-]{6,})"""),
Regex("""[?&]v=([A-Za-z0-9_-]{6,})"""),
Regex("""youtu\.be/([A-Za-z0-9_-]{6,})""")
)
for (p in patterns) p.find(url)?.let { return it.groupValues[1] }
return null
}
/**
* HTML wrapper for a YouTube embed. Loaded via loadDataWithBaseURL(YT_BASE, ...)
* so the iframe has a valid youtube.com origin/referer (a bare loadUrl of the
* embed gives Error 153 "player misconfigured"). Returns null if no video id.
*/
fun youtubeEmbedHtml(url: String): String? {
val id = extractYoutubeId(url) ?: return null
val src = "$YT_BASE/embed/$id?autoplay=1&mute=1&controls=0&rel=0&modestbranding=1&loop=1&playlist=$id&playsinline=1"
return "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" +
"<style>html,body{margin:0;padding:0;height:100%;background:#000;overflow:hidden}iframe{display:block;width:100%;height:100%;border:0}</style>" +
"</head><body><iframe src=\"$src\" allow=\"autoplay; encrypted-media\" allowfullscreen></iframe></body></html>"
}
}