diff --git a/docs/superpowers/specs/2026-08-01-video-call-design.md b/docs/superpowers/specs/2026-08-01-video-call-design.md new file mode 100644 index 0000000..8cab708 --- /dev/null +++ b/docs/superpowers/specs/2026-08-01-video-call-design.md @@ -0,0 +1,107 @@ +# Web Video Call — Design + +**Date:** 2026-08-01 +**Tool:** Network → Video Call (`/tools/video-call`) — NEW +**Type:** New tool (WebRTC media) +**Icon:** `Video` (lucide-react) +**Category:** Network + +Item #5 of the batch. Reuses the entire P2P infrastructure built for file transfer +(`SignalRoom` Durable Object, `signal.lib`, `signal-client`, `ice.lib`, manual serverless +mode, ack gate, reconnect logic). The difference: **media tracks instead of a data channel**, +and it's **symmetric** — both peers capture and display camera+mic. + +## Scope (from the scope decision) + +- **Basics:** mute mic, camera on/off, switch front/rear camera, hang up. +- **Screen sharing:** either participant can replace their outgoing camera with their screen + (`getDisplayMedia` + `RTCRtpSender.replaceTrack`, no renegotiation); both can share at once + (each sees the other's shared screen). Stopping restores the camera. +- **In-call text chat:** a small chat over a WebRTC **data channel** alongside the media. + +1:1 only (room capacity 2, already enforced by the DO). RTMP is not involved (browsers can't +publish RTMP). STUN-only by default; users can add their own TURN in Advanced settings. + +## Reuse vs new + +**Reused unchanged:** `signal.lib`, `signal-client.ts`, `ice.lib.ts`, `SignalRoom` DO, +the ack gate + Advanced settings UI pattern, Network category. + +**Generalized (additive, low-risk to file transfer):** +- `peer.ts` `createPeer` — add optional `localStream?: MediaStream` (adds its tracks) and + `onTrack?: (stream: MediaStream) => void` (wires `pc.ontrack`). Data-channel path + (`onChannel`) unchanged. Adding tracks triggers `onnegotiationneeded` → offer, same as the + data channel does. +- `manual.ts` `createManualConnection` — same optional `localStream` / `onTrack`. + +**New:** +- `src/tools/webrtc/chat.lib.ts` — pure chat message encode/decode (tested). +- `src/hooks/useVideoCall.ts` — orchestrates media capture + signaling + media peer + chat. + Signaling/reconnect logic mirrors `useFileTransfer` (kept separate to avoid destabilizing + the shipped file-transfer hook; a future refactor can extract a shared core). +- `src/islands/network/VideoCall.tsx` — the tool UI. +- Registry entry `video-call`. + +## Media capture (inside `useVideoCall`) + +- `getUserMedia({ video: { facingMode }, audio: true })` → local stream; show a self-view. +- **Mute mic / camera off:** toggle `track.enabled` on the local audio/video track (no + renegotiation). +- **Switch camera:** re-`getUserMedia` with the other `facingMode`; `replaceTrack` the video + sender with the new track; stop the old track. (Reuses the release-before-acquire lesson + from `useCamera`.) +- **Screen share:** `getDisplayMedia({ video: true })` → `replaceTrack` the video sender with + the screen track; on the screen track's `ended` (user clicks the browser "stop sharing"), + restore the camera track. Toggle button. +- **Hang up:** close the peer + signaling, stop all local tracks. + +## Peer / negotiation + +Both peers add their local tracks at peer creation, so the offer (initiator) and answer +(guest) each include their media (`sendrecv`). The initiator also creates the chat data +channel. `pc.ontrack` delivers the remote stream → attach to the remote `