A two-person call app. PHP handles the page and a tiny signaling relay; audio, video, screen share, and chat all travel directly between the two browsers via WebRTC — none of it passes through your server.
- Voice call, on by default the moment both people join.
- Camera, off by default — click the camera button to turn yours on. The other person sees it as soon as you do; turning it off removes your video instantly on their end too.
- Screen share — uses your browser's native screen/window/tab picker. While sharing, your screen shows large on the other person's screen, with your camera (if also on) as a small thumbnail. Clicking the browser's own "Stop sharing" bar works exactly like clicking the button in Wire.
- Chat — a slide-out panel sent over a WebRTC data channel, which is the same direct peer-to-peer pipe as the audio/video. Messages never touch your server, and there's no history — closing the tab clears it, by design.
- You open the page → it generates a random room code (or use one already in the URL) and shows you an invite link.
- You send that link to the other person. When they open it, both
browsers start polling
signal.phponce a second. - Once both are present, they automatically exchange a WebRTC
offer/answer and ICE candidates through
signal.php(this is just a few KB of text — "here's how to reach me"). This is called signaling, and it's the only thing that touches your server. - As soon as that handshake finishes, the browsers open a direct
peer-to-peer connection and audio (and a data channel for chat)
flow straight between them.
signal.phpgoes idle for the rest of the call — except briefly again any time someone turns their camera or screen share on/off, which needs a quick renegotiation round through the same relay before the new video starts flowing, peer-to-peer, same as everything else.
A pair of free public STUN servers (Google's) are used so each browser can discover its own public address — this is also just address discovery, not media relay.
- True peer-to-peer media is achieved for most networks, but a small fraction of users sit behind "symmetric NAT" routers (some corporate and mobile networks) where two browsers genuinely cannot find a direct path. In that case WebRTC needs a TURN relay server — a third party that forwards the encrypted media because no direct route exists. I did not include one because that means media does pass through a server (just not necessarily yours). If you hit calls that get stuck at "Connecting…", that's almost always this — see below.
- Signaling uses 1-second polling, so call setup (and turning on camera/ screen share for the first time) takes ~1-2 seconds longer than a websocket-based app would. Once connected, this has zero effect on call quality — media is real-time WebRTC, not polled.
- This is a two-person, one-room-at-a-time design: a third visitor to the same room link is told the room is full rather than joining.
- No persistence anywhere: room state lives in flat files in
storage/rooms/and is pruned automatically; chat has no history; nothing is logged or kept after the call. - Screen share captures video only (no system audio) — this matches most browsers' default screen-share audio support, which is patchy across OSes. Let me know if you'd like system-audio capture added for browsers that support it.
Needs PHP 7.4+ with write access to storage/rooms/.
# Quick local test:
cd wire/
php -S localhost:8000
# then open http://localhost:8000 in two different browsers/devicesFor real use between two people on different networks, this needs to be on a publicly reachable server (any shared host with PHP works) served over HTTPS — browsers refuse microphone/camera/screen-share access on plain HTTP for any host other than localhost.
your-domain.com/
├── index.php # generates/joins room, renders the UI
├── signal.php # the only server-side logic: message relay
├── style.css
├── app.js # WebRTC + polling logic (call, video, screen, chat)
└── storage/
└── rooms/ # must be writable by the web server user
If both people consistently get stuck at "Connecting…", it's almost
always restrictive NAT/firewalls on one or both sides, not a bug. The
fix is adding a TURN server to the iceServers list in app.js — free
options like Twilio's or Cloudflare's TURN have a generous free tier
for this. If you want, I can wire one in — it's a few lines — just know
that with a TURN relay involved, media for those specific stuck calls
would route through that third-party relay instead of staying fully
peer-to-peer.