Add picture-in-picture to the video player #8

Merged
kyuno merged 2 commits from feature/picture-in-picture into master 2026-09-16 15:50:06 +02:00

2 commits

Author SHA1 Message Date
dfd109fc50 Merge remote-tracking branch 'origin/master' into feature/picture-in-picture
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
# Conflicts:
#	README.md
#	docs/architecture.md
2026-09-16 10:16:16 +02:00
d6a65ce8b7 Add picture-in-picture to the video player
Some checks failed
Publier l'image Docker / test (pull_request) Failing after 4s
Publier l'image Docker / publish (pull_request) Has been skipped
Asked for directly, mobile in particular: a phone has no second window
to put this app in the way a desktop might, so the standard browser
Picture-in-Picture API - popping the video out into a small, OS-managed
floating window - is the one way to keep watching while switching to
another app. Standard requestPictureInPicture()/exitPictureInPicture(),
not anything WebKit-prefixed - confirmed directly in a real Chromium
build that pictureInPictureEnabled/requestPictureInPicture/
exitPictureInPicture are all present, which is what actually matters
for Android Chrome, the mobile browser this was asked for.

New #pipBtn, right next to #fullscreenBtn - both are "how is this
displayed" toggles, so they share the same .fsBtn sizing, and a new
.fsBtn[aria-pressed="true"] rule gives it the same "clearly on" orange
treatment #castBtn already has (its own rule rather than combined onto
.iconBtn, which disagrees with .fsBtn on padding/border-radius/
background). Feature-detected once at mount, not live the way
#castBtn's own availability listener is - browser support for this
doesn't appear/disappear mid-session the way a Chromecast on the
network does.

State (aria-pressed) syncs via the video's own enterpictureinpicture/
leavepictureinpicture events rather than the button's click handler
alone, so it also reflects the browser's native PiP window being
closed directly. No cleanup needed on navigation: browsers already
close an active PiP window once its source video leaves the document,
and every route change here already destroys and replaces <video> -
the deliberate opposite of this app's own choice for casting, which
keeps a Chromecast session alive after navigating away since it has an
existence independent of the local video element.

Verified: full typecheck/build (client only - no server involved).
Confirmed directly in a real browser, not just type-checked:
pictureInPictureEnabled is true, and running the exact show/hide +
click-handling logic against a real (sourceless) video correctly shows
the button and catches the resulting InvalidStateError from
requestPictureInPicture() without it becoming an uncaught exception.
Not verified: an actual floating PiP window on a real mobile browser
playing a real Emby stream - no device/login reachable from this
sandbox.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 10:12:59 +02:00