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.
7.7 KiB
ScreenTinker — Tizen TV Player (.wgt)
A Samsung Tizen TV / signage web port of the ScreenTinker player. It speaks the
exact same /device socket.io protocol as the Android player, so a Tizen
display pairs and plays from the same dashboard with no server changes.
What it does
- Enter a server URL → connects to
{server}/device(socket.io v4). - Registers, shows a 6-digit pairing code; you claim it in the dashboard
(Devices → Pair a display). On
device:pairedit switches to playback. - Reconnects automatically with a stored
device_id+device_token. - Renders multi-zone layouts (matching the Android player) when a layout is assigned —
each zone has its own percent geometry,
z_index,fit_mode, background, and rotates its own assignments independently — and falls back to fullscreen single-zone when no layout is set, looping:- image → shown for
duration_sec(min 3s) - video (
/api/content/{id}/fileorremote_url) → plays to end, then next; single item loops - YouTube (
mime video/youtube) → muted autoplay<iframe>embed - widget →
<iframe>of{server}/api/widgets/{id}/render
- image → shown for
- Sends
device:heartbeatevery 15s (with best-effort Tizen telemetry). - Keeps the screen awake (
tizen.power/ Samsungappcommonscreensaver-off). - Video walls (mirrors the web player): when the device is a wall member the payload
carries
wall_config; the stage is positioned (in vw/vh) as this screen's slice of the wall, the leader broadcastswall:syncand followers align index + drift-correct their video to the leader's clock. Per-tilerotationis not applied yet (matches the web player); video walls have no Android equivalent.
Files
config.xml Tizen TV web-app manifest (privileges, profile, icon)
index.html setup / pairing / stage screens
css/style.css
js/app.js device protocol client (register, pair, heartbeat, state)
js/player.js fullscreen playlist renderer
js/socket.io.min.js socket.io-client v4.7.5 (bundled)
icon.png
build-wgt.sh package (signed if Tizen CLI present, else unsigned)
Build
./build-wgt.sh # -> ScreenTinker.wgt
Without the Tizen CLI this is an unsigned .wgt.
Why the released
.wgtis unsigned: Samsung distributor certificates are locked to the DUID of the signer's own TVs, so a.wgtwe signed would not install on your TV anyway. Releases therefore ship it unsigned (for inspection only). To actually run it, use path A (no signing) or sign it yourself with your own certificate (path B).
Deploy — two paths
A) URL Launcher / TV browser (easiest, no signing)
No package, no Tizen Studio. Point the TV's URL Launcher (or just its web
browser) at your server's built-in web player: https://<your-instance>/player.
The TV runs it as a web app on boot, pairs with a 6-digit code, and plays - best
for Samsung B2B signage (SSSP). (You can instead self-host this tizen/ folder
and point the URL Launcher at …/index.html for the Tizen-specific build.)
B) Signed .wgt (installed app)
A signing profile is already set up on the build box (Tizen Studio CLI 6.1):
- Profile
ScreenTinker= a self-signed author cert (~/tizen-studio-data/keystore/author/st_author.p12) + the default Tizen distributor cert../build-wgt.shauto-detects the CLI and signs with it, producing a.wgtwithauthor-signature.xml+signature1.xml. - This installs on developer-mode Samsung TVs and the Tizen emulator —
the right path for a self-hosted fleet you control (enable Developer Mode
on each TV once: Apps → enter
12345→ set the host IP).
Install onto a dev-mode TV:
sdb connect <tv-ip>
tizen install -n ScreenTinker.wgt -t <tv-device>
Production / retail (no developer mode): re-sign with a Samsung Partner
or Public distributor certificate from the Tizen Certificate Manager
(free Samsung account; distributor cert tied to each TV's DUID), then
./build-wgt.sh <thatProfile>. The self-signed author cert is not committed (it
lives in ~/tizen-studio-data, password screentinker).
Validated (2026-06-09)
- Protocol: headless test against the live server passed end-to-end —
register(pairing_code) → device:registered → pair → reconnect(device_id+token) → device:playlist-update(2 items) → GET /api/content/{id}/file = 200. - Runtime: loads + renders in Chromium with no JS errors (setup screen verified).
- Not yet on real Tizen hardware — needs signing + a TV (or URL Launcher).
Remote control & preview (#120 / #121)
The Tizen player now listens for the same dashboard events as the web/Android player. What it can actually do depends on what a sideloaded web app is allowed to do on the TV runtime:
Command (device:command type) |
Tizen behaviour |
|---|---|
refresh |
location.reload() |
launch / screen_on |
clears the screen-off overlay + re-requests screen-awake |
screen_off |
black full-screen overlay (content keeps running behind) |
update |
toast: must re-install the .wgt (see Updates below) |
reboot / shutdown |
MDM-only — not reachable from a sideloaded app (toast) |
device:screenshot-request |
best-effort capture (see note) |
device:remote-start / -stop |
start/stop ~1 fps preview streaming |
Screenshot/preview note: the TV decodes
<video>onto a hardware overlay plane and plays YouTube in a cross-origin<iframe>, neither of which can be read back into a<canvas>. So images capture for real; video/YouTube fall back to a status card (device + timestamp). The dashboard preview shows a truthful frame rather than a dead button. Full-fidelity video preview isn't feasible on the sideloaded Tizen runtime.
screen_offuses an overlay, not a real panel power-off — a sideloaded app has no clean panel-power API. On B2B/MDM (SSSP) firmware, true power andreboot/shutdowngo through Samsung's device-management channel, not this app.
Updates (#122)
There is no in-app OTA for a sideloaded, signed .wgt. Updating a screen means
re-building and re-sideloading the .wgt (path B above), or — on Samsung B2B
signage — pushing it through the URL Launcher refresh / MDM (MagicINFO / SSSP)
channel. The dashboard update command therefore just tells the screen an update is
pending; it cannot self-apply. If you run the URL Launcher path (A), a plain
TV reboot re-fetches …/player and you're current with the server with no .wgt step.
Auto-launch on boot (#122)
Boot auto-start for a sideloaded consumer TV web app is a display setting, not an
app setting — there's no config.xml autostart for the TV profile. Configure it on
the panel:
- URL Launcher path (A): set the URL Launcher as the boot app (it relaunches on power-up automatically) — the recommended signage setup.
- Signed-app path (B): use the TV's kiosk / auto-start app setting (B2B/SSSP firmware) to launch ScreenTinker on boot; on dev-mode consumer TVs there's no guaranteed boot-launch, so the URL Launcher path is preferred for unattended screens.
Version reporting (#119)
app_version is sourced from config.xml's version="" — read at runtime via the
Tizen application API, with a build-stamped constant fallback (build-wgt.sh stamps it
from config.xml). The dashboard always shows the version actually installed.