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 dotted commands (comp1.play, system.showmode=on) — plus the classic comma form (play,comp1) — one per line, 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
ConfigurationMenu → Settings → Communication → TCP Listen
Line terminator\r (CR), \n (LF) or \r\n (CRLF) — empty lines are ignored
EncodingUTF-8 (control characters are removed)

Up to 32 connections are served at once. An idle connection stays open indefinitely; a peer that disappears is detected by TCP keepalive after about 50 s. Each command produces exactly one response line (or multiple lines ending with END for list commands), and the engine never sends anything it was not asked for.

While a project is loading, commands are dropped without a reply (the connection stays open). A controller should read with a timeout and resend.

Changing the Listen Address/Port ​

The TCP Listen config field takes an ADDRESS:PORT entry. Use * as the address to listen on every network interface:

*:8100

To listen on a specific interface only:

192.168.1.5:8100

A change takes effect immediately — the listener re-binds. * is one socket on every address (0.0.0.0): it includes loopback, and an adapter that gets its address later is covered too. A specific address that cannot be bound yet (its network is still coming up, or another program holds the port) is retried every 5 s for up to five minutes.

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.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 — no space after a comma (play, comp1 looks for a composition called comp1).
  • A field in double quotes may contain commas; the quotes are removed: set:cue,comp1,"Act 1, Scene 2" plays the cue named Act 1, Scene 2.
  • The first field is always the command name.
  • Commands that target a composition take a composition reference as the second field (see below).
  • Reply fields are not escaped: in a get:complist line the name is everything after the first comma.

Addressing a composition ​

Wherever a command takes a composition reference (comp_main stands in for it in the tables below), both of these work:

  • The composition's ID (its variable name): comp1, comp2, … — shown in the composition's Inspector (the inline ID field holds the number: ID 11 → comp11) and listed by get:complist. The ID always wins if a name happens to match another composition's ID.
  • The composition's name as shown in the editor: play,Main Show. Matching is case-insensitive, and _ and space are interchangeable (play,main_show works too). If several compositions share a name, the first in project order is used.

In the dotted syntax the same rule applies to the first path component (Main Show.cue.go=Blackout) — except that a name containing a . cannot be used there, because the dot separates path components; use the ID for such names.

For both syntaxes: 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 and a read command returns the value.

Replies that start with ERR, are errors:

  • ERR,command_failed — the path or verb did not resolve, or the action failed (unknown cue, next at the end of a Playlist, a Timeline-only getter on a plain composition, …).
  • A getter may return an error as its value: ERR,none_playing (get:playingitem, get:cuename), ERR,cue_not_found (get:cueinfo=), ERR,no_devices (system.pjlink-all=).
  • With no project loaded every dotted command, system.* included, answers ERR,command_failed.

Always start with the composition ID (or a screen ID, or system). A line whose first component is not a composition, a screen, system or project is applied to the project itself: a mistyped set:audio.volume=80 without a composition answers OK and writes a project value audio.volume instead of changing any volume.

Transport & cue verbs (on a composition) ​

Dotted commandResponseAction
comp_main.playOKStart playback
comp_main.pauseOKPause playback
comp_main.stopOKStop playback
comp_lobby.restartOKPlay from start. Playlist: the current item goes back to its start and plays on — a looped clip starts over without stopping. Timeline: back to 0 and playing. Paused or stopped, it starts
comp_main.set:time=45.5OKSeek to position (seconds). Timeline: the timeline position. Playlist: the time within the current item — the same TIME that get:status reports
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: play the next item (nothing playing → the first playable item). Timeline: jump to the next cue marker and trigger it
comp_lobby.prevOKPlaylist: play the previous item. Timeline: jump to the previous cue marker and trigger it
comp_lobby.media3.playOKPlaylist: play this item (media3 = the item's variable name)
comp_lobby.media3.restartOKPlaylist: play this item from its start. The item that is playing goes back to its start and plays on — a looped clip starts over without stopping; paused, it plays. Any other item starts from its start

cue.go without an argument answers ERR,command_failed. At the end or start of a Playlist, next / prev answer ERR,command_failed (the classic form answers the more specific ERR,no_next_item / ERR,already_at_first).

Setter verbs ​

Dotted commandResponseAction
comp_main.set:vol=80OKSet volume (0–100, decimals allowed; out-of-range values are clamped)
comp_main.set:alpha=50OKSet master alpha (0–100, clamped)
comp_main.set:loop=onOKEnable/disable looping (on/off, true/false, 1/0)
<path>.set:<property>=<value>OKGeneric: set any inspector property, e.g. comp_main.set:audio.volume=80, comp_main.media3.set:warp.enabled=1, comp_main.media3.set:text.content=Now boarding (live text layer update)

Multi-client: on a master, a set: on a composition or anything inside it (a clip, a track, …) — and the classic set:vol / set:alpha / set:loop — is passed on to every client, like an edit in the Inspector. system.set: values are this machine's own and stay here. Sent over UDP the same lines behave the same way.

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.

Decimals land as decimals. The value is coerced to the property's own type — on/true/1 for a switch, the text for a text property — and a number keeps every digit you send, whatever the property currently holds: comp_main.media1.set:realtime.params.u_audio=0.62 writes 0.62, not 0. (Earlier builds truncated a fractional value whenever the property's stored value happened to be a whole number — which is what made a generator parameter driven from a desk, a curve or a tracker appear not to move at all.)

