Files
ProdDash/band-lineup-display/propresenter-app/README.md
T

234 lines
11 KiB
Markdown
Raw Normal View History

# 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.