Appearance
TCP Command API
Exaplay 3 exposes a plain-text TCP command interface intended for show control systems, touch panels, and other devices that cannot easily send HTTP or OSC packets. It accepts simple comma-delimited commands terminated by a carriage-return and is designed to stay open as a persistent 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 | TCP (raw socket) |
| Default Port | 8100 |
| Configuration | Config → Network → TCP Listen |
| Line terminator | \r (CR) or \r\n (CRLF) |
| Encoding | ASCII / UTF-8 |
The server accepts multiple simultaneous connections. Each command produces exactly one response line (or multiple lines ending with END for list commands).
Changing the Listen Address/Port
The TCP Listen config field accepts one or more entries (one per line), each in ADDRESS:PORT format. Use * as the address to listen on all network interfaces:
*:8100To listen on a specific interface only:
192.168.1.5:8100Command Format
The protocol accepts two command syntaxes. The dotted syntax is the primary one — it addresses any object in the project tree systematically and every new capability lands there first. The comma syntax is the classic format; it remains fully supported for existing integrations.
Dotted syntax (primary)
OBJECT_PATH.VERB[=ARGUMENT]<CR>The path walks the project tree (composition, optionally deeper: media, layer, …), the last component is the verb:
comp_main.play
comp_main.cue.go=Blackout
comp_main.cue3.go
comp_main.get:status
comp_main.set:audio.volume=80
project.comp_main.media3.set:warp.grid.enabled=1
system.showmode=on
system.pjlink=192.168.1.50,POWR,1See Dotted command syntax below for the full verb list.
Comma syntax (classic)
COMMAND,COMPOSITION_ID[,ARGUMENT]<CR>- Fields are separated by commas.
- The first field is always the command name.
- Commands that target a composition require the composition's variable name (e.g.
comp_1) as the second field.
For both syntaxes: retrieve variable names with get:complist; responses end with \r\n.
Dotted command syntax (primary)
Any line whose first field contains a . is a dotted command — the same systematic addressing cue scripts use internally, resolved against the project object tree. The path pins the target object, so "cue X in composition Y" is always unambiguous. A write command answers OK, a read command returns the value, and a failed resolution answers ERR,command_failed.
Transport & cue verbs (on a composition)
| Dotted command | Response | Action |
|---|---|---|
comp_main.play | OK | Start playback |
comp_main.pause | OK | Pause playback |
comp_main.stop | OK | Stop playback |
comp_main.set:time=45.5 | OK | Seek to position (seconds) |
comp_main.cue.go=5 | OK | Trigger cue by index (Timeline: variable index, Playlist: 1-based position) |
comp_main.cue.go=Blackout | OK | Trigger cue by name (case-insensitive, within this composition) |
comp_main.cue3.go | OK | Trigger a cue addressed as a tree node (cue3 = the cue's variable name) |
comp_lobby.next | OK | Playlist: advance to the next item |
comp_lobby.prev | OK | Playlist: go back to the previous item |
Setter verbs
| Dotted command | Response | Action |
|---|---|---|
comp_main.set:vol=80 | OK | Set volume (0–100) |
comp_main.set:alpha=50 | OK | Set master alpha (0–100) |
comp_main.set:loop=1 | OK | Enable/disable looping |
<path>.set:<property>=<value> | OK | Generic: set any inspector property, e.g. comp_main.set:audio.volume=80, comp_main.media3.set:warp.grid.enabled=1 |
The former hyphen spellings (
set-time=,set-vol=,set-alpha=,set-loop=) are deprecated; they still work as silent aliases so cue scripts saved in existing projects keep running, but new integrations must use the unifiedset:<what>=<value>form.
Getter verbs
| Dotted command | Response format |
|---|---|
comp_main.get:status | STATUS,TIME,FRAME,CUEINDEX,TOTAL (same as comma get:status) |
comp_main.get:type | timeline, cuelist, or composition |
comp_main.get:vol | <0–100> (integer) |
comp_main.get:duration | Timeline: DURATION · Playlist: COUNT,TOTAL |
comp_main.get:itemcount | <count> |
comp_main.get:playingitem | Playlist: INDEX,NAME,FILE · Timeline: INDEX,CUENAME,TIME |
comp_main.get:cuename | <name> of the active cue/item |
comp_main.get:cuelist | One line per cue/item, then END |
comp_main.get:cueinfo=5 | Timeline: INDEX,NAME,OFFSET,ACTIVE · Playlist: INDEX,NAME,FILE,DURATION (index or name) |
<path>.get:<property> | Generic: read any inspector property, e.g. comp_main.get:audio.volume, comp_main.get:alpha, comp_main.get:loop, comp_main.get:name |
System verbs (top-level system node)
The project tree has a top-level system node — everything engine-wide that is not a composition lives there: Show Mode, engine restart, machine shutdown/reboot, PJLink projector control, and (via the generic set:/get:) the display/output configuration.
| Dotted command | Response | Action |
|---|---|---|
system.showmode=on / =off / =toggle | OK | Set global Show Mode |
system.get:showmode | 1 or 0 | Query Show Mode |
system.restart | OK | Restart the engine and reload the current project |
system.restart=clean | OK | Restart the engine with no project loaded |
system.shutdown | OK | Shut down the machine (same as the SHUTDOWN cue action) |
system.reboot | OK | Reboot the machine (same as the RESTART cue action) |
system.pjlink=IP,CMD[,PARAM[,PASSWORD[,PORT]]] | OK or query value | Send a PJLink command to a projector |
system.pjlink-all=on / =off | OK,<succeeded>/<total> | Power all configured PJLink projectors (device list = project value pjlink_devices, synced by the Devices tab) |
system.get:ver | Exaplay,<version> | Engine version |
system.get:projname | <project name> | Loaded project name |
system.get:<path> / system.set:<path>=<value> | value / OK | Generic: the system node's own values (displays, outputs, target, …) |
PJLink examples (port defaults to 4352, password empty):
→ system.pjlink=192.168.1.50,POWR,1 (projector power ON)
← OK
→ system.pjlink=192.168.1.50,POWR,? (query power state)
← 1
→ system.pjlink=192.168.1.50,AVMT,31 (shutter/mute ON)
← OK
→ system.pjlink=192.168.1.50,POWR,0,secret,4352
← OK
→ system.pjlink-all=on (all configured projectors)
← OK,3/3
→ system.pjlink-all=off
← OK,3/3The user panel's All Projectors On/Off and Show Mode ON/OFF buttons use these same code paths (system.pjlink-all= via POST /cmd {"req":"pjlink-all"}, Show Mode via the global show-mode value), so panel and external show control always behave identically.
Custom control items are not separate commands — a value-set control is exactly a generic
set:on the controlled object's path (e.g.comp_main.media3.set:alpha=50), an action control is the composition verb (comp_main.play).
Example session
→ comp_main.play
← OK
→ comp_main.cue.go=Blackout
← OK
→ comp_main.cue3.go
← OK
→ system.showmode=on
← OK
→ system.pjlink=192.168.1.50,POWR,1
← OK
→ comp_main.get:status
← 1,42.1230,2527,2,0.0000
→ comp_lobby.get:cuelist
← 1,Opening,/media/opening.mp4
← 2,Act 1,/media/act1.mp4
← END
→ comp_lobby.cue.go=Act 1
← OK
→ comp_main.get:audio.volume
← 80
→ comp_main.cue.go=NoSuchCue
← ERR,command_failedNote: arguments may contain commas (
comp_main.cue.go=Act 1, Scene 2) — a dotted first field always routes the whole line as a dotted command. Protocol-prefixed lines (OSC>,TCP>, …) remain internal-only and are not accepted.
The comma-delimited commands below are the classic equivalents of these verbs and remain fully supported.
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 |
exit or quit | OK | Close the connection gracefully |
get:complist response format
<varname>,<display name>
<varname>,<display name>
...
ENDExample session:
→ get:complist
← comp_main,Main Show
← comp_lobby,Lobby Loop
← ENDComposition Commands
All composition commands use the classic format COMMAND,COMPID[,ARGUMENT]. Each has a dotted equivalent (see Dotted command syntax) — prefer the dotted form for new integrations.
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) |
Audio
| Command | Arguments | Response | Description |
|---|---|---|---|
get:vol,COMPID | — | <0–100> | Get volume (integer %) |
set:vol,COMPID,VALUE | VALUE — integer 0–100 | OK | Set volume |
Display
| Command | Arguments | Response | Description |
|---|---|---|---|
get:alpha,COMPID | — | <0–100> | Get master alpha (integer %) |
set:alpha,COMPID,VALUE | VALUE — integer 0–100 | OK | Set master alpha |
Playback Options
| Command | Arguments | Response | Description |
|---|---|---|---|
get:loop,COMPID | — | 0 or 1 | Get loop state |
set:loop,COMPID,VALUE | VALUE — 0 or 1 | 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> | Number of media items |
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 | float (seconds) | Current playback position |
FRAME | integer | Current frame number (TIME × 60) |
CUEINDEX | integer | Active cue index (timeline) or current item position (cuelist) |
TOTAL | float (seconds) | Total duration of current item (cuelist only) |
get:duration response:
- Timeline:
DURATION— total timeline length 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.
| 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,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 (so identically named cues in other compositions are never hit). Everything after the second comma counts as the name, so names containing commas work:
set:cue,comp_main,Act 1, Scene 2.
get:cuelist (Timeline) response format:
<index>,<name>,<time-offset seconds>
<index>,<name>,<time-offset seconds>
...
ENDget:cueinfo (Timeline) response fields: INDEX,NAME,OFFSET_SECONDS,IS_ACTIVE
CueList Commands
These commands are only valid when the composition is a CueList.
| 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 |
prev,COMPID | — | OK | Go back to the previous item |
get:cuelist,COMPID | — | One line per item, then END | List all items |
get:cueinfo,COMPID,INDEX | INDEX — 1-based item number | INDEX,NAME,FILE,DURATION | Details about a specific item |
get:cuelist (CueList) response format:
<1-based index>,<name>,<file path>
<1-based index>,<name>,<file path>
...
ENDNote: Timeline cue indices match the cue's variable index (arbitrary integer). CueList item indices are always 1-based sequential numbers.
Error Responses
All error responses begin with ERR,:
| Error | Meaning |
|---|---|
ERR,unknown_command | Command string not recognised |
ERR,composition_not_found | No composition with the given variable name exists |
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 | Command is not applicable to this composition type |
ERR,no_project | No project is currently loaded |
ERR,command_failed | A tree-path command could not be resolved/executed |
ERR,exception | An internal error occurred |
Example Sessions
Connection test
→ hello
← halloDiscover and play a composition
→ get:complist
← comp_main,Main Show
← comp_lobby,Lobby Loop
← END
→ play,comp_main
← OK
→ get:status,comp_main
← 1,12.3456,740,0,0.0000Work with a CueList
→ get:type,comp_lobby
← cuelist
→ get:cuelist,comp_lobby
← 1,Opening,/media/opening.mp4
← 2,Act 1,/media/act1.mp4
← 3,Closing,/media/closing.mp4
← END
→ set:cue,comp_lobby,2
← OK
→ get:playingitem,comp_lobby
← 2,Act 1,/media/act1.mp4
→ next,comp_lobby
← OKWork with a Timeline
→ get:type,comp_main
← timeline
→ get:cuelist,comp_main
← 1,Intro,0.0000
← 2,Scene A,30.0000
← 3,Scene B,90.0000
← END
→ set:cue,comp_main,2
← OK
→ set:cue,comp_main,Scene B
← OK
→ set:cue,comp_main,Scene C
← ERR,cue_not_foundAdjust volume and alpha
→ set:vol,comp_main,75
← OK
→ get:vol,comp_main
← 75
→ set:alpha,comp_main,50
← OKShow Mode
→ showmode:on
← OK
→ get:showmode
← 1
→ showmode:toggle
← OK
→ get:showmode
← 0Close the connection
→ exit
← OKConnecting from Common Show Control Systems
Medialon / Alcorn McBride
Both support raw TCP socket commands natively. Configure a TCP Device pointing to the Exaplay machine on port 8100 and map commands directly.
Crestron / AMX
Use the Crestron TCPClient symbol (SIMPL+) or the TCPClient class (SIMPL# Pro). Send commands as ASCII strings with \r\n terminators.
SIMPL+ example:
TCP_CLIENT ExaplayClient;
FUNCTION Init()
ExaplayClient.Connect("192.168.1.5", 8100);
END_FUNCTION
FUNCTION PlayComp(STRING compId)
STRING cmd[64];
cmd = "play," + compId + "\x0D\x0A";
ExaplayClient.SendData(cmd);
END_FUNCTIONSIMPL# Pro example:
csharp
var client = new TCPClient("192.168.1.5", 8100, 4096);
client.ConnectToServer();
void Send(string cmd) =>
client.SendData(Encoding.ASCII.GetBytes(cmd + "\r\n"), cmd.Length + 2);
// Map touch-panel button
Send("play,comp_main");
Send("set:cue,comp_main,3");
Send("next,comp_main");Q-SYS (QSC)
Q-SYS cores run a Lua 5.3 environment with a built-in TcpSocket object. The following snippet opens a persistent connection to Exaplay and wires up Named Controls:
lua
-- Q-SYS Lua: Exaplay TCP control
local EXAPLAY_IP = "192.168.1.5"
local EXAPLAY_PORT = 8100
local sock = TcpSocket.New()
local connected = false
local function connect()
sock:Connect(EXAPLAY_IP, EXAPLAY_PORT)
end
sock.EventHandler = function(s, evt, err)
if evt == TcpSocket.Events.Connected then
connected = true
print("Exaplay: connected")
elseif evt == TcpSocket.Events.Disconnected then
connected = false
Timer.CallAfter(connect, 5) -- auto-reconnect after 5 s
elseif evt == TcpSocket.Events.Data then
local resp = s:Read(s:Lines())
print("Exaplay response: " .. resp)
end
end
local function send(cmd)
if connected then sock:Write(cmd .. "\r\n") end
end
Controls["Play"].EventHandler = function() send("play,comp_main") end
Controls["Stop"].EventHandler = function() send("stop,comp_main") end
Controls["Next"].EventHandler = function() send("next,comp_main") end
Controls["SetCue"].EventHandler = function()
send(string.format("set:cue,comp_main,%d",
math.floor(Controls["SetCue"].Value)))
end
connect()Q-SYS also has a built-in HttpClient for one-shot REST requests:
lua
HttpClient.Download({
Url = "http://192.168.1.5:8123/cue/trigger",
Method = "POST",
Headers = { ["Content-Type"] = "application/json" },
Data = '{"name":"Scene 1 - Opening"}',
EventHandler = function(tbl, code, data, err)
if code == 200 then print("Cue fired OK")
else print("HTTP error: " .. tostring(err)) end
end
})CueLab
CueLab (by Avolites/Cogenta) outputs OSC natively — configure it in Settings → Network → OSC Output:
| CueLab Action field | Value |
|---|---|
| Destination IP | 192.168.1.5 |
| Destination Port | 8000 |
| OSC Address | /exaplay/<comp>/cue (e.g. /exaplay/comp_main/cue) |
| Argument (integer) | cue index |
Other useful OSC addresses to map to CueLab actions (replace <comp> with the composition's variable name):
/exaplay/<comp>/play → start composition
/exaplay/<comp>/start → start composition (alias for play)
/exaplay/<comp>/stop → stop composition
/exaplay/<comp>/pause → pause composition
/exaplay/<comp>/cuetime <s> → seek to position (client instances only)
/exaplay/global/start → start ALL compositions
/exaplay/global/stop → stop ALL compositions
/exaplay/global/showmode <on|off|toggle>To fire a cue by name rather than index, use CueLab's Web Request action instead: Method POST, URL http://192.168.1.5:8123/cue/trigger, body {"name":"My Cue"}.
Python (testing / scripting)
python
import socket
HOST = "192.168.1.5"
PORT = 8100
with socket.create_connection((HOST, PORT)) as sock:
def send(cmd):
sock.sendall((cmd + "\r\n").encode())
return sock.recv(4096).decode().strip()
print(send("hello")) # hallo
print(send("get:complist")) # comp_main,Main Show\r\nEND
print(send("play,comp_main")) # OKnetcat (quick diagnostics)
bash
nc 192.168.1.5 8100
hello
get:complist
get:status,comp_main
exitFirewall
Ensure TCP port 8100 is open on the Exaplay machine:
batch
netsh advfirewall firewall add rule name="Exaplay TCP Control" ^
dir=in action=allow protocol=TCP localport=8100See Also
- HTTP REST API — JSON-based HTTP API (port 8123) with equivalent seeking and transport commands
- UDP Command API — fire-and-forget datagram variant of this API
