mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
4 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9face2fdd4 |
Show a panel's IPv6, and size the pairing code to the screen it is on
Two field-reported gaps, unrelated except that both are about being able to read something off a screen. A PANEL'S IPv6 WAS NEVER COLLECTED, LET ALONE SHOWN. DeviceInfo.getLocalIp() filters to Inet4Address, so a v6-only panel reported no address at all and the dashboard rendered a dash for a screen that was perfectly reachable. It now reports both stacks in their own fields: a dual-stack panel genuinely has two addresses and either may be the one you need, so collapsing them into one column would make it mean "whichever interface enumerated first". Link-local (fe80::/10) is deliberately excluded. Every interface has one, they tend to enumerate first, and none can be dialled without also knowing the zone index — so admitting them would fill the field with a string nobody can paste anywhere and hide the address that works. Any %iface suffix is trimmed for the same reason. The 45-char cap the writer already applied is exactly the longest legitimate IPv6 text form, so it needed no change. The dashboard card renders only when a panel actually has a v6 address, rather than showing an empty row to the overwhelmingly v4 fleet. THE PAIRING CODE DID NOT SCALE, WHICH IS WORST WHERE IT MATTERS MOST. Every size on the pre-playback screens was a hard-coded pixel value. A CSS pixel covers a quarter of the screen area on a 4K panel that it does on 1080p, and a sixteenth on 8K — so the 72px code that fills a 1080p screen is a smudge on the 4K wall it was installed on, which is where signage actually goes. What has to stay constant is ANGULAR size, so the root font size is now viewport-proportional and everything on those screens is a rem against it. The code holds 6.67% of screen height at every resolution: 72px at 1080p — bit for bit what it renders today, so nothing changes for the existing fleet — 144px at 4K, 288px at 8K. Verified in a browser rather than by arithmetic: at a 1409px viewport the root computes to 13.0473px, which is 0.926vmin to four decimals. vmin, not vw, because portrait-mounted panels are common here and vw would render a 1080x1920 screen at half size. Clamped at both ends so the dashboard's preview iframe stays legible instead of microscopic and an ultrawide does not get silly. Applied to the web player (which BrightSign also runs) and to Tizen, where a 1920x1080 logical viewport makes it arithmetically identical to the values it replaces — the point being the panels where it is not. A test asserts the scaling cannot reach playback content: the whole safety argument is that only the chrome uses rem, and a stage or zone rule adopting it would start resizing CONTENT, which is a worse bug than the one being fixed. Android is untouched — its pairing code already autosizes within a dp-scaled layout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014skWYXJUWhF73EvNPgB2AS |
||
|
|
965920cd17
|
PiP overlay MVP: push image/web overlays to a device or group (#109) (#127)
* PiP overlay MVP: push image/web overlays to a device or group (#109) Implements the #109 MVP from docs proposal: a floating overlay PUSHED to a device or group in real time, rendered above the playlist without disturbing it. Scope is the MVP only — video/RTSP, MQTT, offline-queue, and the priority/stacking system are deferred to follow-up PRs as the proposal specifies. Protocol (/device socket, player-agnostic): - device:pip-show { pip_id, type:image|web, uri, position, width, height, duration, title?, title_color?, background_color?, opacity?, border_radius?, close_button? } - device:pip-clear { pip_id? } The player fetches uri itself (same trust model as remote_url content; server never proxies). type:web is full-trust by design, hence the 'full' token scope. Server (server/routes/pip.js, new; mounted in config/api-surface.js PUBLIC_ROUTERS): - POST /api/pip and POST /api/pip/clear + DELETE /api/pip, all requireScope('full'). - Resolves device_id to a device OR a group, expands a group to members, and emits per-device — reusing the group command route's room-size online check and {device_id, name, status: sent|offline} result shape. Generates pip_id. - Validates type/position allowlists, uri http(s), numeric bounds on width/height/duration/opacity/border_radius, colors via the existing VALID_COLOR (#RRGGBB; transparency is the separate opacity field). - Workspace-isolated: every target query is scoped to req.workspaceId, so a token bound to workspace A can't address workspace B (404). Offline devices are reported, never queued (PiP is ephemeral). Player overlay layer (Tizen; tizen/js/pip-overlay.js, new): - A #pip sibling ABOVE #stage that PlaylistPlayer/ZoneRenderer never touch. - applyOrientation now applies the SAME transform to #pip as #stage, so corner positions track the visible CONTENT in all four orientations. - image -> <img>, web -> <iframe> (muted by default: empty allow= denies autoplay), sized/positioned/styled per payload, optional title bar. - Single overlay slot, last-show-wins; duration timer (0 = until cleared); pip-clear (id-aware) or timer tears down; teardown wrapped so a malformed payload can't wedge the layer. Reports show/clear over device:log (tag 'pip'). Dashboard: a minimal "Send overlay" / "Clear overlay" tester on the device-detail controls (device/group via the open device, type, uri, position, duration), calling POST /api/pip through the api helper. Tests (server suite green, 161/161): - api.test.js: PiP tier — authz (read/write 403, full passes), workspace isolation (wsA token -> wsB device 404), payload validation, device + group targeting, clear; plus the PUBLIC_ROUTERS snapshot-firewall updated for /api/pip. - pip-overlay.test.js: loads the real player.js + pip-overlay.js in a vm with a DOM shim; proves the overlay shows, auto-dismisses on the duration timer, and never changes the playlist signature / touches #stage; web->iframe, last-show-wins, id-aware clear, malformed-payload safety. Not in this PR (intentional): - Android player overlay — fast-follow. Protocol + server are player-agnostic; the Android layer (an overlay View above the player, orientation-matched to MainActivity's rootView rotation) is the same shape and lands next. - OpenAPI docs for POST /api/pip — the contract test's scope heuristic only treats 'command' paths as full-scope, so documenting a full-scope non-command route there needs that heuristic extended first; deferred with the docs item (proposal §8.6). - video/rtsp types, MQTT, offline queue-on-reconnect, priority/stacking, arbitrary (x,y)/selector positioning (proposal §6). Refs #109 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * PiP overlay: add Android + web players (#109) Extends the #109 PiP MVP to the other two players so the protocol (device:pip-show / device:pip-clear) is honored fleet-wide, not just on Tizen. No server/protocol changes — the route and socket messages are player-agnostic; these are the two missing surfaces. Web player (server/player/index.html): - New #pipContainer layer above #playerContainer, pointer-transparent, that the playlist render never touches. The same orientation transform is applied to it as to #playerContainer (extended to also reset width/height on landscape so a portrait->landscape switch realigns), so corner positions track the visible content. - Inline PiP logic mirroring tizen/js/pip-overlay.js: image -> <img>, web -> <iframe> (muted by default via empty allow=), position/size/bg/opacity/radius/title, single slot last-show-wins, duration timer (0 = until cleared), id-aware clear, wrapped teardown. - device:pip-show/clear handlers; reports show/clear over device:log (tag "pip"). Android player: - activity_main.xml: a pipLayout FrameLayout as the LAST child of rootLayout — it draws above the content AND inherits rootView's orientation rotation/translation, so corner positioning is orientation-matched for free. - PipOverlay.kt (new): builds the overlay box into pipLayout. image -> ImageView (decoded off-thread via ImageLoader, dropped if torn down mid-decode); web -> WebView with mediaPlaybackRequiresUserGesture=true (mute-by-default). Gravity-based corner/center placement with a 4% inset, GradientDrawable bg + corner radius, alpha=opacity, optional title bar. Single slot last-show-wins; duration timer; id-aware clear; teardown wrapped and also run on activity destroy (WebView cleanup). - WebSocketService: onPipShow/onPipClear callbacks + safeOn handlers posted to the main thread (they build Views) + a sendLog(tag, level, message) emitter for device:log. - MainActivity: instantiate PipOverlay (log -> wsService.sendLog("pip", ...)), wire the callbacks, tear down on destroy. Verified: Android assembleDebug builds clean; web player inline JS parses; server suite still 161/161 (no server changes this commit). Not yet validated on real hardware — four-orientation corner positioning mirrors the player container/rootView transform but should be eyeballed on a panel. Refs #109 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9c4b48800f |
Tizen player 1.9.1-beta3: bug fixes, multi-zone layouts, video walls
Brings the Tizen TV player to parity with the other players: closes the five Tizen issues Bold Media Group filed (#118-#122) and adds the two larger renderer features it was still missing. Fixes (#118-#122) - #118 Sticky "Not authenticated" banner. On TV sleep/wake the socket reconnects and a heartbeat could land on the fresh, not-yet-registered socket; the server rejected it and the old handler painted a permanent banner AND dropped the saved credentials, forcing a re-pair. Heartbeats are now gated on a per-connection authenticated flag (true only between device:registered and disconnect/auth-error), the heartbeat stops on connect/disconnect/auth-error, the banner clears on device:registered, and the auth-error toast is non-sticky. - #119 app_version stuck at 1.0.0. Resolved at runtime from config.xml via the Tizen application API, with a fallback constant that build-wgt.sh stamps from config.xml. - #121 Remote commands. Added a device:command handler (refresh/launch/screen_on/ screen_off; honest no-op toasts for update/reboot/shutdown, which need B2B/MDM privileges a sideloaded app lacks). Removed the dead device:reload listener. - #120 Dashboard preview. Added device:screenshot-request + remote-start/remote-stop. Images capture; video/YouTube fall back to a status card (TV hardware video plane and cross-origin iframes can't be read into a canvas). - #122 Updates/boot. Documented the real paths (re-sideload or URL Launcher/MDM refresh; display-level kiosk/boot settings) since a sideloaded .wgt has no in-app OTA or config.xml autostart. Multi-zone layouts (Android parity) - New ZoneRenderer ports the Android ZoneManager: zones positioned by percent geometry with z_index/fit_mode/background, assignments grouped by zone_id (unassigned content goes to the first zone), each zone rotating independently with the same per-item schedule gating (#74/#75). app.js selects the renderer from payload.layout; single-zone playback is unchanged. Video walls (web-player parity; Android has none) - New WallController mirrors the web player: when payload.wall_config is present the stage is positioned (vw/vh) as this screen's slice of the wall. The leader plays normally and broadcasts wall:sync at 4Hz; followers hold the leader's item, align index, and lock their video to the leader's clock with a latency-compensated drift controller (hard-seek past 0.3s, gentle +/-3% playbackRate nudge past 0.05s), and request an immediate position on (re)connect via wall:sync-request. Per-tile rotation is not applied yet (matches the web player). Wall emits are gated on auth + connection so a pre-register tick can't trip device:auth-error. Not ported: video-wall per-tile rotation, plus the minor Android-only reporting events (device:playback-state, device:log) and the N/A offline-cache events (device:content-ack/content-delete). None affect on-screen playback. Verified: JS syntax + headless unit tests of zone grouping/geometry and wall leader/follower + drift logic. NOT yet validated on Tizen hardware - multi-screen video sync in particular needs a real wall to tune. |
||
|
|
0cfa09046c |
feat(tizen): Samsung Tizen TV web player (.wgt)
Ports the ScreenTinker player to a Tizen TV / signage web app, speaking the SAME /device socket.io protocol as the Android player — no server changes; a Tizen display pairs from the same dashboard. - app.js: device protocol client — register (pairing_code | device_id+token), device:registered/paired/unpaired/playlist-update, 15s heartbeat, keep-awake. Always reaches the server prompt until the display is actually paired; a saved-but-unreachable server falls back to the prompt (no blank screen); BACK returns to it. - player.js: fullscreen single-zone renderer — image (duration timer), video (play-to-end + loop), YouTube (iframe embed), widget (iframe render endpoint). - config.xml: Tizen TV manifest; build-wgt.sh packages (signs if Tizen CLI present, else unsigned); README covers URL-Launcher and signed-.wgt deploy. Validated: headless protocol test vs the live server passed end-to-end (register -> pair -> reconnect-auth -> playlist(2) -> content 200); loads + renders in Chromium with no JS errors. Not yet ported (fullscreen single-zone covers most signage): multi-zone, video walls, screenshots, remote control, self-OTA. .wgt is a build artifact (gitignored). |