screentinker/tizen/README.md
ScreenTinker 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.
2026-06-18 13:28:08 -05:00

137 lines
7.7 KiB
Markdown

# 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:paired` it 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}/file` or `remote_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`
- Sends `device:heartbeat` every 15s (with best-effort Tizen telemetry).
- Keeps the screen awake (`tizen.power` / Samsung `appcommon` screensaver-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 broadcasts `wall:sync` and followers align index + drift-correct their
video to the leader's clock. Per-tile `rotation` is 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
```bash
./build-wgt.sh # -> ScreenTinker.wgt
```
Without the Tizen CLI this is an **unsigned** `.wgt`.
> **Why the released `.wgt` is unsigned:** Samsung **distributor** certificates
> are locked to the **DUID** of the signer's own TVs, so a `.wgt` we 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.sh` auto-detects the CLI and signs with it,
producing a `.wgt` with `author-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:
```bash
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_off`** uses an overlay, not a real panel power-off — a sideloaded app has no
> clean panel-power API. On B2B/MDM (SSSP) firmware, true power and `reboot`/`shutdown`
> go 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.