Learn how to easily add video calls to your web app with our step-by-step guide. Boost engagement and user experience today!

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
The Big Picture: What We're Building
Adding video calls to your web application isn't just a feature checkbox—it's opening up real-time visual communication between your users. Before we dive into implementation details, let's understand what this really means: we're creating a system where browsers can exchange audio and video streams directly between users, often with server coordination but minimal server processing.
WebRTC: The Foundation
At the core of modern web-based video calling is WebRTC (Web Real-Time Communication)—an open-source project that enables peer-to-peer communication between browsers without plugins. WebRTC handles the heavy lifting of:
Implementation Approaches: The Three Paths
There are three primary ways to add video calls to your web app:
This approach gives you complete control but requires deeper technical understanding.
// Basic WebRTC implementation (simplified)
async function startCall() {
// 1. Get local media stream
const localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
// 2. Display your video to yourself
document.getElementById('localVideo').srcObject = localStream;
// 3. Create peer connection
const peerConnection = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] // Public STUN server
});
// 4. Add tracks to the connection
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// 5. Handle incoming remote streams
peerConnection.ontrack = event => {
document.getElementById('remoteVideo').srcObject = event.streams[0];
};
// 6. Create and send offer (signaling server code not shown)
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
// 7. Send offer to remote peer via your signaling server
sendToSignalingServer({ type: 'offer', offer });
}
What's Missing in the Raw Approach:
These libraries abstract away WebRTC complexities while still giving you significant control.
Popular Options:
// Example using PeerJS (much simpler than raw WebRTC)
import Peer from 'peerjs';
function initializeVideoCall() {
// Create peer with a random ID
const peer = new Peer();
// Get local stream
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
// Show local video
document.getElementById('localVideo').srcObject = stream;
// Answer incoming calls
peer.on('call', call => {
// Answer with our stream
call.answer(stream);
// Receive remote stream
call.on('stream', remoteStream => {
document.getElementById('remoteVideo').srcObject = remoteStream;
});
});
// Connect to a peer button
document.getElementById('connectBtn').onclick = () => {
const remotePeerId = document.getElementById('remotePeerId').value;
// Initiate call with remote peer
const call = peer.call(remotePeerId, stream);
// When they answer, display their video
call.on('stream', remoteStream => {
document.getElementById('remoteVideo').srcObject = remoteStream;
});
};
});
}
These services provide complete infrastructure and SDKs, ideal for faster implementation and scaling.
Popular Services:
// Example using Twilio Video
import Video from 'twilio-video';
async function startVideoCall(roomName, token) {
try {
// Get local camera and microphone
const localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
// Display your video
const localVideoElement = document.getElementById('localVideo');
localVideoElement.srcObject = localStream;
// Connect to the Twilio room
const room = await Video.connect(token, {
name: roomName,
audio: true,
video: true
});
// When a remote participant connects
room.on('participantConnected', participant => {
console.log(`Participant ${participant.identity} connected`);
// When the participant publishes media
participant.on('trackSubscribed', track => {
// Attach the track to the DOM
const remoteMediaContainer = document.getElementById('remoteVideos');
const trackElement = track.attach();
trackElement.classList.add('remote-video');
remoteMediaContainer.appendChild(trackElement);
});
});
// Handle disconnection
room.on('disconnected', room => {
// Clear remote participants
document.getElementById('remoteVideos').innerHTML = '';
});
// Cleanup function
return () => room.disconnect();
} catch (error) {
console.error(`Failed to join video call: ${error.message}`);
}
}
When to Choose Each Approach:
Phase 1: Technical Preparation
Phase 2: Core Implementation
// UI for handling permission states and connection status
function updateUIForPermissionState(state) {
const statusElement = document.getElementById('connectionStatus');
switch(state) {
case 'requesting':
statusElement.textContent = 'Requesting camera and microphone access...';
statusElement.className = 'status-requesting';
break;
case 'granted':
statusElement.textContent = 'Devices connected. Ready to call.';
statusElement.className = 'status-ready';
break;
case 'denied':
statusElement.textContent = 'Camera or microphone access denied. Call functionality limited.';
statusElement.className = 'status-error';
break;
case 'connecting':
statusElement.textContent = 'Establishing connection...';
statusElement.className = 'status-connecting';
break;
case 'connected':
statusElement.textContent = 'Call in progress';
statusElement.className = 'status-connected';
break;
case 'disconnected':
statusElement.textContent = 'Call ended';
statusElement.className = 'status-disconnected';
setTimeout(() => {
statusElement.textContent = 'Ready to call';
statusElement.className = 'status-ready';
}, 3000);
break;
}
}
Phase 3: Enhanced Features
// Example of toggling video/audio and sharing screen
function setupMediaControls(localStream, peerConnection) {
// Video toggle
document.getElementById('toggleVideo').addEventListener('click', () => {
const videoTrack = localStream.getVideoTracks()[0];
if (videoTrack) {
videoTrack.enabled = !videoTrack.enabled;
document.getElementById('toggleVideo').textContent =
videoTrack.enabled ? 'Turn Off Camera' : 'Turn On Camera';
}
});
// Audio toggle
document.getElementById('toggleAudio').addEventListener('click', () => {
const audioTrack = localStream.getAudioTracks()[0];
if (audioTrack) {
audioTrack.enabled = !audioTrack.enabled;
document.getElementById('toggleAudio').textContent =
audioTrack.enabled ? 'Mute Microphone' : 'Unmute Microphone';
}
});
// Screen sharing
document.getElementById('shareScreen').addEventListener('click', async () => {
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true
});
// Replace video track with screen track
const videoSender = peerConnection
.getSenders()
.find(sender => sender.track.kind === 'video');
if (videoSender) {
videoSender.replaceTrack(screenStream.getVideoTracks()[0]);
}
// Listen for screen sharing end
screenStream.getVideoTracks()[0].onended = () => {
// Replace screen track with camera track again
videoSender.replaceTrack(localStream.getVideoTracks()[0]);
};
} catch (error) {
console.error('Error sharing screen:', error);
}
});
}
Bandwidth Management
Video calls consume significant bandwidth. Implement adaptive bitrate techniques:
// Simple bandwidth monitoring and adaptation
function setupBandwidthMonitoring(peerConnection) {
let lastBytesSent = 0;
let lastTimestamp = Date.now();
setInterval(() => {
peerConnection.getStats().then(stats => {
stats.forEach(report => {
if (report.type === 'outbound-rtp' && report.mediaType === 'video') {
const now = Date.now();
const bytesSent = report.bytesSent;
const bitrate = 8 * (bytesSent - lastBytesSent) / (now - lastTimestamp);
lastBytesSent = bytesSent;
lastTimestamp = now;
// Log current bitrate in Mbps
console.log(`Current outbound video bitrate: ${(bitrate / 1000000).toFixed(2)} Mbps`);
// Adjust video quality based on bitrate
if (bitrate < 500000) { // Less than 0.5 Mbps
// Lower resolution or framerate
adjustVideoQuality('low');
} else if (bitrate < 1500000) { // Between 0.5 and 1.5 Mbps
adjustVideoQuality('medium');
} else {
adjustVideoQuality('high');
}
}
});
});
}, 3000); // Check every 3 seconds
}
function adjustVideoQuality(quality) {
const videoTrack = localStream.getVideoTracks()[0];
if (!videoTrack) return;
const constraints = {};
switch(quality) {
case 'low':
constraints.width = 320;
constraints.height = 240;
constraints.frameRate = 15;
break;
case 'medium':
constraints.width = 640;
constraints.height = 480;
constraints.frameRate = 25;
break;
case 'high':
constraints.width = 1280;
constraints.height = 720;
constraints.frameRate = 30;
break;
}
videoTrack.applyConstraints(constraints)
.catch(error => console.error('Could not adjust video quality:', error));
}
Scaling to Multiple Users
When scaling beyond simple one-to-one calls, consider these architectural patterns:
Let's walk through a practical example to illustrate the decision-making process:
The Scenario: A healthcare platform needs to add video consultation between doctors and patients. They need HIPAA compliance, reliability across various patient devices, and recording functionality for medical records.
The Decision Process:
Implementation Highlights:
// Healthcare video system with Twilio (simplified)
import Video from 'twilio-video';
class MedicalVideoConsult {
constructor() {
this.room = null;
this.recording = false;
}
async initialize(token, roomName, participantRole) {
try {
// Track initialization for analytics
this.trackEvent('consultation_initialized', { participantRole });
// Get devices with appropriate constraints for medical use
// Higher resolution for doctor's view of patient
const videoConstraints = participantRole === 'doctor'
? { width: 1280, height: 720, frameRate: 30 }
: { width: 640, height: 480, frameRate: 24 };
const localStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: videoConstraints
});
// Connect to room with appropriate quality settings
this.room = await Video.connect(token, {
name: roomName,
audio: true,
video: videoConstraints,
// Higher video bandwidth for doctor role
bandwidthProfile: {
video: {
mode: participantRole === 'doctor' ? 'collaboration' : 'presentation',
maxSubscriptionBitrate: participantRole === 'doctor' ? 2500000 : 1000000,
}
},
// Enable network quality indicators
networkQuality: { local: 3, remote: 3 }
});
// Setup UI elements
this.setupLocalParticipant(localStream);
this.setupRemoteParticipants();
this.setupNetworkQualityIndicator();
// Doctor-specific controls
if (participantRole === 'doctor') {
this.setupRecordingControls();
}
// Required warning for patients about recording
if (participantRole === 'patient') {
this.displayRecordingConsent();
}
return true;
} catch (error) {
this.trackEvent('consultation_initialization_failed', {
error: error.message,
participantRole
});
console.error('Failed to initialize video consultation:', error);
return false;
}
}
// Other methods for participant handling, recording, etc.
startRecording() {
if (this.room && !this.recording) {
// Call server endpoint to start cloud recording
fetch('/api/start-recording', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ roomSid: this.room.sid })
})
.then(response => response.json())
.then(data => {
this.recording = true;
this.recordingSid = data.recordingSid;
this.displayRecordingIndicator();
this.trackEvent('consultation_recording_started');
})
.catch(error => {
console.error('Failed to start recording:', error);
this.trackEvent('consultation_recording_failed', { error: error.message });
});
}
}
}
When adding video calls to your web app, remember that the technology choice should align with your business goals:
The best video calling implementation is one that becomes invisible to your users—they should be focused on communicating, not wrestling with the technology. Your job is to make that magic happen, whether through carefully crafted custom code or strategic integration of existing solutions.
Explore the top 3 video call use cases to enhance your web app’s communication features effectively.
Video calls transform geographically dispersed teams into cohesive units by providing real-time visual communication that captures nuance, body language, and emotional context missing from text-based interactions. Research shows teams using video collaboration experience 25-35% higher productivity compared to those relying solely on email and messaging.
Video calls establish deeper connections with clients through face-to-face interactions without geographic limitations. Studies indicate that sales cycles involving video meetings close 41% faster than those without visual engagement, as trust develops more rapidly when people can see facial expressions and reactions.
During critical incidents or production outages, video calls facilitate rapid coordination among technical teams. Organizations implementing video-based incident response protocols report 27% faster resolution times due to improved information sharing and the elimination of communication barriers.
From startups to enterprises and everything in between, see for yourself our incredible impact.
Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We’ll discuss your project and provide a custom quote at no cost.Â