Skip to content

scenes: document picture-in-picture + pseudo-fullscreen fallback - #24

Merged
karngyan merged 15 commits into
mainfrom
scenes-pip-fullscreen
Jul 21, 2026
Merged

scenes: document picture-in-picture + pseudo-fullscreen fallback#24
karngyan merged 15 commits into
mainfrom
scenes-pip-fullscreen

Conversation

@karngyan

@karngyan karngyan commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Adds two capabilities to the scenes entry (audio-driven React scene sequences):

Document picture-in-picture (Chromium desktop)

  • canPiP is capability-gated on documentPictureInPicture presence; the existing PipButton picks it up with zero UI changes. Safari/Firefox/mobile see no button.
  • Muted-mirror architecture: the master iframe never moves and never reloads. It keeps playing in the main tab behind an opaque placeholder, staying the single source of truth for audio and state. enterPiP opens a documentPictureInPicture window and mounts a second, muted mirror iframe (same src) whose only job is visuals: it initializes at the master's position via the new optional startTime field on kino:init (additive, protocol version stays 1), plays muted (always allowed by autoplay policy, no user-activation dependence), and gets drift-corrected via seek whenever it strays more than 0.3s from the master. play/pause/seek/rate fan out to both. Enter and exit are both seamless: audible playback is never interrupted in either direction.
    • An earlier design moved the master iframe into the pip window; the cross-document reload made resume activation-dependent (the pip window never has user activation to delegate), surfacing as playback resuming paused. The spec addendum documents the change.
  • The host clock element is a hidden playsinline muted-capable <video> (Chrome's muted-autoplay exemption does not cover <audio>).
  • Pip window gets a kino-styled overlay (play/pause, time readout, progress line, caption cue mirror with aria-live, auto-hide while playing); the inline placeholder is clickable and, while pip is active, the gesture layer routes a single click to exitPiP instead of play/pause.
  • One pagehide handler owns every exit path (button, placeholder, browser close, destroy()); enterPiP is guarded against double-invoke and mid-request teardown.

Pseudo-fullscreen fallback

iPhone-class WebKit (Safari and iOS Chrome) has no Element.requestFullscreen. The scenes provider now falls back to a pseudo-fullscreen presentation: fixed-position wrapper at 100dvh with safe-area padding and scroll lock, custom kino chrome stays on screen. Native fullscreen is still preferred wherever it exists; restore is guaranteed on exit and destroy().

Security note

The host accepts commands sourced from window.parent.opener (the pip mirror's parent is the pip window while commands originate from the main tab, the pip window's opener). With parentOrigin locked down (production) the origin check runs first on every message, so no new principal is admitted. With the "*" dev default this extends playback-command trust (play/pause/seek only) to whatever window opened the embedding tab.

Embedder note

While pip is active the mirror fetches the sequence a second time (same tokened src). Playback tokens must remain reusable within their TTL for pip to work.

Verification

  • 188 vitest tests (26 files), typecheck, lint, build, format all green.
  • Live in desktop Chrome (real docPiP window): master audio uninterrupted through enter, mirror playing muted with ~130ms natural drift, overlay controls and time tracking the master, placeholder click returns with zero blip.
  • Outstanding: iPhone fullscreen check on a real device after release.

Changeset included (minor, releases as 0.7.0).

karngyan added 12 commits July 21, 2026 17:40
- GestureLayer is pip-aware: while pip is active a single click exits pip
  immediately instead of the deferred play/pause toggle, and double-click
  fullscreen is unreachable. The gesture layer sits over the inline
  placeholder (z-index 1 over the z-index 0 video host), so without this
  the placeholder's own click handler never fired inside kino chrome.
- enterPiP sets 100% height on the pip document's html and body so a
  standards-mode auto-height body cannot collapse the percentage-height
  iframe to 150px.
- Docs: replace "lesson" wording in the pip/fullscreen spec with
  sequence/embedder phrasing.
- Host guards the kino:play command's play() promise like the init path.
- Pip overlay cue div announces via aria-live=polite; play/pause glyphs
  carry U+FE0E so they render as text, not emoji.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
kino cc8c7ee Commit Preview URL

Branch Preview URL
Jul 21 2026, 01:00 PM

@karngyan
karngyan merged commit ad29d1f into main Jul 21, 2026
5 checks passed
@karngyan
karngyan deleted the scenes-pip-fullscreen branch July 21, 2026 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant