Files
band-lineup-display/propresenter-app/README.md
T
christianandClaude Opus 5 96ac2dec2d Band Lineup Display, the backstage app itself
The Electron app that runs on the backstage PC: stage lineup photos with
mic/pack labels, packaged for Windows. It also carries the ProPresenter
Now/Next feature in two forms that share one engine — propresenter-core, where
every ProPresenter 7/20 quirk lives, and propresenter-app, the standalone
display it serves.

Until now this existed only on the production machine and as a read-only
snapshot inside the ProdDash repo. Source only: the 231 MB dist/ is build
output, and credentials, photos and processed images are written to the
Electron userData directory, never here. tmp/pp-mock.js stays — it is the
ProPresenter mock ProdDash's own CLAUDE.md tells people to run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 15:41:21 -04:00

11 KiB

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

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.

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:

npm run kiosk
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:

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.