summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-10-23 10:34:01 +0000
committerPeter Stone <thepeterstone@gmail.com>2026-10-23 10:34:01 +0000
commit04408cceea17f3e32b6e88edf4da94b26385b026 (patch)
tree53de57968c54e09501319a7abe3415073abf504e /app
parentd4538340fdbe7016a7b3a2eb040c7b133df19efe (diff)
Give YouTube's player a height, so the picture has somewhere to go
Peter, three builds running: audio plays, YouTube's title, controls and scrubber thumbnails all draw, and where the picture belongs there is a blank rectangle. None of my guesses were right. It is not the embed, not the window, and not the surface. It is layout. YouTube's player sizes itself with `body { position: absolute; height: 100% }`, and in this WebView percentage heights resolve against a containing block of zero height. Measured in the running app: window.innerHeight correctly reports 214, while 100vh and every percentage height resolve to 0. The body collapsed, the player chain under it collapsed, and the video element was laid out 380x0. That explains the whole symptom, including the part I kept failing to account for. The page, the chrome and the stream were all healthy -- the picture simply had nowhere to go, and what showed through the zero-height player was YouTube's own white <html> background. So the fix pins the player chain to the pixel height the page does know and dispatches a resize so YouTube's own code re-measures. Verified on a device, with the shipped code and nothing injected by hand: the video element goes 380x0 -> 380x214 and the video plays. Two things ruled out along the way, recorded so they are not retried: hardware-accelerating the dialog's window changed nothing, and nor did moving the player out of a dialog into an in-window overlay. An A/B against BrowserScreen looked decisive for the window and was not -- that WebView differs in layout too, and layout was the real variable. This treats the symptom. Why the viewport has no height is below this app, in WebView. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'app')
-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
3 files changed, 74 insertions, 0 deletions
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; }"))
+ }
}