WebRTC Video Chat Setup
- Published on
- Published on
- /3 mins read/---
Basic WebRTC Setup
class WebRTCService {
private peerConnection: RTCPeerConnection;
private localStream: MediaStream | null = null;
private remoteStream: MediaStream | null = null;
constructor() {
this.peerConnection = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:your-turn-server.com',
username: process.env.TURN_USERNAME,
credential: process.env.TURN_CREDENTIAL
}
]
});
// Set up event handlers
this.setupEventHandlers();
}
private setupEventHandlers() {
// Handle ICE candidate events
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// Send the candidate to the remote peer via signaling server
this.signalingService.sendIceCandidate(event.candidate);
}
};
// Handle connection state changes
this.peerConnection.onconnectionstatechange = () => {
switch(this.peerConnection.connectionState) {
case 'connected':
console.log('Peers connected');
break;
case 'disconnected':
console.log('Peers disconnected');
break;
case 'failed':
console.log('Connection failed');
break;
}
};
// Handle remote stream
this.peerConnection.ontrack = (event) => {
this.remoteStream = event.streams[0];
// Update UI with remote stream
this.updateRemoteVideo(this.remoteStream);
};
}
async startLocalStream() {
try {
this.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// Add tracks to peer connection
this.localStream.getTracks().forEach(track => {
if (this.localStream) {
this.peerConnection.addTrack(track, this.localStream);
}
});
// Update UI with local stream
this.updateLocalVideo(this.localStream);
} catch (error) {
console.error('Error accessing media devices:', error);
}
}
async createOffer() {
try {
const offer = await this.peerConnection.createOffer();
await this.peerConnection.setLocalDescription(offer);
// Send the offer to the remote peer via signaling server
this.signalingService.sendOffer(offer);
} catch (error) {
console.error('Error creating offer:', error);
}
}
async handleAnswer(answer: RTCSessionDescriptionInit) {
try {
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
} catch (error) {
console.error('Error handling answer:', error);
}
}
async handleIceCandidate(candidate: RTCIceCandidateInit) {
try {
await this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
} catch (error) {
console.error('Error adding ICE candidate:', error);
}
}
private updateLocalVideo(stream: MediaStream) {
const videoElement = document.getElementById('localVideo') as HTMLVideoElement;
if (videoElement) {
videoElement.srcObject = stream;
}
}
private updateRemoteVideo(stream: MediaStream) {
const videoElement = document.getElementById('remoteVideo') as HTMLVideoElement;
if (videoElement) {
videoElement.srcObject = stream;
}
}
}
React Component Implementation
import { useEffect, useRef, useState } from 'react'
export function VideoChat() {
const [isConnected, setIsConnected] = useState(false)
const webrtcService = useRef<WebRTCService>()
useEffect(() => {
webrtcService.current = new WebRTCService()
// Start local stream
webrtcService.current.startLocalStream()
return () => {
// Cleanup
if (webrtcService.current) {
// Stop all tracks
webrtcService.current.localStream?.getTracks().forEach(track => track.stop())
}
}
}, [])
const handleStartCall = async () => {
if (webrtcService.current) {
await webrtcService.current.createOffer()
setIsConnected(true)
}
}
return (
<div className="grid grid-cols-2 gap-4">
<div className="relative">
<video
id="localVideo"
autoPlay
playsInline
muted
className="w-full rounded-lg"
/>
<span className="absolute bottom-2 left-2 bg-black/50 px-2 py-1 text-white rounded">
You
</span>
</div>
<div className="relative">
<video
id="remoteVideo"
autoPlay
playsInline
className="w-full rounded-lg"
/>
<span className="absolute bottom-2 left-2 bg-black/50 px-2 py-1 text-white rounded">
Remote
</span>
</div>
<button
onClick={handleStartCall}
disabled={isConnected}
className="col-span-2 bg-blue-500 text-white px-4 py-2 rounded disabled:bg-gray-300"
>
{isConnected ? 'Connected' : 'Start Call'}
</button>
</div>
)
}
Usage
- First, create an instance of the WebRTC service:
const webrtcService = new WebRTCService();
- Start the local video stream:
await webrtcService.startLocalStream();
- To initiate a call (caller side):
await webrtcService.createOffer();
- Handle incoming signals (callee side):
// When receiving an offer
webrtcService.handleAnswer(answer);
// When receiving ICE candidates
webrtcService.handleIceCandidate(candidate);
Notes
- Remember to implement proper signaling server logic
- Handle cleanup when component unmounts
- Consider implementing reconnection logic
- Add error handling for various scenarios
- Test with different network conditions