Fix screen-share audio feedback loop (echo cancellation)
BroFerence — WebRTC Video Conferencing
Live demo: https://broference.cam/
A self-hosted, multi-participant WebRTC video conferencing app. No accounts, no third-party media servers — just a Python signaling server, dual TURN relay, and a browser client.
Features
- Multi-participant video — Unlimited users per room (mesh topology, best for ≤10)
- Real-time text chat — In-app messaging with optional IRC bridge
- Password-protected rooms — PBKDF2-HMAC-SHA256 hashed, per-room
- AI Noise Suppression — Adjustable noise gate with real-time mic level visualization
- Microphone selector — Switch input device live, including NVIDIA Broadcast / RTX Voice
- Low Bandwidth Mode — Caps video to 480p/15fps and audio to 32kbps
- Video quality selector — 480p, 720p, or 1080p; persists across sessions
- Moderator controls — Kick, mute, rename, promote/demote co-moderators
- Moderator succession — Auto-transfers to next user by join order on mod disconnect
- End-to-end encryption — Optional AES-GCM-256 for audio and video (moderator-toggled)
- Speaking indicator — Glowing ring pulses with voice activity
- Connection quality indicator — Signal bars showing RTT and packet loss
- Per-user volume controls — Independent volume per participant
- Per-participant hide video — Disables inbound track decoding to save CPU/GPU
- DEFCON button — Kill all video feeds instantly
- Screen share with audio mixer — Independent mic and desktop audio sliders
- Hardware codec preference — H.264 → VP9 → AV1 → VP8
- Gravatar avatars — Set your email in Options; shared peer-to-peer automatically
- Nickname persistence — Display name saved and auto-filled on return
- Spotlight mode — Click any video to fullscreen it
- Theme selector — Matrix, Cyberpunk, Ocean, Sunset, Amber, Corporate
- Mobile optimized — Tap-to-unmute, auto noise suppression on mobile
- IRC bridge (on-demand) — Bridge rooms to IRC channels when needed
- Multi-domain SSL — Auto-discovers Let's Encrypt certs across domains
- Dual TURN relay — Two independent coturn servers, asymmetric ICE paths, no third-party TURN needed
- Echo cancellation, noise suppression, auto gain control — Built-in audio enhancements
Quick Start
Local Development
Windows:
start-local-dev.bat
Linux/Mac:
chmod +x start-local-dev.sh && ./start-local-dev.sh
Opens at http://localhost:8080
Production Deployment
Prerequisites: Docker, Docker Compose, a domain with DNS pointed at your server, SSL certificates (Let's Encrypt recommended).
1. Clone the repo
git clone https://github.com/s4turns/BroFerence.git
cd BroFerence
2. Get SSL certificates
apt install certbot
certbot certonly --standalone -d yourdomain.com
3. Deploy
bash update-vps.sh
update-vps.sh handles everything in one shot:
- Pulls latest code
- Generates a new random TURN credential
- Auto-detects your server's public IP
- Updates TURN config and client JS
- Rebuilds and restarts all Docker containers
- Syncs fail2ban config if installed
4. (First time) Set up fail2ban
sudo bash setup-fail2ban.sh
5. Open firewall ports
| Port | Protocol | Purpose |
|---|---|---|
| 22 | TCP | SSH |
| 443 | TCP | HTTPS |
| 8080 | TCP | HTTP (redirects to HTTPS) |
| 8765 | TCP | WebSocket signaling |
| 3479 | TCP+UDP | TURN |
| 49152–65535 | UDP | TURN relay |
Components
Signaling Server (server/)
Python + WebSockets. Handles room management, WebRTC offer/answer/ICE relay, password protection, IRC bridge, and SSL cert discovery.
TURN Servers (config/)
Two independent Coturn instances for asymmetric relay — covers same-NAT hairpin without a third-party provider. Credentials auto-rotate on every deploy.
Web Client (client/)
Vanilla JS + WebRTC API. No frameworks. Mesh peer connections, dynamic video grid, audio worklet noise gate, E2E encryption worker.
Usage
Joining a Room
- Open the app in your browser
- Enter your name (auto-filled from last visit)
- Enter a room name
- Optionally set a room password or IRC channel
- Click Continue → configure camera/mic → Join Room
Invite Links
https://yourdomain.com/?room=RoomName
https://yourdomain.com/?room=RoomName&name=YourName
Controls
| Control | Action |
|---|---|
| 🎤 | Mute/unmute mic |
| 📹 | Camera on/off |
| 🖥️ | Share screen (with optional audio) |
| 💬 | Toggle chat sidebar |
| ☰ | Options panel |
| Click video | Spotlight/fullscreen that participant |
| Hover video | Volume slider for that participant |
Options Panel
- Change name mid-call
- Gravatar email
- AI Noise Suppression + threshold
- Low Bandwidth Mode
- Video quality (480p / 720p / 1080p)
- Theme selector
- Copy invite link
- DEFCON (all video off/on)
- E2E Encryption (moderator only)
- Leave room
Connection Quality
Signal bars in the bottom-right of each video tile:
| Bars | Colour | Meaning |
|---|---|---|
| 4 | Green | Excellent (RTT < 100ms, loss < 1%) |
| 3 | Green | Good |
| 2 | Yellow | Fair |
| 1 | Red | Poor |
Hover the bars for exact RTT and packet loss numbers.
Configuration
TURN Server
TURN credentials are auto-rotated by update-vps.sh on every deploy. To manually set credentials, edit config/turnserver.production.conf:
user=webrtc:YOUR_STRONG_PASSWORD
realm=yourdomain.com
external-ip=YOUR_PUBLIC_IP
And update PRIMARY_TURN_CREDENTIAL in client/conference.js to match.
Second TURN Server
A second independent Coturn instance at a separate IP improves relay coverage. Configure its IP in the turn2Config block in client/conference.js:
const turn2Config = {
urls: ['turn:YOUR_SECOND_TURN_IP:3479', 'turn:YOUR_SECOND_TURN_IP:3479?transport=tcp'],
username: 'webrtc',
credential: 'YOUR_SECOND_CREDENTIAL'
};
SSL Certificates
The signaling server auto-discovers certs in priority order:
./ssl/— local/custom certs/etc/letsencrypt/live/— Let's Encrypt (all domains scanned)/etc/ssl/— system fallback
Supported filenames: fullchain.pem / cert.pem / certificate.pem and privkey.pem / key.pem / private.pem.
IRC Bridge
On-demand only — no IRC connection is made unless a user specifies a channel on room creation. To configure the IRC server, edit server/irc_bridge.py (default: no server configured).
Security
Layers
| Layer | What it does |
|---|---|
| Cloud firewall (Linode/etc.) | Drops traffic before it reaches the server |
| iptables | Host-level INPUT DROP policy, rate-limited SSH, DOCKER-USER WebSocket flood limiting |
| fail2ban | Bans IPs after repeated SSH / nginx / TURN auth failures |
| PBKDF2 room passwords | 260k iterations, random salt — not reversible |
| E2E encryption | Optional AES-GCM-256 for audio/video streams |
| TURN credential rotation | New random 32-char credential on every deploy |
fail2ban Jails
| Jail | Watches | Bans after |
|---|---|---|
sshd |
/var/log/auth.log |
5 failures / 10 min → 24h ban |
nginx-botsearch |
nginx access log | 10 hits / 1 min → 1h ban |
nginx-req-limit |
nginx error log | 10 hits / 1 min → 1h ban |
coturn-auth |
TURN log | 10 failures / 1 min → 1h ban |
iptables (INPUT chain)
loopback → ACCEPT
ESTABLISHED → ACCEPT
SSH :22 → ACCEPT (rate-limited: 4 new/min)
HTTPS :443 → ACCEPT
HTTP :8080 → ACCEPT
WS :8765 → ACCEPT
TURN :3479 TCP → ACCEPT
TURN :3479 UDP → ACCEPT
Relay :49152-65535 UDP → ACCEPT
everything else → DROP
DOCKER-USER: rate-limits new WebSocket connections to 20/min per IP.
Helper Scripts
| Script | Purpose |
|---|---|
update-vps.sh |
Full deploy: pull, rotate TURN creds, rebuild containers, sync fail2ban |
setup-fail2ban.sh |
Install and configure fail2ban (run as root, first time only) |
setup-turn-ip.sh |
Manually set TURN external-ip in config |
test-turn-server.sh |
Diagnose TURN server connectivity |
start-local-dev.sh / .bat |
Start local dev server |
All scripts are generic — no hardcoded hostnames or paths. They auto-detect from their environment or accept overrides via environment variables:
# setup-fail2ban.sh
REPO_DIR=/opt/BroFerence APP_USER=myuser sudo bash setup-fail2ban.sh
# setup-turn-ip.sh
./setup-turn-ip.sh yourdomain.com
# test-turn-server.sh
HOSTNAME=yourdomain.com TURN_PORT=3479 bash test-turn-server.sh
Project Structure
BroFerence/
├── client/
│ ├── app.html # Main conference UI
│ ├── conference.js # WebRTC logic, ICE, media, UI
│ ├── e2ee-worker.js # AES-GCM-256 E2E encryption worker
│ ├── noise-processor.js # Audio worklet noise gate
│ ├── styles.css # Retro terminal themes
│ └── admin.html # Admin panel
├── server/
│ ├── signaling_server_v2.py # Production WSS server
│ ├── signaling_server_local.py # Local WS server (no SSL)
│ └── irc_bridge.py # IRC bridge integration
├── config/
│ ├── turnserver.production.conf # Production TURN config (auto-updated by deploy)
│ └── turnserver.conf # Dev/local TURN config
├── fail2ban/
│ ├── jail.local # fail2ban jail definitions
│ └── filter.d/
│ ├── coturn-auth.conf # TURN auth failure filter
│ └── nginx-req-limit.conf # Nginx rate limit filter
├── ssl/ # SSL certificates (gitignored)
├── logs/ # Container log mounts for fail2ban (gitignored)
├── docker-compose.yml
├── Dockerfile.web
├── update-vps.sh # Deploy script
├── setup-fail2ban.sh # fail2ban setup (run as root)
├── setup-turn-ip.sh # TURN IP config helper
└── test-turn-server.sh # TURN diagnostic
Troubleshooting
WebSocket won't connect
docker compose logs signaling
docker compose ps
Video slow to connect or not connecting
- Run
./test-turn-server.shto verify TURN is reachable - Check
external-ipin TURN config matches your server's actual public IP - Verify relay ports 49152–65535 UDP are open in your firewall
TURN relay shows wrong IP
# update-vps.sh auto-fixes this on deploy, or manually:
./setup-turn-ip.sh yourdomain.com
grep external-ip config/turnserver.production.conf
fail2ban not starting
# Ensure log dirs exist (setup-fail2ban.sh creates them, or manually):
sudo mkdir -p /path/to/BroFerence/logs/nginx /path/to/BroFerence/logs/coturn
sudo systemctl restart fail2ban
sudo fail2ban-client status
Browser cache issues
Ctrl+Shift+N # Incognito/private window
F12 → Network → Disable cache
Full container rebuild
docker compose down -v
docker compose build --no-cache --pull
docker compose up -d
Browser Compatibility
| Browser | Support |
|---|---|
| Chrome / Edge 90+ | ✅ |
| Firefox 88+ | ✅ |
| Safari 14.1+ | ✅ |
| Opera 76+ | ✅ |
| Internet Explorer | ❌ |
Requires: WebRTC, WebSocket, getUserMedia, getDisplayMedia, AudioWorklet.
Development
# Python linting
cd server && pip install flake8
flake8 *.py --max-line-length=120
# JS linting
cd client && npx eslint *.js
Dependencies (Python):
websockets>=12.0cryptography>=41.0.0
pip install -r server/requirements.txt
Recent Updates
v1.8 (2026-05)
- New domain — Migrated to broference.cam
- TURN realm — Updated to broference.cam across all configs
- fail2ban — SSH, nginx, and TURN jails with log volume mounts from containers
- iptables — INPUT DROP policy, rate-limited SSH, DOCKER-USER WebSocket flood protection
- Generic scripts — All setup scripts now auto-detect hostname/paths, no hardcoded values
v1.7 (2026-04-26)
- Gravatar support — Hashed client-side, broadcast peer-to-peer
- Nickname persistence — Saved to localStorage, auto-filled on return
- Video quality selector — 480p / 720p / 1080p, persists across sessions
- Options menu consolidation — Invite, DEFCON, bug report moved into options panel
- Room name in tab title
- Dual coturn relay — Replaced Metered.ca dependency with second self-hosted coturn
v1.6 (2026-04-01)
- Per-participant hide video — Disables inbound track decoding to save CPU/GPU
- DEFCON button — Kill all video feeds at once
- Screen share audio mixer — Independent mic/desktop audio sliders
- Hardware codec preference — H.264 → VP9 → AV1 → VP8
- Corporate theme
- AI Noise Suppression off by default
- PBKDF2 password hashing — PBKDF2-HMAC-SHA256, 260k iterations
- XSS hardening — All user strings sanitized before DOM insertion
v1.5 (2026-03-25)
- Low Bandwidth Mode — 480p/15fps, capped bitrates
- Moderator succession — Auto-transfers on mod disconnect
- iOS/Safari fix — Zero relay candidate fallback to P2P
- WebSocket reconnect — Preserves already-connected peers
v1.4 (2026-03-25)
- ICE restart on disconnect — Triggers after 6s, fixes silent dead connections
- WebSocket auto-reconnect — Exponential backoff (2s → 30s)
- Cache-busting — Git commit hash stamped on asset URLs
v1.3 (2026-02-18)
- Microphone device selector — Switch live, supports NVIDIA Broadcast / RTX Voice
- Fixed scratchy audio from
Math.expin audio worklet hot path - Fixed outgoing audio distortion and mobile glitchy audio
v1.2 (2026-02)
- AI Noise Suppression — Adjustable noise gate with mic level viz
- Per-user volume controls
- Theme selector — 5 themes
- Screen share with audio
- E2E encryption — AES-GCM-256 (moderator-controlled)
v1.1 (2026-02)
- Multi-domain SSL certificate auto-discovery
- On-demand IRC bridge
- Dynamic hostname detection in update script
v1.0 (2026-01)
- Initial release — multi-participant video, TURN server, IRC bridge, Matrix UI