JavaScript WebRTC: Real-Time Communication in the Browser
Master WebRTC for building real-time communication applications. Learn peer-to-peer connections, media streaming, data channels, and signaling implementation.
WebRTC (Web Real-Time Communication) enables peer-to-peer communication of audio, video, and data directly between browsers without plugins. It's the foundation for video conferencing, screen sharing, and real-time collaboration applications.
Understanding WebRTC
WebRTC consists of three main APIs: MediaStream (getUserMedia), RTCPeerConnection, and RTCDataChannel, working together to enable real-time communication.
Basic WebRTC Setup
// Get user media
async function getUserMedia() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
// Display in video element
const videoElement = document.getElementById('localVideo');
videoElement.srcObject = stream;
return stream;
} catch (error) {
console.error('Error accessing media devices:', error);
}
}
// Create peer connection
function createPeerConnection() {
const configuration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
],
};
const pc = new RTCPeerConnection(configuration);
// Handle ICE candidates
pc.onicecandidate = (event) => {
if (event.candidate) {
// Send candidate to remote peer
sendSignal({
type: 'ice-candidate',
candidate: event.candidate,
});
}
};
// Handle remote stream
pc.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
return pc;
}
// Basic connection flow
async function initiateCall() {
const localStream = await getUserMedia();
const pc = createPeerConnection();
// Add local stream tracks to peer connection
localStream.getTracks().forEach((track) => {
pc.addTrack(track, localStream);
});
// Create offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// Send offer to remote peer
sendSignal({
type: 'offer',
offer: offer,
});
}
// Handle incoming offer
async function handleOffer(offer) {
const localStream = await getUserMedia();
const pc = createPeerConnection();
// Add local stream
localStream.getTracks().forEach((track) => {
pc.addTrack(track, localStream);
});
// Set remote description
await pc.setRemoteDescription(offer);
// Create answer
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
// Send answer to remote peer
sendSignal({
type: 'answer',
answer: answer,
});
}
Advanced Media Constraints
// Detailed media constraints
const mediaConstraints = {
video: {
width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 480, ideal: 720, max: 1080 },
frameRate: { min: 15, ideal: 30, max: 60 },
facingMode: 'user', // 'user' for front camera, 'environment' for back
aspectRatio: 16 / 9,
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100,
sampleSize: 16,
channelCount: 2,
autoGainControl: true,
},
};
// Get display media for screen sharing
async function getScreenShare() {
try {
const displayStream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: 'always',
displaySurface: 'monitor', // 'monitor', 'window', 'application', 'browser'
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100,
},
});
return displayStream;
} catch (error) {
console.error('Error sharing screen:', error);
}
}
// Device enumeration
async function getAvailableDevices() {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter((device) => device.kind === 'videoinput');
const audioDevices = devices.filter((device) => device.kind === 'audioinput');
const audioOutputs = devices.filter(
(device) => device.kind === 'audiooutput'
);
return { videoDevices, audioDevices, audioOutputs };
}
// Switch camera
async function switchCamera(currentStream, deviceId) {
// Stop current video track
currentStream.getVideoTracks().forEach((track) => track.stop());
// Get new stream with specific device
const newStream = await navigator.mediaDevices.getUserMedia({
video: { deviceId: { exact: deviceId } },
audio: false,
});
return newStream;
}
Practical Applications
Video Chat Application
class VideoChat {
constructor() {
this.localStream = null;
this.remoteStream = null;
this.peerConnection = null;
this.dataChannel = null;
this.signalingServer = new WebSocket('wss://signaling.example.com');
this.init();
}
async init() {
// Set up signaling
this.signalingServer.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleSignalingMessage(message);
};
this.signalingServer.onopen = () => {
console.log('Connected to signaling server');
};
// Get user media
await this.setupLocalStream();
}
async setupLocalStream() {
try {
this.localStream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
},
audio: {
echoCancellation: true,
noiseSuppression: true,
},
});
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = this.localStream;
} catch (error) {
console.error('Failed to get user media:', error);
}
}
createPeerConnection() {
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turnserver.example.com:3478',
username: 'user',
credential: 'password',
},
],
iceCandidatePoolSize: 10,
};
this.peerConnection = new RTCPeerConnection(config);
// Add local stream
this.localStream.getTracks().forEach((track) => {
this.peerConnection.addTrack(track, this.localStream);
});
// Handle remote stream
this.peerConnection.ontrack = (event) => {
console.log('Received remote track');
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
this.remoteStream = event.streams[0];
};
// Handle ICE candidates
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
this.sendSignal({
type: 'ice-candidate',
candidate: event.candidate,
});
}
};
// Handle connection state
this.peerConnection.onconnectionstatechange = () => {
console.log('Connection state:', this.peerConnection.connectionState);
this.updateConnectionStatus(this.peerConnection.connectionState);
};
// Set up data channel
this.setupDataChannel();
}
setupDataChannel() {
// Create data channel
this.dataChannel = this.peerConnection.createDataChannel('chat', {
ordered: true,
});
this.dataChannel.onopen = () => {
console.log('Data channel opened');
this.enableChat();
};
this.dataChannel.onmessage = (event) => {
this.displayMessage(event.data, 'remote');
};
// Handle incoming data channel
this.peerConnection.ondatachannel = (event) => {
const channel = event.channel;
channel.onopen = () => console.log('Received data channel');
channel.onmessage = (e) => this.displayMessage(e.data, 'remote');
};
}
async startCall() {
this.createPeerConnection();
try {
const offer = await this.peerConnection.createOffer({
offerToReceiveVideo: true,
offerToReceiveAudio: true,
});
await this.peerConnection.setLocalDescription(offer);
this.sendSignal({
type: 'offer',
offer: offer,
});
} catch (error) {
console.error('Failed to create offer:', error);
}
}
async handleSignalingMessage(message) {
switch (message.type) {
case 'offer':
await this.handleOffer(message.offer);
break;
case 'answer':
await this.handleAnswer(message.answer);
break;
case 'ice-candidate':
await this.handleIceCandidate(message.candidate);
break;
}
}
async handleOffer(offer) {
if (!this.peerConnection) {
this.createPeerConnection();
}
try {
await this.peerConnection.setRemoteDescription(offer);
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
this.sendSignal({
type: 'answer',
answer: answer,
});
} catch (error) {
console.error('Failed to handle offer:', error);
}
}
async handleAnswer(answer) {
try {
await this.peerConnection.setRemoteDescription(answer);
} catch (error) {
console.error('Failed to handle answer:', error);
}
}
async handleIceCandidate(candidate) {
try {
await this.peerConnection.addIceCandidate(candidate);
} catch (error) {
console.error('Failed to add ICE candidate:', error);
}
}
sendSignal(message) {
if (this.signalingServer.readyState === WebSocket.OPEN) {
this.signalingServer.send(JSON.stringify(message));
}
}
sendChatMessage(message) {
if (this.dataChannel && this.dataChannel.readyState === 'open') {
this.dataChannel.send(message);
this.displayMessage(message, 'local');
}
}
displayMessage(message, sender) {
const chatContainer = document.getElementById('chat');
const messageElement = document.createElement('div');
messageElement.className = `message ${sender}`;
messageElement.textContent = message;
chatContainer.appendChild(messageElement);
}
updateConnectionStatus(state) {
const statusElement = document.getElementById('connectionStatus');
statusElement.textContent = state;
statusElement.className = `status ${state}`;
}
enableChat() {
const chatInput = document.getElementById('chatInput');
const sendButton = document.getElementById('sendButton');
chatInput.disabled = false;
sendButton.disabled = false;
sendButton.onclick = () => {
const message = chatInput.value.trim();
if (message) {
this.sendChatMessage(message);
chatInput.value = '';
}
};
}
toggleAudio() {
const audioTrack = this.localStream.getAudioTracks()[0];
if (audioTrack) {
audioTrack.enabled = !audioTrack.enabled;
return audioTrack.enabled;
}
}
toggleVideo() {
const videoTrack = this.localStream.getVideoTracks()[0];
if (videoTrack) {
videoTrack.enabled = !videoTrack.enabled;
return videoTrack.enabled;
}
}
async shareScreen() {
try {
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: false,
});
const screenTrack = screenStream.getVideoTracks()[0];
const sender = this.peerConnection
.getSenders()
.find((s) => s.track && s.track.kind === 'video');
if (sender) {
sender.replaceTrack(screenTrack);
}
screenTrack.onended = () => {
// Replace with camera when screen share ends
const cameraTrack = this.localStream.getVideoTracks()[0];
sender.replaceTrack(cameraTrack);
};
} catch (error) {
console.error('Failed to share screen:', error);
}
}
endCall() {
if (this.peerConnection) {
this.peerConnection.close();
this.peerConnection = null;
}
if (this.localStream) {
this.localStream.getTracks().forEach((track) => track.stop());
}
if (this.remoteStream) {
this.remoteStream.getTracks().forEach((track) => track.stop());
}
// Clear video elements
document.getElementById('localVideo').srcObject = null;
document.getElementById('remoteVideo').srcObject = null;
}
}
File Transfer
class FileTransfer {
constructor(peerConnection) {
this.peerConnection = peerConnection;
this.dataChannel = null;
this.receivedBuffers = [];
this.receivedSize = 0;
this.fileMetadata = null;
this.setupDataChannel();
}
setupDataChannel() {
// Create data channel with specific configuration
this.dataChannel = this.peerConnection.createDataChannel('fileTransfer', {
ordered: true,
maxPacketLifeTime: 3000,
});
this.dataChannel.binaryType = 'arraybuffer';
this.dataChannel.onopen = () => {
console.log('File transfer channel opened');
};
this.dataChannel.onmessage = (event) => {
this.handleIncomingData(event.data);
};
// Handle incoming data channel
this.peerConnection.ondatachannel = (event) => {
if (event.channel.label === 'fileTransfer') {
this.handleIncomingChannel(event.channel);
}
};
}
handleIncomingChannel(channel) {
channel.binaryType = 'arraybuffer';
channel.onmessage = (event) => {
this.handleIncomingData(event.data);
};
}
async sendFile(file) {
const chunkSize = 16384; // 16KB chunks
const metadata = {
type: 'metadata',
fileName: file.name,
fileSize: file.size,
fileType: file.type,
};
// Send metadata first
this.dataChannel.send(JSON.stringify(metadata));
// Read and send file
const reader = new FileReader();
let offset = 0;
const readSlice = () => {
const slice = file.slice(offset, offset + chunkSize);
reader.readAsArrayBuffer(slice);
};
reader.onload = (e) => {
this.dataChannel.send(e.target.result);
offset += e.target.result.byteLength;
// Update progress
const progress = (offset / file.size) * 100;
this.updateProgress('send', progress);
if (offset < file.size) {
// Queue next chunk
if (this.dataChannel.bufferedAmount < chunkSize * 10) {
readSlice();
} else {
// Wait for buffer to clear
setTimeout(readSlice, 50);
}
} else {
// Send completion signal
this.dataChannel.send(JSON.stringify({ type: 'complete' }));
}
};
readSlice();
}
handleIncomingData(data) {
if (typeof data === 'string') {
const message = JSON.parse(data);
if (message.type === 'metadata') {
this.fileMetadata = message;
this.receivedBuffers = [];
this.receivedSize = 0;
this.prepareDownload(message);
} else if (message.type === 'complete') {
this.completeDownload();
}
} else {
// Binary data
this.receivedBuffers.push(data);
this.receivedSize += data.byteLength;
// Update progress
const progress = (this.receivedSize / this.fileMetadata.fileSize) * 100;
this.updateProgress('receive', progress);
}
}
prepareDownload(metadata) {
// Show download UI
const downloadInfo = document.getElementById('downloadInfo');
downloadInfo.innerHTML = `
<p>Receiving: ${metadata.fileName}</p>
<p>Size: ${this.formatBytes(metadata.fileSize)}</p>
<progress id="downloadProgress" max="100" value="0"></progress>
`;
}
completeDownload() {
// Combine received buffers
const received = new Blob(this.receivedBuffers, {
type: this.fileMetadata.fileType,
});
// Create download link
const downloadUrl = URL.createObjectURL(received);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = this.fileMetadata.fileName;
a.textContent = 'Download ' + this.fileMetadata.fileName;
document.getElementById('downloads').appendChild(a);
// Auto-download
a.click();
// Clean up
this.receivedBuffers = [];
this.receivedSize = 0;
this.fileMetadata = null;
}
updateProgress(type, progress) {
const progressBar = document.getElementById(
type === 'send' ? 'uploadProgress' : 'downloadProgress'
);
if (progressBar) {
progressBar.value = progress;
}
}
formatBytes(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 Bytes';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + ' ' + sizes[i];
}
}
Multi-party Conference
class ConferenceRoom {
constructor(roomId) {
this.roomId = roomId;
this.participants = new Map();
this.localStream = null;
this.signalingServer = new WebSocket(
`wss://signaling.example.com/room/${roomId}`
);
this.init();
}
async init() {
// Get local stream
this.localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
// Display local video
this.displayVideo('local', this.localStream);
// Set up signaling
this.signalingServer.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleSignalingMessage(message);
};
// Join room
this.signalingServer.onopen = () => {
this.sendSignal({ type: 'join', roomId: this.roomId });
};
}
handleSignalingMessage(message) {
switch (message.type) {
case 'participant-joined':
this.handleParticipantJoined(message.participantId);
break;
case 'participant-left':
this.handleParticipantLeft(message.participantId);
break;
case 'offer':
this.handleOffer(message.from, message.offer);
break;
case 'answer':
this.handleAnswer(message.from, message.answer);
break;
case 'ice-candidate':
this.handleIceCandidate(message.from, message.candidate);
break;
}
}
async handleParticipantJoined(participantId) {
// Create peer connection for new participant
const pc = this.createPeerConnection(participantId);
this.participants.set(participantId, { pc });
// Create offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
this.sendSignal({
type: 'offer',
to: participantId,
offer: offer,
});
}
handleParticipantLeft(participantId) {
const participant = this.participants.get(participantId);
if (participant) {
participant.pc.close();
this.participants.delete(participantId);
this.removeVideo(participantId);
}
}
createPeerConnection(participantId) {
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
// Add local stream
this.localStream.getTracks().forEach((track) => {
pc.addTrack(track, this.localStream);
});
// Handle remote stream
pc.ontrack = (event) => {
this.displayVideo(participantId, event.streams[0]);
};
// Handle ICE candidates
pc.onicecandidate = (event) => {
if (event.candidate) {
this.sendSignal({
type: 'ice-candidate',
to: participantId,
candidate: event.candidate,
});
}
};
return pc;
}
async handleOffer(from, offer) {
const pc = this.createPeerConnection(from);
this.participants.set(from, { pc });
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
this.sendSignal({
type: 'answer',
to: from,
answer: answer,
});
}
async handleAnswer(from, answer) {
const participant = this.participants.get(from);
if (participant) {
await participant.pc.setRemoteDescription(answer);
}
}
async handleIceCandidate(from, candidate) {
const participant = this.participants.get(from);
if (participant) {
await participant.pc.addIceCandidate(candidate);
}
}
displayVideo(participantId, stream) {
const container = document.getElementById('videos');
let videoWrapper = document.getElementById(`video-${participantId}`);
if (!videoWrapper) {
videoWrapper = document.createElement('div');
videoWrapper.id = `video-${participantId}`;
videoWrapper.className = 'video-wrapper';
const video = document.createElement('video');
video.autoplay = true;
video.playsInline = true;
if (participantId === 'local') {
video.muted = true;
}
const label = document.createElement('div');
label.className = 'video-label';
label.textContent =
participantId === 'local' ? 'You' : `Participant ${participantId}`;
videoWrapper.appendChild(video);
videoWrapper.appendChild(label);
container.appendChild(videoWrapper);
}
const video = videoWrapper.querySelector('video');
video.srcObject = stream;
}
removeVideo(participantId) {
const videoWrapper = document.getElementById(`video-${participantId}`);
if (videoWrapper) {
videoWrapper.remove();
}
}
sendSignal(message) {
if (this.signalingServer.readyState === WebSocket.OPEN) {
this.signalingServer.send(JSON.stringify(message));
}
}
toggleAudio() {
const audioTrack = this.localStream.getAudioTracks()[0];
if (audioTrack) {
audioTrack.enabled = !audioTrack.enabled;
// Notify other participants
this.sendSignal({
type: 'audio-status',
enabled: audioTrack.enabled,
});
return audioTrack.enabled;
}
}
toggleVideo() {
const videoTrack = this.localStream.getVideoTracks()[0];
if (videoTrack) {
videoTrack.enabled = !videoTrack.enabled;
// Notify other participants
this.sendSignal({
type: 'video-status',
enabled: videoTrack.enabled,
});
return videoTrack.enabled;
}
}
leave() {
// Close all peer connections
this.participants.forEach((participant) => {
participant.pc.close();
});
// Stop local stream
this.localStream.getTracks().forEach((track) => track.stop());
// Notify server
this.sendSignal({ type: 'leave' });
// Close websocket
this.signalingServer.close();
}
}
Connection Quality Monitor
class ConnectionQualityMonitor {
constructor(peerConnection) {
this.pc = peerConnection;
this.stats = {
video: { bitrate: 0, packetsLost: 0, jitter: 0 },
audio: { bitrate: 0, packetsLost: 0, jitter: 0 },
connection: { rtt: 0, state: 'new' },
};
this.startMonitoring();
}
async startMonitoring() {
setInterval(() => this.collectStats(), 1000);
}
async collectStats() {
const stats = await this.pc.getStats();
stats.forEach((report) => {
if (report.type === 'inbound-rtp') {
this.processInboundStats(report);
} else if (report.type === 'outbound-rtp') {
this.processOutboundStats(report);
} else if (
report.type === 'candidate-pair' &&
report.state === 'succeeded'
) {
this.processConnectionStats(report);
}
});
this.updateUI();
this.checkQuality();
}
processInboundStats(report) {
const mediaType = report.mediaType;
if (!this.stats[mediaType]) return;
// Calculate bitrate
if (this.lastStats && this.lastStats[report.id]) {
const timeDiff =
(report.timestamp - this.lastStats[report.id].timestamp) / 1000;
const bytesDiff =
report.bytesReceived - this.lastStats[report.id].bytesReceived;
this.stats[mediaType].bitrate = Math.round((bytesDiff * 8) / timeDiff);
}
// Packet loss
this.stats[mediaType].packetsLost = report.packetsLost || 0;
// Jitter
this.stats[mediaType].jitter = report.jitter || 0;
// Store for next calculation
if (!this.lastStats) this.lastStats = {};
this.lastStats[report.id] = report;
}
processOutboundStats(report) {
// Similar processing for outbound stats
}
processConnectionStats(report) {
this.stats.connection.rtt = report.currentRoundTripTime * 1000; // Convert to ms
this.stats.connection.state = this.pc.connectionState;
}
checkQuality() {
let quality = 'good';
// Check video quality
if (this.stats.video.bitrate < 500000) {
// Less than 500 kbps
quality = 'poor';
} else if (this.stats.video.bitrate < 1000000) {
// Less than 1 Mbps
quality = 'fair';
}
// Check packet loss
if (this.stats.video.packetsLost > 10) {
quality = 'poor';
}
// Check RTT
if (this.stats.connection.rtt > 300) {
quality = 'poor';
} else if (this.stats.connection.rtt > 150) {
quality = quality === 'good' ? 'fair' : quality;
}
this.notifyQualityChange(quality);
}
notifyQualityChange(quality) {
const event = new CustomEvent('connectionQuality', {
detail: { quality, stats: this.stats },
});
window.dispatchEvent(event);
}
updateUI() {
const statsElement = document.getElementById('connectionStats');
if (statsElement) {
statsElement.innerHTML = `
<div>Video: ${this.formatBitrate(this.stats.video.bitrate)}</div>
<div>Audio: ${this.formatBitrate(this.stats.audio.bitrate)}</div>
<div>RTT: ${Math.round(this.stats.connection.rtt)}ms</div>
<div>State: ${this.stats.connection.state}</div>
`;
}
}
formatBitrate(bitrate) {
if (bitrate > 1000000) {
return `${(bitrate / 1000000).toFixed(2)} Mbps`;
} else if (bitrate > 1000) {
return `${(bitrate / 1000).toFixed(0)} kbps`;
}
return `${bitrate} bps`;
}
}
Best Practices
- Always handle errors gracefully
async function safeGetUserMedia(constraints) {
try {
return await navigator.mediaDevices.getUserMedia(constraints);
} catch (error) {
if (error.name === 'NotFoundError') {
throw new Error('No camera or microphone found');
} else if (error.name === 'NotAllowedError') {
throw new Error(
'Permission denied. Please allow camera/microphone access'
);
} else if (error.name === 'NotReadableError') {
throw new Error('Device is already in use');
} else {
throw error;
}
}
}
- Clean up resources properly
function cleanup() {
// Stop all tracks
if (localStream) {
localStream.getTracks().forEach((track) => {
track.stop();
});
}
// Close peer connections
if (peerConnection) {
peerConnection.close();
}
// Close data channels
if (dataChannel) {
dataChannel.close();
}
// Clear video elements
document.querySelectorAll('video').forEach((video) => {
video.srcObject = null;
});
}
window.addEventListener('beforeunload', cleanup);
- Implement connection retry logic
class ResilientConnection {
constructor() {
this.maxRetries = 3;
this.retryDelay = 1000;
}
async connectWithRetry() {
for (let i = 0; i < this.maxRetries; i++) {
try {
await this.connect();
return;
} catch (error) {
console.error(`Connection attempt ${i + 1} failed:`, error);
if (i < this.maxRetries - 1) {
await new Promise((resolve) =>
setTimeout(resolve, this.retryDelay * Math.pow(2, i))
);
}
}
}
throw new Error('Failed to establish connection after retries');
}
}
- Optimize media quality based on bandwidth
async function adaptVideoQuality(peerConnection) {
const stats = await peerConnection.getStats();
let availableBandwidth = Infinity;
stats.forEach((report) => {
if (report.type === 'candidate-pair' && report.availableOutgoingBitrate) {
availableBandwidth = report.availableOutgoingBitrate;
}
});
const sender = peerConnection
.getSenders()
.find((s) => s.track && s.track.kind === 'video');
if (sender) {
const params = sender.getParameters();
if (availableBandwidth < 500000) {
// Low bandwidth: reduce quality
params.encodings[0].maxBitrate = 300000;
params.encodings[0].scaleResolutionDownBy = 2;
} else if (availableBandwidth < 1000000) {
// Medium bandwidth
params.encodings[0].maxBitrate = 800000;
params.encodings[0].scaleResolutionDownBy = 1.5;
} else {
// Good bandwidth
params.encodings[0].maxBitrate = 2000000;
params.encodings[0].scaleResolutionDownBy = 1;
}
await sender.setParameters(params);
}
}
Conclusion
WebRTC provides powerful capabilities for real-time communication directly in the browser. By understanding peer connections, media streams, and data channels, you can build sophisticated applications for video conferencing, file sharing, and real-time collaboration. The key to successful WebRTC applications lies in proper signaling implementation, error handling, and connection quality management. As WebRTC continues to evolve, it remains the cornerstone technology for building real-time communication features on the web.