The dashboard offered every control to every display, so on a Tizen panel the volume
slider and screen_off did nothing and read as bugs. Two of them were genuinely dead:
- set_volume fell through STDeviceControl.run()'s default case and was answered
"unknown command". It is not a Samsung fleet action and must work on every build,
so it is handled in app.js instead: tizen.tvaudiocontrol where the TV profile
provides it (that is the TV's own volume, the only thing that reaches AVPlay video
on the hardware plane), otherwise the media elements. The level is remembered and
re-applied on 'play' — media elements are created per item, so a one-shot set
lasted only until the playlist advanced.
- screen_off was a z-index overlay, which covers the web layer only. Portrait and
flipped video runs through AVPlay on a separate hardware plane the DOM cannot draw
over, so the overlay went up and the video played straight through it. It now tears
the AVPlay session down as well; screen_on re-mounts via playCurrent(), because a
torn-down session cannot be resumed and gotoIndex() early-returns on an unchanged
index.
js/capabilities.js declares the rest at runtime rather than from a static table,
because on Tizen the answer varies by build: reboot exists only through the B2B
surface injected on a partner-signed .wgt, and tizen.tvaudiocontrol is absent in a
browser context. Against the server baseline this adds display.power, remote.screenshot
and remote.stream (all backed by real handlers) and drops offline.cache — the payload
is cached, but media bytes are still fetched from the network, so content does not
survive an outage and claiming it would overstate.
Adds the tv.audio privilege; without it tvaudiocontrol throws SecurityError.
#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>
bump-version.sh matched `<?xml version="1.0" ...?>` - the XML format version,
which has a leading space before version= just like the widget attribute - and
rewrote it to the app version, producing invalid XML that breaks the Tizen .wgt
build: 'XML version "X.Y.Z" is not supported, only XML 1.0 is supported'. (CI did
not catch it because the no-Tizen-CLI build path just zips the files without
validating the XML.)
- bump-version.sh: skip the `<?xml` declaration line in the tizen version sed.
- tizen/config.xml: restore the declaration to version="1.0" (prior bumps had
corrupted it to 1.8.2).
The widget version and tizen:application required_version are still updated /
left alone correctly (verified with a dummy bump + an XML parse).
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).