The same <path>.<property> addressing drives a property from a timeline curve: a Data Track with the Exaplay value output writes its (scaled) value to e.g. comp_main.media3.realtime.params.u_speed on every change while the composition plays — see Data Tracks.

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, truncated)
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. Numbers come back as stored — get:alpha may answer 37.5 where the classic get:alpha answers 37

get:duration, get:playingitem, get:cuename and get:cuelist need a Timeline or a Playlist; on a plain composition they answer ERR,command_failed.

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, waking and powering other PCs, PJLink projector control, and (via the generic set:/get:) the output configuration.

Dotted commandResponseAction
system.showmode=on / =off / =toggleOKSet global Show Mode (1/0, true/false work too)
system.get:showmode1 or 0Query Show Mode
system.blank=on / =off / =toggleOKBlank every Output to black (and back) over the blank fade time. Add a fade in seconds to use another one, which also becomes the blank fade time: system.blank=on,3 (0–60)
system.get:blank1 or 0Query Blank
system.blankfade=<seconds>OKSet the blank fade time without blanking (0–60; 0 = hard cut)
system.get:blankfadee.g. 1.00Query the blank fade time
system.audiomute=on / =off / =toggleOKMute the show's audio (on a multi-client master: every machine)
system.get:audiomute1 or 0Query audio mute
system.identify=on / =off / =toggleOKLabel every Output on the wall (Identify)
system.get:identify1 or 0Query Identify
system.playallOKStart all compositions
system.stopallOKStop all compositions
system.restartOKRestart the engine and reload the current project (same as =project, =reload, =default)
system.restart=cleanOKRestart the engine with no project loaded (same as =empty, =blank); any other argument answers ERR,command_failed
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. Over TCP/UDP only a projector configured under Control → TCP Devices at that IP (and PORT, default 4352) is reached, with its stored password — a PASSWORD in the line is ignored, and any other address answers ERR,command_failed. ERR,command_failed also if the projector cannot be reached or refuses
system.pjlink-all=on / =offOK,<succeeded>/<total>Power all configured PJLink projectors (device list = project value pjlink_devices, synced by the Devices tab). 1/0, true/false work too. Answers OK,0/3 when every projector failed and ERR,no_devices when none is configured
system.pc=<name>,wake / ,restart / ,shutdownOK,sentWake (Wake-on-LAN), restart or shut down a PC from Control → System → PCs, named by its name (case-insensitive) or id. on/reboot/off work too. The line answers as soon as the command is on its way; the outcome is logged and shown in the Monitor. In a multi-client fleet only the master sends, so a cue line does not power a PC once per machine. An unknown action answers ERR,command_failed
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 (outputs, target, …)

PJLink examples (port defaults to 4352; the projector at that address must be configured under Control → TCP Devices, whose password is used):

→ 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,,4352
← OK

→ system.pjlink=10.0.0.99,POWR,1          (not a configured projector)
← ERR,command_failed

→ 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:image.alpha=50 — a clip's opacity is image.alpha), an action control is the composition verb (comp_main.play).

