Huddle
One-page video calls — rooms, screen sharing and chat, with a PHP signalling server and a no-server demo mode.
Overview
Huddle is a one-page video-call web app: people open a link, type their name and are in a room together — camera, microphone, screen sharing and chat, with no installs or accounts. It uses WebRTC, so audio and video flow directly between browsers; a tiny PHP file (server/signal.php) only introduces the browsers to each other.
It ships with a demo mode that works on any static host with no server at all: a solo demo with simulated guests, and a two-tab call that makes a real peer-to-peer connection between two tabs of the same browser.
Quick start
1. Try it locally (no server)
Serve the folder with any static server and open app/index.html. Without a signalling server the lobby shows the two demo modes. Camera access needs https:// or http://localhost.
2. Real calls
- Upload
app/andserver/to a PHP host (PHP 7.4+, 8.x recommended), keeping them side by side. - Open
app/index.htmlover https. The lobby footer shows Server connected. - Press Join room, then Copy link and send it to the people you want to call.
3. Recommended for production
- Copy
server/config.sample.phptoserver/config.php. - Set up a TURN relay so calls also connect on strict networks — see TURN relay.
- Optionally set an
access_codeso only people who know it can join.
Features
- Lobby — live camera preview, mic level meter, camera / microphone / speaker pickers, name and room fields, random room names.
- Rooms by link —
index.html?room=team-standup; one click copies the invite link. - Multi-party calls — mesh WebRTC for up to 6 people (configurable), automatic grid that fits any screen.
- Camera, mic, screen share — toggles with clear states; screen share replaces your video without renegotiation and gets the big stage automatically.
- Chat — room chat with unread badge and toasts.
- Participant tiles — speaking ring from live audio levels, muted icon, avatar with initials when the camera is off or blocked, pin / spotlight (double-click a tile).
- People panel — who is in the call with mic and camera state.
- Devices dialog — switch camera, microphone or speaker in the middle of a call.
- Graceful permissions — blocked or missing camera/mic never stops anyone from joining; they appear as an avatar and can retry.
- Reconnects — ICE restart when a connection drops; automatic rejoin when the server session expires.
- Demo mode — solo demo (simulated guests + scripted chat) and two-tab call (real RTCPeerConnection over BroadcastChannel). Clearly labelled.
- Responsive and accessible — 390 px phones to wide screens, keyboard shortcuts (
Mmute,Vvideo), labels and focus styles, reduced-motion support. - Private by design — no accounts, no tracking, no external requests; the server stores only short-lived room files.
Demo mode
When signal.php does not answer (or mode: 'demo' is set), the lobby offers:
- Solo demo — your real camera and microphone plus three simulated guests drawn live on a canvas, with speaking animation and scripted chat replies. Every control works.
- Two-tab call — press it, then Open 2nd tab. Both tabs join the same room through
BroadcastChanneland connect with a realRTCPeerConnection(offer/answer, ICE, media). Remote audio is muted in this mode to avoid echo on one computer.
Demo rooms are labelled Demo mode in the header and tiles of simulated people say Simulated. Useful URL parameters: ?demo=1 forces the demo choices without contacting a server (use it for public showcase links), ?auto=solo starts the solo demo directly, ?name=Alex prefills the name.
File structure
huddle/
├─ app/ the web app (static files)
│ ├─ index.html lobby + call screen
│ ├─ config.js brand, colours, server URL, mode ← edit this
│ └─ assets/
│ ├─ huddle.js all app logic (no dependencies)
│ ├─ huddle.css all styles (CSS variables at the top)
│ └─ icon.svg logo / favicon
├─ server/ optional — needed for real calls between devices
│ ├─ signal.php signalling server (no database)
│ ├─ config.sample.php copy to config.php
│ ├─ .htaccess protects config and data (Apache)
│ ├─ nginx.conf.example the same for nginx
│ ├─ TURN-SETUP.md relay server guide
│ └─ turnserver.conf.example
├─ documentation/index.html this page
├─ README.md · CHANGELOG.md · LICENSE.txt · THIRD-PARTY.txtApp settings (config.js)
Edit app/config.js. It is public, so never put secrets in it.
| Key | Default | What it does |
|---|---|---|
brand | 'Huddle' | Name in the header, lobby and tab title. |
tagline | … | Line under the lobby headline. |
signalUrl | '../server/signal.php' | Where the signalling server is (relative or full URL). |
mode | 'auto' | auto = server if it answers, else demo · server = server only · demo = never contact a server. |
iceServers | [] | Fallback STUN/TURN list if the server sends none. |
maxPeers | 6 | Shows a hint when the room grows beyond this. |
accent, accent2 | #7c6cff, #2fd4c4 | Brand colours (buttons, gradients, focus). |
credit | true | Show the credit line in the lobby footer. |
Server settings (config.php)
Copy server/config.sample.php to server/config.php. Everything is optional.
| Key | Default | What it does |
|---|---|---|
data_dir | server/data | Writable folder for short-lived room files. Best outside the web root. |
max_peers | 6 | People per room; extra people get "This room is full". |
access_code | '' | If set, the lobby asks for this code before joining. |
allowed_origins | [] | Restrict which sites may use the server (and enable CORS for them). |
presence_timeout | 40 | Seconds without contact before someone is removed. |
stun | [] | STUN URLs, e.g. stun:stun.example.com:3478. |
turn_rest | off | TURN relay with expiring credentials (secret, host, ttl, tls). |
turn_static | [] | Fixed TURN accounts from any provider. |
rate | … | Per-IP limits for join, send and poll. |
How signalling works
The app POSTs JSON to signal.php?a=…: ping, join, poll (long polling, up to 25 s), send (batched, to one person or *) and leave. Each person gets a random id and a secret key (stored only as a hash). Messages are kept in small per-person queue files that are emptied on read; stale rooms are cleaned up automatically.
Hosting notes
- HTTPS is required for camera access (except on localhost).
- Long polling keeps one PHP request open per person — allow at least 10 PHP workers for a few simultaneous calls. With PHP's built-in test server set
PHP_CLI_SERVER_WORKERS=8. - Apache: the included
.htaccessblocksconfig.phpanddata/. nginx: seenginx.conf.example.
TURN relay
Most calls connect directly. Strict firewalls and some mobile networks need a TURN relay that forwards the media. Install coturn on a small Linux server and connect it with time-limited credentials — the full guide with commands is in server/TURN-SETUP.md, a ready configuration in server/turnserver.conf.example.
'turn_rest' => [
'secret' => 'SAME AS static-auth-secret IN turnserver.conf',
'host' => 'turn.example.com',
'ttl' => 86400,
'tls' => true,
],The secret never leaves your server; each caller receives a username/password pair that expires.
Customise & rebrand
- Name and colours —
brand,accent,accent2inconfig.js. - Logo — replace
app/assets/icon.svg(square, any colours). - Look — every colour, radius and font is a CSS variable at the top of
huddle.css. - Texts — all visible texts are in
index.htmland plainly written inhuddle.js, easy to translate. - Demo guests — names, colours and chat lines are in the
Simsection ofhuddle.js. - Embed — put the app on its own page or in an
<iframe allow="camera; microphone; display-capture; autoplay">.
Browser support
Current Chrome, Edge, Firefox, Safari (macOS and iOS 15+) and Android browsers. Screen sharing needs a desktop browser (the button hides itself where it is not supported). Speaker selection appears in browsers that support choosing an audio output.
FAQ
Do I need a database or Node.js?
No. The server is one PHP file that uses small temporary files.
How many people can join?
Up to 6 by default. Every person sends their video to every other person (mesh), so 4–6 is ideal; you can raise max_peers for audio-heavy calls.
Are calls private?
Media is encrypted end-to-end between browsers by WebRTC (DTLS-SRTP), also when it passes through a TURN relay. The server only sees signalling messages and chat text in transit and stores nothing permanently.
Why do some calls not connect?
Usually a strict network — add a TURN relay (see above).
Can I use it for clients?
Licence: one end product (yours or one client's). Full source: one production domain for you and your clients, including white-label and SaaS. Neither allows reselling the source itself.
Support
Questions or a bug? Email hello@vortaxis.com with your order number. Updates for your version are free. See LICENSE.txt for the licence terms and CHANGELOG.md for changes.