// ───────────────────────────────────────────────────────────── // WebRTC Helper — Manages peer connections for video/audio // ───────────────────────────────────────────────────────────── export const ICE_SERVERS: RTCConfiguration = { iceServers: [ { urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun1.l.google.com:19302' }, { urls: 'stun:stun2.l.google.com:19302' }, { urls: 'stun:stun3.l.google.com:19302' }, ], }; /** * Create an RTCPeerConnection with the configured ICE servers */ export function createPeerConnection(): RTCPeerConnection { return new RTCPeerConnection(ICE_SERVERS); } /** * Get user media with fallback handling */ export async function getUserMedia( video: boolean = true, audio: boolean = true ): Promise { try { return await navigator.mediaDevices.getUserMedia({ video, audio }); } catch (error: any) { // Try audio-only if video fails if (video) { console.warn('Video access failed, trying audio only'); return await navigator.mediaDevices.getUserMedia({ video: false, audio }); } throw error; } } /** * Get display media for screen sharing */ export async function getDisplayMedia(): Promise { return await navigator.mediaDevices.getDisplayMedia({ video: { cursor: 'always' } as any, audio: true, }); } /** * Check if WebRTC is supported */ export function isWebRTCSupported(): boolean { return !!( window.RTCPeerConnection && navigator.mediaDevices && navigator.mediaDevices.getUserMedia ); } /** * Estimate connection quality based on RTCPeerConnection stats */ export async function getConnectionQuality( pc: RTCPeerConnection ): Promise<'excellent' | 'good' | 'poor' | 'disconnected'> { if (pc.connectionState === 'disconnected' || pc.connectionState === 'failed') { return 'disconnected'; } try { const stats = await pc.getStats(); let roundTripTime = 0; let packetsLost = 0; stats.forEach((report) => { if (report.type === 'candidate-pair' && report.currentRoundTripTime) { roundTripTime = report.currentRoundTripTime * 1000; // ms } if (report.type === 'inbound-rtp' && report.packetsLost) { packetsLost = report.packetsLost; } }); if (roundTripTime < 100 && packetsLost < 5) return 'excellent'; if (roundTripTime < 300 && packetsLost < 20) return 'good'; return 'poor'; } catch { return 'good'; // Default if stats unavailable } }