├── .gitignore
├── .prettierrc
├── examples
├── config.json.example
├── pro6-cli
└── ProRemote.html
├── yaml2md
├── README.md
├── yaml2asyncapi
├── Pro6
└── api.yaml
├── Pro6.md
└── Pro7.md
/.gitignore:
--------------------------------------------------------------------------------
1 | config.json
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "useTabs": false
4 | }
5 |
--------------------------------------------------------------------------------
/examples/config.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "HOST" : "localhost",
3 | "PORT" : "60157",
4 | "CONTROL_PASSWORD" : "control",
5 | "OBSERVE_PASSWORD" : "observe",
6 | "STAGEDISPLAY_PASSWORD" : "stagedisplay"
7 | }
--------------------------------------------------------------------------------
/yaml2md:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import yaml
5 |
6 | output = ''
7 |
8 |
9 | def e(s='', end='\n'):
10 | global output
11 | output += s + end
12 |
13 |
14 | def format_type(ttype):
15 | if ttype not in ['bool', 'int', 'float', 'string', 'object', 'list']:
16 | if type(ttype) is list:
17 | retval = []
18 | for t in ttype:
19 | retval.append(format_type(t))
20 | return '|'.join(retval)
21 |
22 | if ttype in types:
23 | link_to = ttype.lower()
24 | ttype = f'[`{ttype}`](#{link_to})'
25 | if 'list[' in ttype:
26 | inner_type = ttype.replace('list[', '')[:-1]
27 | link_to = inner_type.lower()
28 | ttype = f'list[[`{inner_type}`](#{link_to})]'
29 | else:
30 | ttype = f'`{ttype}`'
31 | return ttype
32 |
33 |
34 | if len(sys.argv) == 1:
35 | print(f'''
36 | USAGE:
37 |
38 | {sys.argv[0]} yaml_file output_name
39 |
40 | Will parse an API yaml file to Markdown.
41 | ''')
42 | exit()
43 |
44 | source = sys.argv[1]
45 | target = source + '.md'
46 | target = target.replace('.yaml.md', '.md')
47 | target = target.replace('.yml.md', '.md')
48 |
49 | if len(sys.argv) == 3:
50 | target = sys.argv[2]
51 |
52 | # read input yaml file
53 | with open(source, 'r') as f:
54 | t = f.read()
55 | data = yaml.load(t, Loader=yaml.CLoader)
56 |
57 |
58 | # handle metadata
59 | e(f'# {data["application"]}')
60 | e()
61 | e(f'{data["description"]}')
62 |
63 | # handle channels
64 | for channelkey in data['channels']:
65 | channel = data['channels'][channelkey]
66 | name = channel.get('name', channelkey).strip()
67 | desc = channel['description'].strip()
68 |
69 | e(f'\n## {name}')
70 | e()
71 | e(desc)
72 |
73 | types = channel.get('types', {})
74 | messages = channel.get('messages', {})
75 | actions = channel.get('actions', {})
76 | undocumented = channel.get('undocumented', {})
77 |
78 | # output the typed data definitions
79 | e(f'\n### {name} Types')
80 | for typekey in types:
81 | t = types[typekey]
82 | tname = t.get('name', typekey).strip()
83 | ttype = t['type']
84 | tdesc = t.get('description', '').strip()
85 | texample = t.get('example', '').strip()
86 | formatted_type = format_type(ttype)
87 |
88 | e(f'\n#### {tname}')
89 | e(f'Base Type: {formatted_type}')
90 | e()
91 |
92 | if tdesc != '':
93 | e(tdesc)
94 | e()
95 |
96 | if ttype == 'object':
97 | # print(t)
98 | e('##### PARAMETERS:\n')
99 |
100 | for paramname in t['parameters']:
101 | param = t['parameters'][paramname]
102 | pdesc = f'always `{param}`'
103 | pexample = ''
104 | poptions = None
105 | if type(param) is str:
106 | ptype = 'string'
107 | elif type(param) is int:
108 | ptype = 'int'
109 | elif type(param) is float:
110 | ptype = 'float'
111 | elif type(param) is bool:
112 | ptype = 'bool'
113 | elif type(param) is list:
114 | ptype = 'list'
115 | poptions = param
116 | else:
117 | ptype = param.get('type', 'string')
118 | pdesc = param.get('description', '')
119 | poptions = param.get('options', None)
120 | pexample = param.get('example', '')
121 |
122 | if ptype == 'union':
123 | ptype = param.get('types', 'union')
124 |
125 | pformatted_type = format_type(ptype)
126 | e(f'- **{paramname}** ({pformatted_type})')
127 | extras = []
128 | if (poptions is not None):
129 | poptions = [f'`{x}`' for x in poptions]
130 | extras.append(f' Must be one of these: {poptions}')
131 | if (pdesc != ''):
132 | # need to indent every description line
133 | for line in pdesc.split('\n'):
134 | extras.append(f' {line}')
135 | if (pexample != ''):
136 | if ptype == 'string':
137 | pexample = f'"{pexample}"'
138 | if '`' not in pexample:
139 | pexample = f'```{pexample}```'
140 | extras.append(f' {pexample}')
141 | if len(extras) > 0:
142 | e()
143 | e('\n'.join(extras))
144 | e()
145 | if texample != '':
146 | if '`' not in texample:
147 | texample = f'```{texample}```'
148 |
149 | e(f'\n##### EXAMPLE:\n')
150 | e(texample)
151 | e()
152 | e('********************************\n')
153 |
154 | # output the list of pushy messages (with anchor links)
155 | # output the action messages with their responses
156 | # output the response-only messages
157 |
158 | print(output)
159 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ProPresenter-API
2 |
3 | ## 7.9 UPDATE & PUBLIC API
4 |
5 | The upcoming 7.9 release of ProPresenter will include an officially supported public API.
6 |
7 | The official documentation page is here:
8 |
9 | https://renewedvision.com/propresenter/api/
10 |
11 | The full auto-generated Swagger HTML is a bit easier to navigate, and it is here:
12 |
13 | https://renewedvision.com/api_spec/index.html
14 |
15 | And, the actual source OPENAPI spec document (JS) is here:
16 |
17 | https://renewedvision.com/api_spec/swagger.json
18 |
19 | But I'm mirroring it here as a formatted JSON document too.
20 |
21 | [pro7.9.openapi-spec.json](https://github.com/jeffmikels/ProPresenter-API/blob/master/pro7.9.openapi-spec.json)
22 |
23 | ## NETWORK LINK
24 |
25 | For information on the ProPresenter Network Link protocol, see my other repo here:
26 |
27 | https://github.com/jeffmikels/pro_presenter_network_link_tools
28 |
29 | ## REVERSE-ENGINEERED STAGE DISPLAY & REMOTE DOCUMENTATION
30 |
31 | Documenting RenewedVision's undocumented network protocols with examples
32 |
33 | See below for changes to this repository:
34 |
35 | For ProPresenter 6, go here:
36 | https://jeffmikels.github.io/ProPresenter-API/Pro6/
37 |
38 | For ProPresenter 7, go here:
39 | https://jeffmikels.github.io/ProPresenter-API/Pro7/
40 |
41 | For a Node Library implementing this protocol, go here:
42 | https://github.com/utopiantools/node-propresenter.git
43 |
44 | or
45 |
46 | ```
47 | npm install https://github.com/utopiantools/node-propresenter.git
48 | ```
49 |
50 | For an example of a real app built with node.js that uses this API to turn ProPresenter into a universal remote control:
51 | https://github.com/jeffmikels/propresenter-watcher
52 |
53 | For documentation regarding the new Pro7 file format:
54 | https://github.com/greyshirtguy/ProPresenter7-Proto
55 |
56 | ## CHANGES
57 |
58 | Recently, I have begun to migrate all the documentation into machine-readable formats
59 | so that as the Pro7 API improves, we will be able to more readily track the changes.
60 |
61 | It is also my hope that this effort will inspire RenewedVision to join the documentation
62 | effort directly.
63 |
64 | ## API METHODOLOGY
65 |
66 | AsyncAPI is a great system for defining APIs and for generating code and documentation, but editing it by hand
67 | seemed cumbersome to me, so I built my own solution.
68 |
69 | 1. I converted our former Markdown documentation files into a `api.yaml` format that is inspired by AsyncAPI, but is more easily edited.
70 | 2. I wrote a Python script to automatically convert my `api.yaml` files into AsyncAPI 2.1.0 files.
71 | 3. I use the AsyncAPI command line generator commands to render out html files.
72 |
73 | ## API.YAML SPECIFICATIONS
74 |
75 | The `api.yaml` file begins with a section called `asyncapi`. That section may contain anything an AsyncAPI `2.1.0` file may contain.
76 |
77 | Then, there is a `types` section that holds defined data types similar to the way AsyncAPI does schemas. These will be translated into AsyncAPI schema nodes.
78 |
79 | Types are structured as follows:
80 |
81 | ```yaml
82 | typename:
83 | type: |-
84 | can be a built-in type like
85 |
86 | `integer`, `string`, `number`, `list`, `list[othertype]`, or `object`
87 |
88 | or it can be another defined type or list[othertype]
89 | parameters:
90 | field_1:
91 | type: typename
92 | field_2: constantvalue
93 | field_3:
94 | type: typename
95 | options: [option1, option2, option3]
96 | description: "each field can have a description"
97 | examples: ["each field can have an example"]
98 | example:
99 | field_1: an arbitrary value
100 | field_2: constantvalue
101 | field_3: option1
102 | ```
103 |
104 | Then, there is the `channels` section which defines the main application message endpoints. ProPresenter exposes two channels, `/remote` and `/stagedisplay`.
105 |
106 | Each `channel` is structured as follows:
107 |
108 | ```yaml
109 | channels:
110 | channelname:
111 | summary: Simple name for this channel
112 | description: |
113 | Block markdown content describing this channel in detail
114 | actions: "list of actions, see below"
115 | ```
116 |
117 | Each `channel` has a list of `actions` that can be sent or received.
118 |
119 | Actions may contain a `message` and or `response` and are structured as follows (NOTE: `payload` and `example` follow the same structure as a type, and the `example` must match the payload structure):
120 |
121 | ```yaml
122 | actionName:
123 | summary: simple name for this action
124 | description: markdown text describing this action
125 | message:
126 | payload:
127 | action: actionName
128 | field_2: someConstantValue
129 | field_3:
130 | type: string
131 | options: ["1", "2", "3"]
132 | example: "2"
133 | description: textual description
134 | example:
135 | response:
136 | payload:
137 | example:
138 | ```
139 |
140 | # CONTRIBUTING
141 |
142 | To contribute, please just edit the `api.yaml` file. I will regenerate the other documentation myself until I get Github actions working.
143 |
--------------------------------------------------------------------------------
/examples/pro6-cli:
--------------------------------------------------------------------------------
1 | #!/usr/local/bin/python3
2 |
3 | import json
4 | import sys
5 | import time
6 | import click # pip install click
7 | import websocket # pip install websocket-client
8 |
9 |
10 | class Pro6:
11 |
12 | def __init__(self, host='', port='', password='', type='remote'):
13 |
14 | self.password = password
15 | self.connected = False
16 | self.controlling = False
17 |
18 | if type == 'remote':
19 | self.wsurl = "ws://{}:{}/{}".format(host, port, 'remote')
20 | self.ws = websocket.create_connection(self.wsurl, timeout=2)
21 | # self.ws.on_message = on_message
22 | # self.ws.on_error = on_error
23 | # self.ws.on_close = on_close
24 | # self.ws.on_open = on_open
25 | self.connected = True
26 | self.authenticate()
27 |
28 | elif type == 'stagedisplay':
29 | self.wsurl = "ws://{}:{}/{}".format(host, port, 'stagedisplay')
30 | self.ws = websocket.WebSocketApp(self.wsurl)
31 | self.ws.on_message = self.on_message
32 | self.ws.on_error = self.on_error
33 | self.ws.on_open = self.auth_stagedisplay
34 | self.ws.run_forever()
35 |
36 | def on_message(self, ws, message):
37 | print(message)
38 |
39 | def on_error(self, ws, error):
40 | print(error)
41 |
42 | def auth_stagedisplay(self, ws):
43 | cmd = {"pwd":self.password,"ptl":"610","acn":"ath"}
44 | ws.send(json.dumps(cmd))
45 |
46 | def send(self, obj, awaitAnswer=True):
47 | if self.connected:
48 | s = json.dumps(obj)
49 | # print('SENDING ', s)
50 | try:
51 | self.ws.send(s)
52 | if awaitAnswer:
53 | return json.loads(self.ws.recv())
54 | except websocket._exceptions.WebSocketTimeoutException:
55 | return {"error":"Websocket Timeout. We expected a response, but got nothing. This happens whenever ProPresenter received an invalid command and ignored it."}
56 | else:
57 | return {"error":"Websocket Not Connected"}
58 |
59 | def authenticate(self):
60 | cmd = {
61 | 'action': 'authenticate',
62 | 'protocol': '600',
63 | 'password': self.password
64 | }
65 | return self.send(cmd)
66 |
67 | def get_library(self):
68 | cmd = {"action":"libraryRequest"}
69 | return self.send(cmd)
70 |
71 | def get_playlists(self):
72 | cmd = {"action":"playlistRequestAll"}
73 | return self.send(cmd)
74 |
75 | def get_presentation(self, path):
76 | cmd = {
77 | "action": "presentationRequest",
78 | "presentationPath": path,
79 | "presentationSlideQuality": 0, # disable slide images
80 | }
81 | return self.send(cmd)
82 |
83 | def get_current_presentation(self):
84 | cmd = {
85 | "action": "presentationCurrent",
86 | "presentationSlideQuality": 0, #disable slide images
87 | }
88 | return self.send(cmd)
89 |
90 | res = self.get_slide_index()
91 | if 'slideIndex' in res:
92 | res = self.trigger_slide(res['slideIndex'])
93 | if 'presentationPath' in res:
94 | return self.get_presentation(res['presentationPath'])
95 |
96 | def get_slide_index(self):
97 | cmd = {"action":"presentationSlideIndex"}
98 | return self.send(cmd)
99 |
100 | def trigger_slide(self, n):
101 | cmd = {"action":"presentationTriggerIndex","slideIndex":n}
102 | return self.send(cmd)
103 |
104 | def next_slide(self):
105 | curSlide = 0
106 | numSlides = 0
107 | res = self.get_current_presentation()
108 | for group in res['presentation']['presentationSlideGroups']:
109 | numSlides += len(group['groupSlides']);
110 |
111 | res = self.get_slide_index()
112 | curSlide = int(res['slideIndex'])
113 | if curSlide < numSlides-1:
114 | return self.trigger_slide(curSlide + 1)
115 | else:
116 | return self.trigger_slide(0)
117 |
118 | def prev_slide(self):
119 | curSlide = 0
120 | numSlides = 0
121 | res = self.get_current_presentation()
122 | for group in res['presentation']['presentationSlideGroups']:
123 | numSlides += len(group['groupSlides']);
124 |
125 | res = self.get_slide_index()
126 | curSlide = int(res['slideIndex'])
127 | if curSlide > 0:
128 | return self.trigger_slide(curSlide - 1)
129 |
130 |
131 | @click.command()
132 | @click.option('--action', default='', help='send a specific action')
133 | @click.option('--send', default=None, help='send a raw JSON command')
134 | @click.option('--next', is_flag=True, help='Go to the next slide.')
135 | @click.option('--prev', is_flag=True, help='Go to the previous slide.')
136 | @click.option('--trace', is_flag=True, help='Enable websocket tracing.')
137 | @click.option('--stagedisplay', is_flag=True, help='Act as stage display.')
138 | def command(action, send, next, prev, trace, stagedisplay):
139 | # load configuration
140 | config = json.load(open('config.json','r'))
141 |
142 | if trace:
143 | websocket.enableTrace(True)
144 |
145 | if stagedisplay:
146 | p6 = Pro6(
147 | host=config['HOST'],
148 | port=config['PORT'],
149 | password=config['STAGEDISPLAY_PASSWORD'],
150 | type='stagedisplay'
151 | )
152 |
153 | else:
154 | p6 = Pro6(host=config['HOST'], port=config['PORT'], password=config['CONTROL_PASSWORD'])
155 |
156 | if next:
157 | print(json.dumps(p6.next_slide()))
158 | exit()
159 |
160 | if prev:
161 | print(json.dumps(p6.prev_slide()))
162 | exit()
163 |
164 | if action:
165 | print(json.dumps(p6.send({'action': action})))
166 | exit()
167 |
168 | if send:
169 | print(json.dumps(p6.send(json.loads(send))))
170 | exit()
171 |
172 |
173 | if __name__ == "__main__":
174 | command()
175 | exit()
176 |
177 | # ws = websocket.WebSocket()
178 | # ws.settimeout(2)
179 | # ws.connect(wsurl)
180 | #
181 | # # request control
182 | # result = authenticate(ws)
183 | # print (result)
184 | #
185 | # # get current slide index
186 | # ws.send('{"action":"presentationSlideIndex"}')
187 | # result = json.loads(ws.recv())
188 | # if 'slideIndex' in result:
189 | # slideIndex = result['slideIndex']
190 | #
191 | # # go to next slide
192 | # newSlideIndex = int(slideIndex) + 1
193 | # ws.send('{"action":"presentationTriggerIndex","slideIndex":'+str(newSlideIndex)+'}')
194 | # result = json.loads(ws.recv())
195 | # print(result)
196 | # ws.close()
197 |
--------------------------------------------------------------------------------
/yaml2asyncapi:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 |
3 | import sys
4 | import json
5 | from typing import Literal
6 | import yaml
7 | try:
8 | from yaml import CLoader as Loader, CDumper as Dumper
9 | except ImportError:
10 | from yaml import Loader, Dumper
11 |
12 | from yaml.representer import SafeRepresenter
13 |
14 | # for dumping literal strings as scalar literal blocks
15 |
16 |
17 | class LiteralString(str):
18 | pass
19 |
20 |
21 | def literal_presenter(dumper, data):
22 | if type(data) is tuple:
23 | data = ''.join(data)
24 | return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
25 |
26 |
27 | yaml.add_representer(LiteralString, literal_presenter)
28 |
29 |
30 | def type_to_schema(t):
31 | schema = {}
32 | description = t.get('description', '')
33 |
34 | if description != '':
35 | schema['description'] = LiteralString(description)
36 |
37 | # type
38 | typetype = t['type']
39 |
40 | # if we are given a list of types, handle first
41 | if type(typetype) is list:
42 | oneof = []
43 | for typeitem in typetype:
44 | if typeitem in refs:
45 | oneof.append({'$ref': refs[typeitem]})
46 | else:
47 | oneof.append({'type': typeitem})
48 | schema['oneOf'] = oneof
49 | elif typetype == 'int':
50 | schema['type'] = 'integer'
51 | elif typetype == 'bool':
52 | schema['type'] = 'boolean'
53 | elif typetype == 'object':
54 | schema['type'] = 'object'
55 | elif typetype == 'list':
56 | schema['type'] = 'array'
57 | elif 'list[' in typetype:
58 | schema['type'] = 'array'
59 | subtype = typetype.replace('list[', '')[:-1]
60 | if subtype in refs:
61 | schema['items'] = [{'$ref': refs[subtype]}]
62 | else:
63 | schema['items'] = [{'type': subtype}]
64 | elif typetype in refs:
65 | schema['$ref'] = refs[typetype]
66 | else:
67 | schema['type'] = typetype
68 |
69 | # parameters if there are some
70 | params = t.get('parameters', None)
71 | if params is not None:
72 | schema['properties'] = {}
73 | schema['required'] = []
74 | optional = False
75 | for paramname in params:
76 | p = params[paramname]
77 | if type(p) is dict:
78 | if 'optional' in p and p['optional'] == True:
79 | optional = True
80 | pschema = type_to_schema(p)
81 | elif type(p) is list:
82 | oneof = []
83 | for typeitem in p:
84 | if typeitem in refs:
85 | oneof.append({'$ref': refs[typeitem]})
86 | else:
87 | oneof.append({'type': typeitem})
88 | pschema = {'oneOf': oneof}
89 |
90 | else:
91 | pschema = {'const': p}
92 |
93 | if optional == False:
94 | schema['required'].append(paramname)
95 |
96 | schema['properties'][paramname] = pschema
97 | schema['additionalProperties'] = False
98 |
99 | # check for 'options'
100 | options = t.get('options', None)
101 | if options is not None:
102 | schema['enum'] = options
103 |
104 | # example
105 | example = t.get('example', '')
106 | if example != '':
107 | exampleObject = example
108 | if type(example) is str:
109 | try:
110 | exampleObject = json.loads(example.replace('`', ''))
111 | except:
112 | exampleObject = LiteralString(example)
113 | schema['examples'] = [exampleObject]
114 |
115 | return schema
116 |
117 |
118 | # =============================
119 | # BEGIN MAIN
120 | # =============================
121 | if len(sys.argv) == 1:
122 | print(f'''
123 | USAGE:
124 |
125 | {sys.argv[0]} yaml_file output_name
126 |
127 | Will parse my simple API yaml file to AsyncAPI.
128 | ''')
129 | exit()
130 |
131 | source = sys.argv[1]
132 | target = source + '-asyncapi.yaml'
133 | target = target.replace('.yaml-asyncapi.yaml', '-asyncapi.yaml')
134 | target = target.replace('.yml-asyncapi.yaml', '-asyncapi.yaml')
135 |
136 | if len(sys.argv) == 3:
137 | target = sys.argv[2]
138 | if target == source:
139 | print('ERROR: source file and target file have the same name')
140 | exit(1)
141 |
142 | # read input yaml file
143 | print(f'READING: {source}')
144 | with open(source, 'r') as f:
145 | t = f.read()
146 | data = yaml.load(t, Loader=Loader)
147 |
148 | print('PROCESSING...')
149 |
150 | # include root level asyncapi data
151 | output = data['asyncapi']
152 | output.update({'components': {'schemas': {}, 'messages': {}}, 'channels': {}})
153 |
154 | refs = {}
155 |
156 | # create schemas from the types in two passes.
157 | # pass 1 creates the type refs
158 | for typename in data['types']:
159 | if typename not in refs:
160 | refs[typename] = f'#/components/schemas/{typename}'
161 |
162 | # pass 2 actually creates the types
163 | for typename in data['types']:
164 | t = data['types'][typename]
165 | # print(t)
166 | schema = type_to_schema(t)
167 | output['components']['schemas'][typename] = schema
168 |
169 | # create pub/sub messages from the message / response payloads
170 | for channel in ['remote', 'stagedisplay']:
171 | channelPath = '/'+channel
172 | summary = data['channels'][channel]['summary']
173 | description = data['channels'][channel]['description']
174 | output['channels'][channelPath] = {
175 | 'publish': {
176 | 'summary': summary,
177 | 'description': f'Send messages to the API\n\n{description}',
178 | 'message': {
179 | 'oneOf': []
180 | }
181 | },
182 | 'subscribe': {
183 | 'summary': summary,
184 | 'description': f'Receive messages from the API\n\n{description}',
185 | 'message': {
186 | 'oneOf': []
187 | }
188 | },
189 | }
190 |
191 | for actionName in data['channels'][channel]['actions']:
192 | action = data['channels'][channel]['actions'][actionName]
193 | # print(action)
194 | pubName = f'{actionName}__pub'
195 | subName = f'{actionName}__sub'
196 | messageNames = {'publish': pubName, 'subscribe': subName}
197 | messageTitles = {
198 | 'publish': f'{actionName} (publish)',
199 | 'subscribe': f'{actionName} (subscribe)',
200 | }
201 |
202 | for pubsub in ['publish', 'subscribe']:
203 | messageName = messageNames[pubsub]
204 | message = {}
205 | message['name'] = messageName # machine readable
206 | message['title'] = messageTitles[pubsub] # human readable
207 |
208 | if 'summary' in action:
209 | message['summary'] = action['summary']
210 |
211 | # generate descriptive text for this message
212 | desc = action.get('description', '')
213 | if desc is None:
214 | desc = ''
215 |
216 | if pubsub == 'subscribe' and 'message' in action:
217 | foreign = messageNames['publish']
218 | linkref = f'#message-{foreign}'
219 | linktext = messageTitles["publish"]
220 | desc += f'\n\n**RELATED COMMAND**: [{linktext}]({linkref})\n'
221 | if pubsub == 'subscribe' and 'message' not in action:
222 | desc = desc.strip() + '\n\nTHIS IS A SUBSCRIBE-ONLY MESSAGE'
223 |
224 | if pubsub == 'publish' and 'response' in action:
225 | foreign = messageNames['subscribe']
226 | linkref = f'#message-{foreign}'
227 | linktext = messageTitles["subscribe"]
228 | desc += f'\n\n**RELATED RESPONSE**: [{linktext}]({linkref})\n'
229 | # message['x-response'] = {'$ref': f'#/components/messages/{foreign}'}
230 |
231 | if pubsub == 'publish' and 'response' not in action:
232 | desc = desc.strip() + '\n\nNO RESPONSE'
233 | message['description'] = LiteralString(desc)
234 |
235 | key = 'response'
236 | if pubsub == 'publish':
237 | key = 'message'
238 | if key in action and action[key] != 'none':
239 | refstring = f'#/components/messages/{messageName}'
240 | refs[messageName] = refstring
241 |
242 | # handle payload ... could be a 'type' or a 'payload'
243 | payload = action[key].get('payload', None)
244 | if payload is not None:
245 | forschema = {'type': 'object', 'parameters': payload}
246 | message['payload'] = type_to_schema(forschema)
247 | else:
248 | payloadtype = action[key].get('type', None)
249 | if payloadtype in refs:
250 | message['payload'] = {'$ref': refs[payloadtype]}
251 | else:
252 | print('ERROR: message payload was empty and no type was specified')
253 | print('here is the offending action')
254 | print(json.dumps(action, indent=2))
255 | message['payload'] = {}
256 |
257 | # handle example
258 | if 'example' in action[key]:
259 | example = action[key]['example']
260 | if type(example) is dict:
261 | exampleObject = example
262 | elif type(example) is str:
263 | jsonexample = action[key]['example'].replace('`', '')
264 | try:
265 | exampleObject = json.loads(jsonexample)
266 | except:
267 | exampleObject = LiteralString(jsonexample)
268 | message['examples'] = [{'payload': exampleObject}]
269 |
270 | # add to messages component
271 | output['components']['messages'][messageName] = message
272 |
273 | # add ref to the channel/pubsub/messages
274 | output['channels'][channelPath][pubsub]['message']['oneOf'].append(
275 | {'$ref': refstring, }
276 | )
277 |
278 | # print('== DEBUG ==')
279 | # print(json.dumps(action, indent=2))
280 | # print(yaml.dump(message, sort_keys=False, indent=2, width=60, default_flow_style=False))
281 | # input('Hit enter to continue ... ')
282 |
283 | print(f'WRITING: {target}')
284 | o = yaml.dump(output, sort_keys=False, indent=2, width=120, default_flow_style=False)
285 | with open(target, 'w') as of:
286 | of.write(o)
287 | print('DONE')
288 |
--------------------------------------------------------------------------------
/examples/ProRemote.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | ProRemote
4 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
--------------------------------------------------------------------------------
/Pro6/api.yaml:
--------------------------------------------------------------------------------
1 | # This is a machine-readable format for the ProPresenter (6) Remote API
2 | # it was inspired by the asyncapi, but is written to keep commands
3 | # and responses together
4 | #
5 | # The advantage of asyncapi is that it is a standardized specification
6 | # structure for APIs, but the disadvantage is that it has no real
7 | # notion of the request-response message paradigm that ProPresenter uses.
8 | #
9 | # As a result, I have written this document by hand, and have written
10 | # my own translator to convert this document into asyncapi format
11 | # to use its tools for documentation generation
12 | asyncapi:
13 | asyncapi: '2.1.0'
14 | info:
15 | title: ProPresenter API
16 | version: '6'
17 | description: |
18 | Documenting RenewedVision's undocumented network protocols with examples
19 |
20 | This document refers to **ProPresenter 6.**
21 |
22 | Warning: Be careful! It's easy to CRASH ProPresenter when sending invalid messages!
23 |
24 |
25 | Both the Remote Control and the Stage Display protocols are unencrypted text-based websocket connections from the client to the ProPresenter instance.
26 |
27 | NOTE: if both the Remote Control and the Stage Display interface are enabled in ProPresenter, they both operate over the Remote Control network port.
28 |
29 | "Channels" refers to the communication channels opened up by ProPresenter.
30 | Both versions 6 and 7 expose two channels. One is the "remote" channel, and the
31 | other is the "stagedisplay" channel. They have two separate websocket endpoints
32 | that take the following form:
33 |
34 | ```
35 | ws://[hostname]:[port]/[channel]
36 | ```
37 | defaultContentType: application/json
38 | servers:
39 | pro6:
40 | protocol: ws
41 | url: "ws://{host}:{port}/{channel_name}"
42 | description: |
43 | Connecting to ProPresenter involves making a Websocket connection using
44 | this address and then immediately following the connection with an
45 | authentication message (as described below) for the relevant "channel."
46 | variables:
47 | host:
48 | description: The hostname or ip address of the computer running ProPresenter 6
49 | port:
50 | description: The port specified in the ProPresenter Network settings
51 | channel_name:
52 | enum:
53 | - remote
54 | - stagedisplay
55 | description: |
56 | *remote* for the remote control channel,
57 | or *stagedisplay* for the stage display channel
58 |
59 | types:
60 | # built-in types bool, int, float, string, object, list
61 | # typed lists may be described as list[othertype]
62 |
63 | # general types
64 | boolint:
65 | type: integer
66 | description: 0 for false, 1 for true, (in Pro6, these sometimes show up as strings)
67 |
68 | color:
69 | type: string
70 | description: |
71 | four space-delimited doubles describing the r,g,b,a values of a color from 0-1
72 |
73 | example: |
74 | "0.5 0.8 1 0.5"
75 |
76 | itemLocation:
77 | type: string
78 | description: |
79 | A slash-escaped full pathname, file basename, or playlist location.
80 |
81 | Pathname locations need to escape the slashes BEFORE they are encoded
82 | to JSON.
83 |
84 | ```
85 | \/Path\/To\/ProPresenter\/Library\/Song 1 Title.pro6
86 | ```
87 |
88 | Playlist locations are specified by a zero-based index of the playlist
89 | and playlist group in which the item may be found.
90 |
91 | The first item in the first playlist would be `"0:0"`.
92 |
93 | If the first playlist item is a playlist group, then the first item
94 | in the first playlist in the first playlist group would be `"0.0:0"`.
95 |
96 | That is, the generalized location is this:
97 |
98 | `playlist_index[.child_playlist_index[.deeper_child_index[...]]]:item`
99 |
100 | NOTE: Pro6 supports full pathnames or basenames.
101 | example: "\\/Path\\/To\\/ProPresenter\\/Library\\/Song 1 Title.pro6"
102 |
103 | # presentation types
104 | presentationSlide:
105 | type: object
106 | parameters:
107 | slideEnabled:
108 | type: bool
109 | slideNotes:
110 | type: string
111 | slideAttachmentMask:
112 | type: integer
113 | description: unknown
114 | slideText:
115 | type: string
116 | slideImage:
117 | type: string
118 | description: base64 encoded jpeg image
119 | slideIndex:
120 | type: integer
121 | description: |
122 | it is the zero-based index of the slide in a presentation
123 |
124 | NOTE: it is sometimes transmitted as a JSON string and sometimes a JSON number
125 | slideTransitionType:
126 | type: integer
127 | description: |
128 | `-1` for default transition or another number for a different transition
129 | slideLabel:
130 | type: string
131 | slideColor:
132 | type: color
133 | presentationSlideGroup:
134 | type: object
135 | parameters:
136 | groupsummary:
137 | type: string
138 | groupColor:
139 | type: color
140 | groupSlides:
141 | type: list[presentationSlide]
142 | presentation:
143 | type: object
144 | parameters:
145 | presentationSlideGroups:
146 | type: list[presentationSlideGroup]
147 | presentationsummary:
148 | type: string
149 | presentationHasTimeline:
150 | type: boolint
151 | presentationCurrentLocation:
152 | type: itemLocation
153 | description: |
154 | This field contains the location of the *currently active* presentation if there is one.
155 |
156 | It only refers to the location of *this* presentation if we asked for `presentationCurrent`
157 |
158 | presentationTriggerMessage:
159 | pushy: true
160 | type: object
161 | parameters:
162 | action: presentationTriggerIndex
163 | slideIndex:
164 | type: integer
165 | presentationPath:
166 | type: itemLocation
167 | description: |
168 | If the current presentation was selected from a playlist, it will report the
169 | playlist location (i.e. `0:0`), but if it was selected from the Library directly,
170 | the path value will always be the *basename* only.
171 |
172 | # playlist types for audio or presentation playlists
173 | playlistItemType:
174 | type: object
175 | parameters:
176 | playlistItemType:
177 | type: string
178 | options:
179 | - playlistItemTypeAudio
180 | - playlistItemTypePresentation
181 | playlistItemLocation:
182 | type: itemLocation
183 | playlistItemsummary:
184 | type: string
185 | playlistItemArtist:
186 | type: string
187 | description: only present for playlistItemTypeAudio
188 | playlistTypePlaylist:
189 | type: object
190 | parameters:
191 | playlistLocation:
192 | type: itemLocation
193 | playlistType: playlistTypePlaylist
194 | playlistsummary:
195 | type: string
196 | playlist:
197 | type: list[playlistItemType]
198 | playlistTypeGroup:
199 | type: object
200 | parameters:
201 | playlistType: playlistTypeGroup
202 | playlistsummary:
203 | type: string
204 | playlistLocation:
205 | type: itemLocation
206 | playlist:
207 | type: list[playlistUnionType]
208 | playlistUnionType:
209 | type:
210 | - playlistTypeGroup
211 | - playlistTypePlaylist
212 |
213 |
214 | # audio types
215 | audioPlayPause:
216 | type: string
217 | options:
218 | - Play
219 | - Pause
220 | description: in ProPresenter 7, the "Play" option becomes "Playing"
221 |
222 | # clock types
223 | clockTimeString:
224 | type: string
225 | description: |
226 | - real times are formatted as `"H:MM:SS"` (no padding on the leading H)
227 | - negative times are possible with a leading `-`.
228 | - empty times are `"--:--:--"`
229 |
230 | clockType:
231 | type: integer
232 | options:
233 | - 0
234 | - 1
235 | - 2
236 | description: |
237 | `0` is Countdown, `1` is Countdown to Time, `2` is Elapsed Time
238 |
239 | # in the response from clockRequest
240 | clockDetail:
241 | type: object
242 | parameters:
243 | clockType:
244 | type: clockType
245 | clockState:
246 | type: bool
247 | description: running is `true`, not running is `false`
248 | clocksummary:
249 | type: string
250 | clockIsPM:
251 | type: boolint
252 | clockTime:
253 | type: clockTimeString
254 | clockEndTime:
255 | type: clockTimeString
256 | clockDuration:
257 | type: clockTimeString
258 | clockOverrun:
259 | type: bool
260 | description: indicates if clock should keep running after it hits the endtime.
261 |
262 | clockQuickInfo:
263 | type: list
264 | items:
265 | -
266 | type: clockType
267 | -
268 | type: integer
269 | description: most recently changed value as indicated by the `action`
270 | -
271 | type: clockTimeString
272 |
273 | # in the response from clockStart or clockStop
274 | clockStartStopMessage:
275 | pushy: true
276 | type: object
277 | parameters:
278 | action: clockStartStop
279 | clockTime:
280 | type: clockTimeString
281 | clockState:
282 | type: boolint
283 | description: same as clockState in clockDetail, but given as a boolint
284 | clockIndex:
285 | type: integer
286 | description: zero-based clock index, clock lists are in a stable order
287 | clockInfo:
288 | type: clockQuickInfo
289 |
290 | clockCurrentTimesMessage:
291 | pushy: true
292 | type: object
293 | parameters:
294 | action: clockCurrentTimes
295 | clockTimes:
296 | type: list[clockTimeString]
297 | example: |
298 | `{"action":"clockCurrentTimes","clockTimes":["0:10:00","--:--:--","13:52:23"]}`
299 |
300 | # message types
301 | screenMessage:
302 | type: object
303 | parameters:
304 | messageComponents:
305 | type: list[string]
306 | description: |
307 | Each string is either a literal string or a variable expansion, which will be
308 | concatenated together to make the final on screen message.
309 |
310 | The content inside a `${}` is referred to as a `messageKey` in later commands
311 | messageTitle:
312 | type: string
313 | example:
314 | {
315 | "messageComponents": [
316 | "Session will begin in: ",
317 | "${Countdown 1: H:MM:SS}",
318 | "."
319 | ],
320 | "messageTitle": "Countdown"
321 | }
322 |
323 | # stage display types
324 | stageDisplayFrameGeometry:
325 | type: string
326 | description: |
327 | Frame Geometry is delivered in x,y coordinates based on screen percentages in the
328 | following format:
329 |
330 | `{{upper_left_x_pct, upper_left_y_pct}, {lower_right_x_pct, lower_right_y_pct}}`
331 | example: '{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002,
332 | 0.50108459869848154}}'
333 |
334 | stageDisplayColor:
335 | type: string
336 | description: |
337 | Stage Display colors are strings of three or four space-delimited float values for RGB(A) from 0-1
338 | example: 1.000000 1.000000 1.000000 1.000000
339 |
340 | stageDisplayFrame:
341 | description:
342 | A Stage Display Frame is a box of stage display information.
343 | type: object
344 | parameters:
345 | ufr:
346 | type: stageDisplayFrameGeometry
347 | mde:
348 | type: integer
349 | options: [0, 1, 2]
350 | description: |
351 | * frame mode is indicated by `mde`
352 | * mode 0: static image
353 | * mode 1: text
354 | * mode 2: live slide
355 | tAl:
356 | type: integer
357 | description: Always seems to be `2`
358 | tCl:
359 | type: stageDisplayColor
360 | description: frame text color
361 | tSz:
362 | type: integer
363 | description: Frame font size
364 | nme:
365 | type: string
366 | description: Name of this frame
367 | typ:
368 | type: integer
369 | options: [1,2,3,4,5,6,7,8,9]
370 | description: |
371 | * frame type is indicated by `typ` and determines what content goes in this frame
372 | * type 1: current slide
373 | * type 2: next slide
374 | * type 3: current slide notes
375 | * type 4: next slide notes
376 | * type 5: Stage Message (uses message flash values)
377 | * type 6: Clock
378 | * type 7: Timer Display (uses `uid` to specify timer)
379 | * type 8: Video Countdown
380 | * type 9: Chord Chart
381 | fCl:
382 | type: stageDisplayColor
383 | description: (message frames only) frame message flash color
384 | fCh:
385 | type: bool
386 | description: (message frames only) frame use message flash
387 | uid:
388 | type: string
389 | description: (timer frames only) frame timer uid
390 |
391 | stageDisplayLayout:
392 | type: object
393 | description: |
394 | * `nme` indicates layout name
395 | * `ovr` indicates if overrun color should be used
396 | * `oCl` indicates color for timer overruns
397 | * `brd` indicates if borders and labels should be used
398 | * `uid` indicates layout uid
399 | * `zro` indicates if zeroes should be removed from times
400 | * `fme` indicates array of frame layout specifications
401 | parameters:
402 | nme:
403 | type: string
404 | description: indicates layout name
405 | brd:
406 | type: bool
407 | description: indicates if borders and labels should be used
408 | uid:
409 | type: string
410 | description: indicates layout uid
411 | zro:
412 | type: boolint
413 | description: indicates if leading zeroes should be removed from times
414 | ovr:
415 | type: bool
416 | description: indicates if overrun color should be used
417 | oCl:
418 | type: stageDisplayColor
419 | description: indicates color for timer overruns
420 | fme:
421 | type: list[stageDisplayFrame]
422 | acn: sl
423 |
424 | stageDisplayFrameValue:
425 | type: object
426 | parameters:
427 | uid:
428 | optional: true
429 | type: string
430 | description: (only for slides and timers) uid of the object in this frame
431 | txt:
432 | type: string
433 | description: the text content of this frame, if any
434 | acn:
435 | type: string
436 | options:
437 | - cs
438 | - csn
439 | - ns
440 | - nsn
441 | - msg
442 | - sys
443 | - vid
444 | - tmr
445 | - cc
446 | description: |
447 | * `cs` = current slide text
448 | * `csn` = current slide notes
449 | * `ns` = next slide text
450 | * `nsn` = next slide text
451 | * `msg` = stage display message text
452 | * `sys` = system clock time
453 | * `vid` = video countdown value
454 | * `tmr` = timer value
455 | * `cc` = chord chart
456 | example:
457 | {
458 | "acn": "cs",
459 | "uid": "FAFCA1CB-8CB8-4E53-8B7C-8D61154516D0",
460 | "txt": ""
461 | }
462 |
463 |
464 | stageDisplayLiveSlideFrame:
465 | type: object
466 | parameters:
467 | RVLiveStream_action: RVLiveStream_frameData
468 | RVLiveStream_frameDataLength:
469 | type: integer
470 | description: byte count of this frame data
471 | RVLiveStream_frameData:
472 | type: string
473 | description: base64 encoded jpeg image
474 | example:
475 | {
476 | "RVLiveStream_action": "RVLiveStream_frameData",
477 | "RVLiveStream_frameDataLength": 14625,
478 | "RVLiveStream_frameData": "..."
479 | }
480 |
481 |
482 |
483 | channels:
484 | remote:
485 | summary: Remote Control
486 | description: |
487 | The `remote` channel carries all the
488 | information from the "ProPresenter Remote" app
489 |
490 | CONNECT WITH:
491 |
492 | ```
493 | ws://[hostname]:[port]/remote
494 | ```
495 |
496 | # "pushy" message types
497 | messages:
498 | - presentationTriggerMessage
499 | - clockCurrentTimesMessage
500 | - clockStartStopMessage
501 |
502 | # actions refer to commands that can be sent to ProPresenter
503 | actions:
504 | authenticate:
505 | summary: RemoteAuthentication
506 | description: |
507 | This is the first command ProPresenter expects to receive over the websocket.
508 | This command requests authentication.
509 |
510 | For ProPresenter 6, the protocol must be `600`
511 | message:
512 | payload:
513 | action: authenticate
514 | protocol: 600
515 | password:
516 | type: string
517 | description: as defined in the ProPresenter network settings
518 | example:
519 | action: authenticate
520 | protocol: 600
521 | password: myGreatPassword
522 | response:
523 | payload:
524 | action: authenticate
525 | error:
526 | type: string
527 | description: empty if there is no error, otherwise error message
528 | authenticated:
529 | type: boolint
530 | description: 1 for true, 0 for false
531 | controller:
532 | type: boolint
533 | description: Does user have permission to send control messages? 1 for true, 0 for false
534 | example:
535 | {"controller":1,"authenticated":1,"error":"","action":"authenticate"}
536 |
537 |
538 | libraryRequest:
539 | summary: Get Library (all presentations)
540 | description: |
541 | Will return all items in the ProPresenter library.
542 |
543 | - Note that the response will include escaped slashes in path names.
544 | message:
545 | payload:
546 | action: libraryRequest
547 | response:
548 | payload:
549 | action: libraryRequest
550 | library:
551 | type: list[itemLocation]
552 | description: |
553 | This will be a list of full pathnames for all items in the Presentation library.
554 |
555 | Note the use of slashes in the response. ProPresenter expects library
556 | requests to follow this pattern exactly.
557 | example:
558 | {
559 | "library": [
560 | "\/Path\/To\/ProPresenter\/Library\/Come Alive (Dry Bones).pro6",
561 | "\/Path\/To\/ProPresenter\/Library\/Pour Out My Heart.pro6",
562 | "\/Path\/To\/ProPresenter\/Library\/Away in a manger.pro6",
563 | "... ALL PRESENTATIONS IN THE LIBRARY ..."
564 | ],
565 | "action": "libraryRequest"
566 | }
567 |
568 |
569 | playlistRequestAll:
570 | summary: Get Playlists
571 | description: |
572 | Will return all presentation playlists.
573 |
574 | - Presentation playlists and Audio playlists have a similar format
575 | - A playlist may be a `playlistTypePlaylist` or a `playlistTypeGroup`
576 | - `playlistTypePlaylist` will contain a list of `playlistItemTypePresentation` (or `playlistItemTypeAudio` for audio playlists)
577 | - `playlistTypeGroup` may contain both `playlistTypePlaylist` and `playlistTypeGroup`
578 | message:
579 | payload:
580 | action: playlistRequestAll
581 | response:
582 | payload:
583 | action: playlistRequestAll
584 | playlistAll:
585 | type: list[playlistUnionType]
586 | example:
587 | {
588 | "playlistAll": [
589 | {
590 | "playlistLocation": "0",
591 | "playlistType": "playlistTypePlaylist",
592 | "playlistName": "Default",
593 | "playlist": [
594 | {
595 | "playlistItemName": "!~ PRE-SERVICE",
596 | "playlistItemLocation": "0:0",
597 | "playlistItemType": "playlistItemTypePresentation"
598 | }
599 | ]
600 | },
601 | {
602 | "playlistLocation": "1",
603 | "playlistType": "playlistTypeGroup",
604 | "playlistName": "2017",
605 | "playlist": [
606 | {
607 | "playlistLocation": "1.0",
608 | "playlistType": "playlistTypePlaylist",
609 | "playlistName": "2017-01-28-Vision Dinner",
610 | "playlist": [
611 | {
612 | "playlistItemName": "!MISC2",
613 | "playlistItemLocation": "1.0:0",
614 | "playlistItemType": "playlistItemTypePresentation"
615 | },
616 | {
617 | "playlistItemName": "!MISC1",
618 | "playlistItemLocation": "1.0:1",
619 | "playlistItemType": "playlistItemTypePresentation"
620 | }
621 | ]
622 | }
623 | ]
624 | }
625 | ],
626 | "action": "playlistRequestAll"
627 | }
628 |
629 | presentationRequest:
630 | summary: Request a Playlist by Location
631 | description: |
632 | NOTE: even though the requested action is `presentationRequest` Pro6 responds with
633 | a `presentationCurrent` but it will be different. See below.
634 | message:
635 | description: |
636 | `presentationSlideQuality` is optional. It refers to the pixel width of the
637 | slide previews sent from ProPresenter. If left blank, 100 pixel wide
638 | previews will be sent. If set to 0 previews will not be generated at all.
639 | The remote app asks for quality 25 first and then follows it up with a second
640 | request for quality 100.
641 | payload:
642 | action: presentationRequest
643 | presentationPath:
644 | type: itemLocation
645 | presentationSlideQuality:
646 | type: integer
647 | optional: true
648 | example:
649 | {
650 | "action": "presentationRequest",
651 | "presentationPath": "\\/Path\\/To\\/ProPresenter\\/Library\\/Song 1 Title.pro6",
652 | "presentationSlideQuality": 25
653 | }
654 |
655 | response:
656 | payload:
657 | action: presentationCurrent
658 | presentation:
659 | type: presentation
660 | example:
661 | {
662 | "action": "presentationCurrent",
663 | "presentation": {
664 | "presentationSlideGroups": [
665 | {
666 | "groupName": "[SLIDE GROUP NAME]",
667 | "groupColor": "0 0 0 1",
668 | "groupSlides": [
669 | {
670 | "slideEnabled": true,
671 | "slideNotes": "",
672 | "slideAttachmentMask": 0,
673 | "slideText": "[SLIDE TEXT HERE]",
674 | "slideImage": "[BASE64 ENCODED IMAGE]",
675 | "slideIndex": "0",
676 | "slideTransitionType": -1,
677 | "slideLabel": "[SLIDE LABEL]",
678 | "slideColor": "0 0 0 1"
679 | }
680 | ]
681 | }
682 | ],
683 | "presentationName": "[PRESENTATION TITLE]",
684 | "presentationHasTimeline": 0,
685 | "presentationCurrentLocation": "[PRESENTATION PATH OF CURRENTLY ACTIVE SLIDE]"
686 | }
687 | }
688 |
689 |
690 | presentationCurrent:
691 | summary: Request Current Presentation
692 | description: |
693 | Requests the currently active presentation if there is one. If no presentation has a slide
694 | active, then nothing will be returned.
695 |
696 | NOTE: You can distinguish this response from a presentationRequest request
697 | because this response will include presentationPath as a field at the root level
698 | of the response.
699 | message:
700 | payload:
701 | action: presentationCurrent
702 | presentationSlideQuality:
703 | type: integer
704 | optional: true
705 | response:
706 | payload:
707 | action: presentationCurrent
708 | presentationPath:
709 | type: itemLocation
710 | presentation:
711 | type: presentation
712 |
713 | presentationSlideIndex:
714 | summary: Get the Index of the Current Slide
715 | description: |
716 | - Slides are indexed from 0
717 | - This response always sends the slideIndex as a `string` value
718 | - The ProPresenter remote issues this action every time it does a `presentationRequest`
719 | message:
720 | payload:
721 | action: presentationSlideIndex
722 | response:
723 | payload:
724 | action: presentationSlideIndex
725 | slideIndex:
726 | type: string
727 | example: "0"
728 |
729 | presentationTriggerIndex:
730 | summary: Activate a Slide
731 | description: Works just like clicking on a slide
732 | message:
733 | payload:
734 | action: presentationTriggerIndex
735 | slideIndex:
736 | type: integer
737 | presentationPath:
738 | type: itemLocation
739 | example: "0:0"
740 | response:
741 | pushy: true
742 | type: presentationTriggerMessage
743 |
744 | presentationTriggerNext:
745 | summary: Activate the Next Slide
746 | description: Works just like hitting the right arrow or spacebar.
747 | message:
748 | payload:
749 | action: presentationTriggerNext
750 | response:
751 | type: presentationTriggerMessage
752 |
753 | presentationTriggerPrevious:
754 | summary: Activate the Previous Slide
755 | description: Works just like hitting the left arrow.
756 | message:
757 | payload:
758 | action: presentationTriggerPrevious
759 | response:
760 | type: presentationTriggerMessage
761 |
762 | # audio commands
763 | audioRequest:
764 | summary: Get Audio Library
765 | description: Will return all the items in the audio library
766 | message:
767 | payload:
768 | action: audioRequest
769 | response:
770 | payload:
771 | action: audioRequest
772 | audioPlaylist:
773 | type: list[playlistUnionType]
774 |
775 | audioCurrentSong:
776 | summary: Get the Current Song
777 | description: Will return the data for the currently playing song, if there is one.
778 | message:
779 | payload:
780 | action: audioCurrentSong
781 | response:
782 | payload:
783 | action: audioCurrentSong
784 | audioArtist:
785 | type: string
786 | audiosummary:
787 | type: string
788 | example:
789 | {
790 | "audioArtist": "",
791 | "action": "audioCurrentSong",
792 | "audioName": "Peaceful Instrumental - C"
793 | }
794 |
795 |
796 | audioIsPlaying:
797 | summary: Is Audio Playing (BROKEN)
798 | description: This api request exists, but it always reports `false`
799 | message:
800 | payload:
801 | action: audioIsPlaying
802 | response:
803 | payload:
804 | action: audioIsPlaying
805 | audioIsPlaying: false
806 |
807 | audioStartCue:
808 | summary: Start Audio Cue
809 | description: |
810 | This command will result in an `audioTriggered` message, but since the
811 | play/pause status has changed, ProPresenter will also send an `audioPlayPause` message.
812 | message:
813 | payload:
814 | action: audioStartCue
815 | audioChildPath:
816 | type: itemLocation
817 | response:
818 | pushy: true
819 | payload:
820 | action: audioTriggered
821 | audioArtist:
822 | type: string
823 | audiosummary:
824 | type: string
825 | example:
826 | {
827 | "audioArtist": "",
828 | "action": "audioTriggered",
829 | "audioName": "Peaceful Instrumental - C"
830 | }
831 |
832 |
833 | audioPlayPause:
834 | summary: Toggle the Audio Play State
835 | message:
836 | payload:
837 | action: audioPlayPause
838 | response:
839 | pushy: true
840 | payload:
841 | action: audioPlayPause
842 | audioPlayPause:
843 | type: string
844 | options:
845 | - Play
846 | - Pause
847 |
848 | # timeline commands
849 | timelinePlayPause:
850 | summary: Toggle the Timeline Play State
851 | description: If `presentationPath` is missing, Pro6 will crash.
852 | message:
853 | payload:
854 | action: timelinePlayPause
855 | presentationPath:
856 | type: itemLocation
857 |
858 | timelineRewind:
859 | summary: Reset the Timeline for a Presentation
860 | description: If `presentationPath` is missing, Pro6 will crash.
861 | message:
862 | payload:
863 | action: timelineRewind
864 | presentationPath:
865 | type: itemLocation
866 |
867 | # clock / timer commands
868 | clockRequest:
869 | summary: Request all Clock data
870 | description:
871 | message:
872 | payload:
873 | action: clockRequest
874 | response:
875 | payload:
876 | action: clockRequest
877 | clockInfo:
878 | type: list[clockDetail]
879 |
880 | clockCurrentTimes:
881 | summary: Get Current Times for All Clocks
882 | description:
883 | message:
884 | payload:
885 | action: clockCurrentTimes
886 | response:
887 | type: clockCurrentTimesMessage
888 |
889 | clockStartSendingCurrentTime:
890 | summary: Subscribe to Clock Updates
891 | description: |
892 | ProPresenter will send one clock update every second even if no clock values have changed.
893 |
894 | The response payload is exactly the same as `clockCurrentTimes`
895 | message:
896 | payload:
897 | action: clockStartSendingCurrentTime
898 | response:
899 | type: clockCurrentTimesMessage
900 |
901 | clockStopSendingCurrentTime:
902 | summary: Unsubscribe from Clock Updates
903 | description:
904 | message:
905 | payload:
906 | action: clockStopSendingCurrentTime
907 |
908 | clockStart:
909 | summary: Start a Specific Clock
910 | description:
911 | message:
912 | payload:
913 | action: clockStart
914 | clockIndex:
915 | type: integer
916 | response:
917 | type: clockStartStopMessage
918 |
919 | clockStop:
920 | summary: Stop a Specific Clock
921 | description:
922 | message:
923 | payload:
924 | action: clockStop
925 | clockIndex:
926 | type: integer
927 | response:
928 | type: clockStartStopMessage
929 |
930 | clockReset:
931 | summary: Reset a clock back to initial settings
932 | description:
933 | message:
934 | payload:
935 | action: clockReset
936 | clockIndex:
937 | type: integer
938 | response:
939 | payload:
940 | action: clockResetIndex
941 |
942 | clockUpdate:
943 | summary: Update a Clock (Timer) (eg edit time)
944 | description: |-
945 | * Clocks are referenced by index. See reply from "clockRequest" action above to learn indexes.
946 | * Not all parameters are required for each clock type.
947 | * Countdown clocks only need "clockTime".
948 | * Elapsed Time Clocks need "clockTime" and optionally will use "clockElapsedTime" if you send it (to set the End Time).
949 | * You can rename a clock by optionally including the clockName.
950 | * Type 0 is Countdown
951 | * Type 1 is CountDown to Time
952 | * Type 2 is Elapsed Time.
953 | * Overrun can be modified if you choose to include that as well.
954 | message:
955 | payload:
956 | action: clockUpdate
957 | clockIndex:
958 | type: integer
959 | example: 1
960 | clockType:
961 | type: integer
962 | example: 0
963 | clockTime:
964 | type: string
965 | example: 09:04:00
966 | clockOverrun:
967 | type: integer
968 | example: 0
969 | clockIsPM:
970 | type: integer
971 | example: 1
972 | clocksummary:
973 | type: string
974 | example: Countdown 2
975 | clockElapsedTime:
976 | type: string
977 | example: 0:02:00
978 | example:
979 | {
980 | "action":"clockUpdate",
981 | "clockIndex":1,
982 | "clockType":0,
983 | "clockTime":"09:04:00",
984 | "clockOverrun":false,
985 | "clockIsPM":1,
986 | "clockName":"Countdown 2",
987 | "clockElapsedTime":"0:02:00"
988 | }
989 |
990 |
991 | clockResetAll:
992 | message:
993 | payload:
994 | action: clockResetAll
995 | example:
996 | {"action":"clockResetAll"}
997 |
998 | clockStopAll:
999 | message:
1000 | payload:
1001 | action: clockStopAll
1002 | example:
1003 | {"action":"clockStopAll"}
1004 |
1005 | clockStartAll:
1006 | message:
1007 | payload:
1008 | action: clockStartAll
1009 | example:
1010 | {"action":"clockStartAll"}
1011 |
1012 |
1013 | messageRequest:
1014 | summary: Get all Messages
1015 | description: |-
1016 | * The key is everything inside the curly braces `${}` so that the key for a countdown looks like this `Countdown 1: H:MM:SS`.
1017 | * If the key refers to a countdown, the value is used to update the `duration` field of the countdown timer, but will not perform a "reset".
1018 | * If the key refers to a countdown and the countdown is not running, this will resume it from its current value.
1019 | message:
1020 | payload:
1021 | action: messageRequest
1022 | example:
1023 | {"action":"messageRequest"}
1024 |
1025 | response:
1026 | payload:
1027 | action: messageRequest
1028 | messages:
1029 | type: list[screenMessage]
1030 | example:
1031 | {
1032 | "action": "messageRequest",
1033 | "messages": [
1034 | {
1035 | "messageComponents": [
1036 | "message:",
1037 | "${Message}"
1038 | ],
1039 | "messageTitle": "Message"
1040 | },
1041 | {
1042 | "messageComponents": [
1043 | "Session will begin in: ",
1044 | "${Countdown 1: H:MM:SS}"
1045 | ],
1046 | "messageTitle": "Countdown"
1047 | },
1048 | {
1049 | "messageComponents": [
1050 | "${Message}"
1051 | ],
1052 | "messageTitle": "Message"
1053 | },
1054 | {
1055 | "messageComponents": [
1056 | "Service starts in ",
1057 | "${countDownTimerName_1: H:MM:SS}"
1058 | ],
1059 | "messageTitle": "Countdown"
1060 | }
1061 | ]
1062 | }
1063 |
1064 |
1065 | messageSend:
1066 | summary: Display a Message
1067 | description: |
1068 | Display a message identified by its index. Add as many key, value
1069 | pairs as you like.
1070 |
1071 | See `messageRequest` for more information about keys.
1072 | message:
1073 | payload:
1074 | action: messageSend
1075 | messageIndex:
1076 | type: integer
1077 | messageKeys:
1078 | type: list[string]
1079 | messageValues:
1080 | type: list[string]
1081 | example:
1082 | {"action":"messageSend","messageIndex":0,"messageKeys":["key1","key2","..."],"messageValues":["Value1","Value2","..."]}
1083 |
1084 |
1085 | messageHide:
1086 | summary: Hide a Message
1087 | description: Hide a message identified by its index
1088 | message:
1089 | payload:
1090 | action: messageHide
1091 | messageIndex:
1092 | type: integer
1093 |
1094 | # stage display control messages
1095 | stageDisplaySendMessage:
1096 | summary: Show Stage Display Message
1097 | description:
1098 | message:
1099 | payload:
1100 | action: stageDisplaySendMessage
1101 | stageDisplayMessage:
1102 | type: string
1103 | example: Type a Message Here
1104 | example:
1105 | {"action":"stageDisplaySendMessage","stageDisplayMessage":"Type a Message Here"}
1106 |
1107 | stageDisplayHideMessage:
1108 | summary: Hide Stage Display Message
1109 | description:
1110 | message:
1111 | payload:
1112 | action: stageDisplayHideMessage
1113 | example:
1114 | {"action":"stageDisplayHideMessage"}
1115 |
1116 | stageDisplaySetIndex:
1117 | summary: Select Stage Display Layout
1118 | description: |
1119 | EXPECTED RESPONSE IS THE SAME AS THE SENT COMMAND
1120 | message:
1121 | payload:
1122 | action: stageDisplaySetIndex
1123 | stageDisplayIndex:
1124 | type: integer
1125 | example: 0
1126 | example:
1127 | {"action":"stageDisplaySetIndex","stageDisplayIndex":0}
1128 |
1129 | response:
1130 | payload:
1131 | action: stageDisplaySetIndex
1132 | stageDisplayIndex:
1133 | type: integer
1134 | example: 0
1135 | example:
1136 | {"action":"stageDisplaySetIndex","stageDisplayIndex":0}
1137 |
1138 |
1139 | stageDisplaySets:
1140 | summary: Get Stage Display Layouts
1141 | description:
1142 | message:
1143 | payload:
1144 | action: stageDisplaySets
1145 | example:
1146 | { "action": "stageDisplaySets" }
1147 |
1148 | response:
1149 | payload:
1150 | action: stageDisplaySets
1151 | stageDisplayIndex:
1152 | type: integer
1153 | example: 4
1154 | stageDisplaySets:
1155 | type: list[string]
1156 | example:
1157 | {
1158 | "stageDisplayIndex": 4,
1159 | "action": "stageDisplaySets",
1160 | "stageDisplaySets": [
1161 | "Default",
1162 | "Easter Closer",
1163 | "Live Current - Static Next - no borders",
1164 | "Static Current - Static Next",
1165 | "Songs",
1166 | "Slides"
1167 | ]
1168 | }
1169 |
1170 |
1171 | # general control messages
1172 | clearAll:
1173 | summary: Clear All
1174 | description: ''
1175 | message:
1176 | payload:
1177 | action: clearAll
1178 | example:
1179 | {"action":"clearAll"}
1180 |
1181 |
1182 | clearText:
1183 | summary: Clear Slide
1184 | description: ''
1185 | message:
1186 | payload:
1187 | action: clearText
1188 | example:
1189 | {"action":"clearText"}
1190 |
1191 |
1192 | clearProps:
1193 | summary: Clear Props
1194 | description: ''
1195 | message:
1196 | payload:
1197 | action: clearProps
1198 | example:
1199 | {"action":"clearProps"}
1200 |
1201 |
1202 | clearAudio:
1203 | summary: Clear Audio
1204 | description: ''
1205 | message:
1206 | payload:
1207 | action: clearAudio
1208 | example:
1209 | {"action":"clearAudio"}
1210 |
1211 |
1212 | clearVideo:
1213 | summary: Clear Video
1214 | description: ''
1215 | message:
1216 | payload:
1217 | action: clearVideo
1218 | example:
1219 | {"action":"clearVideo"}
1220 |
1221 |
1222 | clearTelestrator:
1223 | summary: Clear Telestrator
1224 | description: ''
1225 | message:
1226 | payload:
1227 | action: clearTelestrator
1228 | example:
1229 | {"action":"clearTelestrator"}
1230 |
1231 |
1232 | clearToLogo:
1233 | summary: Clear To Logo
1234 | description: ''
1235 | message:
1236 | payload:
1237 | action: clearToLogo
1238 | example:
1239 | {"action":"clearToLogo"}
1240 |
1241 |
1242 | undocumented:
1243 | - socialSendTweet
1244 | - telestratorSettings
1245 | - telestratorEndEditing
1246 | - telestratorSet
1247 | - telestratorUndo
1248 | - telestratorNew
1249 |
1250 | stagedisplay:
1251 | summary: Stage Display API
1252 | protocol: 610
1253 | description: |
1254 | The `stagedisplay` channel carries all the
1255 | information from the "ProPresenter Stage" app
1256 |
1257 | CONNECT WITH:
1258 |
1259 | ```
1260 | ws://[hostname]:[port]/stagedisplay
1261 | ```
1262 |
1263 | ProPresenter also provides a mechanism for getting a high quality thumbnail image
1264 | for any slide or chord chart in the current presentation:
1265 |
1266 | ```
1267 | http://PROPRESENTER_IP:PROPRESENTER_PORT/stage/image/SLIDE_UID
1268 | ```
1269 |
1270 | In ProPresenter 6, the response will be the raw bytes of a jpeg image but the
1271 | response will *NOT* contain the proper Content-Type of `image/jpeg`.
1272 |
1273 |
1274 | # any one of the stagedisplay frame values can be sent independently
1275 | # however, cs, csn, ns, nsn are always sent together with an fv message
1276 | messages:
1277 | - fv
1278 | - sl
1279 | - msg
1280 | - sys
1281 | - tmr
1282 | - cc
1283 | - vid
1284 |
1285 |
1286 | # commands to send
1287 | actions:
1288 | ath:
1289 | summary: StageDisplayAuthentication
1290 | description: ''
1291 | message:
1292 | payload:
1293 | acn: ath
1294 | ptl: 610
1295 | pwd:
1296 | type: string
1297 | example:
1298 | {"pwd":"PASSWORD","ptl":610,"acn":"ath"}
1299 |
1300 | response:
1301 | payload:
1302 | acn: ath
1303 | ath:
1304 | type: bool
1305 | err:
1306 | type: string
1307 | example:
1308 | {"acn":"ath","ath":true,"err":""}
1309 |
1310 |
1311 | asl:
1312 | summary: Get All Stage Display Layouts
1313 | description: |-
1314 | * `acn` of `asl` means "all stage layouts"
1315 | * `ary` indicates array of stage layouts
1316 | message:
1317 | payload:
1318 | acn: asl
1319 | example:
1320 | {"acn":"asl"}
1321 |
1322 | response:
1323 | payload:
1324 | acn: asl
1325 | ary:
1326 | type: list[stageDisplayLayout]
1327 | example:
1328 | {
1329 | "acn": "asl",
1330 | "ary": [
1331 | {
1332 | "brd": true,
1333 | "uid": "753B184F-CCCD-42F9-A883-D1DF86E1FFB8",
1334 | "zro": 0,
1335 | "oCl": "1.000000 0.000000 0.000000",
1336 | "fme": [
1337 | {
1338 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
1339 | "mde": 1,
1340 | "tCl": "1.000000 1.000000 1.000000",
1341 | "tAl": 2,
1342 | "tSz": 60,
1343 | "nme": "Current Slide",
1344 | "typ": 1
1345 | }
1346 | ],
1347 | "ovr": true,
1348 | "acn": "sl",
1349 | "nme": "Default"
1350 | }
1351 | ]
1352 | }
1353 |
1354 |
1355 | psl:
1356 | summary: Request Current Stage Display Layout
1357 | description: ''
1358 | message:
1359 | payload:
1360 | acn: psl
1361 | example:
1362 | {"acn":"psl"}
1363 |
1364 | response:
1365 | payload:
1366 | acn: psl
1367 | uid:
1368 | type: string
1369 | example:
1370 | {"acn":"psl","uid":"F8260B13-9C5B-4D2C-80F1-C72346759F11"}
1371 |
1372 |
1373 | fv:
1374 | summary: Request Frame Values for Stage Display
1375 | description: |
1376 | When the frame value is pushed as a result of a slide trigger event,
1377 | only `cs`, `ns`, `csn`, `nsn` will be sent.
1378 |
1379 | Applications should listen for all the other `acn` values separately.
1380 | message:
1381 | payload:
1382 | acn: fv
1383 | uid:
1384 | type: string
1385 | example:
1386 | {"acn":"fv","uid":"F8260B13-9C5B-4D2C-80F1-C72346759F11"}
1387 |
1388 | response:
1389 | pushy: true
1390 | payload:
1391 | acn: fv
1392 | ary:
1393 | type: list[stageDisplayFrameValue]
1394 | example:
1395 | {
1396 | "acn": "fv",
1397 | "ary": [
1398 | {
1399 | "acn": "cs",
1400 | "uid": "FAFCA1CB-8CB8-4E53-8B7C-8D61154516D0",
1401 | "txt": ""
1402 | },
1403 | {
1404 | "acn": "ns",
1405 | "uid": "95D16968-589A-11EB-8D62-FCAA147AEF2F",
1406 | "txt": ""
1407 | },
1408 | {
1409 | "acn": "csn",
1410 | "txt": "[sermon_start]\nevent[2888]\nvmixoverlay[1,Out]\nlive[1]"
1411 | },
1412 | {
1413 | "acn": "nsn",
1414 | "txt": "vmixoverlay[2,Out]"
1415 | },
1416 | {
1417 | "acn": "msg",
1418 | "txt": ""
1419 | },
1420 | {
1421 | "acn": "sys",
1422 | "txt": " 9:57 AM"
1423 | },
1424 | {
1425 | "acn": "tmr",
1426 | "uid": "47E8B48C-0D61-4EFC-9517-BF9FB894C8E2",
1427 | "txt": "0:09:59"
1428 | },
1429 | {
1430 | "acn": "vid",
1431 | "txt": ""
1432 | },
1433 | {
1434 | "acn": "cc",
1435 | "uid": ""
1436 | }
1437 | ]
1438 | }
1439 |
1440 | # push only messages
1441 | sl:
1442 | summary: On New Stage Display Selected
1443 | description: |-
1444 | * `acn` of `sl` indicates this is a single stage layout
1445 | response:
1446 | type: stageDisplayLayout
1447 |
1448 | sys:
1449 | summary: On New System Time
1450 | description: ''
1451 | response:
1452 | pushy: true
1453 | payload:
1454 | acn: sys
1455 | txt:
1456 | type: string
1457 | example: ' 11:17 AM'
1458 | example:
1459 | {"acn":"sys","txt":" 11:17 AM"}
1460 |
1461 |
1462 | vid:
1463 | summary: On New Video Countdown Time
1464 | description: Time follows the same H:MM:SS format as the remote protocol.
1465 | response:
1466 | pushy: true
1467 | payload:
1468 | acn: vid
1469 | txt:
1470 | type: string
1471 | example: '--:--:--'
1472 | example:
1473 | {"acn":"vid","txt":"0:00:18"}
1474 |
1475 | cc:
1476 | summary: On New Chord Chart
1477 | description: |
1478 | Chord chart images can be downloaded in the same way as slide preview images
1479 |
1480 | http://PROPRESENTER_IP:PROPRESENTER_PORT/stage/image/UID
1481 | response:
1482 | pushy: true
1483 | payload:
1484 | acn: cc
1485 | uid:
1486 | type: string
1487 | example: E65DF4DF-960D-4CF5-ADF5-60F200F6AFF7
1488 | example:
1489 | {"acn":"cc","uid":"E65DF4DF-960D-4CF5-ADF5-60F200F6AFF7"}
1490 |
1491 | tmr:
1492 | summary: On Timer Update
1493 | description: ''
1494 | response:
1495 | pushy: true
1496 | payload:
1497 | acn: tmr
1498 | uid:
1499 | type: string
1500 | description: timer uid
1501 | txt:
1502 | type: string
1503 | description: timer text
1504 | example:
1505 | {
1506 | "acn": "tmr",
1507 | "uid": "[TIMER UID]",
1508 | "txt": "--:--:--"
1509 | }
1510 |
--------------------------------------------------------------------------------
/Pro6.md:
--------------------------------------------------------------------------------
1 | # ProPresenter-API
2 | Documenting RenewedVision's undocumented network protocols with examples
3 |
4 | This document refers to *ProPresenter 6*.
5 |
6 | Both the Remote Control and the Stage Display protocols are unencrypted text-based websocket connections from the client to the ProPresenter instance.
7 |
8 | Note, if both the Remote Control and the Stage Display interface are enabled in ProPresenter, they both operate over the *Remote Control* network port.
9 |
10 | ## Remote Control
11 |
12 |
13 | ### Connecting
14 |
15 | ```javascript
16 | ws://[host]:[port]/remote
17 | ```
18 |
19 | ### Authenticate
20 |
21 | COMMAND TO SEND:
22 |
23 | ```javascript
24 | {"action":"authenticate","protocol":"600","password":"control"}
25 | ```
26 | * protocol is used to perform a version check. ProPresenter 6 seems to check for a value here of at least 600 - otherwise it denies authentication and returns "Protocol out of date. Update application"
27 |
28 | EXPECTED RESPONSE:
29 |
30 | ```javascript
31 | {"controller":1,"authenticated":1,"error":"","action":"authenticate"}
32 | ```
33 |
34 | ### Get Library (all presentations)
35 |
36 | COMMAND TO SEND:
37 |
38 | ```javascript
39 | {"action":"libraryRequest"}
40 | ```
41 |
42 | EXPECTED RESPONSE:
43 |
44 | ```javascript
45 | {
46 | "library": [
47 | "\/Path\/To\/ProPresenter\/Library\/Come Alive (Dry Bones).pro6",
48 | "\/Path\/To\/ProPresenter\/Library\/Pour Out My Heart.pro6",
49 | "\/Path\/To\/ProPresenter\/Library\/Away in a manger.pro6",
50 | "... ALL PRESENTATIONS IN THE LIBRARY ..."
51 | ],
52 | "action": "libraryRequest"
53 | }
54 | ```
55 |
56 | * Note the use of slashes in the response. ProPresenter expects library requests to follow this pattern exactly.
57 |
58 | ### Get All Playlists
59 |
60 | COMMAND TO SEND:
61 |
62 | ```javascript
63 | {"action":"playlistRequestAll"}
64 | ```
65 |
66 | EXPECTED RESPONSE:
67 |
68 | This request returns all playlists according to the following format.
69 |
70 | ```javascript
71 | {
72 | "playlistAll": [
73 | {
74 | "playlistLocation": "0",
75 | "playlistType": "playlistTypePlaylist",
76 | "playlistName": "Default",
77 | "playlist": [
78 | {
79 | "playlistItemName": "!~ PRE-SERVICE",
80 | "playlistItemLocation": "0:0",
81 | "playlistItemType": "playlistItemTypePresentation"
82 | },
83 | ]
84 | },
85 | {
86 | "playlistLocation": "1",
87 | "playlistType": "playlistTypeGroup",
88 | "playlistName": "2017",
89 | "playlist": [
90 | {
91 | "playlistLocation": "1.0",
92 | "playlistType": "playlistTypePlaylist",
93 | "playlistName": "2017-01-28-Vision Dinner",
94 | "playlist": [
95 | {
96 | "playlistItemName": "!MISC2",
97 | "playlistItemLocation": "1.0:0",
98 | "playlistItemType": "playlistItemTypePresentation"
99 | },
100 | {
101 | "playlistItemName": "!MISC1",
102 | "playlistItemLocation": "1.0:1",
103 | "playlistItemType": "playlistItemTypePresentation"
104 | },
105 | ]
106 | },
107 | ]
108 | }
109 | ],
110 | "action": "playlistRequestAll"
111 | }
112 | ```
113 |
114 | ### Request Presentation (set of slides)
115 |
116 | COMMAND TO SEND:
117 |
118 | ```javascript
119 | {
120 | "action": "presentationRequest",
121 | "presentationPath": "\/Path\/To\/ProPresenter\/Library\/Song 1 Title.pro6",
122 | "presentationSlideQuality": 25
123 | }
124 | ```
125 |
126 | * `presentationPath` is required and it can be structured in one of three ways
127 | * It can be a full path to a pro6 file but note that all slashes need to be preceeded by a backslash in the request.
128 | * It can be the basename of a presentation that exists in the library (eg. `Song 1 Title.pro6`) is (sometimes?) good enough.
129 | * It can be the "playlist location" of the presentation. The playlist location is determined according to the order of items in the playlist window, the items are indexed from 0, and groups are sub-indexed with a dot, then presentations inside the playlist are indexed with a colon and a numeral. That is, the first presentation of the first playlist is `0:0` and if the first playlist item is a group, the first item of the first playlist of that group is `0.0:0`
130 | * A presentationPath specified with a playlist address and not a filename seems to be the most reliable.
131 | * `presentationSlideQuality` is optional. It determines the resolution / size of the slide previews sent from ProPresenter. If left blank, high quality previews will be sent. If set to `0` previews will not be generated at all. The remote app asks for quality `25` first and then follows it up with a second request for quality `100`.
132 |
133 | EXPECTED RESPONSE:
134 |
135 | ```javascript
136 | {
137 | "action": "presentationCurrent",
138 | "presentation": {
139 | "presentationSlideGroups": [
140 | {
141 | "groupName": "[SLIDE GROUP NAME]",
142 | "groupColor": "0 0 0 1", // RGBA scale is from 0-1
143 | "groupSlides": [
144 | {
145 | "slideEnabled": true,
146 | "slideNotes": "",
147 | "slideAttachmentMask": 0,
148 | "slideText": "[SLIDE TEXT HERE]",
149 | "slideImage": "[BASE64 ENCODED IMAGE]",
150 | "slideIndex": "0",
151 | "slideTransitionType": -1,
152 | "slideLabel": "[SLIDE LABEL]",
153 | "slideColor": "0 0 0 1"
154 | }
155 | ]
156 | },
157 | ],
158 | "presentationName": "[PRESENTATION TITLE]",
159 | "presentationHasTimeline": 0,
160 | "presentationCurrentLocation": "[PRESENTATION PATH OF CURRENTLY ACTIVE SLIDE]"
161 | }
162 | }
163 | ```
164 |
165 | * The response contains `presentationCurrent` as the action instead of `presentationRequest`. This seems to be a bug in the ProPresenter response.
166 | * The `presentationCurrentLocation` is not the location of the presentation you requested. It is the path of the presentation whose slide is currently active.
167 | * You can distinguish this response from the real `presentationCurrent` request because that response will include `presentationPath` as a field at the root level of the response.
168 |
169 | ### Request Current Presentation
170 |
171 | COMMAND TO SEND:
172 |
173 | ```javascript
174 | { "action":"presentationCurrent", "presentationSlideQuality": 25}
175 | ```
176 |
177 | EXPECTED RESPONSE:
178 |
179 | Same response as `requestPresentation` except this response will include `presentationPath` as a field at the root level of the response.
180 |
181 | * NOTE: This action only seems to work if there is an *active slide*. When ProPresenter starts, no slide is marked active, so this action *returns nothing until a slide has been triggered*.
182 |
183 |
184 | ### Get Index of Current Slide
185 |
186 | COMMAND TO SEND:
187 |
188 | ```javascript
189 | {"action":"presentationSlideIndex"}
190 | ```
191 | EXPECTED RESPONSE:
192 |
193 | ```javascript
194 | {"action":"presentationSlideIndex","slideIndex":"0"}
195 | ```
196 |
197 | * NOTE: The ProPresenter remote issues this action every time it issues a `presentationRequest` action.
198 |
199 | ### Trigger Slide
200 |
201 | COMMAND TO SEND:
202 |
203 | ```javascript
204 | {"action":"presentationTriggerIndex","slideIndex":3,"presentationPath":"[PRESENTATION PATH]"}
205 | ```
206 |
207 | EXPECTED RESPONSE:
208 |
209 | ```javascript
210 | {"slideIndex":3,"action":"presentationTriggerIndex","presentationPath":"[PRESENTATION PATH]"}
211 | ```
212 |
213 | ### Trigger Next Slide
214 |
215 | COMMAND TO SEND:
216 |
217 | ```javascript
218 | {"action":"presentationTriggerNext"}
219 | ```
220 |
221 | EXPECTED RESPONSE:
222 |
223 | ```javascript
224 | {"slideIndex":3,"action":"presentationTriggerIndex","presentationPath":"[PRESENTATION PATH]"}
225 | ```
226 |
227 | ### Trigger Previous Slide
228 |
229 | COMMAND TO SEND:
230 |
231 | ```javascript
232 | {"action":"presentationTriggerPrevious"}
233 | ```
234 |
235 | EXPECTED RESPONSE:
236 |
237 | ```javascript
238 | {"slideIndex":3,"action":"presentationTriggerIndex","presentationPath":"[PRESENTATION PATH]"}
239 | ```
240 |
241 | ### Get Audio Library
242 |
243 | COMMAND TO SEND:
244 |
245 | ```javascript
246 | { "action": "audioRequest" }
247 | ```
248 |
249 | EXPECTED RESPONSE:
250 |
251 | ```javascript
252 | {
253 | "action": "audioRequest",
254 | "audioPlaylist": [
255 | {
256 | "playlistLocation": "0",
257 | "playlistType": "playlistTypePlaylist",
258 | "playlistName": "Library",
259 | "playlist": [
260 | {
261 | "playlistItemName": "1-11 Have Yourself A Merry Little Christmas.mp3",
262 | "playlistItemArtist": "Chinua Hawk",
263 | "playlistItemType": "playlistItemTypeAudio",
264 | "playlistItemLocation": "0:0"
265 | }
266 | ]
267 | },
268 | {
269 | "playlistLocation": "1",
270 | "playlistType": "playlistTypeGroup",
271 | "playlistName": "Service End",
272 | "playlist": [
273 | {
274 | "playlistLocation": "1.0",
275 | "playlistType": "playlistTypePlaylist",
276 | "playlistName": "random",
277 | "playlist": [
278 | {
279 | "playlistItemName": "03 Black Coal.mp3",
280 | "playlistItemArtist": "Sanctus Real",
281 | "playlistItemType": "playlistItemTypeAudio",
282 | "playlistItemLocation": "1.0:0"
283 | }
284 | ]
285 | }
286 | ]
287 | },
288 | {
289 | "playlistLocation": "2",
290 | "playlistType": "playlistTypeGroup",
291 | "playlistName": "Christmas",
292 | "playlist": []
293 | }
294 | ]
295 | }
296 | ```
297 |
298 | ### Get Current Song
299 |
300 | COMMAND TO SEND:
301 |
302 | ```javascript
303 | { "action": "audioCurrentSong" }
304 | ```
305 |
306 | EXPECTED RESPONSE:
307 |
308 | ```javascript
309 | {
310 | "audioArtist": "",
311 | "action": "audioCurrentSong",
312 | "audioName": "Peaceful Instrumental - C"
313 | }
314 | ```
315 |
316 | ### Check if Audio is Playing (BROKEN)
317 |
318 | ProPresenter 6 always replies "false" to this request.
319 |
320 | COMMAND TO SEND:
321 |
322 | ```javascript
323 | { "action": "audioIsPlaying" }
324 | ```
325 |
326 | EXPECTED RESPONSE:
327 |
328 | ```javascript
329 | {"audioIsPlaying":false,"action":"audioIsPlaying"}
330 | ```
331 |
332 | ### Start Audio Cue
333 |
334 | COMMAND TO SEND:
335 |
336 | ```javascript
337 | {"action":"audioStartCue", "audioChildPath","[Same as Presentation Path Format]"}
338 | ```
339 |
340 | EXPECTED RESPONSE:
341 |
342 | There are multiple responses for an audio cue trigger.
343 |
344 | ```javascript
345 | {"action":"audioPlayPause","audioPlayPause":"Play"}
346 | ```
347 |
348 | ```javascript
349 | {
350 | "audioArtist": "",
351 | "action": "audioTriggered",
352 | "audioName": "Peaceful Instrumental - C"
353 | }
354 | ```
355 |
356 | ### Audio Play/Pause Toggle
357 |
358 | COMMAND TO SEND:
359 |
360 | ```javascript
361 | {"action":"audioPlayPause"}
362 | ```
363 |
364 | EXPECTED RESPONSE:
365 |
366 | ```javascript
367 | {"action":"audioPlayPause","audioPlayPause":"Play"}
368 | ```
369 |
370 |
371 | ### Timeline Play/Pause Toggle
372 |
373 | Note: If Timeline commands are sent without a `presentationPath`, ProPresenter will crash.
374 |
375 | COMMAND TO SEND:
376 |
377 | ```javascript
378 | {"action":"timelinePlayPause","presentationPath":"[PRESENTATION PATH]"}
379 | ```
380 |
381 | NO RESPONSE
382 |
383 | ### Timeline Rewind
384 |
385 | COMMAND TO SEND:
386 |
387 | ```javascript
388 | {"action":"timelineRewind":,"presentationPath":"[PRESENTATION PATH]"}
389 | ```
390 |
391 | NO RESPONSE
392 |
393 | ## Clocks Data
394 |
395 | ### Request all Clocks (timers)
396 |
397 | COMMAND TO SEND:
398 |
399 | ```javascript
400 | {"action":"clockRequest"}
401 | ```
402 |
403 | EXPECTED RESPONSE:
404 |
405 | ```javascript
406 | {
407 | "clockInfo": [
408 | {
409 | "clockType": 0,
410 | "clockState": false,
411 | "clockName": "Countdown 1",
412 | "clockIsPM": 0,
413 | "clockDuration": "0:10:00",
414 | "clockOverrun": false,
415 | "clockEndTime": "--:--:--",
416 | "clockTime": "--:--:--"
417 | },
418 | {
419 | "clockType": 1,
420 | "clockState": false,
421 | "clockName": "Countdown 2",
422 | "clockIsPM": 1,
423 | "clockDuration": "7:00:00",
424 | "clockOverrun": false,
425 | "clockEndTime": "--:--:--",
426 | "clockTime": "--:--:--"
427 | },
428 | {
429 | "clockType": 2,
430 | "clockState": false,
431 | "clockName": "Elapsed Time",
432 | "clockIsPM": 0,
433 | "clockDuration": "0:00:00",
434 | "clockOverrun": false,
435 | "clockEndTime": "--:--:--",
436 | "clockTime": "13:52:23"
437 | }
438 | ],
439 | "action": "clockRequest"
440 | }
441 | ```
442 |
443 | ### Get Clock Current Times
444 |
445 | COMMAND TO SEND:
446 |
447 | ```javascript
448 | {"action":"clockCurrentTimes"}
449 | ```
450 |
451 | EXPECTED RESPONSE:
452 |
453 | ```javascript
454 | {"action":"clockCurrentTimes","clockTimes":["0:10:00","--:--:--","13:52:23"]}
455 | ```
456 |
457 | ### Start Receiving Updates for Clocks (Timers)
458 |
459 | COMMAND TO SEND:
460 |
461 | ```javascript
462 | {"action":"clockStartSendingCurrentTime"}
463 | ```
464 |
465 | EXPECTED RESPONSE IS SAME AS `clockCurrentTimes` (every second):
466 |
467 | ### Stop Receiving Updates for Clocks (Timers)
468 |
469 | COMMAND TO SEND:
470 |
471 | ```javascript
472 | {"action":"clockStopSendingCurrentTime"}
473 | ```
474 |
475 | NO EXPECTED RESPONSE
476 |
477 | ### Start a Clock (Timer)
478 |
479 | COMMAND TO SEND:
480 |
481 | ```javascript
482 | {"action":"clockStart","clockIndex":0}
483 | ```
484 |
485 | EXPECTED RESPONSE:
486 |
487 | ```javascript
488 | {"clockTime":"0:00:00","clockState":1,"clockIndex":0,"clockInfo":[1,1,"0:00:00"],"action":"clockStartStop"}
489 | ```
490 | * `clockState` indicates if the clock is running or not
491 | * Clocks are referenced by index. See reply from "clockRequest" action above to learn indices.
492 |
493 | ### Stop a Clock (Timer)
494 |
495 | COMMAND TO SEND:
496 |
497 | ```javascript
498 | {"action":"clockStop","clockIndex":0}
499 | ```
500 |
501 | EXPECTED RESPONSE:
502 |
503 | ```javascript
504 | {
505 | "clockTime": "0:09:59",
506 | "clockState": 0,
507 | "clockIndex": 0,
508 | "clockInfo": [
509 | 1,
510 | 0,
511 | "0:09:59"
512 | ],
513 | "action": "clockStartStop"
514 | }
515 | ```
516 |
517 | * `clockState` indicates if the clock is running or not
518 | * `clockInfo` is a quick list of information:
519 | * The first item is the clockType
520 | * The second item is the value that was recently changed indicated by the `action`
521 | * The third item is the current clock timestring.
522 | * Clocks are referenced by index. See reply from "clockRequest" action above to learn indices.
523 |
524 | ### Reset a Clock (Timer)
525 |
526 | COMMAND TO SEND:
527 |
528 | ```javascript
529 | {"action":"clockReset","clockIndex":"0"}
530 | ```
531 |
532 | EXPECTED RESPONSE:
533 |
534 | ```javascript
535 | {
536 | "action": "clockResetIndex",
537 | "clockIndex": 1
538 | }
539 | ```
540 |
541 | * Clocks are referenced by index. See reply from "clockRequest" action above to learn indices.
542 |
543 |
544 |
545 | ### Update a Clock (Timer) (eg edit time)
546 |
547 | COMMAND TO SEND:
548 |
549 | ```javascript
550 | {
551 | "action":"clockUpdate",
552 | "clockIndex":1,
553 | "clockType":0,
554 | "clockTime":"09:04:00",
555 | "clockOverrun":false,
556 | "clockIsPM":1,
557 | "clockName":"Countdown 2",
558 | "clockElapsedTime":"0:02:00"
559 | }
560 | ```
561 |
562 | * Clocks are referenced by index. See reply from "clockRequest" action above to learn indexes.
563 | * Not all parameters are required for each clock type.
564 | * Countdown clocks only need "clockTime".
565 | * Elapsed Time Clocks need "clockTime" and optionally will use "clockElapsedTime" if you send it (to set the End Time).
566 | * You can rename a clock by optionally including the clockName.
567 | * Type 0 is Countdown
568 | * Type 1 is CountDown to Time
569 | * Type 2 is Elapsed Time.
570 | * Overrun can be modified if you choose to include that as well.
571 |
572 |
573 | ### Additional Clock Actions
574 |
575 | `clockResetAll`, `clockStopAll`, `clockStartAll`
576 |
577 |
578 | ### Get all Messages
579 |
580 | COMMAND TO SEND:
581 |
582 | ```javascript
583 | {"action":"messageRequest"}
584 | ```
585 |
586 | EXPECTED RESPONSE:
587 |
588 | ```javascript
589 | {
590 | "action": "messageRequest",
591 | "messages": [
592 | {
593 | "messageComponents": [
594 | "message:",
595 | "${Message}"
596 | ],
597 | "messageTitle": "Message"
598 | },
599 | {
600 | "messageComponents": [
601 | "Session will begin in: ",
602 | "${Countdown 1: H:MM:SS}"
603 | ],
604 | "messageTitle": "Countdown"
605 | },
606 | {
607 | "messageComponents": [
608 | "${Message}"
609 | ],
610 | "messageTitle": "Message"
611 | },
612 | {
613 | "messageComponents": [
614 | "Service starts in ",
615 | "${countDownTimerName_1: H:MM:SS}"
616 | ],
617 | "messageTitle": "Countdown"
618 | }
619 | ]
620 | }
621 | ```
622 |
623 | * The key is everything inside the curly braces `${}` so that the key for a countdown looks like this `Countdown 1: H:MM:SS`.
624 | * If the key refers to a countdown, the value is used to update the `duration` field of the countdown timer, but will not perform a "reset".
625 | * If the key refers to a countdown and the countdown is not running, this will resume it from its current value.
626 |
627 | ### Display a Message
628 |
629 | Display a message identified by its index. Add as many key, value pairs as you like. Keys can be name of timers.
630 |
631 | COMMAND TO SEND:
632 |
633 | ```javascript
634 | {"action":"messageSend","messageIndex":0,"messageKeys":"["key1","key2"....]","messageValues":"["Value1","Value2"...]"}
635 | ```
636 |
637 | AN EXAMPLE USING THE DATA ABOVE:
638 |
639 | ```javascript
640 | {"action":"messageSend","messageIndex":0,"messageKeys":["Message"],"messageValues":["Test"]}
641 | ```
642 |
643 | ### Hide a Message
644 |
645 | COMMAND TO SEND:
646 | Hide a message identified by its index
647 |
648 | ```javascript
649 | {"action":"messageHide","messageIndex","0"}
650 | ```
651 |
652 | ### Clear All
653 |
654 | COMMAND TO SEND:
655 |
656 | ```javascript
657 | {"action":"clearAll"}
658 | ```
659 |
660 | ### Clear Slide
661 |
662 | COMMAND TO SEND:
663 |
664 | ```javascript
665 | {"action":"clearText"}
666 | ```
667 |
668 | ### Clear Props
669 |
670 | COMMAND TO SEND:
671 |
672 | ```javascript
673 | {"action":"clearProps"}
674 | ```
675 |
676 | ### Clear Audio
677 |
678 | COMMAND TO SEND:
679 |
680 | ```javascript
681 | {"action":"clearAudio"}
682 | ```
683 |
684 | ### Clear Video
685 |
686 | COMMAND TO SEND:
687 |
688 | ```javascript
689 | {"action":"clearVideo"}
690 | ```
691 |
692 | ### Clear Telestrator
693 |
694 | COMMAND TO SEND:
695 |
696 | ```javascript
697 | {"action":"clearTelestrator"}
698 | ```
699 |
700 | ### Clear To Logo
701 |
702 | COMMAND TO SEND:
703 |
704 | ```javascript
705 | {"action":"clearToLogo"}
706 | ```
707 |
708 | ### Show Stage Display Message
709 |
710 | COMMAND TO SEND:
711 |
712 | ```javascript
713 | {"action":"stageDisplaySendMessage","stageDisplayMessage":"Type a Message Here"}
714 | ```
715 |
716 | THERE IS NO EXPECTED RESPONSE
717 |
718 | ### Hide Stage Display Message
719 |
720 | COMMAND TO SEND:
721 |
722 | ```javascript
723 | {"action":"stageDisplayHideMessage"}
724 | ```
725 |
726 | THERE IS NO EXPECTED RESPONSE
727 |
728 | ### Select Stage Display Layout
729 |
730 | COMMAND TO SEND:
731 |
732 | ```javascript
733 | {"action":"stageDisplaySetIndex","stageDisplayIndex":"[STAGE DISPLAY INDEX]"}
734 | ```
735 |
736 | EXPECTED RESPONSE IS THE SAME AS THE SENT COMMAND
737 |
738 | ### Get Stage Display Layouts
739 |
740 | COMMAND TO SEND:
741 |
742 | ```javascript
743 | { "action": "stageDisplaySets" }
744 | ```
745 |
746 | EXPECTED RESPONSE:
747 |
748 | ```javascript
749 | {
750 | "stageDisplayIndex": 4,
751 | "action": "stageDisplaySets",
752 | "stageDisplaySets": [
753 | "Default",
754 | "Easter Closer",
755 | "Live Current - Static Next - no borders",
756 | "Static Current - Static Next",
757 | "Songs",
758 | "Slides"
759 | ]
760 | }
761 | ```
762 |
763 | ## TODO: Complete documentation for remaining remote commands...
764 | socialSendTweet
765 | telestratorSettings
766 | telestratorEndEditing
767 | telestratorSet
768 | telestratorUndo
769 | telestratorNew
770 |
771 | ## Stage Display API
772 |
773 | ### Connecting
774 |
775 | ```javascript
776 | ws://[host]:[port]/stagedisplay
777 | ```
778 |
779 | ### Authenticate
780 |
781 | COMMAND TO SEND:
782 |
783 | ```javascript
784 | {"pwd":PASSWORD,"ptl":610,"acn":"ath"}
785 | ```
786 |
787 | EXPECTED RESPONSE:
788 |
789 | ```javascript
790 | {"acn":"ath","ath":true,"err":""}
791 | ```
792 |
793 | ### Get All Stage Display Layouts
794 |
795 | COMMAND TO SEND:
796 |
797 | ```javascript
798 | {"acn":"asl"}
799 | ```
800 |
801 | EXPECTED RESPONSE:
802 |
803 | ```javascript
804 | {
805 | "acn": "asl",
806 | "ary": [
807 | {
808 | "brd": true,
809 | "uid": "753B184F-CCCD-42F9-A883-D1DF86E1FFB8",
810 | "zro": 0,
811 | "oCl": "1.000000 0.000000 0.000000",
812 | "fme": [
813 | {
814 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
815 | "mde": 1,
816 | "tCl": "1.000000 1.000000 1.000000",
817 | "tAl": 2,
818 | "tSz": 60,
819 | "nme": "Current Slide",
820 | "typ": 1
821 | },
822 | {
823 | "ufr": "{{0.024390243902439025, 0.27223427331887201}, {0.40182926829268295, 0.10412147505422993}}",
824 | "mde": 1,
825 | "tCl": "1.000000 1.000000 1.000000",
826 | "tAl": 2,
827 | "tSz": 60,
828 | "nme": "Current Slide Notes",
829 | "typ": 3
830 | },
831 | {
832 | "ufr": "{{0.45000000000000001, 0.47396963123644253}, {0.29999999999999999, 0.40021691973969631}}",
833 | "mde": 1,
834 | "tCl": "1.000000 1.000000 1.000000",
835 | "tAl": 2,
836 | "tSz": 60,
837 | "nme": "Next Slide",
838 | "typ": 2
839 | },
840 | {
841 | "ufr": "{{0.45000000000000001, 0.37310195227765725}, {0.29999999999999999, 0.1019522776572668}}",
842 | "mde": 1,
843 | "tCl": "1.000000 1.000000 1.000000",
844 | "tAl": 2,
845 | "tSz": 60,
846 | "nme": "Next Slide Notes",
847 | "typ": 4
848 | },
849 | {
850 | "ufr": "{{0.77500000000000002, 0.37418655097613884}, {0.20000000000000001, 0.40130151843817785}}",
851 | "nme": "Chord Chart",
852 | "mde": 1,
853 | "typ": 9
854 | },
855 | {
856 | "ufr": "{{0.050000000000000003, 0.89913232104121477}, {0.20000000000000001, 0.1019522776572668}}",
857 | "mde": 1,
858 | "tCl": "1.000000 1.000000 1.000000",
859 | "tAl": 2,
860 | "tSz": 200,
861 | "nme": "Clock",
862 | "typ": 6
863 | },
864 | {
865 | "ufr": "{{0.40000000000000002, 0.89913232104121477}, {0.20000000000000001, 0.1019522776572668}}",
866 | "mde": 1,
867 | "tCl": "1.000000 1.000000 1.000000",
868 | "tAl": 2,
869 | "tSz": 200,
870 | "nme": "Video Countdown",
871 | "typ": 8
872 | },
873 | {
874 | "ufr": "{{0.050000000000000003, 0.024945770065075923}, {0.90000000000000002, 0.10086767895878525}}",
875 | "fCl": "0.000000 1.000000 0.000000",
876 | "mde": 1,
877 | "tCl": "1.000000 1.000000 1.000000",
878 | "tAl": 2,
879 | "fCh": true,
880 | "tSz": 60,
881 | "nme": "Message",
882 | "typ": 5
883 | },
884 | {
885 | "ufr": "{{0.68978420350609759, 0.89488713394793928}, {0.20000000000000001, 0.1019522776572668}}",
886 | "uid": "47E8B48C-0D61-4EFC-9517-BF9FB894C8E2",
887 | "mde": 1,
888 | "tCl": "1.000000 1.000000 1.000000",
889 | "tAl": 2,
890 | "tSz": 200,
891 | "nme": "Countdown 1",
892 | "typ": 7
893 | }
894 | ],
895 | "ovr": true,
896 | "acn": "sl",
897 | "nme": "Default"
898 | },
899 | {
900 | "brd": false,
901 | "uid": "50AF3434-4328-40AC-846F-CC9583381311",
902 | "zro": 0,
903 | "oCl": "0.985948 0.000000 0.026951",
904 | "fme": [
905 | {
906 | "ufr": "{{0.60304878048780486, 0.1963123644251627}, {0.39695121951219514, 0.80043383947939262}}",
907 | "mde": 1,
908 | "tCl": "0.990463 1.000000 0.041173",
909 | "tAl": 1,
910 | "tSz": 80,
911 | "nme": "Current Slide",
912 | "typ": 1
913 | },
914 | {
915 | "ufr": "{{0.0024390243902439024, 0.0021691973969631237}, {0.599390243902439, 0.99457700650759218}}",
916 | "mde": 1,
917 | "tCl": "0.679783 1.000000 0.885215",
918 | "tAl": 0,
919 | "tSz": 120,
920 | "nme": "Current Slide Notes",
921 | "typ": 3
922 | },
923 | {
924 | "ufr": "{{0.60304878048780486, 0.0021691973969631237}, {0.39512195121951221, 0.19305856832971802}}",
925 | "uid": "D1096B85-CF31-4365-A6E6-ED94264E7DCA",
926 | "mde": 1,
927 | "tCl": "1.000000 1.000000 1.000000",
928 | "tAl": 2,
929 | "tSz": 200,
930 | "nme": "Elapsed Time",
931 | "typ": 7
932 | }
933 | ],
934 | "ovr": false,
935 | "acn": "sl",
936 | "nme": "Easter Closer"
937 | },
938 | {
939 | "brd": false,
940 | "uid": "F8260B13-9C5B-4D2C-80F1-C72346759F11",
941 | "zro": 0,
942 | "oCl": "0.985948 0.000000 0.026951",
943 | "fme": [
944 | {
945 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
946 | "mde": 2,
947 | "tCl": "1.000000 1.000000 1.000000",
948 | "tAl": 2,
949 | "tSz": 60,
950 | "nme": "Current Slide",
951 | "typ": 1
952 | },
953 | {
954 | "ufr": "{{0.025000000000000001, 0.27440347071583515}, {0.40000000000000002, 0.10086767895878525}}",
955 | "mde": 1,
956 | "tCl": "1.000000 1.000000 1.000000",
957 | "tAl": 2,
958 | "tSz": 60,
959 | "nme": "Current Slide Notes",
960 | "typ": 3
961 | },
962 | {
963 | "ufr": "{{0.45000000000000001, 0.47396963123644253}, {0.29999999999999999, 0.40021691973969631}}",
964 | "mde": 0,
965 | "tCl": "1.000000 1.000000 1.000000",
966 | "tAl": 2,
967 | "tSz": 60,
968 | "nme": "Next Slide",
969 | "typ": 2
970 | },
971 | {
972 | "ufr": "{{0.45000000000000001, 0.37310195227765725}, {0.29999999999999999, 0.1019522776572668}}",
973 | "mde": 1,
974 | "tCl": "1.000000 1.000000 1.000000",
975 | "tAl": 2,
976 | "tSz": 60,
977 | "nme": "Next Slide Notes",
978 | "typ": 4
979 | },
980 | {
981 | "ufr": "{{0.77500000000000002, 0.37418655097613884}, {0.20000000000000001, 0.40130151843817785}}",
982 | "nme": "Chord Chart",
983 | "mde": 1,
984 | "typ": 9
985 | },
986 | {
987 | "ufr": "{{0, 0.89804772234273322}, {0.20060975609756099, 0.10303687635574837}}",
988 | "mde": 1,
989 | "tCl": "1.000000 1.000000 1.000000",
990 | "tAl": 2,
991 | "tSz": 200,
992 | "nme": "Clock",
993 | "typ": 6
994 | },
995 | {
996 | "ufr": "{{0.79878048780487809, 0.89696312364425168}, {0.20060975609756099, 0.10303687635574837}}",
997 | "mde": 1,
998 | "tCl": "1.000000 1.000000 1.000000",
999 | "tAl": 2,
1000 | "tSz": 200,
1001 | "nme": "Video Countdown",
1002 | "typ": 8
1003 | },
1004 | {
1005 | "ufr": "{{0.050000000000000003, 0.024945770065075923}, {0.90000000000000002, 0.10086767895878525}}",
1006 | "fCl": "0.135296 1.000000 0.024919",
1007 | "mde": 1,
1008 | "tCl": "1.000000 1.000000 1.000000",
1009 | "tAl": 2,
1010 | "fCh": true,
1011 | "tSz": 60,
1012 | "nme": "Message",
1013 | "typ": 5
1014 | }
1015 | ],
1016 | "ovr": false,
1017 | "acn": "sl",
1018 | "nme": "Live Current - Static Next - no borders"
1019 | },
1020 | {
1021 | "brd": true,
1022 | "uid": "12CB7383-FA02-47BB-B501-747ADCA860D3",
1023 | "zro": 0,
1024 | "oCl": "0.985948 0.000000 0.026951",
1025 | "fme": [
1026 | {
1027 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
1028 | "mde": 0,
1029 | "tCl": "1.000000 1.000000 1.000000",
1030 | "tAl": 2,
1031 | "tSz": 60,
1032 | "nme": "Current Slide",
1033 | "typ": 1
1034 | },
1035 | {
1036 | "ufr": "{{0.025000000000000001, 0.27440347071583515}, {0.40000000000000002, 0.10086767895878525}}",
1037 | "mde": 1,
1038 | "tCl": "1.000000 1.000000 1.000000",
1039 | "tAl": 2,
1040 | "tSz": 60,
1041 | "nme": "Current Slide Notes",
1042 | "typ": 3
1043 | },
1044 | {
1045 | "ufr": "{{0.45000000000000001, 0.47396963123644253}, {0.29999999999999999, 0.40021691973969631}}",
1046 | "mde": 0,
1047 | "tCl": "1.000000 1.000000 1.000000",
1048 | "tAl": 2,
1049 | "tSz": 60,
1050 | "nme": "Next Slide",
1051 | "typ": 2
1052 | },
1053 | {
1054 | "ufr": "{{0.45000000000000001, 0.37310195227765725}, {0.29999999999999999, 0.1019522776572668}}",
1055 | "mde": 1,
1056 | "tCl": "1.000000 1.000000 1.000000",
1057 | "tAl": 2,
1058 | "tSz": 60,
1059 | "nme": "Next Slide Notes",
1060 | "typ": 4
1061 | },
1062 | {
1063 | "ufr": "{{0.77500000000000002, 0.37418655097613884}, {0.20000000000000001, 0.40130151843817785}}",
1064 | "nme": "Chord Chart",
1065 | "mde": 1,
1066 | "typ": 9
1067 | },
1068 | {
1069 | "ufr": "{{0, 0.89804772234273322}, {0.20060975609756099, 0.10303687635574837}}",
1070 | "mde": 1,
1071 | "tCl": "1.000000 1.000000 1.000000",
1072 | "tAl": 2,
1073 | "tSz": 200,
1074 | "nme": "Clock",
1075 | "typ": 6
1076 | },
1077 | {
1078 | "ufr": "{{0.79878048780487809, 0.89696312364425168}, {0.20060975609756099, 0.10303687635574837}}",
1079 | "mde": 1,
1080 | "tCl": "1.000000 1.000000 1.000000",
1081 | "tAl": 2,
1082 | "tSz": 200,
1083 | "nme": "Video Countdown",
1084 | "typ": 8
1085 | },
1086 | {
1087 | "ufr": "{{0.050000000000000003, 0.024945770065075923}, {0.90000000000000002, 0.10086767895878525}}",
1088 | "fCl": "0.135296 1.000000 0.024919",
1089 | "mde": 1,
1090 | "tCl": "1.000000 1.000000 1.000000",
1091 | "tAl": 2,
1092 | "fCh": true,
1093 | "tSz": 60,
1094 | "nme": "Message",
1095 | "typ": 5
1096 | }
1097 | ],
1098 | "ovr": false,
1099 | "acn": "sl",
1100 | "nme": "Static Current - Static Next"
1101 | }
1102 | ]
1103 | }
1104 | ```
1105 |
1106 | * `acn` of `asl` means "all stage layouts"
1107 | * `ary` indicates array of stage layouts
1108 | * `nme` indicates layout name
1109 | * `ovr` indicates if overrun color should be used
1110 | * `oCl` indicates color for timer overruns
1111 | * `brd` indicates if borders and labels should be used
1112 | * `uid` indicates layout uid
1113 | * `zro` indicates if zeroes should be removed from times
1114 | * `fme` indicates array of frame layout specifications
1115 | * frame positions are indicated by `ufr` and specified in terms of screen percentages
1116 | * frame name is indicated by `nme`
1117 | * frame text color is indicated by `tCl`
1118 | * frame font size is indicated by `tSz`
1119 | * frame message flash color is indicated by `fCl`
1120 | * frame use message flash indicated by `fCh`
1121 | * frame timer uid is indicated by `uid`
1122 | * frame mode is indicated by `mde`
1123 | * mode 0: static image
1124 | * mode 1: text
1125 | * mode 2: live slide
1126 | * frame type is indicated by `typ` and determines what content goes in this frame
1127 | * type 1: current slide
1128 | * type 2: next slide
1129 | * type 3: current slide notes
1130 | * type 4: next slide notes
1131 | * type 5: Stage Message (uses message flash values)
1132 | * type 6: Clock
1133 | * type 7: Timer Display (uses `uid` to specify timer)
1134 | * type 8: Video Countdown
1135 | * type 9: Chord Chart
1136 |
1137 | ### On New Stage Display Selected
1138 |
1139 | EXPECTED RESPONSE:
1140 |
1141 | ```javascript
1142 | {
1143 | "brd": true,
1144 | "uid": "12CB7383-FA02-47BB-B501-747ADCA860D3",
1145 | "zro": 0,
1146 | "oCl": "0.985948 0.000000 0.026951",
1147 | "fme": [
1148 | {
1149 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
1150 | "mde": 0,
1151 | "tCl": "1.000000 1.000000 1.000000",
1152 | "tAl": 2,
1153 | "tSz": 60,
1154 | "nme": "Current Slide",
1155 | "typ": 1
1156 | },
1157 | {
1158 | "ufr": "{{0.025000000000000001, 0.27440347071583515}, {0.40000000000000002, 0.10086767895878525}}",
1159 | "mde": 1,
1160 | "tCl": "1.000000 1.000000 1.000000",
1161 | "tAl": 2,
1162 | "tSz": 60,
1163 | "nme": "Current Slide Notes",
1164 | "typ": 3
1165 | },
1166 | {
1167 | "ufr": "{{0.45000000000000001, 0.47396963123644253}, {0.29999999999999999, 0.40021691973969631}}",
1168 | "mde": 0,
1169 | "tCl": "1.000000 1.000000 1.000000",
1170 | "tAl": 2,
1171 | "tSz": 60,
1172 | "nme": "Next Slide",
1173 | "typ": 2
1174 | },
1175 | {
1176 | "ufr": "{{0.45000000000000001, 0.37310195227765725}, {0.29999999999999999, 0.1019522776572668}}",
1177 | "mde": 1,
1178 | "tCl": "1.000000 1.000000 1.000000",
1179 | "tAl": 2,
1180 | "tSz": 60,
1181 | "nme": "Next Slide Notes",
1182 | "typ": 4
1183 | },
1184 | {
1185 | "ufr": "{{0.77500000000000002, 0.37418655097613884}, {0.20000000000000001, 0.40130151843817785}}",
1186 | "nme": "Chord Chart",
1187 | "mde": 1,
1188 | "typ": 9
1189 | },
1190 | {
1191 | "ufr": "{{0, 0.89804772234273322}, {0.20060975609756099, 0.10303687635574837}}",
1192 | "mde": 1,
1193 | "tCl": "1.000000 1.000000 1.000000",
1194 | "tAl": 2,
1195 | "tSz": 200,
1196 | "nme": "Clock",
1197 | "typ": 6
1198 | },
1199 | {
1200 | "ufr": "{{0.79878048780487809, 0.89696312364425168}, {0.20060975609756099, 0.10303687635574837}}",
1201 | "mde": 1,
1202 | "tCl": "1.000000 1.000000 1.000000",
1203 | "tAl": 2,
1204 | "tSz": 200,
1205 | "nme": "Video Countdown",
1206 | "typ": 8
1207 | },
1208 | {
1209 | "ufr": "{{0.050000000000000003, 0.024945770065075923}, {0.90000000000000002, 0.10086767895878525}}",
1210 | "fCl": "0.135296 1.000000 0.024919",
1211 | "mde": 1,
1212 | "tCl": "1.000000 1.000000 1.000000",
1213 | "tAl": 2,
1214 | "fCh": true,
1215 | "tSz": 60,
1216 | "nme": "Message",
1217 | "typ": 5
1218 | }
1219 | ],
1220 | "ovr": false,
1221 | "acn": "sl",
1222 | "nme": "Static Current - Static Next"
1223 | }
1224 | ```
1225 |
1226 | * `acn` of `sl` indicates this is a single stage layout
1227 |
1228 | ### Request Current Stage Display Layout
1229 |
1230 | COMMAND TO SEND:
1231 |
1232 | ```javascript
1233 | {"acn":"psl"}
1234 | ```
1235 |
1236 | EXPECTED RESPONSE (also used when stage display is updated):
1237 |
1238 | ```javascript
1239 | {"acn":"psl","uid":"[STAGE DISPLAY UID]"}
1240 | ```
1241 |
1242 | ### Request Frame Values for Stage Display
1243 |
1244 | COMMAND TO SEND:
1245 |
1246 | ```javascript
1247 | {"acn":"fv","uid":"[STAGE DISPLAY UID"}
1248 | ```
1249 |
1250 | EXPECTED RESPONSE:
1251 |
1252 | ```javascript
1253 | {
1254 | "acn": "fv",
1255 | "ary": [
1256 | {
1257 | "acn": "cs",
1258 | "uid": "FAFCA1CB-8CB8-4E53-8B7C-8D61154516D0",
1259 | "txt": ""
1260 | },
1261 | {
1262 | "acn": "ns",
1263 | "uid": "95D16968-589A-11EB-8D62-FCAA147AEF2F",
1264 | "txt": ""
1265 | },
1266 | {
1267 | "acn": "msg",
1268 | "txt": ""
1269 | },
1270 | {
1271 | "acn": "sys",
1272 | "txt": " 9:51 AM"
1273 | },
1274 | {
1275 | "acn": "vid",
1276 | "txt": ""
1277 | }
1278 | ]
1279 | }
1280 | ```
1281 |
1282 | ### On New Live Slide Frame
1283 |
1284 | EXPECTED RESPONSE:
1285 |
1286 | ```javascript
1287 | {
1288 | "RVLiveStream_action": "RVLiveStream_frameData",
1289 | "RVLiveStream_frameDataLength": 14625,
1290 | "RVLiveStream_frameData": "/9j//gAQTGF2YzU3LjUzLjEwMAD/2wBDAAgEBAQEBAUFBQUFBQYGBgYGBgYGBgYGBgYHBwcICAgHBwcGBgcHCAgICAkJCQgICAgJCQoKCgwMCwsODg4RERT/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsBAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/wAARCAEOAeADASIAAhIAAxIA/9oADAMBAAIRAxEAPwDwqiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKOtAAAUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRS7H/un8jRsf8Aun8jQAAJRS7H/un8jRsf+6fyNAAAlFLsf+6fyNGx/wC6fyNAAAlFLsf+6fyNGx/7p/I0AACUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRS7H/un8jRsf8Aun8jQAAJRS7H/un8jRsf+6fyNAAAlFLsf+6fyNGx/wC6fyNAAAlFLsf+6fyNGx/7p/I0AACUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRS7H/un8jRsf8Aun8jQAAJRS7H/un8jRsf+6fyNAAAlFLsf+6fyNGx/wC6fyNAAAlFLsf+6fyNGx/7p/I0AACUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRS7H/un8jRsf8Aun8jQAAJRS7H/un8jRsf+6fyNAAAlFLsf+6fyNGx/wC6fyNAAAlFLsf+6fyNGx/7p/I0AACUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRRRQAAFFFFAAAUUUUAABXafs9wQXPxg8LRTxRzRtcT7o5EV0b/AEWY8qwIPPqK4uu2/Z0/5LL4U/6+bj/0kmoAAPrL/hH9B/6BOm/+Adv/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"
1291 | }
1292 | ```
1293 |
1294 | * Base64 Encoded Image Bytes
1295 | * Only the Current Slide can be "Live"
1296 | * Live slide images are pushed to the client over the websocket.
1297 |
1298 | ### To Get Static Slide Images
1299 |
1300 | TO RETRIEVE STATIC SLIDE IMAGES:
1301 |
1302 | Issue a normal GET request to the following URL:
1303 |
1304 | http://PROPRESENTER_IP:PROPRESENTER_PORT/stage/image/SLIDE_UID
1305 |
1306 | EXPECTED RESPONSE:
1307 |
1308 | normal jpeg image
1309 |
1310 | ### On New Time
1311 |
1312 | EXPECTED RESPONSE:
1313 |
1314 | ```javascript
1315 | {"acn":"sys","txt":" 11:17 AM"}
1316 | ```
1317 |
1318 | ### On Timer Update
1319 |
1320 | EXPECTED RESPONSE:
1321 |
1322 | ```javascript
1323 | { acn: 'tmr',
1324 | uid: '[TIMER UID]',
1325 | txt: '--:--:--' }
1326 | ```
1327 |
1328 | ### On New Slide
1329 |
1330 | EXPECTED RESPONSE:
1331 |
1332 | ```javascript
1333 | {
1334 | "acn": "fv",
1335 | "ary": [
1336 | {
1337 | "acn": "cs", # CURRENT SLIDE
1338 | "uid": "[SLIDE UID]",
1339 | "txt": "[SLIDE TEXT]"
1340 | },
1341 | {
1342 | "acn": "ns", # NEXT SLIDE
1343 | "uid": "[SLIDE UID]",
1344 | "txt": "[SLIDE TEXT]"
1345 | },
1346 | {
1347 | "acn": "csn", # CURRENT SLIDE NOTES
1348 | "txt": "[SLIDE NOTES]"
1349 | },
1350 | {
1351 | "acn": "nsn", # NEXT SLIDE NOTES
1352 | "txt": "[SLIDE NOTES]"
1353 | }
1354 | ]
1355 | }
1356 | ```
1357 |
--------------------------------------------------------------------------------
/Pro7.md:
--------------------------------------------------------------------------------
1 | # ProPresenter7-API
2 |
3 | Documenting RenewedVision's undocumented network protocols with examples.
4 |
5 | This document refers to _ProPresenter 7_.
6 |
7 | **Warning:** Be careful! It's easy to CRASH ProPresenter when sending invalid messages!
8 |
9 | Both the Remote Control and the Stage Display protocols are unencrypted text-based WebSocket connections from the client App to the ProPresenter instance.
10 |
11 | ## IMPORTANT DIFFERENCE FROM PRO 6
12 | Pro 7 implements a slightly different WebSocket protocol that most websocket clients should handle transparently; however, in rare cases this implementation can cause problems for developers. Of note is that the protocol requires two http request headers to be included in addition to the standard WebSocket upgrade headers.
13 |
14 | `Sec-WebSocket-Key` and `Sec-WebSocket-Version`
15 |
16 | The key must be a Base64 encoded key similar to this: `a8STpzZ6qXqaQvnHNehseA==`
17 |
18 | The Version must be at least 13.
19 |
20 | Note also that Pro7 incorrectly expects the HTTP header keys to appear in CamelCase which violates the HTTP spec, but nonetheless is what it is.
21 |
22 | Therefore, a correctly formed connection to the Pro7 server must have headers looking like this:
23 |
24 | ```
25 | GET /stagedisplay HTTP/1.1
26 | Upgrade: websocket
27 | Connection: Upgrade
28 | Sec-WebSocket-Key: /8STpzZ6qXqaQvnHNehseA==
29 | Sec-WebSocket-Version: 13
30 | ```
31 |
32 | ## Remote Control
33 |
34 | ### Connecting
35 |
36 | ```javascript
37 | ws://[host]:[port]/remote
38 | ```
39 |
40 | ### Authenticate
41 |
42 | COMMAND TO SEND:
43 |
44 | ```javascript
45 | {"action":"authenticate","protocol":"701","password":"control"}
46 | ```
47 |
48 | - Once connected, you must authenticate first before sending any other messages.
49 | - The value of the protocol parameter is used to perform a version check on the remote client.
50 | - ProPresenter 7.0 - 7.4.1 checks for a value here of at least 700 - otherwise it denies authentication and returns "Protocol out of date. Update application"
51 | - ProPresenter 7.4.2 and later checks for a value here of at least 701 - otherwise it denies authentication and returns "Protocol out of date. Update application"
52 |
53 | EXPECTED RESPONSE:
54 |
55 | ```javascript
56 | {"controller":1,"authenticated":1,"error":"","majorVersion":7,"minorVersion":6,"action":"authenticate"}
57 | ```
58 |
59 | - authenticated should be 1 when sucessful and 0 when failed. The application version numbers are included in this response.
60 |
61 | ### Trigger a Pro7 Macro
62 | ```javascript
63 | {"action":"macrosTrigger","macroName":""}
64 | or
65 | {"action":"macrosTrigger","macroID":""}
66 | or
67 | {"action":"macrosTrigger","macroIndex":}
68 | ```
69 | - You can optionally send a macrosTrigger action that includes multiple identifier parameters, they are handled in the following order of precedence:
70 | 1. macroID
71 | 2. macroName
72 | 3. macroIndex
73 |
74 | ### Request list of all Pro7 Macros
75 | ```javascript
76 | {"action":"macrosRequest"}
77 | ```
78 | EXPECTED RESPONSE:
79 | ```javascript
80 | {
81 | "action": "macrosRequest",
82 | "macros": [
83 | {
84 | "macroName": "",
85 | "macroID": "",
86 | "macroColor": " ",
87 | "macroIndex":
88 | }
89 | ]
90 | }
91 | ```
92 | - Return a list of all Macros.
93 | - Color is provided as three double values separated by spaces: "r g b"
94 |
95 | ### Trigger a Pro7 Look
96 | ```javascript
97 | {"action":"looksTrigger","lookName":""}
98 | or
99 | {"action":"looksTrigger","lookID":""}
100 | or
101 | {"action":"looksTrigger","lookIndex":}
102 | ```
103 | - Triggering a Pro7 Look makes that Look "Live"
104 | - You can optionally send a looksTrigger action that includes multiple identifier parameters, they are handled in the following order of precedence:
105 | 1. lookID
106 | 2. lookName
107 | 3. lookIndex
108 |
109 | ### Request list of all Pro7 Looks
110 | ```javascript
111 | {"action": "looksRequest"}
112 | ```
113 | EXPECTED RESPONSE:
114 | ```javascript
115 | {
116 | "action": "looksRequest",
117 | "looks": [
118 | {
119 | "lookName": "",
120 | "lookID": "",
121 | "lookIndex":
122 | }
123 | ],
124 | "activeLook": {
125 | "lookName": "",
126 | "lookID": ""
127 | }
128 | }
129 | ```
130 | - Return a list of all Looks.
131 | - Includes the currently active Look (activeLook)
132 |
133 | ### Get Library (all presentations in all libraries)
134 |
135 | COMMAND TO SEND:
136 |
137 | ```javascript
138 | {"action":"libraryRequest"}
139 | ```
140 |
141 | EXPECTED RESPONSE (MacOS):
142 |
143 | ```javascript
144 | {
145 | "library": [
146 | "\/Path\/To\/ProPresenter\/Library\/Come Alive (Dry Bones).pro",
147 | "\/Path\/To\/ProPresenter\/Library\/Pour Out My Heart.pro",
148 | "\/Path\/To\/ProPresenter\/Library\/Away in a manger.pro",
149 | "... ALL PRESENTATIONS IN ALL LIBRARIES AS A SINGLE LIST ..."
150 | ],
151 | "action": "libraryRequest"
152 | }
153 | ```
154 |
155 | - Note the use of slashes in the response. ProPresenter expects library requests to follow this pattern exactly.
156 |
157 | EXPECTED RESPONSE (Windows):
158 |
159 | ```javascript
160 | {
161 | "library": [
162 | "C:/Users/media/Documents/ProPresenter/Libraries/Default/Alive.pro",
163 | "C:/Users/media/Documents/ProPresenter/Libraries/Default/All Because of Jesus.pro",
164 | "C:/Users/media/Documents/ProPresenter/Libraries/Sample/Announcements.pro",
165 | "C:/Users/media/Documents/ProPresenter/Libraries/Sample/Another In The Fire.pro",
166 | "... ALL PRESENTATIONS IN ALL LIBRARIES AS A SINGLE LIST ..."
167 | ],
168 | "action": "libraryRequest"
169 | }
170 | ```
171 |
172 | ### Get All Playlists
173 |
174 | COMMAND TO SEND:
175 |
176 | ```javascript
177 | {"action":"playlistRequestAll"}
178 | ```
179 |
180 | EXPECTED RESPONSE:
181 |
182 | This request returns all playlists according to the following format.
183 |
184 | ```javascript
185 | {
186 | "playlistAll": [
187 | {
188 | "playlistLocation": "0",
189 | "playlistType": "playlistTypePlaylist",
190 | "playlistName": "Default",
191 | "playlist": [
192 | {
193 | "playlistItemName": "!~ PRE-SERVICE",
194 | "playlistItemLocation": "0:0",
195 | "playlistItemType": "playlistItemTypePresentation"
196 | },
197 | ]
198 | },
199 | {
200 | "playlistLocation": "1",
201 | "playlistType": "playlistTypeGroup",
202 | "playlistName": "2017",
203 | "playlist": [
204 | {
205 | "playlistLocation": "1.0",
206 | "playlistType": "playlistTypePlaylist",
207 | "playlistName": "2017-01-28-Vision Dinner",
208 | "playlist": [
209 | {
210 | "playlistItemName": "!MISC2",
211 | "playlistItemLocation": "1.0:0",
212 | "playlistItemType": "playlistItemTypePresentation"
213 | },
214 | {
215 | "playlistItemName": "!MISC1",
216 | "playlistItemLocation": "1.0:1",
217 | "playlistItemType": "playlistItemTypePresentation"
218 | },
219 | ]
220 | },
221 | ]
222 | }
223 | ],
224 | "action": "playlistRequestAll"
225 | }
226 | ```
227 |
228 | playlistItemThumbnail will be included for some items - these will be Base64 encoded Jpegs
229 |
230 | ### Request Presentation (set of slides)
231 |
232 | COMMAND TO SEND:
233 |
234 | ```javascript
235 | {
236 | "action": "presentationRequest",
237 | "presentationPath": "\/Path\/To\/ProPresenter\/Library\/Song 1 Title.pro",
238 | "presentationSlideQuality": 25
239 | }
240 | ```
241 |
242 | - `presentationPath` is required and it can be structured in one of three ways
243 | - It can be a full path to a pro file but note that all slashes need to be preceeded by a backslash in the request.
244 | - It can be the basename of a presentation that exists in the library (eg. `Song 1 Title.pro`) is (sometimes?) good enough.
245 | - It can be the "playlist location" of the presentation. The playlist location is determined according to the order of items in the playlist window, the items are indexed from 0, and groups are sub-indexed with a dot, then presentations inside the playlist are indexed with a colon and a numeral. That is, the first presentation of the first playlist is `0:0` and if the first playlist item is a group, the first item of the first playlist of that group is `0.0:0`
246 | - A presentationPath specified with a playlist address and not a filename seems to be the most reliable.
247 | - `presentationSlideQuality` is optional. It determines the resolution / size of the slide previews sent from ProPresenter. If left blank, high quality previews will be sent. If set to `0` previews will not be generated at all. The remote app asks for quality `25` first and then follows it up with a second request for quality `100`.
248 |
249 | EXPECTED RESPONSE:
250 |
251 | ```javascript
252 | {
253 | "action": "presentationCurrent",
254 | "presentation": {
255 | "presentationSlideGroups": [
256 | {
257 | "groupName": "[SLIDE GROUP NAME]",
258 | "groupColor": "0 0 0 1", // RGBA scale is from 0-1
259 | "groupSlides": [
260 | {
261 | "slideEnabled": true,
262 | "slideNotes": "",
263 | "slideAttachmentMask": 0,
264 | "slideText": "[SLIDE TEXT HERE]",
265 | "slideImage": "[BASE64 ENCODED IMAGE]",
266 | "slideIndex": "0",
267 | "slideTransitionType": -1,
268 | "slideLabel": "[SLIDE LABEL]",
269 | "slideColor": "0 0 0 1"
270 | }
271 | ]
272 | },
273 | ],
274 | "presentationName": "[PRESENTATION TITLE]",
275 | "presentationHasTimeline": 0,
276 | "presentationCurrentLocation": "[PRESENTATION PATH OF CURRENTLY ACTIVE SLIDE]"
277 | }
278 | }
279 | ```
280 |
281 | - The response contains `presentationCurrent` as the action instead of `presentationRequest`. This seems to be a bug in the ProPresenter response.
282 | - The `presentationCurrentLocation` is not the location of the presentation you requested. It is the path of the presentation whose slide is currently active.
283 | - You can distinguish this response from the real `presentationCurrent` request because that response will include `presentationPath` as a field at the root level of the response.
284 |
285 | ### Request Current Presentation
286 |
287 | COMMAND TO SEND:
288 |
289 | ```javascript
290 | { "action":"presentationCurrent", "presentationSlideQuality": 25}
291 | ```
292 |
293 | EXPECTED RESPONSE:
294 |
295 | Same response as `requestPresentation` except this response will include `presentationPath` as a field at the root level of the response.
296 |
297 | - NOTE: This action only seems to work if there is an _active slide_. When ProPresenter starts, no slide is marked active, so this action _returns nothing until a slide has been triggered_.
298 |
299 | TODO: Investigate impact of presentationSlideQuality values for Pro7 on both MacOS and Win (0 will return slides without any slide image in Pro6, but Pro7 on MacOS seems to always include them)
300 |
301 | ### Get Index of Current Slide
302 |
303 | COMMAND TO SEND:
304 |
305 | ```javascript
306 | {"action":"presentationSlideIndex"}
307 | ```
308 |
309 | EXPECTED RESPONSE:
310 |
311 | ```javascript
312 | {"action":"presentationSlideIndex","slideIndex":"0"}
313 | ```
314 |
315 | - NOTE: The ProPresenter remote issues this action every time it issues a `presentationRequest` action.
316 | In Pro7, when a presentation is automatically advancing on the annoucements layer while a user is triggering slides in another presentation on the normal output layer - the index returns can sometimes _vary_ between the two different presentations!
317 |
318 | ### Trigger Slide
319 |
320 | COMMAND TO SEND:
321 |
322 | ```javascript
323 | {"action":"presentationTriggerIndex","slideIndex":"3","presentationPath":"[PRESENTATION PATH]"}
324 | ```
325 |
326 | EXPECTED RESPONSE:
327 |
328 | ```javascript
329 | {"slideIndex":3,"action":"presentationTriggerIndex","presentationPath":"[PRESENTATION PATH]"}
330 | ```
331 |
332 | NOTES:
333 |
334 | - Currently, the protocol requires SENDING the slideIndex as a string value even though the response delivers it as an integer value.
335 | - Also, the presentation path needs to be either a full pathname to the `.pro` file on the computer or the `playlistIndex:playlistItemIndex` style
336 |
337 | presentationTriggerIndex messages are sent to all connected clients to inform them all when a slide is triggered (if a user triggers a slide, you will get this message sent to you)
338 |
339 | ### Trigger Next Slide
340 |
341 | COMMAND TO SEND:
342 |
343 | ```javascript
344 | {"action":"presentationTriggerNext"}
345 | ```
346 |
347 | ### Trigger Previous Slide
348 |
349 | COMMAND TO SEND:
350 |
351 | ```javascript
352 | {"action":"presentationTriggerPrevious"}
353 | ```
354 |
355 | ### Start Audio Cue
356 |
357 | COMMAND TO SEND:
358 |
359 | ```javascript
360 | {"action":"audioStartCue", "audioChildPath":"[Same as Presentation Path Format]", "audioPath":"0"}
361 | ```
362 |
363 | TODO: describe audioPath paramter required for Pro7 on MacOS
364 |
365 | ### Audio Play/Pause Toggle
366 |
367 | COMMAND TO SEND:
368 |
369 | ```javascript
370 | {"action":"audioPlayPause"}
371 | ```
372 |
373 | ### TimeLine Play/Pause Toggle
374 |
375 | COMMAND TO SEND:
376 |
377 | ```javascript
378 | {"action":"timelinePlayPause","presentationPath":"[PRESENTATION PATH]"}
379 | ```
380 |
381 | ### TimeLine Rewind
382 |
383 | COMMAND TO SEND:
384 |
385 | ```javascript
386 | {"action":"timelineRewind":,"presentationPath":"[PRESENTATION PATH]"}
387 | ```
388 |
389 | ### Get Clock (Timers) Info
390 |
391 | COMMAND TO SEND:
392 |
393 | ```javascript
394 | {"action":"clockRequest"}
395 | ```
396 |
397 | ### Start Recieving Updates for Clocks (Timers)
398 |
399 | COMMAND TO SEND:
400 |
401 | ```javascript
402 | {"action":"clockStartSendingCurrentTime"}
403 | ```
404 |
405 | ### Stop Recieving Updates for Clocks (Timers)
406 |
407 | COMMAND TO SEND:
408 |
409 | ```javascript
410 | {"action":"clockStopSendingCurrentTime"}
411 | ```
412 |
413 | ### Request all Clocks
414 |
415 | COMMAND TO SEND:
416 |
417 | ```javascript
418 | {"action":"clockRequest"}
419 | ```
420 |
421 | EXPECTED RESPONSE:
422 |
423 | ```javascript
424 | {
425 | "clockInfo": [
426 | {
427 | "clockType": 0,
428 | "clockState": false,
429 | "clockName": "Countdown 1",
430 | "clockIsPM": 0,
431 | "clockDuration": "0:10:00",
432 | "clockOverrun": false,
433 | "clockEndTime": "--:--:--",
434 | "clockTime": "--:--:--"
435 | },
436 | {
437 | "clockType": 1,
438 | "clockState": false,
439 | "clockName": "Countdown 2",
440 | "clockIsPM": 1,
441 | "clockDuration": "7:00:00",
442 | "clockOverrun": false,
443 | "clockEndTime": "--:--:--",
444 | "clockTime": "--:--:--"
445 | },
446 | {
447 | "clockType": 2,
448 | "clockState": false,
449 | "clockName": "Elapsed Time",
450 | "clockIsPM": 0,
451 | "clockDuration": "0:00:00",
452 | "clockOverrun": false,
453 | "clockEndTime": "--:--:--",
454 | "clockTime": "13:52:23"
455 | }
456 | ],
457 | "action": "clockRequest"
458 | }
459 | ```
460 |
461 | ### Get Clock Current Times
462 |
463 | COMMAND TO SEND:
464 |
465 | ```javascript
466 | {"action":"clockCurrentTimes"}
467 | ```
468 |
469 | EXPECTED RESPONSE:
470 |
471 | ```javascript
472 | {"action":"clockCurrentTimes","clockTimes":["0:10:00","--:--:--","13:52:23"]}
473 | ```
474 |
475 | ### Start a Clock (Timer)
476 |
477 | COMMAND TO SEND:
478 |
479 | ```javascript
480 | {"action":"clockStart","clockIndex":"0"}
481 | ```
482 |
483 | EXPECTED RESPONSE:
484 |
485 | ```javascript
486 | {"clockTime":"0:00:00","clockState":1,"clockIndex":0,"clockInfo":[1,1,"0:00:00"],"action":"clockStartStop"}
487 | ```
488 |
489 | - `clockState` indicates if the clock is running or not
490 | - Clocks are referenced by index. See reply from "clockRequest" action above to learn indices.
491 |
492 | ### Stop a Clock (Timer)
493 |
494 | COMMAND TO SEND:
495 |
496 | ```javascript
497 | {"action":"clockStop","clockIndex":"0"}
498 | ```
499 |
500 | EXPECTED RESPONSE:
501 |
502 | ```javascript
503 | {"clockTime":"0:00:00","clockState":0,"clockIndex":0,"clockInfo":[1,1,"0:00:00"],"action":"clockStartStop"}
504 | ```
505 |
506 | - `clockState` indicates if the clock is running or not
507 | - Clocks are referenced by index. See reply from "clockRequest" action above to learn indices.
508 |
509 | ### Reset a Clock (Timer)
510 |
511 | COMMAND TO SEND:
512 |
513 | ```javascript
514 | {"action":"clockReset","clockIndex":"0"}
515 | ```
516 |
517 | - Clocks are referenced by index. See reply from "clockRequest" action above to learn indices.
518 |
519 | ### Update a Clock (Timer) (eg edit time)
520 |
521 | COMMAND TO SEND:
522 |
523 | ```javascript
524 | {
525 | "action":"clockUpdate",
526 | "clockIndex":"1",
527 | "clockType":"0",
528 | "clockTime":"09:04:00",
529 | "clockOverrun":"false",
530 | "clockIsPM":"1",
531 | "clockName":"Countdown 2",
532 | "clockElapsedTime":"0:02:00"
533 | }
534 | ```
535 |
536 | - Clocks are referenced by index. See reply from "clockRequest" action above to learn indexes.
537 | - Not all parameters are required for each clock type.
538 | - Countdown clocks only need "clockTime".
539 | - Elapsed Time Clocks need "clockTime" and optionally will use "clockElapsedTime" if you send it (to set the End Time).
540 | - You can rename a clock by optionally including the clockName.
541 | - Type 0 is Countdown
542 | - Type 1 is CountDown to Time
543 | - Type 2 is Elapsed Time.
544 | - Overrun can be modified if you choose to include that as well.
545 |
546 | ### Start Getting Clock Updates
547 |
548 | COMMAND TO SEND:
549 |
550 | ```javascript
551 | {"action":"clockStartSendingCurrentTime"}
552 | ```
553 |
554 | EXPECTED RESPONSE (every second):
555 |
556 | ```javascript
557 | {"action":"clockCurrentTimes","clockTimes":["0:10:00","--:--:--","13:52:23"]}
558 | ```
559 |
560 | ### Additional Clock Actions
561 |
562 | `clockResetAll`, `clockStopAll`, `clockStartAll`
563 |
564 | ### Get all Messages
565 |
566 | COMMAND TO SEND:
567 |
568 | ```javascript
569 | {"action":"messageRequest"}
570 | ```
571 |
572 | EXPECTED RESPONSE:
573 |
574 | ```javascript
575 | {
576 | "action": "messageRequest",
577 | "messages": [
578 | {
579 | "messageComponents": [
580 | "message:",
581 | "${Message}"
582 | ],
583 | "messageTitle": "Message"
584 | },
585 | {
586 | "messageComponents": [
587 | "Session will begin in: ",
588 | "${Countdown 1: H:MM:SS}"
589 | ],
590 | "messageTitle": "Countdown"
591 | },
592 | {
593 | "messageComponents": [
594 | "${Message}"
595 | ],
596 | "messageTitle": "Message"
597 | },
598 | {
599 | "messageComponents": [
600 | "Service starts in ",
601 | "${countDownTimerName_1: H:MM:SS}"
602 | ],
603 | "messageTitle": "Countdown"
604 | }
605 | ]
606 | }
607 | ```
608 |
609 | - The key is everything inside the curly braces `${}` so that the key for a countdown looks like this `Countdown 1: H:MM:SS`.
610 | - If the key refers to a countdown, the value is used to update the `duration` field of the countdown timer, but will not perform a "reset".
611 | - If the key refers to a countdown and the countdown is not running, this will resume it from its current value.
612 |
613 | ### Display a Message
614 |
615 | Display a message identified by its index. Add as many key, value pairs as you like. Keys can be name of timers.
616 |
617 | COMMAND TO SEND:
618 |
619 | ```javascript
620 | {"action":"messageSend","messageIndex":0,"messageKeys":"["key1","key2"....]","messageValues":"["Value1","Value2"...]"}
621 | ```
622 |
623 | AN EXAMPLE USING THE DATA ABOVE:
624 |
625 | ```javascript
626 | {"action":"messageSend","messageIndex":0,"messageKeys":["Message"],"messageValues":["Test"]}
627 | ```
628 |
629 | ### Hide a Message
630 |
631 | COMMAND TO SEND:
632 | Hide a message identified by its index
633 |
634 | ```javascript
635 | {"action":"messageHide","messageIndex","0"}
636 | ```
637 |
638 | ### Clear All
639 |
640 | COMMAND TO SEND:
641 |
642 | ```javascript
643 | {"action":"clearAll"}
644 | ```
645 |
646 | ### Clear Slide
647 |
648 | COMMAND TO SEND:
649 |
650 | ```javascript
651 | {"action":"clearText"}
652 | ```
653 |
654 | ### Clear Props
655 |
656 | COMMAND TO SEND:
657 |
658 | ```javascript
659 | {"action":"clearProps"}
660 | ```
661 |
662 | ### Clear Audio
663 |
664 | COMMAND TO SEND:
665 |
666 | ```javascript
667 | {"action":"clearAudio"}
668 | ```
669 |
670 | ### Clear Video
671 |
672 | COMMAND TO SEND:
673 |
674 | ```javascript
675 | {"action":"clearVideo"}
676 | ```
677 |
678 | ### Clear Telestrator
679 |
680 | COMMAND TO SEND:
681 |
682 | ```javascript
683 | {"action":"clearTelestrator"}
684 | ```
685 |
686 | ### Clear To Logo
687 |
688 | COMMAND TO SEND:
689 |
690 | ```javascript
691 | {"action":"clearToLogo"}
692 | ```
693 |
694 | ### Show Stage Display Message
695 |
696 | COMMAND TO SEND:
697 |
698 | ```javascript
699 | {"action":"stageDisplaySendMessage","stageDisplayMessage":"Type a Message Here"}
700 | ```
701 |
702 | ### Hide Stage Display Message
703 |
704 | COMMAND TO SEND:
705 |
706 | ```javascript
707 | {"action":"stageDisplayHideMessage"}
708 | ```
709 |
710 | ### Get Stage Display Layouts
711 |
712 | COMMAND TO SEND:
713 |
714 | ```javascript
715 | {"action":"stageDisplaySets"}
716 | ```
717 |
718 | EXPECTED RESPONSE:
719 |
720 | ```javascript
721 | {
722 | "stageScreens": [
723 | {
724 | "stageScreenName": "Stage Screen",
725 | "stageScreenUUID": "8e383232-2a89-4802-a507-93c09642fa68",
726 | "stageLayoutSelectedLayoutUUID": "54cc1c01-606c-434f-ad02-91b090d8ea41"
727 | }
728 | ],
729 | "stageLayouts": [
730 | {
731 | "stageLayoutName": "Current/Next Stacked - Music",
732 | "stageLayoutUUID": "687e061b-c3fc-48a7-be8c-40837b622fed"
733 | },
734 | {
735 | "stageLayoutName": "Music - text only",
736 | "stageLayoutUUID": "54cc1c01-606c-434f-ad02-91b090d8ea41"
737 | }
738 | ],
739 | "action": "stageDisplaySets"
740 | }
741 | ```
742 |
743 | ### Select Stage Display Layout
744 |
745 | COMMAND TO SEND:
746 |
747 | ```javascript
748 | {"action":"stageDisplayChangeLayout","stageLayoutUUID":"687e061b-c3fc-48a7-be8c-40837b622fed","stageScreenUUID":"8e383232-2a89-4802-a507-93c09642fa68"}
749 | ```
750 |
751 | EXPECTED RESPONSE:
752 |
753 | ```javascript
754 | {
755 | "stageScreens": [
756 | {
757 | "stageScreenName": "Stage Screen",
758 | "stageScreenUUID": "8e383232-2a89-4802-a507-93c09642fa68",
759 | "stageLayoutSelectedLayoutUUID": "687e061b-c3fc-48a7-be8c-40837b622fed"
760 | }
761 | ],
762 | "stageLayouts": [
763 | {
764 | "stageLayoutName": "Current/Next Stacked - Music",
765 | "stageLayoutUUID": "687e061b-c3fc-48a7-be8c-40837b622fed"
766 | },
767 | {
768 | "stageLayoutName": "Music - text only",
769 | "stageLayoutUUID": "54cc1c01-606c-434f-ad02-91b090d8ea41"
770 | }
771 | ],
772 | "action": "stageDisplaySets"
773 | }
774 | ```
775 |
776 | ## TODO: Complete documentation for remaining remote commands...
777 |
778 | audioRequest (Get audio Playlist)
779 | audioCurrentSong (Get current audio playing)
780 | audioIsPlaying (Seems to always return true from MacOS when an image background is triggered)
781 |
782 | ## Stage Display API
783 |
784 | ### Connecting
785 |
786 | ```javascript
787 | ws://[host]:[port]/stagedisplay
788 | ```
789 |
790 | ### Authenticate
791 |
792 | COMMAND TO SEND:
793 |
794 | ```javascript
795 | {"pwd":PASSWORD,"ptl":610,"acn":"ath"}
796 | ```
797 |
798 | EXPECTED RESPONSE:
799 |
800 | ```javascript
801 | {"acn":"ath","ath":true,"err":""}
802 | ```
803 |
804 | ### Get All Stage Display Layouts
805 |
806 | COMMAND TO SEND:
807 |
808 | ```javascript
809 | {"acn":"asl"}
810 | ```
811 |
812 | EXPECTED RESPONSE:
813 |
814 | ```javascript
815 | {
816 | "acn": "asl",
817 | "ary": [
818 | {
819 | "brd": true,
820 | "uid": "753B184F-CCCD-42F9-A883-D1DF86E1FFB8",
821 | "zro": 0,
822 | "oCl": "1.000000 0.000000 0.000000",
823 | "fme": [
824 | {
825 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
826 | "mde": 1,
827 | "tCl": "1.000000 1.000000 1.000000",
828 | "tAl": 2,
829 | "tSz": 60,
830 | "nme": "Current Slide",
831 | "typ": 1
832 | },
833 | {
834 | "ufr": "{{0.024390243902439025, 0.27223427331887201}, {0.40182926829268295, 0.10412147505422993}}",
835 | "mde": 1,
836 | "tCl": "1.000000 1.000000 1.000000",
837 | "tAl": 2,
838 | "tSz": 60,
839 | "nme": "Current Slide Notes",
840 | "typ": 3
841 | },
842 | {
843 | "ufr": "{{0.45000000000000001, 0.47396963123644253}, {0.29999999999999999, 0.40021691973969631}}",
844 | "mde": 1,
845 | "tCl": "1.000000 1.000000 1.000000",
846 | "tAl": 2,
847 | "tSz": 60,
848 | "nme": "Next Slide",
849 | "typ": 2
850 | },
851 | {
852 | "ufr": "{{0.45000000000000001, 0.37310195227765725}, {0.29999999999999999, 0.1019522776572668}}",
853 | "mde": 1,
854 | "tCl": "1.000000 1.000000 1.000000",
855 | "tAl": 2,
856 | "tSz": 60,
857 | "nme": "Next Slide Notes",
858 | "typ": 4
859 | },
860 | {
861 | "ufr": "{{0.77500000000000002, 0.37418655097613884}, {0.20000000000000001, 0.40130151843817785}}",
862 | "nme": "Chord Chart",
863 | "mde": 1,
864 | "typ": 9
865 | },
866 | {
867 | "ufr": "{{0.050000000000000003, 0.89913232104121477}, {0.20000000000000001, 0.1019522776572668}}",
868 | "mde": 1,
869 | "tCl": "1.000000 1.000000 1.000000",
870 | "tAl": 2,
871 | "tSz": 200,
872 | "nme": "Clock",
873 | "typ": 6
874 | },
875 | {
876 | "ufr": "{{0.40000000000000002, 0.89913232104121477}, {0.20000000000000001, 0.1019522776572668}}",
877 | "mde": 1,
878 | "tCl": "1.000000 1.000000 1.000000",
879 | "tAl": 2,
880 | "tSz": 200,
881 | "nme": "Video Countdown",
882 | "typ": 8
883 | },
884 | {
885 | "ufr": "{{0.050000000000000003, 0.024945770065075923}, {0.90000000000000002, 0.10086767895878525}}",
886 | "fCl": "0.000000 1.000000 0.000000",
887 | "mde": 1,
888 | "tCl": "1.000000 1.000000 1.000000",
889 | "tAl": 2,
890 | "fCh": true,
891 | "tSz": 60,
892 | "nme": "Message",
893 | "typ": 5
894 | },
895 | {
896 | "ufr": "{{0.68978420350609759, 0.89488713394793928}, {0.20000000000000001, 0.1019522776572668}}",
897 | "uid": "47E8B48C-0D61-4EFC-9517-BF9FB894C8E2",
898 | "mde": 1,
899 | "tCl": "1.000000 1.000000 1.000000",
900 | "tAl": 2,
901 | "tSz": 200,
902 | "nme": "Countdown 1",
903 | "typ": 7
904 | }
905 | ],
906 | "ovr": true,
907 | "acn": "sl",
908 | "nme": "Default"
909 | },
910 | {
911 | "brd": false,
912 | "uid": "50AF3434-4328-40AC-846F-CC9583381311",
913 | "zro": 0,
914 | "oCl": "0.985948 0.000000 0.026951",
915 | "fme": [
916 | {
917 | "ufr": "{{0.60304878048780486, 0.1963123644251627}, {0.39695121951219514, 0.80043383947939262}}",
918 | "mde": 1,
919 | "tCl": "0.990463 1.000000 0.041173",
920 | "tAl": 1,
921 | "tSz": 80,
922 | "nme": "Current Slide",
923 | "typ": 1
924 | },
925 | {
926 | "ufr": "{{0.0024390243902439024, 0.0021691973969631237}, {0.599390243902439, 0.99457700650759218}}",
927 | "mde": 1,
928 | "tCl": "0.679783 1.000000 0.885215",
929 | "tAl": 0,
930 | "tSz": 120,
931 | "nme": "Current Slide Notes",
932 | "typ": 3
933 | },
934 | {
935 | "ufr": "{{0.60304878048780486, 0.0021691973969631237}, {0.39512195121951221, 0.19305856832971802}}",
936 | "uid": "D1096B85-CF31-4365-A6E6-ED94264E7DCA",
937 | "mde": 1,
938 | "tCl": "1.000000 1.000000 1.000000",
939 | "tAl": 2,
940 | "tSz": 200,
941 | "nme": "Elapsed Time",
942 | "typ": 7
943 | }
944 | ],
945 | "ovr": false,
946 | "acn": "sl",
947 | "nme": "Easter Closer"
948 | },
949 | {
950 | "brd": false,
951 | "uid": "F8260B13-9C5B-4D2C-80F1-C72346759F11",
952 | "zro": 0,
953 | "oCl": "0.985948 0.000000 0.026951",
954 | "fme": [
955 | {
956 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
957 | "mde": 2,
958 | "tCl": "1.000000 1.000000 1.000000",
959 | "tAl": 2,
960 | "tSz": 60,
961 | "nme": "Current Slide",
962 | "typ": 1
963 | },
964 | {
965 | "ufr": "{{0.025000000000000001, 0.27440347071583515}, {0.40000000000000002, 0.10086767895878525}}",
966 | "mde": 1,
967 | "tCl": "1.000000 1.000000 1.000000",
968 | "tAl": 2,
969 | "tSz": 60,
970 | "nme": "Current Slide Notes",
971 | "typ": 3
972 | },
973 | {
974 | "ufr": "{{0.45000000000000001, 0.47396963123644253}, {0.29999999999999999, 0.40021691973969631}}",
975 | "mde": 0,
976 | "tCl": "1.000000 1.000000 1.000000",
977 | "tAl": 2,
978 | "tSz": 60,
979 | "nme": "Next Slide",
980 | "typ": 2
981 | },
982 | {
983 | "ufr": "{{0.45000000000000001, 0.37310195227765725}, {0.29999999999999999, 0.1019522776572668}}",
984 | "mde": 1,
985 | "tCl": "1.000000 1.000000 1.000000",
986 | "tAl": 2,
987 | "tSz": 60,
988 | "nme": "Next Slide Notes",
989 | "typ": 4
990 | },
991 | {
992 | "ufr": "{{0.77500000000000002, 0.37418655097613884}, {0.20000000000000001, 0.40130151843817785}}",
993 | "nme": "Chord Chart",
994 | "mde": 1,
995 | "typ": 9
996 | },
997 | {
998 | "ufr": "{{0, 0.89804772234273322}, {0.20060975609756099, 0.10303687635574837}}",
999 | "mde": 1,
1000 | "tCl": "1.000000 1.000000 1.000000",
1001 | "tAl": 2,
1002 | "tSz": 200,
1003 | "nme": "Clock",
1004 | "typ": 6
1005 | },
1006 | {
1007 | "ufr": "{{0.79878048780487809, 0.89696312364425168}, {0.20060975609756099, 0.10303687635574837}}",
1008 | "mde": 1,
1009 | "tCl": "1.000000 1.000000 1.000000",
1010 | "tAl": 2,
1011 | "tSz": 200,
1012 | "nme": "Video Countdown",
1013 | "typ": 8
1014 | },
1015 | {
1016 | "ufr": "{{0.050000000000000003, 0.024945770065075923}, {0.90000000000000002, 0.10086767895878525}}",
1017 | "fCl": "0.135296 1.000000 0.024919",
1018 | "mde": 1,
1019 | "tCl": "1.000000 1.000000 1.000000",
1020 | "tAl": 2,
1021 | "fCh": true,
1022 | "tSz": 60,
1023 | "nme": "Message",
1024 | "typ": 5
1025 | }
1026 | ],
1027 | "ovr": false,
1028 | "acn": "sl",
1029 | "nme": "Live Current - Static Next - no borders"
1030 | },
1031 | {
1032 | "brd": true,
1033 | "uid": "12CB7383-FA02-47BB-B501-747ADCA860D3",
1034 | "zro": 0,
1035 | "oCl": "0.985948 0.000000 0.026951",
1036 | "fme": [
1037 | {
1038 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
1039 | "mde": 0,
1040 | "tCl": "1.000000 1.000000 1.000000",
1041 | "tAl": 2,
1042 | "tSz": 60,
1043 | "nme": "Current Slide",
1044 | "typ": 1
1045 | },
1046 | {
1047 | "ufr": "{{0.025000000000000001, 0.27440347071583515}, {0.40000000000000002, 0.10086767895878525}}",
1048 | "mde": 1,
1049 | "tCl": "1.000000 1.000000 1.000000",
1050 | "tAl": 2,
1051 | "tSz": 60,
1052 | "nme": "Current Slide Notes",
1053 | "typ": 3
1054 | },
1055 | {
1056 | "ufr": "{{0.45000000000000001, 0.47396963123644253}, {0.29999999999999999, 0.40021691973969631}}",
1057 | "mde": 0,
1058 | "tCl": "1.000000 1.000000 1.000000",
1059 | "tAl": 2,
1060 | "tSz": 60,
1061 | "nme": "Next Slide",
1062 | "typ": 2
1063 | },
1064 | {
1065 | "ufr": "{{0.45000000000000001, 0.37310195227765725}, {0.29999999999999999, 0.1019522776572668}}",
1066 | "mde": 1,
1067 | "tCl": "1.000000 1.000000 1.000000",
1068 | "tAl": 2,
1069 | "tSz": 60,
1070 | "nme": "Next Slide Notes",
1071 | "typ": 4
1072 | },
1073 | {
1074 | "ufr": "{{0.77500000000000002, 0.37418655097613884}, {0.20000000000000001, 0.40130151843817785}}",
1075 | "nme": "Chord Chart",
1076 | "mde": 1,
1077 | "typ": 9
1078 | },
1079 | {
1080 | "ufr": "{{0, 0.89804772234273322}, {0.20060975609756099, 0.10303687635574837}}",
1081 | "mde": 1,
1082 | "tCl": "1.000000 1.000000 1.000000",
1083 | "tAl": 2,
1084 | "tSz": 200,
1085 | "nme": "Clock",
1086 | "typ": 6
1087 | },
1088 | {
1089 | "ufr": "{{0.79878048780487809, 0.89696312364425168}, {0.20060975609756099, 0.10303687635574837}}",
1090 | "mde": 1,
1091 | "tCl": "1.000000 1.000000 1.000000",
1092 | "tAl": 2,
1093 | "tSz": 200,
1094 | "nme": "Video Countdown",
1095 | "typ": 8
1096 | },
1097 | {
1098 | "ufr": "{{0.050000000000000003, 0.024945770065075923}, {0.90000000000000002, 0.10086767895878525}}",
1099 | "fCl": "0.135296 1.000000 0.024919",
1100 | "mde": 1,
1101 | "tCl": "1.000000 1.000000 1.000000",
1102 | "tAl": 2,
1103 | "fCh": true,
1104 | "tSz": 60,
1105 | "nme": "Message",
1106 | "typ": 5
1107 | }
1108 | ],
1109 | "ovr": false,
1110 | "acn": "sl",
1111 | "nme": "Static Current - Static Next"
1112 | }
1113 | ]
1114 | }
1115 | ```
1116 |
1117 | - `acn` of `asl` means "all stage layouts"
1118 | - `ary` indicates array of stage layouts
1119 | - `nme` indicates layout name
1120 | - `ovr` indicates if overrun color should be used
1121 | - `oCl` indicates color for timer overruns
1122 | - `brd` indicates if borders and labels should be used
1123 | - `uid` indicates layout uid
1124 | - `zro` indicates if zeroes should be removed from times
1125 | - `fme` indicates array of frame layout specifications
1126 | - frame positions are indicated by `ufr` and specified in terms of screen percentages
1127 | - frame name is indicated by `nme`
1128 | - frame text color is indicated by `tCl`
1129 | - frame font size is indicated by `tSz`
1130 | - frame message flash color is indicated by `fCl`
1131 | - frame use message flash indicated by `fCh`
1132 | - frame timer uid is indicated by `uid`
1133 | - frame mode is indicated by `mde`
1134 | - mode 0: static image
1135 | - mode 1: text
1136 | - mode 2: live slide
1137 | - frame type is indicated by `typ` and determines what content goes in this frame
1138 | - type 1: current slide
1139 | - type 2: next slide
1140 | - type 3: current slide notes
1141 | - type 4: next slide notes
1142 | - type 5: Stage Message (uses message flash values)
1143 | - type 6: Clock
1144 | - type 7: Timer Display (uses `uid` to specify timer)
1145 | - type 8: Video Countdown
1146 | - type 9: Chord Chart
1147 |
1148 | ### On New Stage Display Selected
1149 |
1150 | ```javascript
1151 | {
1152 | "brd": true,
1153 | "uid": "12CB7383-FA02-47BB-B501-747ADCA860D3",
1154 | "zro": 0,
1155 | "oCl": "0.985948 0.000000 0.026951",
1156 | "fme": [
1157 | {
1158 | "ufr": "{{0.025000000000000001, 0.37418655097613884}, {0.40000000000000002, 0.50108459869848154}}",
1159 | "mde": 0,
1160 | "tCl": "1.000000 1.000000 1.000000",
1161 | "tAl": 2,
1162 | "tSz": 60,
1163 | "nme": "Current Slide",
1164 | "typ": 1
1165 | },
1166 | {
1167 | "ufr": "{{0.025000000000000001, 0.27440347071583515}, {0.40000000000000002, 0.10086767895878525}}",
1168 | "mde": 1,
1169 | "tCl": "1.000000 1.000000 1.000000",
1170 | "tAl": 2,
1171 | "tSz": 60,
1172 | "nme": "Current Slide Notes",
1173 | "typ": 3
1174 | },
1175 | {
1176 | "ufr": "{{0.45000000000000001, 0.47396963123644253}, {0.29999999999999999, 0.40021691973969631}}",
1177 | "mde": 0,
1178 | "tCl": "1.000000 1.000000 1.000000",
1179 | "tAl": 2,
1180 | "tSz": 60,
1181 | "nme": "Next Slide",
1182 | "typ": 2
1183 | },
1184 | {
1185 | "ufr": "{{0.45000000000000001, 0.37310195227765725}, {0.29999999999999999, 0.1019522776572668}}",
1186 | "mde": 1,
1187 | "tCl": "1.000000 1.000000 1.000000",
1188 | "tAl": 2,
1189 | "tSz": 60,
1190 | "nme": "Next Slide Notes",
1191 | "typ": 4
1192 | },
1193 | {
1194 | "ufr": "{{0.77500000000000002, 0.37418655097613884}, {0.20000000000000001, 0.40130151843817785}}",
1195 | "nme": "Chord Chart",
1196 | "mde": 1,
1197 | "typ": 9
1198 | },
1199 | {
1200 | "ufr": "{{0, 0.89804772234273322}, {0.20060975609756099, 0.10303687635574837}}",
1201 | "mde": 1,
1202 | "tCl": "1.000000 1.000000 1.000000",
1203 | "tAl": 2,
1204 | "tSz": 200,
1205 | "nme": "Clock",
1206 | "typ": 6
1207 | },
1208 | {
1209 | "ufr": "{{0.79878048780487809, 0.89696312364425168}, {0.20060975609756099, 0.10303687635574837}}",
1210 | "mde": 1,
1211 | "tCl": "1.000000 1.000000 1.000000",
1212 | "tAl": 2,
1213 | "tSz": 200,
1214 | "nme": "Video Countdown",
1215 | "typ": 8
1216 | },
1217 | {
1218 | "ufr": "{{0.050000000000000003, 0.024945770065075923}, {0.90000000000000002, 0.10086767895878525}}",
1219 | "fCl": "0.135296 1.000000 0.024919",
1220 | "mde": 1,
1221 | "tCl": "1.000000 1.000000 1.000000",
1222 | "tAl": 2,
1223 | "fCh": true,
1224 | "tSz": 60,
1225 | "nme": "Message",
1226 | "typ": 5
1227 | }
1228 | ],
1229 | "ovr": false,
1230 | "acn": "sl",
1231 | "nme": "Static Current - Static Next"
1232 | }
1233 | ```
1234 |
1235 | - `acn` of `sl` indicates this is a single stage layout
1236 |
1237 | ### Request Current Stage Display Layout
1238 |
1239 | COMMAND TO SEND:
1240 |
1241 | ```javascript
1242 | {"acn":"psl"}
1243 | ```
1244 |
1245 | EXPECTED RESPONSE (also used when stage display is updated):
1246 |
1247 | ```javascript
1248 | {"acn":"psl","uid":"[STAGE DISPLAY UID]"}
1249 | ```
1250 |
1251 | ### Request Frame Values for Stage Display
1252 |
1253 | COMMAND TO SEND:
1254 |
1255 | ```javascript
1256 | {"acn":"fv","uid":"[STAGE DISPLAY UID"}
1257 | ```
1258 |
1259 | ### On New Live Slide Frame
1260 |
1261 | ```javascript
1262 | {
1263 | "RVLiveStream_action": "RVLiveStream_frameData",
1264 | "RVLiveStream_frameDataLength": 14625,
1265 | "RVLiveStream_frameData": "/9j//gAQTGF2YzU3LjUzLjEwMAD/2wBDAAgEBAQEBAUFBQUFBQYGBgYGBgYGBgYGBgYHBwcICAgHBwcGBgcHCAgICAkJCQgICAgJCQoKCgwMCwsODg4RERT/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsBAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6EQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/wAARCAEOAeADASIAAhIAAxIA/9oADAMBAAIRAxEAPwDwqiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKKAAAooooAACiiigAAKKKOtAAAUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRS7H/un8jRsf8Aun8jQAAJRS7H/un8jRsf+6fyNAAAlFLsf+6fyNGx/wC6fyNAAAlFLsf+6fyNGx/7p/I0AACUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRS7H/un8jRsf8Aun8jQAAJRS7H/un8jRsf+6fyNAAAlFLsf+6fyNGx/wC6fyNAAAlFLsf+6fyNGx/7p/I0AACUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRS7H/un8jRsf8Aun8jQAAJRS7H/un8jRsf+6fyNAAAlFLsf+6fyNGx/wC6fyNAAAlFLsf+6fyNGx/7p/I0AACUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRS7H/un8jRsf8Aun8jQAAJRS7H/un8jRsf+6fyNAAAlFLsf+6fyNGx/wC6fyNAAAlFLsf+6fyNGx/7p/I0AACUUux/7p/I0bH/ALp/I0AACUUux/7p/I0bH/un8jQAAJRRRQAAFFFFAAAUUUUAABXafs9wQXPxg8LRTxRzRtcT7o5EV0b/AEWY8qwIPPqK4uu2/Z0/5LL4U/6+bj/0kmoAAPrL/hH9B/6BOm/+Adv/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"
1266 | }
1267 | ```
1268 |
1269 | - Base64 Encoded Image Bytes
1270 | - Only the Current Slide can be "Live"
1271 | - Live slide images are pushed to the client over the websocket.
1272 |
1273 | ### To Get Static Slide Images
1274 |
1275 | TO RETRIEVE STATIC SLIDE IMAGES:
1276 |
1277 | Issue a normal GET request to the following URL:
1278 |
1279 | http://PROPRESENTER_IP:PROPRESENTER_PORT/stage/image/SLIDE_UID
1280 |
1281 | EXPECTED RESPONSE:
1282 |
1283 | rgba tiff image
1284 |
1285 | ( DIFFERENCE FROM 6: Pro6 would send images as standard jpeg, but Pro7 transmits them as rgba tiff images with no content-type set in the header )
1286 |
1287 | ### On New Time
1288 |
1289 | EXPECTED RESPONSE:
1290 |
1291 | ```javascript
1292 | {"acn":"sys","txt":" 11:17 AM"}
1293 | ```
1294 |
1295 | ### On Timer Update
1296 |
1297 | EXPECTED RESPONSE:
1298 |
1299 | ```javascript
1300 | { acn: 'tmr',
1301 | uid: '[TIMER UID]',
1302 | txt: '--:--:--' }
1303 | ```
1304 |
1305 | ### On New Slide
1306 |
1307 | EXPECTED RESPONSE:
1308 |
1309 | ```javascript
1310 | {
1311 | "acn": "fv",
1312 | "ary": [
1313 | {
1314 | "acn": "cs", # CURRENT SLIDE
1315 | "uid": "[SLIDE UID]",
1316 | "txt": "[SLIDE TEXT]"
1317 | },
1318 | {
1319 | "acn": "ns", # NEXT SLIDE
1320 | "uid": "[SLIDE UID]",
1321 | "txt": "[SLIDE TEXT]"
1322 | },
1323 | {
1324 | "acn": "csn", # CURRENT SLIDE NOTES
1325 | "txt": "[SLIDE NOTES]"
1326 | },
1327 | {
1328 | "acn": "nsn", # NEXT SLIDE NOTES
1329 | "txt": "[SLIDE NOTES]"
1330 | }
1331 | ]
1332 | }
1333 | ```
1334 |
--------------------------------------------------------------------------------