Janus Bench
Janus Bench is a test console for the server: a browser app, with no backend of its own, that connects to a running janus over its HTTP and WebSocket APIs and exercises every ported part with real WebRTC media. One person can click through it. A script can run the same checks headless and get pass or fail.
It lives in webapp/ (Vite, Preact and TypeScript, outside the Cargo workspace). A copy built from main is published with these docs: open the hosted Bench. It can only reach a server your browser can reach, so for a server on your machine run it locally, which is also the only way to be sure which version of the app you are using.
Run it
cd webapp
npm install
# 1. a server to test: builds janus with every plugin, writes a dev config, starts it
npm run janus # prints the URLs, Ctrl-C stops and cleans up
# 2. the app
npm run dev # open the address Vite printsTo use your own server instead, skip step 1 and type its URLs into the Threshold page. It needs cors.allow_origin set in janus.transport.http.jcfg (the dev config does) because the app runs on another origin.
Camera and microphone
Pick device under Media source on the Threshold page to send your camera and microphone. Browsers allow that on localhost or HTTPS only. The default synthetic source (a 440 Hz tone and an animated canvas) needs no permission and works headless, which is what the checks use.
The pages
| Page | What you do there |
|---|---|
| Threshold | Set the server URLs, read info, see which plugins and transports are up. |
| Run all checks | One button runs every check and shows pass, fail or skip, with the failing step. Each check also runs alone. |
| Raw console | Send any Janus API message, with templates, and fill-in, and a history. HTTP or WebSocket. |
| Admin API | Sessions and handles, handle_info, the log level, and a template for every admin request. |
| EchoTest, VideoRoom, VideoCall, Streaming, RecordPlay, TextRoom, AudioBridge, SIP, NoSIP | One guided panel per plugin with local and remote video, packet counters, and the actions that plugin has. |
Every page shares the transcript on the right. It draws what the client sent on the left in ochre and what the server answered or pushed on the right in verdigris, with the round-trip time, and filters by transport. Click a message to expand it.
Checks
A check is a short script that drives a feature the way a client would and fails on the first thing that is wrong. The categories:
| Group | Proves |
|---|---|
| Core | info over HTTP and WebSockets, every plugin attaches, session lifecycle on both transports, error codes, the HTTP long poll. |
| Admin API | The secret is enforced, list_sessions / list_handles / handle_info see a live session, runtime settings change and echo back, the query requests answer. |
| EchoTest | Offer, answer, trickle, ICE, DTLS, SRTP: media comes back and decodes; data channel echo; recording. |
| VideoRoom | Room management; publish, subscribe and see the media; data channel in a room. |
| VideoCall | Register, call, accept, media both ways, hangup. |
| Streaming | Mountpoints; watch one, and with an RTP feeder running, receive packets. |
| RecordPlay | Record, list the recording, play it back. |
| TextRoom | Setup, data channel, join, announcements, public message, whisper. |
| AudioBridge | Join the mix, hear it, list, mute, leave. |
| SIP, NoSIP | Guest registration and an outgoing call when a peer is configured; plain-RTP SDP generation and processing. |
A check that needs a plugin the server does not list is skipped, not failed. The current results, with every failure explained, are on the Bench results page.
Headless: the same checks in CI
cd webapp
npm run e2e # builds janus, serves the app build, drives Chrome
npm run e2e -- --only echotest,videoroom # id prefixes
npm run e2e -- --transport ws # plugin checks over WebSockets
npm run e2e -- --headed # watch it
npm run e2e -- --mdns # leave Chrome's mDNS privacy on (fails today, see below)
JANUS_BIN=/path/to/janus npm run e2e -- --no-buildscripts/e2e.mjs starts the binary with a generated config in a scratch folder on free ports, starts a UDP feeder for the Streaming mountpoint, builds and serves the app, launches your installed Chrome with Playwright (playwright-core, no browser download; set CHROME_PATH for another binary), calls window.__bench.run(), prints each result as it finishes, and writes webapp/e2e-results/report.json and report.md. The exit code is 1 when a check fails, unless --allow-fail.
Chrome runs with --autoplay-policy=no-user-gesture-required and --disable-features=WebRtcHideLocalIpsWithMdns. The second flag matters: Chrome normally hides the machine's address behind a random .local name, and the server's ICE agent does not pair those names yet, so a connection would stay stuck. With --mdns the runner leaves Chrome alone so you can watch that failure.
You can also call the hook from DevTools on any page of the app:
await window.__bench.run() // all checks, resolves with the report
await window.__bench.run(["videoroom"]) // ids starting with a prefix
window.__bench.frames() // every frame the app sent and receivedThe dev server
webapp/dev/conf/ holds the config the runner uses: HTTP and WebSockets with the Admin API on, rooms and mountpoints for each plugin, ice_enforce_list set to the machine's default-route address, and admin_secret = "janusoverlord". scripts/start-janus.mjs fills in the ports and a scratch folder. It is also a module (startJanus()) with extraConf and extraFiles options that the docs scripts use.
Not packaged as a desktop app
The Bench is a static site, so it already runs in any browser and in a Tauri or Electron shell without changes (vite.config.ts sets base: "./"). It is not wrapped in one: that is a build step nobody needs yet.