class WebRTCManager {
constructor(socket) {
this.socket = socket;
this.peerConnection = null;
this.localStream = null;
this.setupSignaling();
}
setupSignaling() {
// Server instructs us to create offer
this.socket.on('create-offer', () => {
this.createOffer();
});
// Received offer from peer
this.socket.on('webrtc-offer', ({ offer, from }) => {
this.handleOffer(offer, from);
});
// Received answer from peer
this.socket.on('webrtc-answer', ({ answer, from }) => {
this.handleAnswer(answer, from);
});
// Received ICE candidate from peer
this.socket.on('ice-candidate', ({ candidate, from }) => {
this.handleICECandidate(candidate, from);
});
}
async initializeMedia() {
try {
this.localStream = await navigator.mediaDevices.getUserMedia({
video: { width: 1280, height: 720 },
audio: {
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
});
// Display local video
const localVideo = document.getElementById('local-video');
localVideo.srcObject = this.localStream;
// Signal ready for WebRTC
this.socket.emit('ready-to-connect');
return this.localStream;
} catch (error) {
console.error('Error accessing media devices:', error);
throw error;
}
}
createPeerConnection() {
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun2.l.google.com:19302' }
]
};
this.peerConnection = new RTCPeerConnection(config);
// Add local tracks
this.localStream.getTracks().forEach(track => {
this.peerConnection.addTrack(track, this.localStream);
});
// Handle remote stream
this.peerConnection.ontrack = (event) => {
console.log('Received remote track:', event.track.kind);
const remoteVideo = document.getElementById('remote-video');
if (!remoteVideo.srcObject) {
remoteVideo.srcObject = event.streams[0];
}
};
// Handle ICE candidates
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log('Sending ICE candidate');
this.socket.emit('ice-candidate', { candidate: event.candidate });
}
};
// Connection state monitoring
this.peerConnection.onconnectionstatechange = () => {
console.log('Connection state:', this.peerConnection.connectionState);
if (this.peerConnection.connectionState === 'connected') {
console.log('WebRTC connection established!');
}
};
// ICE connection state monitoring
this.peerConnection.oniceconnectionstatechange = () => {
console.log('ICE connection state:', this.peerConnection.iceConnectionState);
if (this.peerConnection.iceConnectionState === 'failed') {
console.error('ICE connection failed');
this.restartICE();
}
};
return this.peerConnection;
}
async createOffer() {
try {
console.log('Creating WebRTC offer');
this.createPeerConnection();
const offer = await this.peerConnection.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await this.peerConnection.setLocalDescription(offer);
console.log('Sending offer to peer');
this.socket.emit('webrtc-offer', { offer: offer });
} catch (error) {
console.error('Error creating offer:', error);
}
}
async handleOffer(offer, from) {
try {
console.log('Handling offer from:', from);
if (!this.peerConnection) {
this.createPeerConnection();
}
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await this.peerConnection.createAnswer();
await this.peerConnection.setLocalDescription(answer);
console.log('Sending answer to peer');
this.socket.emit('webrtc-answer', { answer: answer });
} catch (error) {
console.error('Error handling offer:', error);
}
}
async handleAnswer(answer, from) {
try {
console.log('Handling answer from:', from);
await this.peerConnection.setRemoteDescription(new RTCSessionDescription(answer));
console.log('Remote description set successfully');
} catch (error) {
console.error('Error handling answer:', error);
}
}
async handleICECandidate(candidate, from) {
try {
if (this.peerConnection && candidate) {
await this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
console.log('ICE candidate added');
}
} catch (error) {
console.error('Error adding ICE candidate:', error);
}
}
async restartICE() {
try {
console.log('Restarting ICE...');
const offer = await this.peerConnection.createOffer({ iceRestart: true });
await this.peerConnection.setLocalDescription(offer);
this.socket.emit('webrtc-offer', { offer: offer });
} catch (error) {
console.error('Error restarting ICE:', error);
}
}
cleanup() {
// Stop local tracks
if (this.localStream) {
this.localStream.getTracks().forEach(track => track.stop());
}
// Close peer connection
if (this.peerConnection) {
this.peerConnection.close();
}
this.peerConnection = null;
this.localStream = null;
}
}
// Usage
const socket = io('http://localhost:3001');
const webrtc = new WebRTCManager(socket);
socket.on('chatStart', async ({ withVideo, partnerId }) => {
if (withVideo) {
try {
await webrtc.initializeMedia();
console.log('Ready for video chat with', partnerId);
} catch (error) {
console.error('Failed to initialize video:', error);
}
}
});
// Cleanup when chat ends
socket.on('partnerLeft', () => {
webrtc.cleanup();
});
document.getElementById('next-btn').addEventListener('click', () => {
webrtc.cleanup();
socket.emit('next');
});