mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-29 09:23:16 -06:00
4 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
e2ff8f47b7 |
Tizen player: real Samsung B2B fleet control (#125), folding in #126
#123 already shipped a placeholder device:command handler (#121/#122): screen_off was a black overlay, reboot/shutdown a toast, update a "re-install" toast. This replaces that with the real control surface from #125, reconciled into the single handler #123 introduced (rather than landing a second, competing handler). - NEW tizen/js/device-control.js: window.STDeviceControl = { run, capabilities, backend }. Feature-detects webapis.systemcontrol.* (Tizen 6.5/7, sync/throws) then b2bapis.b2bcontrol.* (SSSP/Tizen 4, async), normalises both to Promises, re-probes each call. run() never rejects; resolves { ok, supported, action, note, reload }. Panel power: setPanelMute (mute ON = backlight OFF) -> setDisplayPanel/setPanelStatus fallback. reboot -> rebootDevice(); shutdown mutes the panel and notes SSSP has no true power-off; update/reload -> reload:true. - tizen/js/app.js: device:command now calls STDeviceControl.run and reports the outcome via reportCmd (device:log tag=command -> dashboard:device-log, plus a structured device:command-result), reloading ~1.2s later on result.reload. screen_off falls back to the existing black overlay (showScreenOff) when no B2B surface exists; screen_on/launch still clear the overlay + keepAwake. Dropped the now-dead tryPowerControl. reportCapabilities() runs on device:registered so the dashboard sees the backend ("none" on web/URL-Launcher/consumer TV). - tizen/config.xml: partner-level b2bcontrol + systemcontrol privileges (ignored, not fatal, on unsigned/URL-Launcher/web/consumer builds). - tizen/index.html: load $WEBAPIS/webapis.js + $B2BAPIS/b2bapis.js before the app scripts (404 harmlessly off-hardware) and device-control.js before app.js. - tizen/README.md: rewrote the remote-control table for real B2B control + a partner-signing caveat; added device-control.js to the file list. Supersedes PR #126 (feat/tizen-device-command-125), which targeted main unaware that this branch already had a device:command handler. Verified: node --check on both JS files; config.xml well-formed (xmllint). Not yet validated on a real SSSP panel — the control surface only takes effect on a partner-signed .wgt (backend reports "none" on the dev/URL-Launcher build). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2ccf3264a9 |
feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire)
Each playlist item can carry schedule blocks (active days, start/end time-of-day, optional start/end dates). An item plays when the screen's local "now" matches at least one block; an item with no blocks always plays. #74 covers time-of-day/day-of-week windows including overnight wrap; #75 covers inclusive date ranges (auto-expiry). Evaluation is on-device, so dayparting and expiry work offline. - Shared evaluator contract: shared/schedule-vectors.json (39 vectors — DST US+AU, overnight-wrap anchoring, timezone correctness, date boundaries). Canonical JS evaluator in server/lib/schedule-eval.js; Kotlin and Tizen ports kept in lockstep by drift guards (Tizen byte-diff test, Kotlin JUnit reads the shared JSON, new android-test CI job). - All three players (web, Android, Tizen) filter by schedule against their own clock, idle with a "Nothing scheduled" message + 30s re-check when everything is filtered, and fail open on any evaluator error. - Editor: per-item schedule modal + row badge in the playlist editor; client validation mirrors the server; editing marks the playlist draft. - Part B (behaviour change): device/group schedule overrides now evaluate in each device's effective timezone instead of server-local time. - Device detail shows the reported timezone + a clock-skew warning. - i18n for en/es/fr/de/pt across all new strings (namespaced itemsched.* to avoid colliding with the device-schedule calendar's schedule.*). - CHANGELOG documents the feature, the Part B change, the fail-open guarantee, and the scheduled-single-video re-render tradeoff. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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). |