feat(#109): implement PiP close_button on Android (was a documented no-op)

The server forwarded close_button (routes/pip.js) and it's in openapi.yaml, but
no player rendered it — Tizen deferred "close-button focus" as non-MVP, the web
player has none, and Android's PipOverlay never read the flag. So the documented
field did nothing on any device.

Implement it on Android: when close_button:true, a tappable ✕ floats at the box's
top-right in a FrameLayout wrapper that is a SIBLING of the box — so it isn't
clipped by the box outline or dimmed by the overlay opacity. Tapping it clears
THIS overlay (id-matched via the captured token). Only the ✕ is clickable; the
rest of the full-screen pipLayout stays touch-transparent, so taps elsewhere
fall through to the playing content (no input regression).

Verified on the emulator over live YouTube: the ✕ renders at the corner, and
tapping it removes the overlay while the video keeps playing.

Parity note: web/Tizen players still don't implement close_button; D-pad focus
of the ✕ on non-touch TV hardware is intentionally not wired (MVP = touch/pointer,
matching the Tizen focus deferral).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
ScreenTinker 2026-06-19 14:45:14 -05:00
parent 10abb3b35f
commit 255d8c0600
2 changed files with 41 additions and 1 deletions

View file

@ -134,7 +134,36 @@ class PipOverlay(
else -> { lp.rightMargin = mx; lp.topMargin = my; Gravity.TOP or Gravity.END } // top-right
}
pipLayout.addView(box, lp)
// Optional close button (close_button:true). Render a tappable ✕ floating at the
// box's top-right corner; tapping clears THIS overlay (id-matched). It lives in a
// FrameLayout wrapper that is a SIBLING of the box — so it isn't clipped by the
// box outline and isn't dimmed by the box's opacity. Only the button is clickable;
// the rest of pipLayout stays touch-transparent so taps fall through to content.
val attach: View = if (p.optBoolean("close_button", false)) {
val token = current
FrameLayout(context).apply {
addView(box, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT))
val d = dm.density
addView(TextView(context).apply {
text = "" // ✕
setTextColor(Color.WHITE)
textSize = 16f
gravity = Gravity.CENTER
background = GradientDrawable().apply {
shape = GradientDrawable.OVAL; setColor(Color.argb(150, 0, 0, 0))
}
isClickable = true
contentDescription = "Close"
setOnClickListener { clear(token) }
}, FrameLayout.LayoutParams((28 * d).toInt(), (28 * d).toInt()).apply {
gravity = Gravity.TOP or Gravity.END
val m = (6 * d).toInt(); topMargin = m; rightMargin = m
})
}
} else box
pipLayout.addView(attach, lp)
pipLayout.visibility = View.VISIBLE
// current was set above (before media build) so loadImageInto's token matches.

View file

@ -162,6 +162,17 @@ over both a static image and live YouTube.
needed — web PiPs never went through the broken image path.
- title + `background_color` box — paints above the video (the original cause-1
fix).
- **`close_button: true`** — the server already forwarded this flag
(`routes/pip.js`) and it's in `openapi.yaml`, but no player rendered it (Tizen
deferred "close-button focus" as non-MVP; the web player has none). Implemented
on Android: a tappable ✕ floats at the box's top-right (a sibling of the box, so
it isn't clipped by the outline or dimmed by `opacity`) and clears THIS overlay
(id-matched) on tap. Only the ✕ is clickable; the rest of the full-screen
`pipLayout` stays touch-transparent so taps fall through to the content. Verified
on the emulator — tapping it removed the overlay and the video kept playing.
Parity note: the web/Tizen players still don't implement `close_button`; D-pad
focus of the ✕ on non-touch TV hardware is intentionally not wired (MVP =
touch/pointer only, matching the Tizen focus deferral).
## If the magenta box is STILL hidden over YouTube on the test device