Spatial WebRTC voice for OpenSimulator — a Windows-native voice server so Firestorm and browser-based viewers can hear each other positionally, without Vivox, on any OpenSim grid.
Vivox has stopped taking new signups for its free OpenSim voice service and has
announced its end (OSGrid's notice).
Grids that already have Vivox set up can still get voice from it today, but nobody
outside Vivox knows for how long, so operators need a replacement they control before
it stops. The existing WebRTC voice backend for OpenSim
(wolfvoice) is built for Linux, with
Docker or WSL as the route for everyone else. Confluence-Voice gives Windows grid
operators another option: a single confluence-voice.exe that runs directly on Windows,
configured by one .toml file, with no Linux VM, container or WSL layer to install and
maintain.
It is not tied to any particular grid or OpenSim distribution. It works with any
OpenSimulator that has the os-webrtc-janus addon (upstream OpenSim and OpenSim-NGC
include it; older trees can add it). The name comes from the Confluence grid where it
was developed and first tested.
Free and self-hosted is the actual difference from the alternatives, not just a preference. wolfvoice is the same idea but needs Linux, Docker or WSL. ThinkVox is a hosted service: free for 10 concurrent users, then priced per tier ($19/mo for 50, $49/mo for 200, $149/mo for 1,000, per their site). Vivox needs no server of your own either, but it's the thing running out — see above. Confluence-Voice costs only your own hardware and bandwidth, with no account, no per-user pricing and no external service in the voice path, at the cost of running and maintaining it yourself.
Best on a grid where every region speaks WebRTC. That's the configuration this
project tests against, and where the checklist in docs/TESTING.md is
being filled in. The Casperia grid Confluence-Voice was developed on migrated all 15 of
its regions off Vivox to WebRTC-only in a single pass (2026-09-27), replacing it
entirely rather than running both side by side — each region's config kept a
.pre-confluencevoice.bak rollback copy from that migration. A grid that mixes WebRTC
with Vivox or ThinkVox regions works too, but hits a real Firestorm quirk — see "Known
limitations" below — that a single-voice-system grid never encounters.
Confluence-Voice is an independent project, derived from
wolfvoice by Wolf Software Systems
Ltd, which is used as a donor of code and ideas — this is not a GitHub fork and
shares no git history with it. See NOTICE for full attribution. The
protocol handling, spatial mixer and session logic are carried over unchanged; what's
different is how it's configured and shipped: a plain .toml file next to the .exe
instead of environment variables and /etc paths, so it runs like any other Windows
program — download, edit one file, double-click.
It is a voice service backend for os-webrtc-janus (original by Robert Adams; Wolf Software Systems maintains a fork at intelligentwolf/os-webrtc-janus). That addon does the OpenSimulator half — capabilities, provider advertisement, session bookkeeping — and Confluence-Voice is what you point it at instead of a Janus gateway.
Firestorm 7.1.10+ / browser viewer
│ ProvisionVoiceAccountRequest + VoiceSignalingRequest (LLSD caps)
▼
OpenSim region + os-webrtc-janus addon ← REQUIRED
│ forwards the caps onward as JSON-RPC
▼
Confluence-Voice ── answers the SDP, terminates DTLS/SRTP/SCTP,
│ mixes a SEPARATE stream per listener
▼
viewers (UDP media)
os-webrtc-janus is a hard requirement. It supplies WebRtcVoice.dll and the
region-side capability handlers that everything here depends on. See
docs/OPENSIM.md for building it — that side is unaffected by
which backend (wolfvoice or Confluence-Voice) you point it at.
For the voice server (this program)
- Windows, with a public IP address and a DNS name pointing at it. TLS is not optional: the SDP carries the server's DTLS fingerprint, so anyone able to rewrite plaintext signalling in flight could become the media endpoint.
- UDP 40000–40999 open in Windows Firewall (range is configurable — see below). This range is also the ceiling on concurrent sessions.
- TCP 9443 reachable from your region hosts only. This endpoint has no authentication of its own.
For viewers — same as upstream: Firestorm 7.1.10+ works with no configuration;
browser viewers need a client speaking the same contract
(client/voice_llwebrtc.js is a working reference).
Build it yourself (see below), or use a release build if one's been provided to
you. Put confluence-voice.exe in its own folder — it will create its config file
and expects its TLS certificate next to itself.
Run it once:
.\confluence-voice.exeThe first run creates confluence-voice.toml beside the exe and exits, telling you
where. Open it and set at least public_ip:
public_ip = "203.0.113.10" # REQUIRED — see the comment in the generated file
rpc_bind = "0.0.0.0:9443"
media_port_lo = 40000
media_port_hi = 40999
max_sessions = 900
tls_cert = "tls\\fullchain.pem"
tls_key = "tls\\privkey.pem"Place your certificate and key at the tls_cert / tls_key paths (relative paths
resolve against the folder the config file is in). Run .\confluence-voice.exe
again.
New-NetFirewallRule -DisplayName "Confluence-Voice RPC" -Direction Inbound -Protocol TCP -LocalPort 9443 -Action Allow
New-NetFirewallRule -DisplayName "Confluence-Voice Media" -Direction Inbound -Protocol UDP -LocalPort 40000-40999 -Action AllowScope the RPC rule to your region hosts' IPs if your firewall profile allows it — that port has no authentication of its own.
Same as upstream from here — see contrib/confluence-voice.ini
and docs/OPENSIM.md. Both estate and parcel voice flags
must be enabled or nothing will happen and nothing will log — see
docs/OPENSIM.md for details.
Firestorm 7.2.4+ needs one more thing: the region must advertise
SimulatorFeatures["stun-servers"], or the viewer refuses to start voice and the mic
stays greyed out. Set StunServers in the ini (see the template) and use an
os-webrtc-janus build that sends the stun-servers key. See
docs/TESTING.md.
curl.exe -k https://voice.example.org:9443/
# {"rooms":0,"service":"confluence-voice","sessions":0}Confluence-Voice is a console program, not a Windows Service. Started from a terminal or another program, it stops when that closes. A Task Scheduler task starts it on its own, at your Windows logon. Run this once in PowerShell, with your install folder:
$dir = "C:\Confluence-Voice"
$action = New-ScheduledTaskAction -Execute "conhost.exe" -Argument "--headless cmd.exe /c confluence-voice.exe 2>> err.log" -WorkingDirectory $dir
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -MultipleInstances IgnoreNew
Register-ScheduledTask -TaskName "Confluence-Voice" -Action $action -Trigger $trigger -Settings $settings
Start-ScheduledTask -TaskName "Confluence-Voice"conhost.exe --headlessruns it with no window. With a visible console window, closing that window stops the server (the log ends in^C), and voice goes down without anyone noticing.- The log goes to
err.login the install folder.cmd.exedoes the redirect because Windows PowerShell 5's2>>writes UTF-16, which turns the log unreadable. ExecutionTimeLimitzero removes Task Scheduler's default three-day limit, which would otherwise stop voice after 72 hours.- It starts at logon, not at boot. On a machine that reboots unattended, set Windows
to sign in automatically, or change the trigger to
-AtStartupand run the task as a service account. - Start it with
Start-ScheduledTask -TaskName "Confluence-Voice". Stop it with the admin page's Stop button.Stop-ScheduledTaskis not enough: it ends only the hiddenconhostwrapper, and Confluence-Voice keeps running.
Needs a Rust toolchain and cmake (the opus crate compiles libopus from source).
$env:CMAKE_POLICY_VERSION_MINIMUM = "3.5"
cargo build --release
cargo test # unit tests, no network requiredThe CMAKE_POLICY_VERSION_MINIMUM variable works around a version mismatch
between the opus crate's vendored libopus (which declares an old
cmake_minimum_required) and CMake 4+, which refuses that outright. This is
CMake's own documented escape hatch, not a fragile hack. Without it the build stops
with "Compatibility with CMake < 3.5 has been removed from CMake". Use a recent CMake
(4.x) so it also recognises current Visual Studio versions.
The built binary is at target\release\confluence-voice.exe. Copy it, together with
your confluence-voice.toml and tls\ folder, wherever you want to run it from.
Working end to end with real viewers (Firestorm 7.2.5) through OpenSim regions: the peer connection connects, the data channel opens, and the speaking indicator shows — including confirmed between two real, separately-located participants (2026-09-28, New Zealand and Indiana, USA) with both speaking dots appearing. Not yet confirmed: actually hearing each other, spatial panning, distance falloff, and per-person mute/volume with two real people — the dots being correct is necessary but not sufficient for those. Details, problems found and fixes: docs/TESTING.md.
Next, in order:
- Multi-person testing. Spatial voice with two or more real participants (hearing each other, panning, distance, other people's speaking dots, per-person volume and mute) is confirmed. Still to test: group voice and person-to-person calls. Progress is tracked in the checklist in docs/TESTING.md; a release should only claim what is ticked there.
- A shared secret for the voice port. TCP 9443 now supports an IP allow-list
(
allowed_region_ipsinconfluence-voice.toml, checked before the TLS handshake) — done. A shared-secret scheme on top of that is not: the region-side connector (os-webrtc-janus'sWebRtcVoiceServiceConnector.cs) has no way to send one today, so this needs a matching region-side change first, not just a Confluence-Voice one. TURN relay supportDone.turn_urls/turn_username/turn_credentialinconfluence-voice.tomlare passed to viewers as a relay of last resort. Verified that a configured TURN server doesn't break normal connections; not yet verified against a real TURN server actually relaying media for a viewer that needs one.
After that:
- Easier setup for operators. Get the
stun-serversfix into upstreamos-webrtc-janus(Firestorm 7.2.4+ needs it), a Windows guide to getting a free TLS certificate, a firewall setup script (PowerShell commands exist in this README; not yet a standalone script), automatic public-IP detection. A dedicated Vivox-to-WebRTC migration guide is still unwritten, but real experience from migrating Casperia's 15 regions in one pass is documented in docs/TESTING.md, including what broke and how it was diagnosed. - Reliability. Done: a status endpoint (
GET /) reporting version, uptime, session counts, cumulative skipped-tick count, and whether auth/TURN are configured; config validation that rejects bad IP entries and incomplete TURN setup at startup with a clear message; and a local admin dashboard (admin_bindinconfluence-voice.toml, loopback-only by default, auto-refreshing) with live status, sessions grouped by room with a per-session Kick button, a recent warnings/errors log panel, an unexpected-restart flag (uptime reset without the admin page's own Restart button having caused it), and Stop/Restart/Reload-config controls. Still open: a TLS certificate expiry warning on the dashboard (so a lapsed cert doesn't silently take voice down), log to a file with rotation, certificate reload without a restart. Starting it with Task Scheduler is documented in Quick start, step 6. - Distribution and trust. Done: automated Windows builds and tests on every push (ci.yml), and a release workflow (release.yml) that builds, packages and publishes a GitHub Release from a version tag. Still open: code signing so SmartScreen does not warn (needs a certificate), and a winget or Scoop package.
- Moderator mute. Estate and group moderators muting another person, a feature
Vivox had. Scoped 2026-10-01, bigger than it looks: the roster's
"m"field is outbound-only (server → viewer, display) — nothing inViewerDataMessage(everything the data channel actually carries) identifies a sender as a moderator or names who to mute. This needs a genuinely new JSON-RPC method and a decision about where the request originates (region-side moderation UI? group/estate permissions checked by whom?) before any server-side code, not just flipping the always-falseflag.
On hold: a Windows Service wrapper (start automatically, no console window). For
reference when this resumes: wolfvoice's contrib/wolfvoice.service restarts on crash
(Restart=always, 2s delay), bounds memory to 2G "to bound a runaway rather than size
normal use," and sandboxes the process heavily (ProtectSystem=strict and similar) —
worth carrying the same intent into whatever Windows equivalent gets built, not just the
"starts automatically" part.
# Correctness: two synthetic viewers, real SDP, real Opus, real mixing.
cargo run --release --example two_clients -- https://127.0.0.1:9443
# Capacity: N clients, M speaking, reports service CPU.
cargo run --release --example load_test -- https://127.0.0.1:9443 40 20Run these on the voice host. Point two_clients at a public_ip that is a real,
bindable address on the machine (a loopback address won't gather a matching ICE
candidate on the client side — that's an ICE property, not specific to this
service).
src/mixer.rs,src/proto.rs,src/room.rs,src/session.rs— the spatial mixing math, wire protocol and session/room bookkeeping. None of it is Unix-specific.docs/PROTOCOL.md,docs/CLIENT.md,docs/OPENSIM.md,client/voice_llwebrtc.js— protocol reference, viewer support notes and the region-side addon, none of which depend on which OS runs the voice backend.examples/two_clients.rs,examples/load_test.rs— the end-to-end and capacity test harnesses.
- Configuration:
confluence-voice.tomlnext to the exe, notWOLFVOICE_PUBLIC_IPand hardcoded/etc/wolfvoice/tls/...paths (seesrc/config.rs). - No installer, no systemd unit: this is meant to be run directly, the way any other Windows program is. A Windows Service wrapper (so it can start automatically and run without a console window) is not implemented and is on hold for now; run it from a console, or start it yourself with Task Scheduler.
- Session IDs are prefixed
cv-instead ofwv-; the health endpoint reports"service": "confluence-voice". Everything on the wire to Firestorm/OpenSim is unchanged — those prefixes are purely internal/log labels.
Mixed grids. On a grid that runs WebRTC regions next to Vivox or ThinkVox regions,
Firestorm's Vivox client can stop for the rest of the session after visiting a WebRTC
region, so voice fails in the Vivox regions until voice is toggled off and on in
Preferences → Sound & Media → Voice (no relog needed) — or, if the thing that actually
failed was a real attempt to reach Vivox's own servers rather than a rejection, not even
that works and a full relog is needed instead (confirmed live, 2026-09-27). This is a
Firestorm behaviour, not something Confluence-Voice can fix on its own. A region-side fix
exists — the WebRTC module hands Vivox-type requests to the Vivox module instead of
rejecting them, so the viewer's Vivox client gets real credentials and never gives up —
committed to Confluence-OpenSim
(not upstream os-webrtc-janus itself yet). The real fix, though, is not running a mixed
grid at all: a grid running WebRTC only, like Casperia now does on all 15 regions, never
hits this. Details in docs/TESTING.md.
- Voice does not reliably survive rapid region-border crossings. Standing near a
corner where several regions meet can put a viewer's voice into a loop — a real,
automatic border handoff (
process_crossed_region) tears down the peer connection and rebuilds a complete new one from scratch (fresh SDP, ICE, DTLS) every 5-15 seconds, so it connects successfully but gets torn down again before anything is heard. A single clean crossing works; the corner case has not been fixed. Whether this is worse than Vivox's own reconnection (likely a cheap SIP channel-switch, vs. WebRTC's full rebuild every time) is an open, unverified question — Vivox is no longer running anywhere on the reference grid to compare against. Details in docs/TESTING.md. - A failed provision request is not retried by the viewer. Firestorm makes one
attempt at
ProvisionVoiceAccountRequestand does not retry on its own if that single attempt hits a transient failure (confirmed live: a momentary connection refusal against an otherwise healthy, half-hour-stable Confluence-Voice instance was enough to permanently fail one viewer's voice while a second viewer connected normally seconds later). The region-side connector now retries a failed round trip up to 3 times with a short backoff before giving up, masking most transient hiccups from the viewer entirely — but this is a region-side (os-webrtc-janus) fix, not something Confluence-Voice itself can do, and it does not help if Confluence-Voice is down for longer than that. - TURN is supported but unverified against a real relay.
confluence-voice.tomlcan point viewers at a TURN server (see Roadmap above) — unlike upstream wolfvoice, which has no way to add one. Without one configured, the situation is the same as upstream: Firestorm hardcodes its own STUN servers tostun:stunN.<grid>.secondlife.io, which don't resolve outside Second Life, so media still connects in the common case (the server advertises a routable host candidate and learns the viewer's address from the connectivity check), but a viewer whose network blocks outbound UDP entirely gets no voice. - Single instance. If Confluence-Voice is down, voice is down everywhere it is configured.
- Group and IM voice (
channel_type: multiagent) is implemented and rooms are keyed correctly, but has had far less exercise than spatial voice.
Other ways to get voice on an OpenSim grid now that the built-in Vivox connector no longer works for most operators. Facts below are as of September 2026; check each project's own pages before relying on them.
- wolfvoice — the project Confluence-Voice is derived from. Same WebRTC design, self-hosted and free. Built for Linux (a Linux host with systemd); on Windows it means Docker or WSL. Choose it if your voice server is Linux.
- ThinkVox — a hosted, paid service (a free tier and
tiers priced by concurrent users) with a web dashboard and an API key; its signup
works much like Vivox's did. Firestorm 7.1.10+ uses WebRTC with nothing to install.
For older Firestorm it asks users to swap in a replacement
SLVoicebinary, and the download its onboarding offered was for Linux x64. Choose it if you would rather not run a voice server yourself. - Confluence-Voice — self-hosted and free, a single
.exeon Windows, with no per-user pricing and no third-party service in the voice path. Firestorm 7.1.10+ uses WebRTC natively, so viewers need no replacement files.
Built on wolfvoice by Wolf Software Systems Ltd for the Wolf Territories Grid, which is itself built on os-webrtc-janus by Robert Adams, which brought WebRTC voice to OpenSimulator. See NOTICE for the full chain.
The wire protocol is Linden Lab's, as implemented in Firestorm.
Apache License 2.0 — see LICENSE.