#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>
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.
- scripts/upgrade.sh: upgrade a self-hosted instance to a tagged release
(default latest). Backs up the db (.backup), checks out the tag, npm ci
--omit=dev, restarts the service (SERVICE_NAME override), reports the version.
- README: replace the git-pull update flow with scripts/upgrade.sh (latest or a
pinned tag); keep main as the bleeding-edge option. Add a Samsung Tizen entry
to device setup (URL Launcher -> /player).
- tizen/README: point path A at the server's built-in /player, and explain why
the released .wgt is unsigned (Samsung distributor certs are DUID-locked).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- config.xml author email -> support@screentinker.net
- build-wgt.sh: stage app files only before signing (keeps README/build script
out of the .wgt), auto-add the Tizen CLI to PATH if installed.
- README: document the configured 'ScreenTinker' signing profile (self-signed
author + default Tizen distributor) — installs on dev-mode TVs / emulator;
production retail needs a Samsung distributor cert.
Signed .wgt + the author cert are not committed (build artifact / secret).
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).