The embed protocol
The message API a host page can opt into: connect to the player, inspect controls, set a parameter, seek, play, pause, trigger and reset.
An embedded player can take instructions from the page around it, over postMessage. The host opts in by naming its own origin; the player answers only that origin, and only after a connect.
#Opt in
const url = new URL(sharedSceneUrl);
url.searchParams.set("embed", "1");
url.searchParams.set("parentOrigin", location.origin);
iframe.src = url.href;Without both parameters there is no host API. The origin must be exact: scheme, host and port, no path.
#Connect
iframe.contentWindow.postMessage(
{ protocol: "puni.embed", version: 1, method: "connect", requestId: crypto.randomUUID() },
url.origin
);Check event.origin and event.source === iframe.contentWindow on every reply. The connect reply carries a channel; send it, with an increasing sequence, on every later request. A reload gets a new channel.
#Methods
| Method | Fields | Does |
|---|---|---|
inspect | optional instanceId | The top-level objects and response targets, or one object's controls with their current values |
set_parameter | instanceId, property, value | Changes a control through the same validation as the editor; pauses playback |
seek | time | Pauses and moves to a time inside the duration |
play, pause | none | Playback; play is refused under reduced motion or while hidden |
trigger | targetId | Plays a response on a visible target |
reset | none | Back to the shared document, paused at zero, muted |
Changes are local to the player. Nothing is saved, and the link's snapshot never changes.
#Events
After connecting, the player sends event: "state" when readiness, time, playback, mute or reduced motion change, at most four times a second, plus an immediate sample after each command, and event: "response" when a response plays.
#Limits and errors
Twenty requests per second. Replies that complete are cached by request ID (the last 64), so a retry of the same body cannot apply twice; rate-limit, schema, not_ready, reduced_motion and hidden failures are not cached, so a retry re-executes; a different body under the same ID fails with request_conflict. Expect invalid_request, invalid_operation, request_conflict, stale_request, stale_channel, not_ready, rate_limited, reduced_motion and hidden.
Only unlocked, visible top-level objects and their ordinary properties are exposed. There is no path from a host page to commands, selection, assets, or your account.
Read Embedding for the iframe itself.
Was this useful?