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
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. - Retrieve variable names with
get:complist. - Responses end with
\r\n.
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 |
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 format COMMAND,COMPID[,ARGUMENT].
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 |
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 |
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 |
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,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
← OKAdjust volume and alpha
→ set:vol,comp_main,75
← OK
→ get:vol,comp_main
← 75
→ set:alpha,comp_main,50
← OKClose 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>/stop → stop composition
/exaplay/<comp>/pause → pause composition
/exaplay/<comp>/cuetime <s> → seek to position (client instances only)
/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
