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
Owner

Summary

  • New #pipBtn next to #fullscreenBtn in the bottom bar, using the standard requestPictureInPicture()/exitPictureInPicture() API - confirmed present on a real Chromium build (relevant for Android Chrome, since this was asked for with mobile specifically in mind).
  • Feature-detected once at mount (hidden entirely when unsupported); state stays in sync with the browser's own PiP window via enterpictureinpicture/leavepictureinpicture, not just this button's own clicks.
  • Pure client-side change, no server involved.

Test plan

  • npm run typecheck / npm run build (client)
  • Confirmed live in a real browser: document.pictureInPictureEnabled is true, and the exact show/hide + click logic against a real video correctly shows the button and cleanly catches the resulting InvalidStateError (a sourceless test video) instead of 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

🤖 Generated with Claude Code

## Summary - New `#pipBtn` next to `#fullscreenBtn` in the bottom bar, using the standard `requestPictureInPicture()`/`exitPictureInPicture()` API - confirmed present on a real Chromium build (relevant for Android Chrome, since this was asked for with mobile specifically in mind). - Feature-detected once at mount (hidden entirely when unsupported); state stays in sync with the browser's own PiP window via `enterpictureinpicture`/`leavepictureinpicture`, not just this button's own clicks. - Pure client-side change, no server involved. ## Test plan - [x] `npm run typecheck` / `npm run build` (client) - [x] Confirmed live in a real browser: `document.pictureInPictureEnabled` is `true`, and the exact show/hide + click logic against a real video correctly shows the button and cleanly catches the resulting `InvalidStateError` (a sourceless test video) instead of 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 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-16 10:13:13 +02:00
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
d6a65ce8b7
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>
kyuno added 1 commit 2026-09-16 10:16:20 +02:00
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
dfd109fc50
# Conflicts:
#	README.md
#	docs/architecture.md
kyuno merged commit e65ba271fd into master 2026-09-16 15:50:06 +02:00
kyuno deleted branch feature/picture-in-picture 2026-09-16 15:50:06 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/watchparty#8
No description provided.