Skip to content

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 cuelist identifier (returned by get:type, used in get:cuelist, etc.). This is intentional so existing integrations continue to work — treat cuelist and CueList in the protocol as synonyms for Playlist in the UI.

Connection

ParameterValue
ProtocolTCP (raw socket)
Default Port8100
ConfigurationConfig → Network → TCP Listen
Line terminator\r (CR) or \r\n (CRLF)
EncodingASCII / 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:

*:8100

To listen on a specific interface only:

192.168.1.5:8100

Command 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,1

See 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 commandResponseAction
comp_main.playOKStart playback
comp_main.pauseOKPause playback
comp_main.stopOKStop playback
comp_main.set:time=45.5OKSeek to position (seconds)
comp_main.cue.go=5OKTrigger cue by index (Timeline: variable index, Playlist: 1-based position)
comp_main.cue.go=BlackoutOKTrigger cue by name (case-insensitive, within this composition)
comp_main.cue3.goOKTrigger a cue addressed as a tree node (cue3 = the cue's variable name)
comp_lobby.nextOKPlaylist: advance to the next item
comp_lobby.prevOKPlaylist: go back to the previous item

Setter verbs

Dotted commandResponseAction
comp_main.set:vol=80OKSet volume (0–100)
comp_main.set:alpha=50OKSet master alpha (0–100)
comp_main.set:loop=1OKEnable/disable looping
<path>.set:<property>=<value>OKGeneric: 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 unified set:<what>=<value> form.

Getter verbs

Dotted commandResponse format
comp_main.get:statusSTATUS,TIME,FRAME,CUEINDEX,TOTAL (same as comma get:status)
comp_main.get:typetimeline, cuelist, or composition
comp_main.get:vol<0–100> (integer)
comp_main.get:durationTimeline: DURATION · Playlist: COUNT,TOTAL
comp_main.get:itemcount<count>
comp_main.get:playingitemPlaylist: INDEX,NAME,FILE · Timeline: INDEX,CUENAME,TIME
comp_main.get:cuename<name> of the active cue/item
comp_main.get:cuelistOne line per cue/item, then END
comp_main.get:cueinfo=5Timeline: 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 commandResponseAction
system.showmode=on / =off / =toggleOKSet global Show Mode
system.get:showmode1 or 0Query Show Mode
system.restartOKRestart the engine and reload the current project
system.restart=cleanOKRestart the engine with no project loaded
system.shutdownOKShut down the machine (same as the SHUTDOWN cue action)
system.rebootOKReboot the machine (same as the RESTART cue action)
system.pjlink=IP,CMD[,PARAM[,PASSWORD[,PORT]]]OK or query valueSend a PJLink command to a projector
system.pjlink-all=on / =offOK,<succeeded>/<total>Power all configured PJLink projectors (device list = project value pjlink_devices, synced by the Devices tab)
system.get:verExaplay,<version>Engine version
system.get:projname<project name>Loaded project name
system.get:<path> / system.set:<path>=<value>value / OKGeneric: 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/3

The 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_failed

Note: 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.

CommandResponseDescription
hellohalloConnection test / keep-alive
get:verExaplay,<version>Get engine version string
get:complistOne line per composition, then ENDList all compositions
get:projname<project name>Get the loaded project name
showmode:onOKEnable global Show Mode
showmode:offOKDisable global Show Mode
showmode:toggleOKToggle global Show Mode
get:showmode1 or 0Get current Show Mode state
exit or quitOKClose the connection gracefully

get:complist response format

<varname>,<display name>
<varname>,<display name>
...
END

Example session:

→ get:complist
← comp_main,Main Show
← comp_lobby,Lobby Loop
← END

Composition 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

CommandArgumentsResponseDescription
play,COMPIDOKStart playback
pause,COMPIDOKPause playback
stop,COMPIDOKStop playback
set:cuetime,COMPID,SECONDSSECONDS — floatOKSeek to position (seconds)

Audio

CommandArgumentsResponseDescription
get:vol,COMPID<0–100>Get volume (integer %)
set:vol,COMPID,VALUEVALUE — integer 0–100OKSet volume

Display

CommandArgumentsResponseDescription
get:alpha,COMPID<0–100>Get master alpha (integer %)
set:alpha,COMPID,VALUEVALUE — integer 0–100OKSet master alpha

Playback Options

CommandArgumentsResponseDescription
get:loop,COMPID0 or 1Get loop state
set:loop,COMPID,VALUEVALUE0 or 1OKEnable/disable looping

Status and Info

CommandResponse formatDescription
get:name,COMPID<name>Get display name
get:type,COMPIDtimeline, cuelist, or compositionGet composition type
get:itemcount,COMPID<count>Number of media items
get:status,COMPIDSTATUS,TIME,FRAME,CUEINDEX,TOTALFull playback status (see below)
get:duration,COMPIDDepends on type (see below)Total duration
get:playingitem,COMPIDDepends on type (see below)Currently playing item
get:cuename,COMPID<name>Name of the currently active cue

get:status response fields:

FieldValuesDescription
STATUS0 stopped, 1 playing, 2 pausedPlayback state
TIMEfloat (seconds)Current playback position
FRAMEintegerCurrent frame number (TIME × 60)
CUEINDEXintegerActive cue index (timeline) or current item position (cuelist)
TOTALfloat (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.

CommandArgumentsResponseDescription
set:cue,COMPID,INDEXINDEX — cue variable indexOKJump to cue by index and trigger it
set:cue,COMPID,NAMENAME — cue name (case-insensitive)OKJump to cue by name and trigger it
get:cuelist,COMPIDOne line per cue, then ENDList all cues
get:cueinfo,COMPID,INDEXINDEX — cue variable indexINDEX,NAME,OFFSET,ACTIVEDetails 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>
...
END

get:cueinfo (Timeline) response fields: INDEX,NAME,OFFSET_SECONDS,IS_ACTIVE


CueList Commands

These commands are only valid when the composition is a CueList.

CommandArgumentsResponseDescription
set:cue,COMPID,INDEXINDEX — 1-based item numberOKPlay item at the given position
set:cue,COMPID,NAMENAME — item name (case-insensitive)OKPlay item by name
next,COMPIDOKAdvance to the next item
prev,COMPIDOKGo back to the previous item
get:cuelist,COMPIDOne line per item, then ENDList all items
get:cueinfo,COMPID,INDEXINDEX — 1-based item numberINDEX,NAME,FILE,DURATIONDetails about a specific item

get:cuelist (CueList) response format:

<1-based index>,<name>,<file path>
<1-based index>,<name>,<file path>
...
END

Note: 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,:

ErrorMeaning
ERR,unknown_commandCommand string not recognised
ERR,composition_not_foundNo composition with the given variable name exists
ERR,missing_argumentRequired argument was not supplied
ERR,cue_not_foundThe cue/item index does not exist
ERR,none_playingRequested info about a playing item, but nothing is playing
ERR,no_next_itemnext reached the end of the cue list
ERR,already_at_firstprev called when the first item is already playing
ERR,cuelist_emptynext/prev called on an empty cue list
ERR,unsupported_typeCommand is not applicable to this composition type
ERR,no_projectNo project is currently loaded
ERR,command_failedA tree-path command could not be resolved/executed
ERR,exceptionAn internal error occurred

Example Sessions

Connection test

→ hello
← hallo

Discover 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.0000

Work 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
← OK

Work 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_found

Adjust volume and alpha

→ set:vol,comp_main,75
← OK

→ get:vol,comp_main
← 75

→ set:alpha,comp_main,50
← OK

Show Mode

→ showmode:on
← OK

→ get:showmode
← 1

→ showmode:toggle
← OK

→ get:showmode
← 0

Close the connection

→ exit
← OK

Connecting 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_FUNCTION

SIMPL# 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 fieldValue
Destination IP192.168.1.5
Destination Port8000
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"))    # OK

netcat (quick diagnostics)

bash
nc 192.168.1.5 8100
hello
get:complist
get:status,comp_main
exit

Firewall

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=8100

See 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

Exaplay 3 User Documentation