Appearance
UDP Command API
Exaplay 3 exposes a UDP command interface that carries the same command set as the TCP Command API. It is aimed at show control systems, touch panels, and embedded controllers that prefer the simplicity of fire-and-forget datagrams over a persistent TCP connection.
Naming note: The Exaplay UI calls this composition type a Playlist. The wire protocol still uses the original
cuelistidentifier (returned byget:type, used inget:cuelist, etc.). This is intentional so existing integrations continue to work — treatcuelistandCueListin the protocol as synonyms for Playlist in the UI.
Connection
| Parameter | Value |
|---|---|
| Protocol | UDP (datagram) |
| Default Port | 8200 |
| Configuration | Menu → Settings → Communication → UDP Listen |
| Line terminator | optional — trailing \r, \n and spaces are ignored |
| Encoding | UTF-8 |
Each datagram contains exactly one command — nothing is split inside a datagram, so comp1.play\r\ncomp2.play is read as one command (and answered ERR,command_failed). Datagrams are processed one at a time; while a project is loading they are dropped without a reply. The engine sends the complete response — which may span multiple logical lines — back to the sender's IP and port as a single reply datagram. There is no persistent session; each command/response exchange is independent.
Note: Because UDP does not guarantee delivery or ordering, avoid sending commands faster than you can process the responses. For reliable bidirectional communication (status polling, multi-step workflows) the TCP Command API is preferable.
Changing the Listen Address/Port
The UDP Listen config field takes an ADDRESS:PORT entry. Use * as the address to listen on every network interface (one socket on 0.0.0.0, loopback included — see the TCP notes):
*:8200To listen on a specific interface only:
192.168.1.5:8200Leave the field empty to disable UDP control.
Command Format
UDP carries the same two syntaxes as TCP. The dotted syntax is the primary one:
OBJECT_PATH.VERB[=ARGUMENT]→ comp_main.play
← OK\r\n
→ comp_main.cue.go=Blackout
← OK\r\n
→ comp_main.get:status
← 1,42.1230,2527,2,0\r\n
→ system.showmode=on
← OK\r\nSee Dotted command syntax in the TCP API reference for the full verb list and its error replies — transport (play/pause/stop/restart/set:time=), cues (cue.go=<index|name>, cue3.go), next/prev (Playlist items, Timeline cue markers), media3.play (a Playlist item), setters (set:vol=, set:alpha=, set:loop=, generic set:<property>=<value>), getters (get:status, get:cuelist, get:cueinfo=, generic get:<property>) and the system.* verbs (Show Mode, restart, PJLink). As over TCP, system.pjlink= only reaches a projector configured under Control → TCP Devices, and on a multi-client master a set: on composition content is passed on to every client.
The classic comma form remains fully supported:
COMMAND,COMPOSITION_ID[,ARGUMENT]- Fields are separated by commas — no space after a comma; a field in double quotes may contain commas, and the quotes are removed (
set:cue,comp1,"Act 1, Scene 2"). - The first field is always the command name.
- Commands that target a composition take the composition's ID (its variable name, e.g.
comp1) or its name (case-insensitive;_and space are interchangeable) as the second field — the same addressing rules as TCP. - Retrieve IDs and names with
get:complist, or read them off the composition's Inspector (Show Control section). - The response is returned as a single UDP datagram. Multi-line responses (list commands) have each line delimited by
\r\nwithin that datagram and are terminated by a finalEND\r\nline.
Global Commands
These commands do not require a composition ID.
| Command | Response | Description |
|---|---|---|
hello | hallo | Connection test / keep-alive |
get:ver | Exaplay,<version> | Get engine version string |
get:complist | One line per composition, then END | List all compositions |
get:projname | <project name> | Get the loaded project name |
showmode:on | OK | Enable global Show Mode |
showmode:off | OK | Disable global Show Mode |
showmode:toggle | OK | Toggle global Show Mode |
get:showmode | 1 or 0 | Get current Show Mode state |
Note:
exit/quitis not available over UDP — there is no session to close; it answersERR,unknown_command.
Dotted equivalents: system.get:ver, system.get:projname, system.showmode=on|off|toggle, system.get:showmode. The rig-wide switches work the same way over UDP: system.blank=on|off|toggle[,<fade s>], system.blankfade=<s>, system.audiomute=…, system.identify=…, system.playall, system.stopall — see the TCP Command API.
A screen's calibration switches work over UDP too, addressed by the screen's ID: screen1.calibration=on|off|toggle, screen1.calibration.blend=…, screen1.calibration.black=…, screen1.get:calibration[.blend|.black] and screen1.calibration.reload — see Screen verbs.
get:complist response format
<varname>,<display name>\r\n
<varname>,<display name>\r\n
...
END\r\nExample:
→ get:complist
← comp_main,Main Show\r\ncomp_lobby,Lobby Loop\r\nEND\r\nComposition Commands
All composition commands use the classic format COMMAND,COMPID[,ARGUMENT]. Each has a dotted equivalent — see the TCP reference.
Transport
| Command | Arguments | Response | Description |
|---|---|---|---|
play,COMPID | — | OK | Start playback |
pause,COMPID | — | OK | Pause playback |
stop,COMPID | — | OK | Stop playback |
set:cuetime,COMPID,SECONDS | SECONDS — float | OK | Seek to position (seconds). On a Playlist, the time within the current item (as get:status reports it). A stopped composition stays stopped |
Audio
| Command | Arguments | Response | Description |
|---|---|---|---|
get:vol,COMPID | — | <0–100> | Get volume (integer %, truncated) |
set:vol,COMPID,VALUE | VALUE — number 0–100 (out-of-range values are clamped) | OK | Set volume |
set:vol, set:alpha and set:loop sent to a multi-client master reach every client, as they do over TCP.
Opacity
| Command | Arguments | Response | Description |
|---|---|---|---|
get:alpha,COMPID | — | <0–100> | Get master alpha (integer %, truncated) |
set:alpha,COMPID,VALUE | VALUE — number 0–100 (clamped) | OK | Set master alpha |
Playback Options
| Command | Arguments | Response | Description |
|---|---|---|---|
get:loop,COMPID | — | 0 or 1 | Get loop state |
set:loop,COMPID,VALUE | VALUE — on/off, true/false or 1/0 | OK | Enable/disable looping |
Status and Info
| Command | Response format | Description |
|---|---|---|
get:name,COMPID | <name> | Get display name |
get:type,COMPID | timeline, cuelist, or composition | Get composition type |
get:itemcount,COMPID | <count> | Playlist: number of items. On a Timeline, clips placed on tracks are not counted |
get:status,COMPID | STATUS,TIME,FRAME,CUEINDEX,TOTAL | Full playback status (see below) |
get:duration,COMPID | Depends on type (see below) | Total duration |
get:playingitem,COMPID | Depends on type (see below) | Currently playing item |
get:cuename,COMPID | <name> | Name of the currently active cue |
get:status response fields:
| Field | Values | Description |
|---|---|---|
STATUS | 0 stopped, 1 playing, 2 paused | Playback state |
TIME | seconds, 4 decimals | Timeline: timeline position. Playlist: position within the current item |
FRAME | integer | Current frame number (TIME × 60) |
CUEINDEX | integer | Timeline: active cue index. Playlist: 1-based current item, 0 if none |
TOTAL | seconds, shortest form (0, 184.2) | Playlist: duration of the current item. Always 0 on a Timeline |
get:duration response:
- Timeline:
DURATION— end of the last clip or cue marker, in seconds (float) - CueList:
COUNT,TOTAL— number of items and sum of all clip lengths (float seconds)
get:playingitem response:
- CueList:
INDEX,NAME,FILE— 1-based item index, friendly name, source file path - Timeline:
INDEX,CUENAME,TIME— cue variable index, cue name, current time (float seconds)
Timeline Commands
These commands are only valid when the composition is a Timeline. On another type they answer ERR,unknown_command. The classic next,/prev, are Playlist-only — on a Timeline use the dotted comp_main.next / comp_main.prev (next / previous cue marker).
| Command | Arguments | Response | Description |
|---|---|---|---|
set:cue,COMPID,INDEX | INDEX — cue variable index | OK | Jump to cue by index and trigger it |
set:cue,COMPID,NAME | NAME — cue name (case-insensitive) | OK | Jump to cue by name and trigger it |
get:cuelist,COMPID | — | One line per cue, then END | List all cues |
get:cueinfo,COMPID,INDEX | INDEX — cue variable index (index only; COMPID.get:cueinfo=NAME takes a name) | INDEX,NAME,OFFSET,ACTIVE | Details about a specific cue |
A purely numeric argument is always treated as the index; anything else is matched (ASCII case-insensitively) against the cue names of this composition. Everything after the second comma counts as the name, so names containing commas work.
get:cuelist (Timeline) response format:
<index>,<name>,<time-offset seconds>\r\n
...
END\r\nget:cueinfo (Timeline) response fields: INDEX,NAME,OFFSET_SECONDS,IS_ACTIVE
CueList Commands
These commands are only valid when the composition is a CueList (Playlist). On another type they answer ERR,unknown_command.
| Command | Arguments | Response | Description |
|---|---|---|---|
set:cue,COMPID,INDEX | INDEX — 1-based item number | OK | Play item at the given position |
set:cue,COMPID,NAME | NAME — item name (case-insensitive) | OK | Play item by name |
next,COMPID | — | OK | Advance to the next item. Nothing playing → start the first item |
prev,COMPID | — | OK | Go back to the previous item. Nothing playing → start the last item |
get:cuelist,COMPID | — | One line per item, then END | List all items |
get:cueinfo,COMPID,INDEX | INDEX — 1-based item number (index only) | INDEX,NAME,FILE,DURATION | Details about a specific item |
get:cuelist (CueList) response format:
<1-based index>,<name>,<file path>\r\n
...
END\r\nNote: Timeline cue indices match the cue's variable index (arbitrary integer). CueList item indices are always 1-based sequential numbers.
Inputs (tracking channels)
The track,… lines of TCP › Inputs work over UDP too, with the same replies:
| Command | Meaning |
|---|---|
track,CHANNEL,X[,Y] | Position (Y optional) |
track,CHANNEL,x,VALUE | X only |
track,CHANNEL,y,VALUE | Y only |
track,CHANNEL,click[,VALUE] | Click (value defaults to 1) |
Error Responses
All error responses begin with ERR,:
| Error | Meaning |
|---|---|
ERR,unknown_command | Command string not recognised — also a composition command without its composition, a type-specific command on the wrong composition type, or a dotted line containing > |
ERR,composition_not_found | No composition matches the given ID or name (also a global command with extra fields; also classic commands with no project loaded) |
ERR,missing_argument | Required argument was not supplied |
ERR,cue_not_found | The cue/item index does not exist |
ERR,none_playing | Requested info about a playing item, but nothing is playing |
ERR,no_next_item | next reached the end of the cue list |
ERR,already_at_first | prev called when the first item is already playing |
ERR,cuelist_empty | next/prev called on an empty cue list |
ERR,unsupported_type | get:playingitem, get:cuename or get:duration on a plain composition |
ERR,no_project | Classic get:projname with no project loaded (a dotted command answers ERR,command_failed then) |
ERR,no_devices | system.pjlink-all with no PJLink projector configured |
ERR,command_failed | A dotted command could not be resolved or failed — also system.pjlink= to an address that is not a configured projector |
ERR,track_expected_channel, ERR,track_expected_value, ERR,track_bad_args | A malformed track,… line (see Inputs) |
ERR,exception | An internal error occurred |
Example Sessions
Connection test
→ hello
← hallo\r\nDiscover and play a composition
→ get:complist
← comp_main,Main Show\r\ncomp_lobby,Lobby Loop\r\nEND\r\n
→ comp_main.play (classic: play,comp_main)
← OK\r\n
→ comp_main.get:status (classic: get:status,comp_main)
← 1,12.3456,740,0,0\r\nWork with a CueList
→ comp_lobby.cue.go=2 (classic: set:cue,comp_lobby,2)
← OK\r\n
→ comp_lobby.next (classic: next,comp_lobby)
← OK\r\nWork with a Timeline
→ comp_main.cue.go=2 (classic: set:cue,comp_main,2)
← OK\r\n
→ comp_main.cue.go=Scene B (classic: set:cue,comp_main,Scene B)
← OK\r\nShow Mode
→ system.showmode=on (classic: showmode:on)
← OK\r\n
→ system.get:showmode (classic: get:showmode)
← 1\r\nSending Commands from Common Environments
Python (testing / scripting)
python
import socket
HOST = "192.168.1.5"
PORT = 8200
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(2.0)
def send(cmd):
sock.sendto((cmd + "\r\n").encode(), (HOST, PORT))
try:
data, _ = sock.recvfrom(65536)
return data.decode().strip()
except socket.timeout:
return "(no response)"
print(send("hello")) # hallo
print(send("get:complist")) # comp_main,Main Show\r\nEND
print(send("comp_main.play")) # OKNode.js
js
const dgram = require('dgram');
const client = dgram.createSocket('udp4');
const HOST = '192.168.1.5';
const PORT = 8200;
function send(cmd) {
return new Promise((resolve) => {
client.once('message', (msg) => resolve(msg.toString().trim()));
client.send(cmd + '\r\n', PORT, HOST);
});
}
(async () => {
console.log(await send('hello')); // hallo
console.log(await send('comp_main.play')); // OK
client.close();
})();netcat / socat (quick diagnostics)
bash
# Send a single command and print the response
echo -ne "hello\r\n" | socat - UDP:192.168.1.5:8200
# Interactive session (socat keeps the socket open)
socat - UDP:192.168.1.5:8200TCP vs UDP — When to Use Which
| Scenario | Recommended |
|---|---|
| Show control system with persistent connection | TCP |
| Embedded controller / microcontroller with UDP only | UDP |
| Single fire-and-forget trigger commands | UDP |
| Polling status at high frequency | TCP |
| Multi-step workflows requiring ordered responses | TCP |
Firewall
Ensure UDP port 8200 is open on the Exaplay machine:
batch
netsh advfirewall firewall add rule name="Exaplay UDP Control" ^
dir=in action=allow protocol=UDP localport=8200See Also
- HTTP REST API — JSON-based HTTP API (port 8123) with equivalent seeking and transport commands
- TCP Command API — persistent connection variant of this API
