mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-29 09:23:16 -06:00
The PiP overlay (#109) returned sent:1 and showed its title in `uiautomator dump`, but nothing painted on screen while YouTube was playing. By elimination (YouTube-specific, landscape so no off-screen transform, real on-screen bounds in the dump) the cause is surface occlusion: pipLayout sat as the last child of rootLayout — the SAME compositing band as R.id.youtubeWebView — so the playing video surface drew over it. Fix (task option 1a): reparent pipLayout out of rootLayout to the window content (android.R.id.content) as a top-level sibling drawn after rootLayout, so it composites above the WebView. MainActivity.mirrorTransformToPip() copies rootView's orientation/wall transform onto it so corner positions still track the rotated content (web/Tizen parity). show() also bringToFront()+ requestLayout()+invalidate() on attach (covers the cause-3 measure/visibility path). Remote-view screenshots now capture the content root so the PiP is still included. Instrumentation (Phase 1, default OFF): PipOverlay.pipDebug paints a solid magenta box + border with media on top (box paints even if media never loads) and logs box/pipLayout/rootView/youtubeWebView geometry over device:log tag "pip"; loadImageInto also logs on success. Toggled via device:command {type:"pip_debug"} (routed through MainActivity.onCommand). Server: POST /api/pip and the clear handler log one concise [pip] dispatch line (target + sent/offline) so journalctl shows PiP activity. Validated end-to-end on an emulator (pixel10/API34) paired to an isolated local server with YouTube playing: no crash, the PiP box composites above the live video frame (center + top-right), clear removes it, and the portrait transform mirror rotates the overlay with the stage (no off-screen). The Fire TV hardware-overlay punch-through still needs real hardware (emulator composites video inline); pipDebug + docs/109-android-pip-visibility.md cover that. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
152 lines
7.4 KiB
Markdown
152 lines
7.4 KiB
Markdown
# #109 — Android PiP overlay not painting over YouTube
|
||
|
||
## Symptom
|
||
|
||
`POST /api/pip` returns `sent: 1`, the overlay's title text appears in
|
||
`uiautomator dump` (so the view IS attached, laid out, and on-screen in the
|
||
accessibility tree), but **nothing paints on the panel**. The repro is while
|
||
**YouTube** content is playing (`R.id.youtubeWebView`).
|
||
|
||
The PiP title and its media are siblings in one box, so "title in the dump but
|
||
nothing on screen" means the **whole box is attached-but-not-painting**, not a
|
||
media-only failure.
|
||
|
||
## The three candidate causes
|
||
|
||
| # | Cause | What the magenta-box instrumentation shows |
|
||
|---|-------|--------------------------------------------|
|
||
| 1 | **Surface occlusion** — the YouTube WebView's hardware video plane composites *above* the in-tree overlay | magenta box visible over a static image but **not** over YouTube |
|
||
| 2 | **Orientation transform** — `rootView`'s rotation/translation pushes the box off-screen | box `getGlobalVisibleRect()` empty / outside the 1920×1080 panel |
|
||
| 3 | **Measure / visibility** — `pipLayout` not laid out, 0-size, or `GONE` on attach | box not shown / 0-size / `pipLayout` `childCount==0` |
|
||
|
||
## Phase 1 — Instrumentation (shipped, default OFF)
|
||
|
||
A `pipDebug` flag (`PipOverlay.pipDebug`, default `false`) is toggled over the
|
||
**existing** `device:command` transport — no new transport invented:
|
||
|
||
```
|
||
device:command { "type": "pip_debug", "payload": { "enabled": true } }
|
||
```
|
||
|
||
When on, `PipOverlay.show()`:
|
||
|
||
- paints the box **solid magenta** (`#CCFF00FF`) with an 8px magenta border and
|
||
renders the media on top, so the **box paints even if the media never loads**;
|
||
- posts a one-shot Runnable that logs, over `device:log` tag **`pip`**:
|
||
- `box` width/height, `getGlobalVisibleRect()`, `isShown`
|
||
- `pipLayout` width/height/visibility/childCount + its index in its parent
|
||
- `rootView` rotation / translationX / translationY / scaleX / `isHardwareAccelerated`
|
||
- `youtubeWebView` visibility + `getGlobalVisibleRect()`
|
||
- `loadImageInto` also logs on **success** (bitmap w/h), not just failure.
|
||
|
||
`pipDebug` is left present and default-false.
|
||
|
||
## Phase 2 — Reproduce
|
||
|
||
Enable remote debug logging from the dashboard (so `device:log` is forwarded),
|
||
then enable the PiP debug flag, then fire a PiP under each content type and read
|
||
the `pip`-tagged lines.
|
||
|
||
**(a) PiP over a static image**
|
||
|
||
1. Assign a single still image to the device; let it display.
|
||
2. `device:command {type:"pip_debug",payload:{enabled:true}}`.
|
||
3. `POST /api/pip {device_id, type:"image", uri:"https://…/x.png", position:"top-right", duration:30}`.
|
||
4. Capture the `pip dbg …` lines + a screenshot.
|
||
|
||
**(b) PiP over YouTube** (the failing repro)
|
||
|
||
1. Assign a YouTube item; let it play in `R.id.youtubeWebView`.
|
||
2. (debug already enabled.)
|
||
3. Same `POST /api/pip` as above.
|
||
4. Capture the `pip dbg …` lines + a screenshot.
|
||
|
||
Decision table (compare a-vs-b):
|
||
|
||
- magenta box visible over the image but **NOT** over YouTube → **(1) surface occlusion**
|
||
- box `globalRect` empty / off the 1920×1080 panel → **(2) orientation**
|
||
- box not shown / 0-size / not laid out → **(3) measure/visibility**
|
||
|
||
> Note: on-device capture must be done on the real signage hardware (Fire TV /
|
||
> Android TV). The WebView hardware-video-overlay behaviour that drives cause (1)
|
||
> is device- and WebView-version-specific and does **not** reproduce on a stock
|
||
> emulator, so it cannot be captured from a CI/dev box with no device attached.
|
||
|
||
## Which cause — and the fix
|
||
|
||
By elimination the symptom points at **(1) surface occlusion**:
|
||
|
||
- It is **YouTube-specific** (a WebView playing HTML5 video). An orientation (2)
|
||
or measure (3) fault would fail over images too, but the overlay is only
|
||
reported broken over YouTube.
|
||
- The repro is **landscape** (the default orientation → `rotation = 0`, no
|
||
translation), so the box cannot be transformed off-screen → not (2).
|
||
- The title shows with **real on-screen bounds** in `uiautomator dump`, so the
|
||
box is laid out at non-zero size and `pipLayout` is `VISIBLE` → not (3).
|
||
|
||
That leaves the video surface compositing above the in-tree overlay.
|
||
|
||
### Fix (cause 1) — file/line
|
||
|
||
`pipLayout` previously lived as the **last child of `rootLayout`**, i.e. in the
|
||
**same compositing band** as `R.id.youtubeWebView`; the WebView's playing video
|
||
surface drew over it. The fix moves the PiP layer to a **top-level view above the
|
||
WebView** (the task's option 1a):
|
||
|
||
- **`MainActivity.onCreate`** (`android/app/src/main/java/com/remotedisplay/player/MainActivity.kt`)
|
||
reparents `R.id.pipLayout` out of `rootLayout` up to the window content
|
||
(`android.R.id.content`), as a sibling drawn **after** `rootLayout` → it
|
||
composites above the WebView.
|
||
- **`MainActivity.mirrorTransformToPip()`** copies `rootView`'s current size +
|
||
rotation/translation/scale onto `pipLayout` after every transform change
|
||
(`applyOrientation` / `applyWallTransform`), so corner positions still track the
|
||
rotated content — mirroring how the web/Tizen players apply the same transform
|
||
to `#pip` as to `#stage`.
|
||
- **`PipOverlay.show()`** (`…/player/PipOverlay.kt`) raises the layer and forces a
|
||
layout/redraw on attach (`bringToFront()` + `requestLayout()` + `invalidate()`),
|
||
which also covers the cause-(3) measure/visibility path.
|
||
- The remote-view screenshot source moved from `rootView` to `captureRoot`
|
||
(the window content) so the reparented PiP is still captured.
|
||
|
||
### Server dispatch logging
|
||
|
||
`POST /api/pip` and the clear handler (`server/routes/pip.js`) now log one
|
||
concise `[pip] …` line each (target kind + id + sent/offline counts) so
|
||
`journalctl` shows PiP activity.
|
||
|
||
## Emulator validation (landscape + portrait)
|
||
|
||
The fix was exercised end-to-end on an Android emulator (pixel10, API 34) paired
|
||
to an isolated local server, with a YouTube item playing in
|
||
`R.id.youtubeWebView`:
|
||
|
||
- **No crash** — provisioning → `MainActivity` → playback ran clean; the reparent
|
||
+ `mirrorTransformToPip()` executed (`Applied orientation: landscape … / portrait
|
||
(rotation=90.0, swap=true)`).
|
||
- **PiP composites above the playing YouTube video** — a `POST /api/pip` box
|
||
(magenta via `background_color`) rendered on top of the live video frame
|
||
(center and top-right placements both correct, 4% inset honoured).
|
||
- **Clear** removed the overlay cleanly; the video kept playing.
|
||
- **Portrait** — the overlay rotated *with* the rotated stage and stayed inside
|
||
the frame (not off-screen), confirming the transform mirror.
|
||
- Server `[pip] show … 1 sent` / `[pip] clear (all) …` dispatch lines appeared.
|
||
|
||
Caveat (unchanged): the emulator's WebView composites video **inline**, so it
|
||
confirms the reparent renders correctly and doesn't regress, but it does **not**
|
||
reproduce the Fire TV / Android TV hardware-overlay punch-through that is the
|
||
strongest form of cause (1). That still needs the real signage device — use the
|
||
`pipDebug` magenta box there to confirm.
|
||
|
||
## If the magenta box is STILL hidden over YouTube on the test device
|
||
|
||
Then it is the stronger form of cause (1): the WebView places its video on a
|
||
**hardware overlay / `SurfaceView` plane** that no in-window view can beat.
|
||
Escalate (task options 1b/1c), keeping the first that works on the device:
|
||
|
||
- host the PiP box in a `SurfaceView` with `setZOrderMediaOverlay(true)` /
|
||
`setZOrderOnTop(true)`, or a small `WindowManager` panel sub-window; or
|
||
- when YouTube is active, render the PiP via the in-tree image path so no
|
||
competing WebView video surface is involved.
|
||
|
||
The `pipDebug` instrumentation stays in place to make that determination.
|