ProdDash: new project with reference forks of ProdCom Listener and BandLineupDisplay
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
# Stage Now / Next
|
||||
|
||||
The ProPresenter **Now / Next** display, as its own program — the same feature that
|
||||
lives in the Band Lineup app's right-hand column, but on its own so it can run on
|
||||
studio machines, hallway TVs, green-room tablets, anywhere in the building.
|
||||
|
||||
Both programs share one engine: [`../propresenter-core`](../propresenter-core/index.js).
|
||||
Fix a ProPresenter quirk there and both get the fix.
|
||||
|
||||
---
|
||||
|
||||
## The easiest way: let the Band Lineup app host it
|
||||
|
||||
If the backstage machine already runs **Band Lineup**, you don't need to start anything here.
|
||||
Open the app, and in the **People** panel find **Studio Displays** → **Turn On**.
|
||||
|
||||
It then lists the addresses to use, best first, each with a **Copy** button. Open one on any
|
||||
screen in the building and bookmark it. Nothing to install on those screens.
|
||||
|
||||
Because the app hands the display its own ProPresenter connection, there is exactly one
|
||||
connection to ProPresenter no matter how many screens are watching, and the studios always
|
||||
show precisely what the backstage screen shows. The connection is still set up in one place — the
|
||||
app's ProPresenter section — and the studio screens' own settings are locked, with a note
|
||||
saying so.
|
||||
|
||||
The displays are live whenever Band Lineup is open. Close the app and every screen says
|
||||
**Display offline** and names the backstage computer, rather than quietly showing a frozen
|
||||
lineup. They come back on their own when the app reopens — no reloading needed.
|
||||
|
||||
### The bookmark that keeps working
|
||||
|
||||
The first address in the list is `http://nownext.local:7654`. The app publishes that name
|
||||
itself over mDNS, so the bookmark survives the backstage machine getting a new IP from DHCP.
|
||||
It resolves on Macs, iPads and Windows 10/11.
|
||||
|
||||
Do **not** bookmark the computer's own name (`http://backstage-pc.local:7654`) — Windows can
|
||||
*resolve* mDNS names but never *publishes* its own, so Macs and iPads get nothing back.
|
||||
|
||||
If `nownext.local` doesn't work on a particular screen (some managed networks and separate
|
||||
VLANs block mDNS), use one of the numeric addresses below it. Once any screen has loaded
|
||||
the page from an address, that address is relabelled **Confirmed working** and moves to the
|
||||
top — so after the first studio connects, the app knows the right answer for your building.
|
||||
|
||||
For a permanently fixed address, ask IT for a DHCP reservation on the backstage machine, and
|
||||
optionally a DNS name pointing at it. One caveat if you go the DNS route: the zone must not
|
||||
end in `.local`, or Apple devices will route the lookup to mDNS and it will fail.
|
||||
|
||||
### If a studio screen can't load the page (Windows)
|
||||
|
||||
Almost always Windows Defender Firewall. Two things make this confusing:
|
||||
|
||||
- The prompt appears when the app **starts serving**, not when a studio first connects — so
|
||||
the backstage machine looks perfectly fine on `localhost` while every other screen times out.
|
||||
- If whoever answered the prompt wasn't a local admin, Windows creates **block** rules no
|
||||
matter which button they pressed, and it never asks again.
|
||||
|
||||
The installer adds the allow rules for you, so a normal install is fine. If you're running
|
||||
an unpacked build or the rules got lost, an admin can restore them:
|
||||
|
||||
```bash
|
||||
netsh advfirewall firewall add rule name="Band Lineup Display (TCP)" dir=in action=allow program="C:\Program Files\Band Lineup\Band Lineup.exe" enable=yes profile=domain,private protocol=TCP
|
||||
```
|
||||
|
||||
The app helps you spot this: if hosting is on and no screen has ever connected, the Studio
|
||||
Displays status line says so and names the port to allow.
|
||||
|
||||
Other things that break a working setup: the backstage PC sleeping (turn off sleep on a machine
|
||||
that hosts displays), and Windows flipping the network profile to *Public* — the allow rules
|
||||
above only cover Domain and Private.
|
||||
|
||||
### Keep the studio screens awake
|
||||
|
||||
The page asks the browser for a screen wake lock, but browsers only grant that on a secure
|
||||
(HTTPS) origin, and this is served over plain HTTP on the LAN — so the request is always
|
||||
refused. Turn off screen sleep in the OS settings on any machine or tablet you leave running
|
||||
as a dedicated display; the web page cannot do it for you.
|
||||
|
||||
---
|
||||
|
||||
## Security: what's protected, and what isn't
|
||||
|
||||
Be clear-eyed about this one, because the honest answer is unusual.
|
||||
|
||||
**Viewing is deliberately open.** Anyone who can reach the port can watch the page. There is no
|
||||
password, and adding one was considered and rejected: a wall TV has to come back by itself
|
||||
after a power cut with nobody standing there, and a login prompt, a key in the URL, or a
|
||||
certificate warning all break exactly that. A key in the URL would also live forever in every
|
||||
bookmark and in the address list volunteers copy into group chats.
|
||||
|
||||
**More importantly, gating this page would not raise the bar much.** ProPresenter's own network
|
||||
API on the same LAN has no authentication at all, and unlike this page it is not read-only.
|
||||
Anyone who can reach this display can already read the same service order straight from
|
||||
ProPresenter — and put whatever they like on the main screen. **Network placement is the control
|
||||
that actually covers both.**
|
||||
|
||||
### What is protected
|
||||
|
||||
- **Nothing can be changed over the network** while the Band Lineup app is hosting. The
|
||||
connection routes return `409`, so a browser on the LAN cannot retarget or disconnect the
|
||||
display. Standalone additionally requires `application/json`, a same-origin request, and a
|
||||
recognised `Host` — which blocks the JavaScript-free form CSRF and DNS-rebinding writes that
|
||||
are otherwise possible against a local HTTP server.
|
||||
- **Hidden items stay hidden.** Anything flagged hidden in ProPresenter — cut songs, contingency
|
||||
cues, staff-only slides — has its name stripped before it leaves the machine, not merely
|
||||
skipped when drawing the page.
|
||||
- **The playlist library is not exposed.** `/api/playlists` is refused while the app is hosting.
|
||||
- **Connection limits.** Concurrent viewers are capped (32 total, 6 per device) so nobody can pile
|
||||
up connections inside the app, and a screen that stops reading is dropped rather than buffered
|
||||
forever. Established screens are never dropped to make room for a new one.
|
||||
- **The mDNS name can't be stolen with one packet.** A claim on `nownext.local` is verified by
|
||||
probing before this host stands aside, and it reclaims the name when the other host leaves.
|
||||
|
||||
### What is exposed
|
||||
|
||||
Song and sermon titles, slide numbers, and the upcoming service order — in the clear, over plain
|
||||
HTTP, to anything that can reach the port. Also the ProPresenter machine's address and port, and
|
||||
the host's LAN addresses. Decide whether an unannounced sermon title or a baptism in the order
|
||||
matters to you before a service; if it does, the answer is the VLAN below, not a password.
|
||||
|
||||
### Where this should live on the network
|
||||
|
||||
- Backstage PC, ProPresenter, and the display screens on a **trusted A/V or management VLAN**.
|
||||
- **Guest Wi-Fi kept off that VLAN.** This is the single highest-value control here.
|
||||
- A **DHCP reservation** for the backstage machine — which you want anyway so bookmarks survive.
|
||||
- If you ever set a ProPresenter network password, treat it as a LAN-visible string: it travels
|
||||
in the query string of ProPresenter's own API on every poll, because that is the interface
|
||||
ProPresenter offers.
|
||||
|
||||
---
|
||||
|
||||
## Run it standalone
|
||||
|
||||
For a machine that doesn't run Band Lineup — or to host the display without it — run the
|
||||
server on its own. Every other screen just opens a web page.
|
||||
|
||||
```bash
|
||||
cd propresenter-app
|
||||
node server.js
|
||||
```
|
||||
|
||||
Then open `http://localhost:7654` on that machine, press the **gear** (bottom-right,
|
||||
appears when you move the mouse or tap), and enter the IP and port of the computer
|
||||
running ProPresenter. That's it — the connection is stored on the server, so every
|
||||
other screen that opens the page is already following along.
|
||||
|
||||
The startup log prints the addresses other screens should use, e.g.
|
||||
`http://10.1.1.42:7654`. They're also listed in the settings panel under
|
||||
**Other screens**.
|
||||
|
||||
Requirements: Node 18 or newer. No `npm install`, no dependencies.
|
||||
|
||||
### ProPresenter side
|
||||
|
||||
Turn the network API on in **ProPresenter ▸ Preferences ▸ Network**. Note the port
|
||||
(1025 by default) and set a password there if you want one.
|
||||
|
||||
## Run it as an app window
|
||||
|
||||
For the machine that should *host* the display, there's an Electron wrapper that
|
||||
starts the same server and shows the page in a window:
|
||||
|
||||
```bash
|
||||
npm run kiosk
|
||||
```
|
||||
|
||||
```bash
|
||||
npm run kiosk -- --kiosk
|
||||
```
|
||||
|
||||
The second form is true kiosk: no window chrome, fullscreen, for a dedicated screen.
|
||||
Either way the server still serves the rest of the building.
|
||||
|
||||
## Options
|
||||
|
||||
| What | How |
|
||||
| --- | --- |
|
||||
| Different port | `PORT=9000 node server.js` |
|
||||
| Lock the settings so viewers can't change the connection | `PP_DISPLAY_READONLY=1 node server.js` |
|
||||
| Move where `config.json` lives (a NAS, a shared path) | `PP_DISPLAY_DATA_DIR=/path/to/dir node server.js` |
|
||||
|
||||
Per-screen looks — text size (Small → Huge), whether to show the playlist name, the
|
||||
progress bar, and whether to tidy titles — are set in the gear panel and saved on
|
||||
**that device only**, so a 65" TV and an iPad can each be sized for their own room.
|
||||
|
||||
Keyboard: `f` toggles fullscreen, `Esc` closes the settings panel.
|
||||
|
||||
## Running it as a service
|
||||
|
||||
Any process manager works since it's a plain Node script. For example, with `pm2`:
|
||||
|
||||
```bash
|
||||
pm2 start server.js --name stage-now-next -- --port 7654
|
||||
```
|
||||
|
||||
## What's in here
|
||||
|
||||
| File | Job |
|
||||
| --- | --- |
|
||||
| `server.js` | Serves the UI and pushes the live view-model to every browser over SSE. Standalone it polls ProPresenter itself; embedded it mirrors the client the Band Lineup app is already polling with |
|
||||
| `config.js` | Reads/writes `config.json` (password obfuscated at rest — treat the file as a secret), and ranks which LAN address to bookmark |
|
||||
| `mdns.js` | Publishes `nownext.local` so a bookmark survives a DHCP change. Zero dependencies |
|
||||
| `public/index.html`, `public/app.js`, `public/styles.css` | The display itself and the settings panel |
|
||||
| `main.js` | Optional Electron kiosk wrapper |
|
||||
| `../propresenter-core/index.js` | The shared ProPresenter client — also used by the Band Lineup app |
|
||||
|
||||
## API
|
||||
|
||||
Anything on the LAN can read the live state, which makes it easy to hang other
|
||||
displays or automations off the same feed:
|
||||
|
||||
| Endpoint | Returns |
|
||||
| --- | --- |
|
||||
| `GET /api/state` | `{ state, status }` — one JSON snapshot |
|
||||
| `GET /api/stream` | Server-Sent Events: `state` on every poll, `status` on connection changes |
|
||||
| `GET /api/status` | Connection info only |
|
||||
| `GET /api/playlists` | Every playlist ProPresenter knows about |
|
||||
| `GET /api/health` | `{ ok, viewers, reachable }` |
|
||||
| `POST /api/connect` | `{ host, port, password? }` — tests, saves, and starts following |
|
||||
| `POST /api/disconnect` | Stops following; keeps host/port for next time |
|
||||
|
||||
The two `POST` routes return `403` when `PP_DISPLAY_READONLY=1`, and `409` when the Band
|
||||
Lineup app is hosting — a browser on the LAN must never be able to retarget or disconnect
|
||||
the backstage app's own ProPresenter connection.
|
||||
|
||||
## Notes
|
||||
|
||||
- **The display auto-follows whatever ProPresenter is presenting** — the live playlist
|
||||
and item come from ProPresenter itself, so there's nothing to pick each week.
|
||||
- **Slide totals respect arrangements.** If an arrangement repeats a chorus, the total
|
||||
counts the repeat, so "Slide 41 / 43" always matches what the operator sees.
|
||||
- **Building a Windows installer** (`npm run dist`) needs `npm install` in this folder
|
||||
first, and packages `../propresenter-core` alongside — keep the two folders siblings.
|
||||
The everyday `node server.js` path needs neither step.
|
||||
Reference in New Issue
Block a user