Files
christianandClaude Opus 5.5 1a658875bc Resi cue countdowns on ProPresenter and in Companion
The Resi -> ProPresenter bridge, its installer and site kit, and the
Companion module resi-studio-cues, as running at North Attleboro.

Bridge (resi_to_propresenter.py)
- Follows a Resi decoder's playhead and keeps ProPresenter's NextCue timer
  counting down to the next cue; optional cue name as a stage message, sent
  once per cue (ProPresenter flashes it on every send) and off by default.
- Playhead estimate no longer learns a cadence from the partial first gap
  after a start, so a 30s Resi doesn't read as paused half the time.
- Backs off on 429, holds 2 min before retrying a failed startup login, and
  logs Resi's actual reason (wrong password, USER_LOCKED, rate limit).
- --setup asks for the login, decoder, ProPresenter and timer.

install_bridge.sh installs it as a login service (outside ~/Documents,
which launchd may not read); make_kit.sh builds the kit for another site.

Companion module resi-studio-cues 0.1.4 (source/, package committed)
- Same playhead fix; login and request hold-off (15s doubling to 5 min);
  a 401/403 on a fresh session is a permission, not a reason to sign in
  again; init never fails on a Resi problem, so Companion can't restart it
  into a login loop.

Tests: test_playhead.py, test_stage_message.py, test_rate_limit.py and
source/backoff_test.js run offline; test_end_to_end.py drives a real
ProPresenter and refuses to run without an explicit flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:11:01 -04:00

