summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--BACKLOG.md25
-rw-r--r--app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt10
-rw-r--r--app/src/main/java/org/terst/feedreader/ui/YouTubeLinks.kt35
-rw-r--r--app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt29
4 files changed, 98 insertions, 1 deletions
diff --git a/BACKLOG.md b/BACKLOG.md
index 5bc7ea0..75d91d3 100644
--- a/BACKLOG.md
+++ b/BACKLOG.md
@@ -64,7 +64,30 @@ video in a cross-origin subframe of it -- and Peter then reported audio playing
over a black screen. Both of those are WebView constructs with a history of
rendering video oddly and neither was needed, so the referrer replaced them.
-**Not verified here: video actually appearing.** The emulator cannot render
+**Root-caused and fixed 2026-10-23, after three wrong guesses.** The symptom
+was audio and controls with a blank rectangle where the picture should be. The
+cause is not the window, the surface, or the embed: YouTube's player lays
+itself out with `body { position: absolute; height: 100% }`, and in this
+WebView percentage heights resolve against a zero-height containing block.
+Measured in the running app: `window.innerHeight` correctly reports 214 while
+`100vh` and every percentage height resolve to 0, so the body collapsed and
+the video element was laid out 380x0. The white Peter saw was YouTube's own
+`<html>` background showing through a player with no height.
+
+`YOUTUBE_FIT_PLAYER_JS` runs on page load and pins the player chain to the
+pixel height the page does know, then dispatches a resize so YouTube
+re-measures. Verified on a device: video element 380x0 -> 380x214, picture
+appears. It treats the symptom; why the viewport is zero-height sits below
+this app in WebView itself.
+
+Two earlier guesses are recorded because they were wrong and should not be
+retried: setting FLAG_HARDWARE_ACCELERATED on the dialog's window changed
+nothing, and neither did moving the player out of a dialog into an in-window
+overlay. An A/B against BrowserScreen looked decisive for the window
+hypothesis and was not -- BrowserScreen's WebView also differs in layout, and
+layout was the real variable.
+
+**Superseded note, kept for the record:** The emulator cannot render
video in a WebView at all -- it hard-crashes the whole AVD, twice, including on
a plain `<video>` element in an ordinary non-dialog WebView. Everything short of
display is confirmed on-device (no error, 854x480, readyState 4, playing), but
diff --git a/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt b/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt
index 3af460a..68562e8 100644
--- a/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt
+++ b/app/src/main/java/org/terst/feedreader/ui/ReaderWebView.kt
@@ -416,6 +416,16 @@ fun YouTubePlayerDialog(videoId: String, onDismiss: () -> Unit) {
host == "youtube.com" || host.endsWith(".youtube.com") || host == "youtu.be"
return !allowed
}
+
+ // The player has no height of its own in here: YouTube sizes itself with
+ // percentage heights, and in this WebView those resolve to zero however
+ // tall the view actually is. Without this the video element is laid out
+ // 380x0 -- audio plays, the controls draw, and the picture has nowhere
+ // to go. See YOUTUBE_FIT_PLAYER_JS.
+ override fun onPageFinished(view: WebView, url: String?) {
+ super.onPageFinished(view, url)
+ view.evaluateJavascript(YOUTUBE_FIT_PLAYER_JS, null)
+ }
}
webChromeClient = WebChromeClient()
// The embed as this WebView's own ordinary document, with a Referer
diff --git a/app/src/main/java/org/terst/feedreader/ui/YouTubeLinks.kt b/app/src/main/java/org/terst/feedreader/ui/YouTubeLinks.kt
index 00624a1..a971615 100644
--- a/app/src/main/java/org/terst/feedreader/ui/YouTubeLinks.kt
+++ b/app/src/main/java/org/terst/feedreader/ui/YouTubeLinks.kt
@@ -97,6 +97,41 @@ fun youTubeEmbedUrl(videoId: String): String =
"https://www.youtube-nocookie.com/embed/${scrub(videoId)}?autoplay=1&playsinline=1&rel=0"
/**
+ * A script that gives YouTube's player a height, run once the embed has loaded.
+ *
+ * **Why a WebView needs this and a browser does not.** YouTube's embed lays itself out with
+ * `body { position: absolute; height: 100% }`, which resolves against the initial containing
+ * block -- and in this WebView that block has zero height. Measured in the running app:
+ * `window.innerHeight` correctly reports 214, while `100vh` and every percentage height resolve
+ * to 0. So the body collapsed, the player chain under it collapsed, and the video element was
+ * laid out 380x0.
+ *
+ * That is why the symptom was a *blank rectangle with working audio and working controls*: the
+ * page, the chrome and the stream were all fine, and the picture had nowhere to go. What showed
+ * through the zero-height player was YouTube's own white `<html>` background.
+ *
+ * The script pins the chain to the pixel height the page does know, and dispatches a resize so
+ * the player's own code re-measures. Confirmed on a device: the video element goes from 380x0 to
+ * 380x214 and the picture appears.
+ *
+ * This treats the symptom rather than whatever makes the viewport zero-height in the first
+ * place, which is below this app in the WebView itself. `!important` is deliberate -- YouTube's
+ * own styles would otherwise win it back on the next layout.
+ */
+const val YOUTUBE_FIT_PLAYER_JS: String =
+ """(function () {
+ var h = window.innerHeight;
+ if (!h) { return; }
+ var css = document.createElement('style');
+ css.textContent =
+ 'html,body{height:' + h + 'px !important;min-height:' + h + 'px !important}' +
+ '#player,#movie_player,.html5-video-player,.html5-video-container{height:' + h + 'px !important}' +
+ 'video{height:' + h + 'px !important}';
+ document.head.appendChild(css);
+ window.dispatchEvent(new Event('resize'));
+ })();"""
+
+/**
* The id with anything outside YouTube's own alphabet removed.
*
* The ids reaching here are already matched against `[a-zA-Z0-9_-]{11}` upstream, so this
diff --git a/app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt b/app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt
index af9884d..d2c623c 100644
--- a/app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt
+++ b/app/src/test/java/org/terst/feedreader/ui/YouTubeLinksTest.kt
@@ -127,4 +127,33 @@ class YouTubeLinksTest {
fun `the watch link for a video is the one a phone can hand to the YouTube app`() {
assertEquals("https://www.youtube.com/watch?v=dQw4w9WgXcQ", youTubeWatchUrl("dQw4w9WgXcQ"))
}
+
+ // --- giving the player a height -------------------------------------------------------
+
+ @Test
+ fun `the fit script sizes the player from the height the page does know`() {
+ val js = YOUTUBE_FIT_PLAYER_JS
+
+ assertTrue("percentage heights are what resolve to zero: $js", js.contains("window.innerHeight"))
+ assertTrue(js.contains("dispatchEvent"))
+ }
+
+ @Test
+ fun `the fit script overrides YouTube's own styles, which would otherwise win`() {
+ assertTrue(YOUTUBE_FIT_PLAYER_JS.contains("!important"))
+ }
+
+ @Test
+ fun `the fit script covers the video element and the containers above it`() {
+ val js = YOUTUBE_FIT_PLAYER_JS
+
+ assertTrue(js.contains("html,body"))
+ assertTrue(js.contains("#movie_player"))
+ assertTrue(js.contains("video{height:"))
+ }
+
+ @Test
+ fun `the fit script does nothing when the page has no height to copy`() {
+ assertTrue("must not pin the player to zero", YOUTUBE_FIT_PLAYER_JS.contains("if (!h) { return; }"))
+ }
}