Church presentation software whose outputs render as live HTML in the browser.
A fork of FreeShow by
ChurchApps — see NOTICE.
Credit where it is due. Almost all of this software is FreeShow, built by ChurchApps and its contributors. This fork changes one thing — how output reaches a browser — and removes NDI for now. If you find it useful, please support the upstream project at freeshow.app. Licensed GPL-3.0, same as upstream.
FreeShow's "HTML Output" mirrors an output by capturing the output window and streaming compressed frames. Measured on this hardware for a 1080p output, that costs roughly:
| stage | cost |
|---|---|
capturePage() |
18–55 ms (18 with a working GPU, 55 without) |
| resize to stream size | 12–17 ms |
| JPEG encode | 8–20 ms |
…before a frame even leaves the app, giving ~55 ms end to end at best and ~95 ms in a software-rendering session. Those costs are structural: they scale with pixel count and cannot be tuned away.
FreePresenter sends the output data instead and lets the browser draw the slide. That is one network hop, and the result stays sharp at any window size instead of being a fixed-resolution JPEG.
An output window in FreeShow is not special — it is the same app bundle, told TYPE: "output" over the
STARTUP channel, talking to the main process through window.api. The only Electron-specific part is the
transport.
So the browser runs the same renderer, with window.api backed by a socket:
src/electron/output/htmlRenderShim.ts— thewindow.apiserved ahead of the bundlesrc/electron/output/HtmlRenderBridge.ts— forwards everything an output window receives to the browsers watching that output, and dispatches their sends into the same main-process handlers/render/<output path>— serves the bundle with the shim injected
Because it is the same renderer, there is no second implementation to keep in sync with the real output.
- NDI is not included. The native
grandioseaddon is not a dependency and the feature is hidden behindNDI_ENABLEDinsrc/frontend/values/features.ts. The code is still present and loads the addon lazily, so re-enabling it is a one-line change plus the dependency. - Separate identity and data. Different appId, product name and data folder (
Documents/FreePresenter), so it installs and stores data alongside FreeShow rather than over it. To reuse an existing FreeShow library, point the data location at the old folder in settings. - Upstream API keys and crash reporting are not carried over. See NOTICE; supply your own keys for the Planning Center / ChurchApps / Amazing Life integrations.
- Kept deliberately unchanged: the
.freeshowimport formats, the ChurchApps API field names, the OSC/freeshowaddress and the bonjour service type — those name the format or service, not this app.
Against a real library (234 shows), both clients open on the same output at once, driven by real slide changes, 10 rounds — with slide transitions disabled so the numbers reflect the pipeline rather than a fade:
| path | changes seen | p50 | min |
|---|---|---|---|
HTML renderer /render/<path> |
10/10 | 91 ms | 75 ms |
pixel stream /<path> |
10/10 | 473 ms | 159 ms |
The residual ~75 ms is the app's own command handling (loading the show, store updates, reactivity), which both paths pay; the pixel path then adds ~380 ms of capture, resize and encode on top. With FreeShow's default 500 ms text fade enabled both rise by roughly that fade, as you would expect.
Working: the renderer boots into output mode, renders real slides (including non-Latin text), and local media is served over HTTP so backgrounds load. Both output paths can be open at once.
Not yet verified: video backgrounds — only still images have been exercised — and playback-position sync across clients, which is the genuinely hard part. The pixel stream remains as the fallback path.
npm install
npm run build
npx electron-builder --config config/building/electron-builder.yaml --linux AppImageUpstream FreeShow remains available as the upstream git remote.
GPL-3.0, as FreeShow.