Docs Documentation
The render protocol
Developer guide for renderer.html: transports, the message flow, the timeline rule, the messages each way, network and data, and the rules a renderer must follow.
This guide is for developers who write their own renderer.html. Start from the Minimal example renderer in Settings → Player (also at /examples/minimal-renderer.html): about 300 lines of plain JavaScript that do everything below.
Finding the host
Every message is one JSON object with a string type and the protocol version v (currently 1). Send v on every message. Ignore any message type and any field you do not know: the protocol only grows by adding within a version. Payloads may arrive as objects or as JSON strings, so accept both.
Check for the host in this order when your script starts:
- Raspberry Pi — the page address has
?host_ws=ws://127.0.0.1:<port>/render: open that WebSocket and send and receive JSON text. - Desktop app —
window.__TAURI__?.eventexists: listen forwf:render, emitwf:renderer. - Android TV (bridge) —
window.WunderframeRenderer?.postexists: the host callswindow.__wfRender(json), you callWunderframeRenderer.post(json). - Browser — you are in an iframe:
messageevents fromwindow.parent, andwindow.parent.postMessage(msg, "*"). - Android TV (port) — none of the above: the host posts the string
wf-render-portwith aMessagePort. It may arrive after your script ran, so queue what you send until a transport exists.
The conversation
renderer host
| -- ready ---------------> | as soon as the script runs
| <---------------- load -- | stage program P off-screen
| -- prepared {P} --------> | pictures decoded, fonts loaded, first video buffered
| <---------------- play -- | show P from position X
| -- shot {index, at} ----> | each time a shot comes on screen
| <------------ position -- | follower correction on a wall
| <---------------- data -- | new data, swapped at the next shot boundary
| <---------------- load -- | the next program Q, staged while P plays
| -- prepared {Q} --------> |
| <---------------- play -- | switch to QIf the host sees no ready within 20 seconds, or the renderer crashes, that screen runs the stock renderer instead and reports why.
The timeline rule
Walls stay frame-accurate only if what you draw is a pure function of the timeline position.
play { epoch, position_ms, at }means: at host timeat, the timeline was atposition_ms.- The position now is
position_ms + (hostNow() − at), wherehostNow()isDate.now()plus the offset from the lastclockmessage. - Recompute it every frame from the last anchor. Never add up frame times or count timer ticks.
- A
positionmessage is a new anchor: jump to it. Seek videos to the position too.
Messages from the host
load— a program to prepare off-screen: its shots, blocks, fonts and the local address of every asset. Answerpreparedwhen every picture is decoded and the first video can play.play— show a prepared program from a position.position— a correction from the wall’s leader.data— a new data pack (records, feeds, lists). Swap it in at the next shot boundary.overlay— what covers or marks the program: identify, the idle card, offline marker, a wall panel’s name.presenterandcast— the live presenter or cast on this stage, with what you need to join their video.monitor— someone is watching this screen; show a small dot if you can.clock— the host’s offset to server time and the venue’s time zone.vars— the stage’s current TV channel changed; switch that video’s source only.unload,fetch_result— drop a program; the answer to yourfetch.
Messages from the renderer
ready— first, unprompted, withv, yourrenderer_version(the name Devices shows) and thecapabilitiesyou implement. Announce only what you do.prepared— a loaded program is ready to show.shot— a shot came on screen: its index and when. This is the wall’s sync beat.ended— one pass of the program is over.planesandaudio— on TV players, where the video layers go and which sounds to play (see below).channel— how a TV channel you play in the page is doing.fetch— ask the host to fetch something for you; the answer comes asfetch_result.errorandperf— what went wrong and how the page is doing; they appear on Devices. An error never stops the show: keep drawing what you can.
Network and data
A renderer has no network of its own. Everything it needs comes in load and data, with local addresses that work offline. Anything live goes through the host’s fetch, with limits on size and time and no cookies; keep the last good value. What you fetch yourself is not cached, not offline-safe and not in step across a wall — it will look broken on screens. During the self-test every fetch is refused.
Planes, video and sound on TV players
On Android TV and Raspberry Pi, video and TV channels play in native layers, not in your page. The host may open your page several times, each with ?plane=n, and stack them with video layers between them. Draw only the blocks that belong to your plane, and leave the video’s area transparent. load tells you the host’s limits and whether sound plays natively; then your page stays muted and announces sounds to the host instead.
Rules
- One self-contained HTML file, at most 8 MB. No
<script src>, stylesheet links, imports from a URL, relative paths,<base>or frames. - You never see credentials: no player key, no token. Do not try to read the host’s storage.
- The download address is public. Do not put secrets in the file.
- Keep
v: 1inready, follow the timeline rule, and ignore what you do not know.