Screen verbs (a screen's calibration) ​

A screen is addressed by its ID — screen1, screen2, … (shown in the screen's Inspector; an ID is never re-used, so a rename or a deleted screen never redirects a command). Three switches act on the screen's VIOSO calibration, the same three as the Calibration, Blending and Black level switches in the calibration's Edit… dialog:

Dotted commandResponseAction
screen1.calibration=on|off|toggleOKDraw the screen's calibration, or not. Off draws the screen as if no calibration file were set — Exaplay's own warping, overlap & blend and black level apply. The calibration stays loaded, so on applies in the next frame
screen1.calibration.blend=on|off|toggleOKThe calibration's edge blending (blending works too)
screen1.calibration.black=on|off|toggleOKEvery black-level compensation that rides on the calibration: the calibration's own black level, a custom black level and the overall lift (blacklevel works too)
screen1.get:calibration / screen1.get:calibration.blend / screen1.get:calibration.black1 or 0Query a switch
screen1.calibration.reloadOKReapply: build the calibration again from its file. The current calibration keeps drawing until the new one is ready, then the two are swapped in one frame

1/0, true/false and yes/no work for on/off. A switch applies in exactly one frame on every output of the screen, with no frame in between; in a multi-client rig every PC switches at the same moment (see Multi-Client). The switches are part of the show: they are saved with the project and, on a master, sent to every client. On a client the line answers ERR,command_failed — switch the master. The generic screen1.set:calibration.blend=0 is the same command.

Webcapture verbs (a web page clip) ​

A Webcapture clip (a web page as a live source) takes the browser actions of its Set Live Parameters dialog as tree-path verbs, so a command button, a cue line, a TCP line or an Inputs action can drive the page:

Dotted commandResponseAction
comp1.media3.webcapture.reloadOKReload the page
comp1.media3.webcapture.back / .forwardOKBrowser history back / forward
comp1.media3.webcapture.stopOKStop loading
comp1.media3.webcapture.click=0.25,0.6OKClick the page at x,y — each 0 … 1 across the page (0,0 top left). Out of range answers ERR,command_failed
comp1.media3.webcapture.clickOKClick at the clip's stored click position (webcapture.click.x / .y, the dialog's Click Position). Drive the two values from an input (comp1.media3.set:webcapture.click.x=0.4, or an Inputs Set a value mapping on a touch surface's X / Y) and fire this on its press: a touch field on the page
comp1.media3.webcapture.js=document.body.style.zoom='150%'OKRun JavaScript in the page (everything after js= is the script)

The line answers OK when the clip accepted the command; the browser acts a moment later, on the clip's own thread. A clip whose page is not running (stopped, or Render page off) drops the command and logs it. A cue runs on every machine of a multi-client rig, so each PC's own browser gets the line; sent over TCP or from an Inputs action the line reaches the machine that ran it.

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

→ 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. A dotted line must not contain > anywhere, not even in the value: protocol-prefixed lines (OSC>, TCP>, …) are cue-script actions, not remote commands, and any line with a > answers ERR,unknown_command.

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.

CommandDotted equivalentResponseDescription
hello—halloConnection test / keep-alive
get:versystem.get:verExaplay,<version>Get engine version string
get:complist—One line per composition, then ENDList all compositions
get:projnamesystem.get:projname<project name>Get the loaded project name
showmode:onsystem.showmode=onOKEnable global Show Mode
showmode:offsystem.showmode=offOKDisable global Show Mode
showmode:togglesystem.showmode=toggleOKToggle global Show Mode
get:showmodesystem.get:showmode1 or 0Get current Show Mode state
exit or quit—OKClose 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,COMPID—OKStart playback
pause,COMPID—OKPause playback
stop,COMPID—OKStop playback
set:cuetime,COMPID,SECONDSSECONDS — floatOKSeek to position (seconds). On a Playlist, the time within the current item (as get:status reports it). A stopped composition stays stopped

Audio ​

CommandArgumentsResponseDescription
get:vol,COMPID—<0–100>Get volume (integer %, truncated)
set:vol,COMPID,VALUEVALUE — number 0–100 (out-of-range values are clamped)OKSet volume

Opacity ​

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

Playback Options ​

CommandArgumentsResponseDescription
get:loop,COMPID—0 or 1Get loop state
set:loop,COMPID,VALUEVALUE — on/off, true/false or 1/0OKEnable/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>Playlist: number of items. On a Timeline, clips placed on tracks are not counted
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
TIMEseconds, 4 decimalsTimeline: timeline position. Playlist: position within the current item
FRAMEintegerCurrent frame number (TIME × 60)
CUEINDEXintegerTimeline: active cue index. Playlist: 1-based current item, 0 if none
TOTALseconds, 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.

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,COMPID—One line per cue, then ENDList all cues
get:cueinfo,COMPID,INDEXINDEX — cue variable index (index only; use COMPID.get:cueinfo=NAME for a name)INDEX,NAME,OFFSET,ACTIVEDetails about a specific cue

The classic next, / prev, are Playlist-only; on a Timeline use the dotted comp_main.next / comp_main.prev, which jump to the next / previous cue marker.

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 (Playlist). On another type they answer ERR,unknown_command.

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,COMPID—OKAdvance to the next item. Nothing playing → start the first item
prev,COMPID—OKGo back to the previous item. Nothing playing → start the last item
get:cuelist,COMPID—One line per item, then ENDList all items
get:cueinfo,COMPID,INDEXINDEX — 1-based item number (index only)INDEX,NAME,FILE,DURATIONDetails about a specific item

next / prev step from the item that was last triggered, even if it has not started yet — two quick next commands advance two items.

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.


Inputs (tracking channels) ​

A controller can feed an Inputs channel over the same connection:

CommandMeaning
track,CHANNEL,X[,Y]Position (Y optional)
track,CHANNEL,x,VALUEX only
track,CHANNEL,y,VALUEY only
track,CHANNEL,click[,VALUE]Click (value defaults to 1)

Answers OK, or ERR,track_expected_channel / ERR,track_expected_value / ERR,track_bad_args.


Error Responses ​

All error responses begin with ERR,:

ErrorMeaning
ERR,unknown_commandCommand string not recognised — also a composition command sent without its composition (play), a type-specific command on the wrong composition type, or a dotted line containing >
ERR,composition_not_foundNo composition matches the given ID or name (also: a global command with extra fields, e.g. get:ver,x). Without a loaded project, classic commands answer this too
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_typeget:playingitem, get:cuename or get:duration on a plain composition (neither Timeline nor Playlist)
ERR,no_projectClassic get:projname with no project loaded (a dotted command answers ERR,command_failed then)
ERR,no_devicessystem.pjlink-all with no PJLink projector configured
ERR,command_failedA 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_argsA malformed track,… line (see Inputs)
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

→ comp_main.play              (classic: play,comp_main)
← OK

→ comp_main.get:status        (classic: get:status,comp_main)
← 1,12.3456,740,0,0

Work with a CueList ​

→ comp_lobby.get:type
← cuelist

→ comp_lobby.get:cuelist
← 1,Opening,/media/opening.mp4
← 2,Act 1,/media/act1.mp4
← 3,Closing,/media/closing.mp4
← END

→ comp_lobby.cue.go=2         (classic: set:cue,comp_lobby,2)
← OK

→ comp_lobby.get:playingitem
← 2,Act 1,/media/act1.mp4

→ comp_lobby.next             (classic: next,comp_lobby)
← OK

Work with a Timeline ​

→ comp_main.get:type
← timeline

→ comp_main.get:cuelist
← 1,Intro,0.0000
← 2,Scene A,30.0000
← 3,Scene B,90.0000
← END

→ comp_main.cue.go=2          (classic: set:cue,comp_main,2)
← OK

→ comp_main.cue.go=Scene B    (classic: set:cue,comp_main,Scene B)
← OK

→ comp_main.cue.go=Scene C
← ERR,command_failed          (classic set:cue answers ERR,cue_not_found)

Adjust volume and alpha ​

→ comp_main.set:vol=75        (classic: set:vol,comp_main,75)
← OK

→ comp_main.get:vol
← 75

→ comp_main.set:alpha=50      (classic: set:alpha,comp_main,50)
← OK

Show Mode ​

→ system.showmode=on          (classic: showmode:on)
← OK

→ system.get:showmode         (classic: get:showmode)
← 1

→ system.showmode=toggle
← OK

→ system.get:showmode
← 0

Close the connection ​

→ exit
← OK

Connecting from Common Show Control Systems ​

Production-oriented, independently versioned examples are installed with Exaplay in its integrations folder (by default C:\Program Files\Exaplay 3\integrations, also reachable from the Start menu as Show-control integrations) — or download them in any browser that can open Exaplay under Control → Integrations. It includes a Q-SYS plugin (.qplug), Crestron SIMPL+ modules with their SIMPL# library (.clz), Q-SYS Lua, AMX NetLinx, QLab/CueLab, ETC Eos, a Bitfocus Companion module, an Extron Global Scripter driver, Medialon and Alcorn McBride recipes, a Node-RED flow, TouchDesigner and Home Assistant templates, and generic TCP/OSC clients, with compatibility metadata, validation labels, reconnect behavior, framing, response parsing, and complete transport/cue actions. Each carries a validation label saying what was actually tested — most are illustrative source reviewed against this API rather than code compiled in the vendor's tooling.

The snippets below are retained as a quick orientation only. They are illustrative and have not been validated in the vendors' authoring tools; use the corresponding integration README before deployment.

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. The recipes in integrations\medialon and integrations\alcorn-mcbride list every string with its reply, the line-ending rules, and the OSC bytes for the blank (over TCP the blank is system.blank=, see System verbs).

Crestron / AMX ​

Crestron: ready-made SIMPL+ modules ship in that folder at integrations\crestron\simpl\ — Exaplay 3 Engine (connection, Show Mode, projectors, composition list, console) and Exaplay 3 Composition (one per Timeline or Playlist: transport, cues, volume), together with the SIMPL# library they load, ExaplayCrestron.clz, for 4-Series processors and VC-4. Copy the three files into the SIMPL Windows program folder, give both symbols the same Engine_ID, and wire joins: Playing_Fb, Position$, Active_Cue$, Show_Mode_Fb and the rest come back from the engine on their own. The library is built with Crestron's SDK and its protocol logic is covered by automated tests, but the modules have not been compiled in SIMPL Windows, run on a processor, or pointed at a live engine — read integrations\crestron\VERIFICATION.md before relying on them in a show. A SIMPL# Pro program can use the same library directly (see the folder's README).

AMX, or your own Crestron code: open a TCP client to port 8100 and send ASCII lines ending in \r\n; read state back by polling comp_main.get:status.

csharp
// SIMPL# Pro, by hand
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);

Send("comp_main.play");
Send("comp_main.cue.go=3");
Send("comp_main.next");

Q-SYS (QSC) ​

A ready-made Q-SYS plugin ships in that folder at integrations\qsys\ (exaplay.qplug): drop it into Documents\QSC\Q-SYS Designer\Plugins\ and it appears in the schematic library as Exaplay → Exaplay 3 Media Server, with transport, cues, volume, Show Mode, PJLink, status readbacks and a command console. Its Lua is covered by automated tests, but it has not been run in Q-SYS Designer, on a Core, or against a live engine — read its VERIFICATION.md for the commissioning checklist before you rely on it in a show.

A recorded walkthrough shows the Exaplay side of the setup — the address the plugin connects to, a test command before the Core is online, and the Core's traffic in the Monitor: ☰ menu → Walkthroughs → Inputs & show control → Q-SYS drives the show.

The snippet below is the minimal alternative. Q-SYS cores run a Lua 5.3 environment with a built-in TcpSocket object; this 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("comp_main.play")  end
Controls["Stop"].EventHandler  = function() send("comp_main.stop")  end
Controls["Next"].EventHandler  = function() send("comp_main.next")  end
Controls["SetCue"].EventHandler = function()
  send(string.format("comp_main.cue.go=%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)
Argumentcue index (integer) or cue name (string)

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>/frame <n>     → Timeline: jump to frame n
/exaplay/global/start         → start ALL compositions
/exaplay/global/stop          → stop ALL compositions
/exaplay/global/showmode <on|off|toggle>
/exaplay/global/videomute <on|off|toggle> [<fade seconds>]   → blank the outputs to black
/exaplay/global/blankfade <seconds>                          → set the blank fade time

A string argument fires a cue by name within that composition (/exaplay/comp_main/cue "My Cue"). To search every composition for a name, use CueLab's Web Request action instead: Method POST, URL http://192.168.1.5:8123/cue/trigger, body {"name":"My Cue"}. Full address list: OSC Reference.

Python (testing / scripting) ​

python
import socket

HOST = "192.168.1.5"
PORT = 8100

with socket.create_connection((HOST, PORT), timeout=5) as sock:
    reader = sock.makefile("r", encoding="utf-8", newline="\r\n")

    def send(cmd, multiline=False):
        sock.sendall((cmd + "\r\n").encode())
        if not multiline:
            return reader.readline().strip()
        lines = []                      # list replies end with END
        while (line := reader.readline().strip()) != "END":
            lines.append(line)
        return lines

    print(send("hello"))                        # hallo
    print(send("get:complist", multiline=True)) # ['comp_main,Main Show']
    print(send("comp_main.play"))               # OK

netcat (quick diagnostics) ​

bash
nc 192.168.1.5 8100
hello
get:complist
comp_main.get:status
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