Thanks for wanting to help. This document is the short version of how the project works, so you can spend your time on the change rather than on guessing the conventions.
Open an issue first for anything larger than a bug fix. Not as a formality —
this project has some strong opinions baked into it (video is never re-encoded,
the stack is fixed, FFmpeg is driven through os/exec and nothing else), and it
is much kinder to find out in an issue that a design conflicts with one of them
than after you have written it.
Small, obvious fixes need no ceremony. Send the pull request.
These are what the project is. A change that breaks one of them will be declined however good it is otherwise.
| Constraint | Why |
|---|---|
| Video is never re-encoded on a destination path | It is the whole performance story. -c:v copy is what makes a dozen destinations cost almost nothing. |
| One static binary, no cgo | CGO_ENABLED=0 everywhere. It is why the thing runs on a NAS, a Pi, and a cloud box from the same release. |
FFmpeg via os/exec — no bindings, no libav linkage |
The command line is a stable, inspectable, debuggable interface. Bindings are none of those. |
SQLite through modernc.org/sqlite |
Pure Go. mattn/go-sqlite3 would reintroduce cgo. |
The UI ships embedded via go:embed |
One artefact. No separate web server, no asset CDN, no "did you build the frontend?" |
If you think one of these is wrong, that is a conversation worth having in an issue — but have it before writing the code.
git clone https://github.com/rainmanjam/polyemesis
cd polyemesis
make build # builds the UI, embeds it, produces ./polyemesis
./polyemesisYou need Go 1.27.0+, Node 20.19+ or 22.12+ (Vite 8's floor; CI builds on 24), and FFmpeg 6.0+ — 8.x recommended. See docs/INSTALL.md for platform detail and docs/DEPENDENCIES.md for what is pinned and why.
For UI work you want two terminals: make dev runs the backend only, and
make ui-dev runs Vite against it on :8080 for hot reload. Running make build per change works and is much slower.
make check # gofmt, vet, go test, tsc, oxlint, vitest, UI build — no Docker
make check-full # the above plus the Playwright suite (needs Docker, ~4 min)
go test ./... -race # the race detector finds real bugs here
./scripts/acceptance.sh # end-to-end against a real binary and real FFmpegmake check is the one to run before pushing. It deliberately needs nothing but
Go and Node — the moment the pre-commit gate needs a daemon running, it becomes
the gate you skip. make check-browser is the Playwright suite on its own, and
make check-full is both; run one of those when you have touched the UI, because
that suite has caught defects that everything in check passed.
If you work in git worktrees, make check prints the worktree, branch and
whether the tree is dirty before it does anything, and make check BRANCH=my-branch
refuses to run unless that is the branch you are actually on. A green run on
the wrong branch looks exactly like a green run on the right one.
docs/TESTING.md lists every suite and what it covers. docs/TEST-STRATEGY.md explains what is deliberately not covered and why.
This project cares more than most about tests that measure rather than
assert. Some of that is the domain — you cannot tell whether audio routing works
by checking that a function returned nil — and some of it is hard-won.
- Measure the effect, not the call. The audio suites route a distinct tone
into each track and then measure the RMS of each output through a bandpass
filter. That catches a mis-wired mix; asserting that
Compile()returned no error does not. - Include the positive case. A confinement test that only tries traversals passes just as happily when the feature is broken and refuses everything.
- Prefer a fixed-value guard on the count. Several suites here have checks
behind
if the file existsbranches. One suite once reported "7 passed, 0 failed — PASSED" having silently skipped five checks. Suites now assert how many checks ran, not just that none failed. - Comment the failure, not the code.
// the destination restarted, which drops the platform connectionis worth ten lines describing what the function does.
Run make fmt and make check before pushing — CI gates on gofmt -l being
empty, so an unformatted file fails the build rather than being tidied for you.
Beyond that:
Comments explain why, and especially why not. The codebase is full of
comments recording the approach that was tried and rejected, with the
measurement that settled it — for example, why the video delay uses the setts
bitstream filter rather than -itsoffset (measured: -itsoffset moved audio and
video in lockstep and delivered 0 ms for every requested value). Those comments
are the most valuable thing in the repo. If you discover something surprising,
leave the note.
Name things after what they are for. refuseIfSilent beats checkAudio.
Errors are for the operator. An error string ends up in a toast in front of
somebody whose stream is broken. "source disabled" with nothing on screen
explaining it was a real bug here. Say what happened and what to do.
Write commit messages that explain the problem, not just the change. If you fixed something subtle, the message is where the next person learns what was subtle about it.
A pull request should say:
- what problem it solves,
- how you know it works (which suite, what you measured),
- anything you deliberately did not do.
On your pull request CI runs: gofmt gating, build, vet and the unit tests
with -race; the same build and tests plus a measured broadcast on macOS and
Windows; the UI typecheck, lint and build; and thirteen host acceptance suites.
Not on your pull request: the three container suites and the browser E2E.
Each builds the image and publishes real streams into it, so they run on main
and on a schedule instead. That means a change can pass everything visible on
your PR and still break a suite after merge — if you have touched ingest,
one-port token routing, or anything the UI drives end to end, say so in the
description so it gets watched.
If a test is flaky, say so rather than re-running until it passes. A flaky test is a bug report.
Use the issue templates. The most useful bug report for this project includes:
- the output of
polyemesis -versionandffmpeg -version, - your ingest mode and roughly what your destinations look like,
- the relevant part of the server log (
-log debugif you can reproduce it), - for anything about audio or timing: what you measured, not just what it sounded like.
Security issues go to SECURITY.md instead — please do not open a public issue for those.
By contributing you agree that your contribution is licensed under the MIT Licence, the same as the rest of the project.