Stream your phone camera to your laptop browser using WebRTC, with a tiny Socket.io signaling server.
server/: Node.js + Express + Socket.io signaling server (port 3001)nextjs-app/: Next.js 14 app (port 3000)- Laptop page:
/ - Mobile page:
/mobile/[roomId]
- Laptop page:
cd server
npm install
npm startHealth check: GET /health → { ok: true }
cd nextjs-app
npm install
npm run devIf your phone is on the same Wi‑Fi and you want it to reach your laptop’s signaling server, do not use localhost.
- Good:
http://192.168.1.50:3001(your laptop’s LAN IP) - Bad:
http://localhost:3001(points to the phone itself)
Copy .env.example to .env.local (or set env var another way):
NEXT_PUBLIC_SIGNAL_URL=http://192.168.1.50:3001Then restart next dev.
Browsers only allow camera access on secure pages (HTTPS or localhost). If you open the app on your phone via http://192.168.x.x:3000, you’ll see “Camera requires a secure page…”.
Options:
- Tunnel (local testing): Expose your laptop with an HTTPS URL and open that on your phone.
- ngrok:
ngrok http 3000(Next.js) and optionallyngrok http 3001(signaling server). Use thehttps://URLs; setNEXT_PUBLIC_SIGNAL_URLto the signaling server’s ngrok URL. - Cloudflare Tunnel: similar idea, free tier available.
- ngrok:
- Deploy: Deploy the Next.js app (e.g. Vercel) and the signaling server (e.g. Railway). Then the app and server are served over HTTPS and the camera will work on your phone.
There are two peers:
- Laptop opens
/- Creates a room via
GET /create-room - Shows a QR code to
/mobile/<roomId> - Joins the room as
join-laptop(roomId) - Receives the phone’s media via
ontrackand renders it in<video>
- Creates a room via
- Mobile opens
/mobile/<roomId>- Calls
getUserMedia(...)to capture camera + microphone - Joins the room as
join-mobile(roomId) - Adds tracks to an
RTCPeerConnection - Creates an offer and sends it through signaling
- Calls
Signaling messages relayed by the server (Socket.io):
offer→ mobile → laptopanswer→ laptop → mobileice-candidate↔ both directions
Control plane (Socket.io, laptop → mobile):
flip-camera→ mobile togglesfacingModeandreplaceTrack(...)change-quality→ mobile restartsgetUserMedia(...)with new constraints andreplaceTrack(...)
- Signaling server: deploy to Railway (or any Node host)
- Set
PORTif your platform requires it (server defaults to3001)
- Set
- Next.js app: deploy to Vercel
- In Vercel → Project → Settings → Environment Variables, add
NEXT_PUBLIC_SIGNAL_URL= your signal server’s HTTPS URL (e.g.https://your-app.railway.app). - Required: the app is served over HTTPS, so the signal URL must be https:// (browsers block mixed content: HTTPS page →
ws://).
- In Vercel → Project → Settings → Environment Variables, add
- This demo streams to the laptop browser (it does not create a true OS-level “webcam device”).
- WebRTC connectivity depends on network/NAT; the app uses Google STUN servers.