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

  1. Upload app/ and server/ to a PHP host (PHP 7.4+, 8.x recommended), keeping them side by side.
  2. Open app/index.html over https. The lobby footer shows Server connected.
  3. Press Join room, then Copy link and send it to the people you want to call.

3. Recommended for production

  • Copy server/config.sample.php to server/config.php.
  • Set up a TURN relay so calls also connect on strict networks — see TURN relay.
  • Optionally set an access_code so 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 (M mute, V video), 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 BroadcastChannel and connect with a real RTCPeerConnection (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.txt

App settings (config.js)

Edit app/config.js. It is public, so never put secrets in it.

KeyDefaultWhat 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.
maxPeers6Shows a hint when the room grows beyond this.
accent, accent2#7c6cff, #2fd4c4Brand colours (buttons, gradients, focus).
credittrueShow the credit line in the lobby footer.

Server settings (config.php)

Copy server/config.sample.php to server/config.php. Everything is optional.

KeyDefaultWhat it does
data_dirserver/dataWritable folder for short-lived room files. Best outside the web root.
max_peers6People 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_timeout40Seconds without contact before someone is removed.
stun[]STUN URLs, e.g. stun:stun.example.com:3478.
turn_restoffTURN 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 .htaccess blocks config.php and data/. nginx: see nginx.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, accent2 in config.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.html and plainly written in huddle.js, easy to translate.
  • Demo guests — names, colours and chat lines are in the Sim section of huddle.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.

Licence and production activation

Single-domain and Full-source purchases each activate one production hostname. Full source adds editable code, white-label and SaaS rights; it does not include unlimited installations or redistribution. Localhost and .test development hosts are allowed.

Set the delivered key and production hostname in LICENSING/licence.json. Use https://vortaxis.com/licence/ to view activation or request a domain transfer. Vortaxis Studio approves replacement domains. Production activation requires a network connection; public demos remain unrestricted.

See LICENSE.txt for the complete terms. Existing purchases retain their original terms.