// Created by: Claude // Date: 2026-01-03 // Purpose: Indicateur de qualité de connexion WebRTC // Refs: client/CLAUDE.md import React, { useEffect, useState } from 'react' import styles from './ConnectionIndicator.module.css' export interface ConnectionIndicatorProps { peerConnection?: RTCPeerConnection peerId: string username: string } type ConnectionQuality = 'excellent' | 'good' | 'poor' | 'disconnected' const ConnectionIndicator: React.FC = ({ peerConnection, peerId, username, }) => { const [quality, setQuality] = useState('disconnected') const [stats, setStats] = useState<{ rtt?: number // Round-trip time en ms packetsLost?: number jitter?: number // En ms }>({}) useEffect(() => { if (!peerConnection) { setQuality('disconnected') return } // Surveiller l'état de connexion const handleConnectionStateChange = () => { const state = peerConnection.connectionState console.log(`[${username}] Connection state:`, state) if (state === 'connected') { setQuality('good') } else if (state === 'connecting') { setQuality('poor') } else if (state === 'disconnected' || state === 'failed' || state === 'closed') { setQuality('disconnected') } } peerConnection.addEventListener('connectionstatechange', handleConnectionStateChange) handleConnectionStateChange() // État initial // Récupérer les stats toutes les 2 secondes const statsInterval = setInterval(async () => { if (peerConnection.connectionState !== 'connected') { return } try { const stats = await peerConnection.getStats() let rtt: number | undefined let packetsLost = 0 let jitter: number | undefined stats.forEach((report) => { if (report.type === 'candidate-pair' && report.state === 'succeeded') { rtt = report.currentRoundTripTime ? report.currentRoundTripTime * 1000 : undefined } if (report.type === 'inbound-rtp' && report.kind === 'video') { packetsLost = report.packetsLost || 0 jitter = report.jitter ? report.jitter * 1000 : undefined } }) setStats({ rtt, packetsLost, jitter }) // Déterminer la qualité selon RTT if (rtt !== undefined) { if (rtt < 100) { setQuality('excellent') } else if (rtt < 200) { setQuality('good') } else { setQuality('poor') } } } catch (error) { console.error('Error getting WebRTC stats:', error) } }, 2000) return () => { peerConnection.removeEventListener('connectionstatechange', handleConnectionStateChange) clearInterval(statsInterval) } }, [peerConnection, username]) const getQualityIcon = () => { switch (quality) { case 'excellent': return '📶' case 'good': return '📡' case 'poor': return '⚠️' case 'disconnected': return '❌' } } const getQualityLabel = () => { switch (quality) { case 'excellent': return 'Excellente' case 'good': return 'Bonne' case 'poor': return 'Faible' case 'disconnected': return 'Déconnecté' } } return (
{getQualityIcon()} {getQualityLabel()}
) function getTooltip(): string { if (quality === 'disconnected') { return 'Pas de connexion' } const parts: string[] = [] if (stats.rtt !== undefined) { parts.push(`RTT: ${stats.rtt.toFixed(0)}ms`) } if (stats.packetsLost !== undefined && stats.packetsLost > 0) { parts.push(`Paquets perdus: ${stats.packetsLost}`) } if (stats.jitter !== undefined) { parts.push(`Jitter: ${stats.jitter.toFixed(1)}ms`) } return parts.length > 0 ? parts.join(' | ') : getQualityLabel() } } export default ConnectionIndicator