Screenshot from the server instead of the browser
getDisplayMedia costs a picker, a permission, and a "Sharing this tab" bar parked over the projector all night. None of it can be suppressed from the page — the indicator is a security surface with no API, and the Chromium switches that hide it were no help here. The server runs on the machine the screen is plugged into, so it can take the screenshot itself. server/screen.js drives grim for one region and /shot returns the finished crop, which means grab() hands back a URL for an <img> rather than a data URL off a canvas: same synchronous contract, so the animations are untouched, and no tainting to work around either. Coordinates line up because a fullscreen page's CSS pixels are the compositor's logical pixels, which is the space grim's -g takes. The monitor layout comes from hyprctl, so a projector is measured against itself rather than the laptop panel, and -s pins the scale to that monitor: it keeps a mixed-DPI desk deterministic, and it keeps grim native, where resampling costs 440ms against 35ms. Needs a wlroots compositor and grim on PATH; without them the route reports 501 and the player falls back to getDisplayMedia, probed at load because that call cannot be reached through an await without losing transient activation. Also skipped when the page is not on localhost, where the server would be photographing its own screen rather than the viewer's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9v2Xoa6eoeGYpK6gp95k9
This commit is contained in:
@@ -98,27 +98,39 @@ how you look at a single animation without waiting for the cycle to reach it.
|
||||
**Auto** (the default) hands selection back to the shuffle bag. Switching takes
|
||||
effect immediately; while pinned, track changes leave the animation running.
|
||||
|
||||
**The corners and trail animations ask to share your screen.** The decks are
|
||||
**The corners and trail animations photograph the frame.** The decks are
|
||||
cross-origin YouTube iframes, so nothing on the page can read their pixels —
|
||||
capturing the screen and cropping out the frame is the only way to photograph
|
||||
what is playing. The prompt appears once, on the first click of **Start**, which
|
||||
also puts the page into fullscreen.
|
||||
a screenshot is the only way to capture what is playing.
|
||||
|
||||
The crop assumes the captured surface *is* the viewport, so what you pick in the
|
||||
picker matters:
|
||||
The server takes it. It runs on the same machine as the screen, so it can grab
|
||||
the region itself with [`grim`](https://sr.ht/~emersion/grim) and hand back the
|
||||
finished crop, which the player drops straight into an `<img>`. No picker, no
|
||||
permission prompt, and no "Sharing this tab" bar parked over the party. This
|
||||
needs a wlroots compositor — Hyprland, Sway, river — and `grim` on `PATH`;
|
||||
`server/screen.js` reads the monitor layout from `hyprctl` so the right screen
|
||||
is measured when a projector is plugged in.
|
||||
|
||||
Without `grim`, the player falls back to `getDisplayMedia` and asks to share
|
||||
your screen on the first click of **Start**. That path costs a picker and a
|
||||
sharing indicator, and what you choose in the picker matters:
|
||||
|
||||
- **Chrome/Chromium** preselects *This tab* — take it, and the mapping is exact.
|
||||
- **Firefox** has no tab capture; the picker offers only windows and screens.
|
||||
Choose the **entire screen**. Fullscreen is what makes it line up, so if you
|
||||
leave fullscreen, press <kbd>F11</kbd> to get screenshots back.
|
||||
Choose the **entire screen**.
|
||||
- **A single window** cannot be used: it includes the tab and URL bars, and
|
||||
Wayland gives the page no window coordinates to subtract.
|
||||
|
||||
Declining is fine: the frame still parks in its corner and still glides, just
|
||||
without the screenshots. The prompt is skipped entirely on a plain-HTTP origin
|
||||
(i.e. when you open the player from another machine over the network), for the
|
||||
same reason. Whenever screenshots are off or misaligned, the player says why in
|
||||
the bottom-left corner and logs it to the console.
|
||||
**Either way the page has to be fullscreen**, which is why **Start** requests it:
|
||||
only then does a CSS pixel line up with a screen pixel. Leave fullscreen and the
|
||||
screenshots pause until you press <kbd>F11</kbd>. The exception is Chrome's tab
|
||||
capture, which is the viewport by definition and lines up in a window too.
|
||||
|
||||
Going without is fine: the frame still parks in its corner and still glides,
|
||||
just unphotographed. That is what happens when you open the player from another
|
||||
machine over the network — the server would be screenshotting its own screen
|
||||
rather than yours, so the player does not ask it to, and a plain-HTTP origin has
|
||||
no `getDisplayMedia` either. Whenever screenshots are off or misaligned, the
|
||||
player says why in the bottom-left corner and logs it to the console.
|
||||
|
||||
## Adding animations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user