303 lines
9.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdatePresets = UpdatePresets;
const base_1 = require("@companion-module/base");
function UpdatePresets(self) {
const presets = {};
// Helper to reference THIS instance’s variables in button text
// Companion variable syntax uses the instance label.
const L = self.label;
// ----------------------------
// STATUS / HEALTH
// ----------------------------
presets['status_connected'] = {
type: 'button',
category: 'Resi • Status',
name: 'Connected (green) / Error (red)',
style: {
text: `Resi\n$(${L}:auth_status)`,
size: '14',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(40, 40, 40),
show_topbar: true,
},
steps: [],
feedbacks: [
{
feedbackId: 'resi_connected',
options: {},
},
{
feedbackId: 'resi_error',
options: {},
},
],
};
presets['status_player_active'] = {
type: 'button',
category: 'Resi • Status',
name: 'Player Active',
style: {
text: `Player\n$(${L}:current_position)`,
size: '14',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(40, 40, 40),
show_topbar: true,
},
steps: [],
feedbacks: [
{
feedbackId: 'player_active',
options: {},
},
],
};
presets['status_frozen'] = {
type: 'button',
category: 'Resi • Status',
name: 'Playback Frozen Warning',
style: {
text: `FROZEN?\n$(${L}:current_position)`,
size: '14',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(40, 40, 40),
show_topbar: true,
},
steps: [],
feedbacks: [
{
feedbackId: 'playback_frozen',
options: {
staleSeconds: 6,
},
},
],
};
// ----------------------------
// CUE DISPLAY (NO ACTIONS)
// ----------------------------
presets['cue_next_display'] = {
type: 'button',
category: 'Resi • Cues',
name: 'Next Cue Display',
style: {
text: `NEXT:\n$(${L}:next_cue_name)\nT- $(${L}:next_cue_time_in_hms)`,
size: '14',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(0, 0, 0),
show_topbar: false,
},
steps: [],
feedbacks: [
{
feedbackId: 'approaching_next_cue',
options: { threshold: 30 },
},
],
};
presets['cue_prev_display'] = {
type: 'button',
category: 'Resi • Cues',
name: 'Previous Cue Display',
style: {
text: `PREV:\n$(${L}:prev_cue_name)\n+ $(${L}:prev_cue_time_ago_in_hms)`,
size: '14',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(0, 0, 0),
show_topbar: false,
},
steps: [],
feedbacks: [],
};
// ----------------------------
// MAINTENANCE / REFRESH
// ----------------------------
presets['refresh_profile'] = {
type: 'button',
category: 'Resi • Maintenance',
name: 'Refresh Profile',
style: {
text: 'Refresh\nProfile',
size: '18',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(60, 60, 60),
show_topbar: false,
},
steps: [
{
down: [
{
actionId: 'refresh_profile',
options: {},
},
],
up: [],
},
],
feedbacks: [
{ feedbackId: 'resi_connected', options: {} },
{ feedbackId: 'resi_error', options: {} },
],
};
presets['refresh_lists'] = {
type: 'button',
category: 'Resi • Maintenance',
name: 'Refresh Venues/Players',
style: {
text: 'Refresh\nLists',
size: '18',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(60, 60, 60),
show_topbar: false,
},
steps: [
{
down: [
{
actionId: 'refresh_lists',
options: {},
},
],
up: [],
},
],
feedbacks: [
{ feedbackId: 'resi_connected', options: {} },
{ feedbackId: 'resi_error', options: {} },
],
};
// ----------------------------
// CUE ACTIONS
// ----------------------------
presets['add_cue_player'] = {
type: 'button',
category: 'Resi • Cues',
name: 'Add Cue @ Player Position',
style: {
text: 'Add Cue\n@ Player',
size: '18',
color: (0, base_1.combineRgb)(0, 0, 0),
bgcolor: (0, base_1.combineRgb)(180, 180, 180),
show_topbar: false,
},
steps: [
{
down: [
{
actionId: 'add_cue_at_position',
options: {
name: 'Web Cue',
privateCue: false,
positionMode: 'player',
customPosition: '00:00:00.000',
},
},
],
up: [],
},
],
feedbacks: [
{ feedbackId: 'resi_connected', options: {} },
{ feedbackId: 'resi_error', options: {} },
],
};
presets['add_cue_custom'] = {
type: 'button',
category: 'Resi • Cues',
name: 'Add Cue @ Custom Time',
style: {
text: 'Add Cue\n@ Custom',
size: '18',
color: (0, base_1.combineRgb)(0, 0, 0),
bgcolor: (0, base_1.combineRgb)(180, 180, 180),
show_topbar: false,
},
steps: [
{
down: [
{
actionId: 'add_cue_at_position',
options: {
// user can edit this preset, OR replace with a variable
name: 'Custom Cue',
privateCue: false,
positionMode: 'custom',
customPosition: '00:10:00.000',
},
},
],
up: [],
},
],
feedbacks: [
{ feedbackId: 'resi_connected', options: {} },
{ feedbackId: 'resi_error', options: {} },
],
};
presets['lookup_cue'] = {
type: 'button',
category: 'Resi • Cues',
name: 'Lookup Cue (stores in lookup_* vars)',
style: {
text: 'Lookup\nCue',
size: '18',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(0, 110, 200),
show_topbar: false,
},
steps: [
{
down: [
{
actionId: 'lookup_cue_by_name',
options: {
// user edits this preset to their cue name or uses variables
cueName: 'sermon start',
matchMode: 'exact',
},
},
],
up: [],
},
],
feedbacks: [
{ feedbackId: 'resi_connected', options: {} },
{ feedbackId: 'resi_error', options: {} },
],
};
presets['delete_cue'] = {
type: 'button',
category: 'Resi • Cues',
name: 'Delete Cue by Name',
style: {
text: 'DELETE\nCue',
size: '18',
color: (0, base_1.combineRgb)(255, 255, 255),
bgcolor: (0, base_1.combineRgb)(140, 0, 0),
show_topbar: false,
},
steps: [
{
down: [
{
actionId: 'delete_cue_by_name',
options: {
// user edits this preset to the cue they want to delete
cueName: 'Companion Test',
matchMode: 'exact',
deleteMode: 'first',
onlyMine: true,
},
},
],
up: [],
},
],
feedbacks: [
{ feedbackId: 'resi_connected', options: {} },
{ feedbackId: 'resi_error', options: {} },
],
};
self.setPresetDefinitions(presets);
}
//# sourceMappingURL=presets.js.map