Intro to WebRTC Hooks
A complete, composable WebRTC stack for React.
Fiber UI provides a production-grade suite of React hooks for building real-time communication apps. These hooks abstract the complexity of the WebRTC API while giving you full control over the connection lifecycle.
The WebRTC Architecture
Our stack separates concerns into three layers: Capture, Connection, and Data/Control. This composable approach makes it easier to build complex apps like video conferencing, file sharing, or screen sharing tools.
+---------------------------------------------------------------------+
| Your React App |
+-------------------+----------------------------+--------------------+
| Media Capture | Core Connection | Controls |
+-------------------+----------------------------+--------------------+
| | | |
| [useUserMedia] --+--> [usePeerConnection] <---+-- [useDataChannel] |
| ^ | ^ ^ | |
| | | | | | |
| [useMediaDevices] | | +-----------+-- [useTrackToggle] |
| | | | |
| [useScreenShare] -+ v | |
| | [Signaling] | [useAudioLevel] |
| | (Socket/HTTP) | |
+-------------------+----------------------------+--------------------+Available Hooks
| Hook | Purpose | MDN Reference |
|---|---|---|
| usePeerConnection | Managing RTCPeerConnection, SDP negotiation, and ICE candidates. | RTCPeerConnection |
| useUserMedia | Capturing camera/mic streams with getUserMedia. | MediaDevices.getUserMedia |
| useScreenShare | Capturing screen content with getDisplayMedia. | MediaDevices.getDisplayMedia |
| useDataChannel | Low-latency P2P data transfer (text, binary, JSON). | RTCDataChannel |
| useTrackToggle | Muting and unmuting specific media tracks. | MediaStreamTrack.enabled |
| useAudioLevel | Audio visualization and speaking detection. | Web Audio API |
| useMediaDevices | Listing and selecting input/output devices. | MediaDevices.enumerateDevices |
Hook Flow Map
This diagram illustrates how data flows between the hooks and what role each one plays in the lifecycle of a connection.
[ User Input ]
|
+---------------------------------------v--------------------------------------+
| Device Selection Layer |
| |
| [useMediaDevices] <----(Enumerates)----(Microphones / Cameras / Speakers) |
| | |
+---------+--------------------------------------------------------------------+
| (Select Device ID)
v
+---------+--------------------------+ +--------------------------------------+
| Media Capture Layer | | Screen Share Layer |
| | | |
| [useUserMedia] | | [useScreenShare] |
| | | | | |
| +----(Analyzes)------------+--+---------+-----> [useAudioLevel] |
| | | | | (Visualizer) |
| +----(Controls)------------+--+---------+-----> [useTrackToggle] |
| | | | | (Mute/Unmute) |
+---------+--------------------------+ +---------+----------------------------+
| |
| (MediaStream) | (MediaStream)
v v
+------------------------------------------------------------------------------+
| Connection Layer |
| |
| [usePeerConnection] |
| ^ |
| | |
| (Data) |
| | |
| v |
| [useDataChannel] |
| (Chat / File Transfer) |
+------------------------------------------------------------------------------+
^
| (SDP / ICE)
v
[Signaling Server]Core Concepts
1. The Signaling Process
WebRTC connections cannot be established without a "signaling service" to exchange connection information. Fiber UI hooks are agnostic to your signaling method. You can use WebSockets (Socket.io), Firebase, HTTP polling, or even manual copy-paste.
The signaling flow involves exchanging two types of data:
- Session Description Protocol (SDP): Describes the media capabilities (codecs, formats).
- ICE Candidates: Network paths (IP:Port) to reach the peer.
The Offer/Answer Dance
Peer A (Caller) Peer B (Callee)
| |
1. Create Offer |
| |
+---[ Send Offer SDP (via Signal) ]-->
| |
| 2. Set Remote Desc
| |
| 3. Create Answer
| |
4. Set Remote Desc <---[ Send Answer SDP ]---+
| |
| |
5. ICE Candidates <----[ Exchange ICE ]-----> ICE Candidates
| |
v v
[ P2P Connection Established (Media & Data Flowing) ]Read more: Signaling and Video Calling (MDN)
2. ICE Candidates (Connectivity)
ICE (Interactive Connectivity Establishment) is how WebRTC traverses NATs and Firewalls.
- Components: The protocol gathers candidates (host IP, server reflex IP, relay IP).
- Trickle ICE: The modern standard where candidates are sent as soon as they are discovered, rather than waiting for all of them.
usePeerConnectionsupports Trickle ICE out of the box.
Read more: WebRTC Connectivity (WebRTC.org)
3. Tracks vs. Streams
Understanding the difference is crucial for effective media control:
- MediaStreamTrack: A single media source (e.g., audio track from mic, video track from camera).
- MediaStream: A synchronization container for multiple tracks.
Use Case: useTrackToggle operates on tracks to mute/unmute them without stopping the entire stream.
Implementation Guide
Step 1: Capture Media
Use useUserMedia to get the local stream. This handles browser permissions and device selection.
import { useUserMedia } from "@repo/hooks/webrtc/use-user-media";
const { stream, start, error } = useUserMedia({
constraints: { audio: true, video: true },
});
// Start camera on mount
useEffect(() => {
start();
}, []);Step 2: Initialize Connection
Pass the stream to usePeerConnection. This hook manages the complex RTCPeerConnection state machine.
import { usePeerConnection } from "@repo/hooks/webrtc/use-peer-connection";
const {
peerConnection,
createOffer,
createAnswer,
setRemoteDescription,
addIceCandidate,
connectionState,
} = usePeerConnection({
// Automatic ICE handling
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
// Handle incoming remote stream
onTrack: (event) => {
remoteVideoRef.current.srcObject = event.streams[0];
},
// Send local candidates to your signaling server
onIceCandidate: (candidate) => {
if (candidate) signalingServer.send("ice-candidate", candidate);
},
});Step 3: Add Media to Connection
Once you have both the stream and the peerConnection, add the tracks.
useEffect(() => {
if (stream && peerConnection) {
stream.getTracks().forEach((track) => {
// Use our wrapper to avoid duplicate track errors
addTrack(track, stream);
});
}
}, [stream, peerConnection]);Step 4: Perform Signaling
This part depends on your backend, but the hook provides the primitives:
Caller:
const startCall = async () => {
const offer = await createOffer();
signalingServer.send("offer", offer);
};Callee:
// On receiving offer
signalingServer.on("offer", async (remoteOffer) => {
await setRemoteDescription(remoteOffer);
const answer = await createAnswer();
signalingServer.send("answer", answer);
});Best Practices
- Cleanup: Always clean up media streams when components unmount.
useUserMediahandles this automatically, but be mindful of side effects. - State Management: WebRTC is asynchronous. Use the
connectionStateandiceConnectionStatereturned byusePeerConnectionto show loading spinners or status indicators. - Error Handling: Permissions can be denied, and networks can fail. Always check the
errorobject returned byuseUserMediaand handleonConnectionStateChangefailures. - Security: WebRTC requires a Secure Context (HTTPS or localhost). It will not work on HTTP.
Security Info: Why WebRTC Needs HTTPS (MDN)