¿Qué es WebRTC?
- Obtener vínculo
- X
- Correo electrónico
- Otras apps
Te explico WebRTC (Web Real-Time Communication) y cómo integrarlo con PHP y JavaScript.
¿Qué es WebRTC?
WebRTC es una tecnología que permite comunicación en tiempo real (audio, video y datos) directamente entre navegadores, sin necesidad de plugins ni servidores intermedios para el flujo de datos.
Características clave:
P2P (Peer-to-Peer): Los datos viajan directamente entre usuarios
Bajo consumo: Optimizado para streaming en tiempo real
Seguro: Cifrado obligatorio (DTLS/SRTP)
Multiplataforma: Funciona en Chrome, Firefox, Safari, Edge
Arquitectura Básica
[Cliente A] ←→ [Servidor de Señalización (PHP)] ←→ [Cliente B]
↕ (P2P directo) ↕
[Cliente A] ←─────────── (Media/Data) ───────────→ [Cliente B]Componentes:
Servidor de señalización (PHP): Intercambia metadatos para establecer la conexión
Clientes (JS): Manejan la lógica de medios y conexión P2P
STUN/TURN Server: Ayuda a atravesar NATs y firewalls
Flujo de Trabajo
1️⃣ Cliente A inicia sesión
// JavaScript - Crear conexión const pc = new RTCPeerConnection(config); // Obtener medios locales const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); // Crear oferta (SDP - Session Description Protocol) const offer = await pc.createOffer(); await pc.setLocalDescription(offer); // Enviar oferta al servidor PHP fetch('signal.php', { method: 'POST', body: JSON.stringify({ type: 'offer', sdp: offer, room: 'sala1' }) });
2️⃣ PHP recibe y guarda la oferta
// signal.php - Guardar oferta <?php session_start(); $data = json_decode(file_get_contents('php://input'), true); $room = $data['room'] ?? 'default'; // Guardar en sesión o base de datos if ($data['type'] === 'offer') { $_SESSION['offers'][$room] = $data['sdp']; echo json_encode(['status' => 'offer_saved']); } // Cliente B solicita la oferta if ($data['type'] === 'get_offer') { $offer = $_SESSION['offers'][$room] ?? null; echo json_encode(['offer' => $offer]); } ?>
3️⃣ Cliente B recibe la oferta y responde
// Cliente B - Obtener oferta const response = await fetch('signal.php?action=get_offer&room=sala1'); const data = await response.json(); if (data.offer) { const pc = new RTCPeerConnection(config); // Establecer oferta remota await pc.setRemoteDescription(new RTCSessionDescription(data.offer)); // Crear respuesta (answer) const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); // Enviar respuesta al servidor fetch('signal.php', { method: 'POST', body: JSON.stringify({ type: 'answer', sdp: answer, room: 'sala1' }) }); }
4️⃣ PHP entrega la respuesta al Cliente A
// signal.php - Manejar answer if ($data['type'] === 'answer') { $_SESSION['answers'][$room] = $data['sdp']; } // Cliente A solicita la respuesta if ($data['type'] === 'get_answer') { $answer = $_SESSION['answers'][$room] ?? null; echo json_encode(['answer' => $answer]); }
5️⃣ Cliente A completa la conexión
// Cliente A - Obtener respuesta const resp = await fetch('signal.php?action=get_answer&room=sala1'); const ansData = await resp.json(); if (ansData.answer) { await pc.setRemoteDescription( new RTCSessionDescription(ansData.answer) ); // ¡Conexión establecida! }
Servidor de Señalización con PHP (Completo)
// signal.php - Versión robusta con MySQL <?php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); $pdo = new PDO('mysql:host=localhost;dbname=webrtc', 'user', 'pass'); $action = $_GET['action'] ?? $_POST['action'] ?? ''; $room = $_GET['room'] ?? 'default'; switch($action) { case 'offer': $sdp = json_decode(file_get_contents('php://input'), true)['sdp']; // Guardar oferta $stmt = $pdo->prepare("INSERT INTO sessions (room, offer, created_at) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE offer = ?"); $stmt->execute([$room, $sdp, $sdp]); echo json_encode(['status' => 'ok']); break; case 'get_offer': $stmt = $pdo->prepare("SELECT offer FROM sessions WHERE room = ?"); $stmt->execute([$room]); $result = $stmt->fetch(PDO::FETCH_ASSOC); echo json_encode(['offer' => $result['offer'] ?? null]); break; case 'answer': $sdp = json_decode(file_get_contents('php://input'), true)['sdp']; $stmt = $pdo->prepare("UPDATE sessions SET answer = ? WHERE room = ?"); $stmt->execute([$sdp, $room]); echo json_encode(['status' => 'ok']); break; case 'get_answer': $stmt = $pdo->prepare("SELECT answer FROM sessions WHERE room = ?"); $stmt->execute([$room]); $result = $stmt->fetch(PDO::FETCH_ASSOC); echo json_encode(['answer' => $result['answer'] ?? null]); break; } ?>
Configuración STUN/TURN
// Configuración para producción const config = { iceServers: [ { urls: 'stun:stun.l.google.com:19302' }, // STUN gratuito { urls: 'turn:turn.myserver.com:3478', username: 'user', credential: 'pass' } ], iceCandidatePoolSize: 10 };
Ejemplo Completo HTML + JS
<!DOCTYPE html> <html> <body> <video id="localVideo" autoplay muted></video> <video id="remoteVideo" autoplay></video> <button onclick="startCall()">Iniciar Llamada</button> <button onclick="joinCall()">Unirse</button> <script> let pc, localStream; const room = 'sala1'; const serverUrl = 'signal.php'; async function startCall() { localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); document.getElementById('localVideo').srcObject = localStream; pc = new RTCPeerConnection(config); localStream.getTracks().forEach(track => pc.addTrack(track, localStream) ); pc.ontrack = (event) => { document.getElementById('remoteVideo').srcObject = event.streams[0]; }; const offer = await pc.createOffer(); await pc.setLocalDescription(offer); // Enviar oferta via PHP await fetch(serverUrl, { method: 'POST', body: JSON.stringify({ action: 'offer', room, sdp: offer }) }); // Polling para obtener respuesta checkForAnswer(); } async function checkForAnswer() { const resp = await fetch(`${serverUrl}?action=get_answer&room=${room}`); const data = await resp.json(); if (data.answer) { await pc.setRemoteDescription(new RTCSessionDescription(data.answer)); return; } setTimeout(checkForAnswer, 1000); // Polling cada 1s } async function joinCall() { localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); document.getElementById('localVideo').srcObject = localStream; pc = new RTCPeerConnection(config); localStream.getTracks().forEach(track => pc.addTrack(track, localStream) ); pc.ontrack = (event) => { document.getElementById('remoteVideo').srcObject = event.streams[0]; }; // Obtener oferta const resp = await fetch(`${serverUrl}?action=get_offer&room=${room}`); const data = await resp.json(); if (data.offer) { await pc.setRemoteDescription(new RTCSessionDescription(data.offer)); const answer = await pc.createAnswer(); await pc.setLocalDescription(answer); await fetch(serverUrl, { method: 'POST', body: JSON.stringify({ action: 'answer', room, sdp: answer }) }); } } </script> </body> </html>
Conceptos Clave
| Término | Significado |
|---|---|
| SDP | Session Description Protocol - Metadatos de la sesión |
| ICE | Interactive Connectivity Establishment - Descubre cómo conectar peers |
| STUN | Session Traversal Utilities for NAT - Descubre IP pública |
| TURN | Traversal Using Relays around NAT - Relay cuando P2P falla |
| Signaling | Intercambio de metadatos para establecer conexión |
Mejores Prácticas
Usa WebSockets en lugar de HTTP polling para señalización en tiempo real
Implementa autenticación en el servidor PHP
Configura TURN server para casos donde P2P falle
Maneja desconexiones: Implementa reconnection logic
Cifra los datos: Usa
RTCDataChannelconencrypted: true
Limitaciones
⚠️ PHP no maneja la conexión P2P, solo señalización
⚠️ Requiere HTTPS (o localhost) para funcionar
⚠️ Depende del navegador para la implementación
⚠️ No apto para muchos usuarios (es P2P, no broadcast)
¿Necesitas profundizar en alguna parte específica (como manejo de canales de datos, salas múltiples, o integración con WebSockets)?
- Obtener vínculo
- X
- Correo electrónico
- Otras apps
Comentarios
Publicar un comentario