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

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.

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
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 format COMMAND,COMPID[,ARGUMENT].

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
get:cuelist,COMPIDOne line per cue, then ENDList all cues
get:cueinfo,COMPID,INDEXINDEX — cue variable indexINDEX,NAME,OFFSET,ACTIVEDetails about a specific cue

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

Adjust volume and alpha

→ set:vol,comp_main,75
← OK

→ get:vol,comp_main
← 75

→ set:alpha,comp_main,50
← OK

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>/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"))    # 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