244 lines
8.6 KiB
Markdown
244 lines
8.6 KiB
Markdown
# Resi Studio Cues (Bitfocus Companion Module)
|
||||
|
|
|
|||
|
|
This module logs into **studio.resi.io / central.resi.io** with a username + password, reads decoder/player playback position, loads cue points for the current event, and exposes variables/actions for cue timing and cue management.
|
|||
|
|
|
|||
|
|
The main goal is to drive things like a **countdown clock in ProPresenter** (time until next cue, next cue name, etc).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Features
|
|||
|
|
|
|||
|
|
### Authentication
|
|||
|
|
- Logs in via `POST /api/v3/login?newToken=true`
|
|||
|
|
- Stores auth cookies (`refreshToken`, `livingas1`) and uses them on subsequent requests
|
|||
|
|
- Exposes status via:
|
|||
|
|
- Instance status (green/red in Companion)
|
|||
|
|
- Variables: `auth_status`, `last_error`
|
|||
|
|
|
|||
|
|
### Player polling (live)
|
|||
|
|
- Polls decoder/player position from:
|
|||
|
|
- `GET /api_v2.svc/users/{venueUuid}/players`
|
|||
|
|
- Updates variables like:
|
|||
|
|
- `current_position`
|
|||
|
|
- `current_position_sec`
|
|||
|
|
- `current_event_id`
|
|||
|
|
|
|||
|
|
### Cue loading (per event)
|
|||
|
|
When the current `eventId` changes, the module loads:
|
|||
|
|
- Event detail:
|
|||
|
|
- `GET /api/v3/customers/{customerId}/events/{eventId}`
|
|||
|
|
- Captures `startTime` for ToD (time-of-day) math
|
|||
|
|
- Cues:
|
|||
|
|
- `GET /api_v2.svc/streamprofiles/{eventProfileId}/events/{eventId}/cues?canISetCues=1`
|
|||
|
|
|
|||
|
|
Cues are stored locally and used to compute **next/prev** cue timing from the live playback position.
|
|||
|
|
|
|||
|
|
### Cue timing variables
|
|||
|
|
- Next cue:
|
|||
|
|
- `next_cue_name`
|
|||
|
|
- `next_cue_time_in_seconds`
|
|||
|
|
- `next_cue_time_in_hms`
|
|||
|
|
- `next_cue_position`
|
|||
|
|
- `next_cue_tod` (24-hour)
|
|||
|
|
- Previous cue:
|
|||
|
|
- `prev_cue_name`
|
|||
|
|
- `prev_cue_time_ago_in_seconds`
|
|||
|
|
- `prev_cue_time_ago_in_hms`
|
|||
|
|
- `prev_cue_position`
|
|||
|
|
- `prev_cue_tod` (24-hour)
|
|||
|
|
|
|||
|
|
### Cue management actions
|
|||
|
|
- Add cue (player position or custom time)
|
|||
|
|
- Lookup cue by name (stores into `lookup_*` variables)
|
|||
|
|
- Delete cue by name (looks up UUID, deletes by UUID)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Installation / Testing
|
|||
|
|
|
|||
|
|
### Best (recommended): Developer Module via Git
|
|||
|
|
1. Push this repo to GitHub/GitLab (public or private).
|
|||
|
|
2. On the test system, add it as a Developer Module in Companion.
|
|||
|
|
3. Restart Companion.
|
|||
|
|
|
|||
|
|
### Offline / manual
|
|||
|
|
1. `yarn install`
|
|||
|
|
2. `yarn build`
|
|||
|
|
3. Zip the module folder (without `node_modules`) and provide to tester.
|
|||
|
|
4. Tester places it in Companion’s modules folder and restarts Companion.
|
|||
|
|
|
|||
|
|
(Exact folder paths vary by OS / Companion version.)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Configuration
|
|||
|
|
|
|||
|
|
Typical config fields:
|
|||
|
|
- **Username / Email**
|
|||
|
|
- **Password** (secret)
|
|||
|
|
- **Default Player** (optional)
|
|||
|
|
- **Polling intervals**
|
|||
|
|
- Playback position refresh (fast, e.g. 2000ms)
|
|||
|
|
- Account/venue refresh (slow, e.g. 60000ms)
|
|||
|
|
- Cue refresh while playing (optional, e.g. 15000ms)
|
|||
|
|
- **Fallback when no next cue** (optional)
|
|||
|
|
|
|||
|
|
> Note: dynamic "read-only" status display inside config is not supported by Companion config fields.
|
|||
|
|
> Use Instance Status + module variables for connection state.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Actions
|
|||
|
|
|
|||
|
|
- **Refresh profile**: Reload `/users/me`
|
|||
|
|
- **Refresh lists**: Reload venues + players list
|
|||
|
|
- **Select decoder/player**: Choose which player to poll
|
|||
|
|
- **Add cue at player position (or custom time)**
|
|||
|
|
- Options:
|
|||
|
|
- Cue name
|
|||
|
|
- Private cue
|
|||
|
|
- Position source: player vs custom
|
|||
|
|
- Custom time string
|
|||
|
|
- Text fields support Companion variables (e.g. `$(internal:...)`)
|
|||
|
|
- **Lookup cue by name**
|
|||
|
|
- Writes result into:
|
|||
|
|
- `lookup_cue_found`, `lookup_cue_uuid`, `lookup_cue_position`, etc.
|
|||
|
|
- **Delete cue by name**
|
|||
|
|
- Finds matching cue(s), deletes by UUID via:
|
|||
|
|
- `DELETE /.../cues/{uuid}`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Feedbacks (for button coloring)
|
|||
|
|
|
|||
|
|
Recommended “production” feedback set:
|
|||
|
|
1. **Connected (auth OK)** → Green
|
|||
|
|
2. **Error (auth/API)** → Red
|
|||
|
|
3. **Player active** (position > 0) → Blue
|
|||
|
|
4. **Approaching next cue** (threshold seconds) → Yellow
|
|||
|
|
5. **Playback frozen** (position not changing) → Red
|
|||
|
|
|
|||
|
|
Playback frozen requires:
|
|||
|
|
- variable `playback_last_change_epoch_ms` to be updated when playback position changes.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Presets Included
|
|||
|
|
|
|||
|
|
### Status
|
|||
|
|
- Connected / Error indicator
|
|||
|
|
- Player active indicator
|
|||
|
|
- Playback frozen warning
|
|||
|
|
|
|||
|
|
### Cues
|
|||
|
|
- Next cue display (with T- countdown)
|
|||
|
|
- Previous cue display
|
|||
|
|
|
|||
|
|
### Maintenance
|
|||
|
|
- Refresh profile
|
|||
|
|
- Refresh venues/players list
|
|||
|
|
|
|||
|
|
### Cue actions
|
|||
|
|
- Add cue @ player position
|
|||
|
|
- Add cue @ custom time
|
|||
|
|
- Lookup cue by name
|
|||
|
|
- Delete cue by name
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Notes / Limitations
|
|||
|
|
|
|||
|
|
- The Resi API endpoints used here are not public/stable; breaking changes may occur.
|
|||
|
|
- Avoid polling cues every 2 seconds. Recommended cue refresh while playing is 10–30 seconds unless you have a specific need.
|
|||
|
|
- Companion config fields are not truly read-only, so status is exposed via variables + instance status.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Roadmap Ideas
|
|||
|
|
|
|||
|
|
- Optional periodic cue refresh while playing (rate-limited)
|
|||
|
|
- Extra feedback: "No player selected"
|
|||
|
|
- Extra feedback: "No next cue"
|
|||
|
|
- More presets for ProPresenter integration workflows
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Playhead accuracy (0.1.0)
|
|||
|
|
|
|||
|
|
Resi only republishes a decoder's `position` every few seconds, on an irregular
|
|||
|
|
cadence, and each reading is already slightly stale when it appears. Showing that
|
|||
|
|
number raw makes the countdown jump in steps and sit several seconds behind.
|
|||
|
|
|
|||
|
|
This build instead keeps a running estimate:
|
|||
|
|
|
|||
|
|
- every reading is stored as **media time minus wall time**; the largest recent value
|
|||
|
|
is the least-delayed reading and is the one trusted, so a slow round trip or an
|
|||
|
|
unlucky poll can no longer drag the countdown backwards
|
|||
|
|
- the display is recomputed **every second** from that offset, so the countdown ticks
|
|||
|
|
smoothly between Resi's updates instead of stepping
|
|||
|
|
- a reading that differs from the locked offset by more than the link's measured
|
|||
|
|
noise is treated as a **seek** - the lock is dropped, the estimate snaps, and the
|
|||
|
|
cue list is re-fetched immediately rather than on its timer
|
|||
|
|
- playback is only called **PAUSED** once the playhead has been still for clearly
|
|||
|
|
longer than Resi's own reporting cadence, so an irregular gap no longer freezes the
|
|||
|
|
countdown
|
|||
|
|
|
|||
|
|
`position_update_interval_sec` reports how often Resi is actually updating. That
|
|||
|
|
number is the floor on how fast a jump can possibly be noticed.
|
|||
|
|
|
|||
|
|
### Beating that floor
|
|||
|
|
|
|||
|
|
If cues are fired from Companion, the module does not have to wait for Resi to
|
|||
|
|
notice. Add one of these to the same button:
|
|||
|
|
|
|||
|
|
- **Resync playhead to cue** - give it the cue name; the estimate moves there at once
|
|||
|
|
- **Resync playhead to the NEXT cue**
|
|||
|
|
|
|||
|
|
The next real reading from Resi confirms or corrects it.
|
|||
|
|
|
|||
|
|
### Settings that matter
|
|||
|
|
|
|||
|
|
- **Player position poll (ms)** - 1000 is recommended. Accuracy no longer depends on
|
|||
|
|
this, but a faster poll notices a jump sooner.
|
|||
|
|
- **Cue list refresh (ms)** - 5000. Also forced immediately whenever a seek is seen.
|
|||
|
|
- **Position trim (ms)** - leave at 0. Only needed if the whole pipeline is
|
|||
|
|
consistently behind; positive values push the estimate forward.
|
|||
|
|
|
|||
|
|
## Startup fix (0.1.2)
|
|||
|
|
|
|||
|
|
On a Resi that publishes every ~30s, the module could start a few seconds before a
|
|||
|
|
publish and learn a 2s cadence from that partial first gap. It then threw out every
|
|||
|
|
real 30s gap, so `playback_state` flipped to **PAUSED** 15s into each update - the
|
|||
|
|
countdown froze and the next cue lagged, worst with cues close together.
|
|||
|
|
|
|||
|
|
- the partial gap after startup (or after a manual resync) is no longer learned
|
|||
|
|
- a single outlier gap can no longer lock out the real cadence
|
|||
|
|
- until the cadence is measured, PAUSED waits 45s instead of 15s
|
|||
|
|
|
|||
|
|
## Resi login back-off (0.1.3)
|
|||
|
|
|
|||
|
|
0.1.2 retried a failed Resi login on every player poll (once a second, for ever), and
|
|||
|
|
answered every 401/403 by signing in again. With a wrong password, a rate limit or a
|
|||
|
|
login that may not see decoders, that is thousands of sign-ins an hour: Resi replies
|
|||
|
|
with 429 and then locks the account (it locked one at North Attleboro).
|
|||
|
|
|
|||
|
|
- after any failed login, or a 429 on any request, every Resi call holds off: 15s,
|
|||
|
|
doubling to 5min, or as long as Resi's Retry-After asks
|
|||
|
|
- a 401/403 renews the session only if it is over a minute old; a refusal straight
|
|||
|
|
after signing in is a permission ("ask a Resi admin for access") and holds off
|
|||
|
|
- status and `last_error` say which: locked (USER_LOCKED), rate-limited, password
|
|||
|
|
rejected, no access, or unreachable
|
|||
|
|
- the log says it once per problem, not once a second
|
|||
|
|
- the cache-busting parameter is no longer dropped for good on a 401/403/429
|
|||
|
|
|
|||
|
|
## Start-up never crashes on a Resi problem (0.1.4)
|
|||
|
|
|
|||
|
|
In 0.1.3 a failed Resi login at start-up (here: a locked account) made the module's
|
|||
|
|
init fail. Companion counts that as a crash and restarts the module every few
|
|||
|
|
seconds, and each restart signed in again from scratch, outside the hold-off.
|
|||
|
|
|
|||
|
|
- init no longer fails when Resi refuses or is down: it logs "Not connected to Resi
|
|||
|
|
yet … will keep trying", starts the account poll, and the hold-off paces retries
|
|||
|
|
- once Resi answers, the configured decoder is picked up automatically
|
|||
|
|
- saving new credentials clears the old hold-off and starts over
|