guide
connecting to the websocket server

this one is a bit technical! if you’re only looking into getting the Stream Deck plugin to work, refer to the Stream Deck guide.

if you’re looking into writing your own integration for veadotube, you can use the local websocket server hosted by veado.

finding the server

if you don’t have the server address, and would rather see the list of open veadotube instances and their respective websocket addresses (if any), check inside the instances folder (refer to the data folder docs for that); said folder won’t change even if the user sets a custom save folder, so you can rely on that.

inside the folder, each file represents a different instance. the file name is the instance id, which can be parsed into three segments separated by dashes:

  • instance type, which should be mini for veadotube mini, live for veadotube live, editor for veadotube editor;
  • launch timestamp, hex number, always 16 characters;
  • process id, hex number, always 8 characters.

only the instance type should be useful for you, depending on which program type you’re targeting! the other segments are for differentiating between instances and ensuring each instance has an unique id.

the file contents represent a JSON object with the following keys:

  • time: unix timestamp;
  • name: display name for the instance;
  • server: the websocket address;
  • id: the instance id, again;
  • version: the app version;
  • language: the current language being used.

these last 3 keys aren’t present in mini 2.0a and older.

you should ignore instances with a unix timestamp that’s too old – internally we use 10 seconds as the time limit. instances should clean their instance files before leaving, and new instances should be able to clean old instance files automatically too :]

connecting to the server

let’s say you’re hosting at 127.0.0.1:2424. in order to connect to it, you should be using some sort of websocket client, obviously; the address you’re gonna try and connect would be like ws://127.0.0.1:2424?n=Name, where Name would be the display name of your client (e.g. if you’re developing a plugin for the Stream Deck, the name here would be simply “Stream Deck”).

channels and messages

all messages that go through the websocket are prefixed with an id that represents which channel this message is being sent through, followed by a colon. for example, a message that comes from or goes to the nodes channel would look like nodes:{"event":"list"}.

in this guide, a green box of code tells you what you can send to veadotube, and a red box tells you what to expect to receive.

instance

the instance channel reports on the state of the current veadotube instance. to request information about the instance, you can send:

{
	"event": "info"
}

which gets you the same JSON object you’d get by inspecting the instance file, minus the unix timestamp:

{
	"event": "info",
	"name": "veadotube mini",
	"id": "mini-xxxxxxxxxxxxxxxx-xxxxxxxx",
	"version": "2.1",
	"language": "en",
	"server": "127.0.0.1:2424"
}

the server should also send you this message upon connection.

nodes

the nodes channel is where websocket nodes in the node system are exposed. to list all available nodes:

{
	"event": "list"
}

which gets you something like this:

{
	"event": "list",
	"entries": [
		{ "type": <node type>, "id": <node id>, "name": <node display name> },
		{ "type": <node type>, "id": <node id>, "name": <node display name> },
		...
	]
}

in mini, the only exposed nodes are:

  • type stateEvents, id mini: controls the avatar state stack;
  • type boolean, id mini: controls push-to-talk.

you can assume that and not rely on receiving a list event.

to send payload data to a node, you’d send the following:

{
	"event": "payload",
	"type": <node type>,
	"id": <node id>,
	"payload": <payload data>
}

you’d be receiving, then, more payload like so:

{
	"event": "payload",
	"type": <node type>,
	"id": <node id>,
	"name": <node display name>,
	"payload": <payload object or value>
}

stateEvents payloads

to get a list of states:

{ "event": "list" }
{
	"event": "list",
	"states": [
		{ "id": <state id>, "name": <state display name>, "thumbHash": <hash for the thumbnail image> },
		{ "id": <state id>, "name": <state display name>, "thumbHash": <hash for the thumbnail image> },
		...
	]
}

in mini 2.0a and older, state ids and display names are different from each other, and thumbnail hashes aren’t present; starting with mini 2.1, both the state id and name should be the same value.

to get the current state on the top of the stack:

{ "event": "peek" }
{
	"event": "peek",
	"state": <state id>
}

you can also automatically receive the previous messages on list/state change by sending listen events:

{ "event": "listen", token: <unique id for the listener> }
{ "event": "unlisten", token: <unique id for the listener> }

listening to a node in mini 2.0a and older won’t get you list updates automatically.

to get the thumbnail image for a state:

{ "event": "thumb", "state": <state id> }
{
	"event": "thumb",
	"state": <state id>,
	"hash": <hash for the thumbnail image>
	"width": <image width>,
	"height": <image height>,
	"png": <base64 encoded image data>
}

to modify the state stack:

{ "event": "set", "state": <state id> }
{ "event": "push", "state": <state id> }
{ "event": "pop", "state": <state id> }
{ "event": "toggle", "state": <state id> }

note: set clears the stack and pushes a given state to the stack. pop is able to remove a given state from the stack even if it’s not from the top. toggle pushes a state if it’s not on the stack, and pops if it is.

boolean payloads

to get the current value, or listen to value changes:

{ "event": "get" }
{ "event": "listen", token: <unique id for the listener> }
{ "event": "unlisten", token: <unique id for the listener> }

the payload you receive should always be a single boolean value:

true
false

to modify the value:

{ "event": "set", "value": true }
{ "event": "set", "value": false }
{ "event": "toggle" }

number payloads

to get the current value, or listen to value changes:

{ "event": "get" }
{ "event": "listen", token: <unique id for the listener> }
{ "event": "unlisten", token: <unique id for the listener> }

the payload you receive might contain a range of valid values as well:

{ "value": <number value> }
{
	"value": <number value>,
	"min": <minimum possible value>,
	"max": <maximum possible value>
}

to set a new value:

{ "event": "set", "value": <number value> }
{ "event": "set", "value": { "value": <number value> } }
{ "event": "set", "value": { "value": <number value>, "min": <minimum possible value>, "max": <maximum possible value> } }

to add to the current value (if no range is specified, previously set range is preserved):

{ "event": "add", "value": <number value> }
{ "event": "set", "value": { "value": <number value> } }
{ "event": "add", "value": { "value": <number value>, "min": <minimum possible value>, "max": <maximum possible value> } }

when modifying the value, if a range is declared, the final value is always clamped to it.