101 lines
4.9 KiB
Markdown
101 lines
4.9 KiB
Markdown
# Resi Studio Cues
|
||||
|
|
|
|||
|
|
Puts a live "time until the next Resi cue" countdown on the stage screen.
|
|||
|
|
|
|||
|
|
**This folder is not a ProdDash tile.** It has no `module.json`, so the shell's module
|
|||
|
|
scan skips it. It lives here because this is where the production tooling lives, not
|
|||
|
|
because ProdDash loads it. That is also why it carries its own README, unlike the real
|
|||
|
|
modules, which are documented in the repo README's *Included modules*.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## What it does
|
|||
|
|
|
|||
|
|
`resi_to_propresenter.py` watches a Resi decoder's playhead, works out how long until
|
|||
|
|
its next cue point, and keeps a ProPresenter countdown timer armed with that number.
|
|||
|
|
ProPresenter renders the countdown itself, so the stage screen is smooth and styled in
|
|||
|
|
whatever the stage layout uses, and nothing has to be pushed every second. The cue's
|
|||
|
|
name goes up as a ProPresenter stage message alongside it.
|
|||
|
|
|
|||
|
|
The cue list and the playhead both come from Resi's cloud, not from the decoder — the
|
|||
|
|
decoder reports its position upstream, and `central.resi.io` hands it back. Nothing has
|
|||
|
|
to be installed at the decoder end.
|
|||
|
|
|
|||
|
|
## Why the playhead is estimated rather than displayed
|
|||
|
|
|
|||
|
|
Resi republishes a decoder's `position` only every few seconds, on an irregular
|
|||
|
|
cadence, and each reading is already a little stale when it appears. Sent straight to
|
|||
|
|
ProPresenter, the countdown sits several seconds behind and lurches.
|
|||
|
|
|
|||
|
|
So every reading is kept as *media time minus wall time*, and the **largest** recent
|
|||
|
|
value is the one trusted — that is the reading that reached us with the least delay, so
|
|||
|
|
a slow round trip or an unlucky poll cannot drag the countdown backwards. A reading
|
|||
|
|
that falls outside the link's measured noise floor is treated as a seek: the lock is
|
|||
|
|
dropped, the estimate snaps, and the cue list is re-read at once instead of on its
|
|||
|
|
timer. Playback is only called paused once the playhead has been still for clearly
|
|||
|
|
longer than Resi's own cadence, because a fixed timeout mistakes a slow update for a
|
|||
|
|
stop and freezes the countdown mid-service.
|
|||
|
|
|
|||
|
|
How quickly a fired cue can possibly show on stage is bounded by how often Resi
|
|||
|
|
republishes the position. Nothing on this end beats that; the log prints the measured
|
|||
|
|
cadence so you can see what it is.
|
|||
|
|
|
|||
|
|
## Running it
|
|||
|
|
|
|||
|
|
Settings live outside the checkout, at
|
|||
|
|
`~/Library/Application Support/ResiCues/config.json`, so a `git pull` never touches
|
|||
|
|
them. `config.example.json` here is the template. The Resi password is read from the
|
|||
|
|
Keychain and is never written to disk:
|
|||
|
|
|
|||
|
|
security add-generic-password -a "you@yourchurch.org" -s resi-propresenter -w
|
|||
|
|
|
|||
|
|
Then pick a decoder:
|
|||
|
|
|
|||
|
|
python3 resi_to_propresenter.py --list
|
|||
|
|
|
|||
|
|
and put its `playerHardwareId` into `player` in the config (leave it blank if the
|
|||
|
|
account has only one). `--dry-run` prints what it would send without touching
|
|||
|
|
ProPresenter.
|
|||
|
|
|
|||
|
|
**Start it at login:** double-click `install-agent.command`. It writes a LaunchAgent
|
|||
|
|
that runs the script at login and restarts it if it stops, logging to
|
|||
|
|
`~/Library/Logs/resi-propresenter.log`. Run it again after a pull to repoint the agent.
|
|||
|
|
|
|||
|
|
ProPresenter needs its network API on (Settings › Network) and a **countdown** timer
|
|||
|
|
whose name matches `timer_name` in the config — `NextCue` by default — placed on the
|
|||
|
|
stage layout, with a Message object for the cue name.
|
|||
|
|
|
|||
|
|
## Tests
|
|||
|
|
|
|||
|
|
python3 test_playhead.py # the estimator against 9 simulated Resi profiles
|
|||
|
|
python3 test_end_to_end.py http://10.1.14.225:1025
|
|||
|
|
|
|||
|
|
`test_playhead.py` drives the estimator on a fake clock against a simulated Resi with
|
|||
|
|
a configurable update cadence, staleness, cadence jitter and network round trip; pass
|
|||
|
|
those four as arguments to run one profile. `test_end_to_end.py` runs the real script
|
|||
|
|
loop against a **real** ProPresenter with a simulated decoder, checks the timer is
|
|||
|
|
armed and counts down in step across a cue rollover, and puts ProPresenter back as it
|
|||
|
|
found it.
|
|||
|
|
|
|||
|
|
Both suites are written so a deliberate break turns them red — six of seven mutations
|
|||
|
|
of the estimator do. Neither covers the live Resi calls, which need real credentials.
|
|||
|
|
|
|||
|
|
## The Companion sibling
|
|||
|
|
|
|||
|
|
Before the stage-screen version, the same estimator shipped as a Bitfocus Companion
|
|||
|
|
module for Stream Deck buttons — a patched build of
|
|||
|
|
[reykroona/companion-module-resi-studio-cues](https://github.com/reykroona/companion-module-resi-studio-cues)
|
|||
|
|
(MIT), with the estimator, a PLAYING/PAUSED variable and resync actions added. It is
|
|||
|
|
not needed for the stage display and is not checked in here; the packaged `.tgz` and
|
|||
|
|
its patched sources are in `~/Desktop/Resi Companion/`.
|
|||
|
|
|
|||
|
|
## Resi's API
|
|||
|
|
|
|||
|
|
Undocumented and unsupported — it is what `studio.resi.io` itself uses. Don't open a
|
|||
|
|
Resi ticket about it. Authentication is `POST /api/v3/login?newToken=true` with
|
|||
|
|
`{userName, password}`, which sets a `livingas1` cookie used as
|
|||
|
|
`Authorization: X-Bearer <cookie>`; from there `/api_v2.svc/users/me`,
|
|||
|
|
`/api/v3/customers/{id}/venues`, `/api_v2.svc/users/{venueUuid}/players`,
|
|||
|
|
`/api/v3/customers/{id}/events/{eventId}` and
|
|||
|
|
`/api_v2.svc/streamprofiles/{profileId}/events/{eventId}/cues`.
|