Elysia Tools

Explora herramientas, ejemplos, explicaciones visuales y guías temáticas en un solo espacio claro.

Categorías populares

Temas

Idioma
ZHESPTRUFRDEEN

Elysia Tools

Navegación

Tecnologías web

Muestras de Comunicación en Tiempo Real WebRTC

Muestras completas de WebRTC para comunicación de audio/video P2P, canales de datos, compartir pantalla y servidor de señalización

Ejemplos

Entradas de esta colección

CódigoIntermedio

Implementación de Videollamada P2P

Configuración completa de videollamada WebRTC con acceso a cámara y audio

Dificultad

7/10

Tiempo estimado

30 min

Etiquetas

webrtc, p2p, video, audio, peer-connection, media-stream

plaintext
// WebRTC Peer-to-Peer Video Call Implementation
// Complete setup for video and audio communication

class WebRTCPeerConnection {
  constructor(config = {}) {
    this.localVideo = document.getElementById('localVideo');
    this.remoteVideo = document.getElementById('remoteVideo');

    this.pc = new RTCPeerConnection({
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        {
          urls: 'turn:turn.example.com:3478',
          username: 'user',
          credential: 'password'
        }
      ],
      ...config
    });

    this.setupPeerConnection();
    this.setupMediaStreams();
  }

  async setupMediaStreams() {
    try {
      // Get local media stream with video and audio
      const stream = await navigator.mediaDevices.getUserMedia({
        video: {
          width: { ideal: 1280 },
          height: { ideal: 720 },
          facingMode: 'user'
        },
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: true
        }
      });

      this.localVideo.srcObject = stream;

      // Add tracks to peer connection
      stream.getTracks().forEach(track => {
        this.pc.addTrack(track, stream);
      });

    } catch (error) {
      console.error('Error accessing media devices:', error);
    }
  }

  setupPeerConnection() {
    // Handle incoming tracks
    this.pc.ontrack = (event) => {
      if (event.streams && event.streams[0]) {
        this.remoteVideo.srcObject = event.streams[0];
      }
    };

    // Handle ICE candidates
    this.pc.onicecandidate = (event) => {
      if (event.candidate) {
        // Send candidate to remote peer via signaling server
        this.sendSignalingMessage({
          type: 'ice-candidate',
          candidate: event.candidate
        });
      }
    };

    // Handle connection state changes
    this.pc.onconnectionstatechange = () => {
      console.log('Connection state:', this.pc.connectionState);

      if (this.pc.connectionState === 'connected') {
        console.log('WebRTC connection established!');
      }
    };

    // Handle ICE connection state changes
    this.pc.oniceconnectionstatechange = () => {
      console.log('ICE connection state:', this.pc.iceConnectionState);
    };
  }

  async createOffer() {
    try {
      const offer = await this.pc.createOffer({
        offerToReceiveAudio: true,
        offerToReceiveVideo: true
      });

      await this.pc.setLocalDescription(offer);

      // Send offer to remote peer
      this.sendSignalingMessage({
        type: 'offer',
        offer: offer
      });

    } catch (error) {
      console.error('Error creating offer:', error);
    }
  }

  async handleOffer(offer) {
    try {
      await this.pc.setRemoteDescription(offer);

      const answer = await this.pc.createAnswer();
      await this.pc.setLocalDescription(answer);

      // Send answer back
      this.sendSignalingMessage({
        type: 'answer',
        answer: answer
      });

    } catch (error) {
      console.error('Error handling offer:', error);
    }
  }

  async handleAnswer(answer) {
    try {
      await this.pc.setRemoteDescription(answer);
    } catch (error) {
      console.error('Error handling answer:', error);
    }
  }

  async handleIceCandidate(candidate) {
    try {
      await this.pc.addIceCandidate(candidate);
    } catch (error) {
      console.error('Error adding ICE candidate:', error);
    }
  }

  sendSignalingMessage(message) {
    // Implement WebSocket or signaling server communication
    console.log('Sending signaling message:', message);
  }

  // Media controls
  toggleAudio() {
    const audioTrack = this.localVideo.srcObject.getAudioTracks()[0];
    if (audioTrack) {
      audioTrack.enabled = !audioTrack.enabled;
    }
  }

  toggleVideo() {
    const videoTrack = this.localVideo.srcObject.getVideoTracks()[0];
    if (videoTrack) {
      videoTrack.enabled = !videoTrack.enabled;
    }
  }

  // Hang up the call
  hangup() {
    if (this.pc) {
      this.pc.close();
      this.pc = null;
    }

    // Stop local media
    if (this.localVideo.srcObject) {
      this.localVideo.srcObject.getTracks().forEach(track => track.stop());
      this.localVideo.srcObject = null;
    }

    this.remoteVideo.srcObject = null;
  }
}

// Usage example
const webrtcConnection = new WebRTCPeerConnection();

// Start the call
webrtcConnection.createOffer();
format
JavaScript
apis
["RTCPeerConnection","MediaDevices","MediaStream"]
features
["ice-handling","media-stream","connection-management"]
browsers
["Chrome","Firefox","Safari","Edge"]
CódigoIntermedio

Comunicación Canal Datos WebRTC

Implementación de canales de datos para mensajería y transferencia de archivos en tiempo real

Dificultad

6/10

Tiempo estimado

25 min

Etiquetas

data-channel, real-time, messaging, file-transfer, p2p

plaintext
// WebRTC Data Channel Implementation
// Real-time data communication between peers

class WebRTCDataChannel {
  constructor(peerConnection) {
    this.pc = peerConnection;
    this.dataChannel = null;
    this.messageCallbacks = new Map();

    this.setupDataChannel();
  }

  setupDataChannel() {
    // Create data channel for reliable messaging
    this.dataChannel = this.pc.createDataChannel('chat', {
      ordered: true,
      maxRetransmits: 3,
      protocol: 'json'
    });

    this.setupDataChannelHandlers();
  }

  setupDataChannelHandlers() {
    this.dataChannel.onopen = () => {
      console.log('Data channel opened');
      this.onChannelOpen();
    };

    this.dataChannel.onmessage = (event) => {
      this.handleMessage(event.data);
    };

    this.dataChannel.onclose = () => {
      console.log('Data channel closed');
      this.onChannelClose();
    };

    this.dataChannel.onerror = (error) => {
      console.error('Data channel error:', error);
      this.onChannelError(error);
    };
  }

  handleMessage(data) {
    try {
      const message = JSON.parse(data);

      switch (message.type) {
        case 'chat':
          this.onChatMessage(message);
          break;
        case 'file':
          this.onFileTransfer(message);
          break;
        case 'typing':
          this.onTypingIndicator(message);
          break;
        case 'cursor':
          this.onCursorUpdate(message);
          break;
        default:
          console.log('Unknown message type:', message.type);
      }

      // Call custom callbacks
      if (this.messageCallbacks.has(message.type)) {
        this.messageCallbacks.get(message.type)(message);
      }

    } catch (error) {
      console.error('Error parsing message:', error);
    }
  }

  // Send different types of messages
  sendChatMessage(text, sender = 'Anonymous') {
    const message = {
      type: 'chat',
      text: text,
      sender: sender,
      timestamp: Date.now()
    };

    this.sendMessage(message);
  }

  sendFile(file, onProgress) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = (event) => {
        const message = {
          type: 'file',
          name: file.name,
          size: file.size,
          mimeType: file.type,
          data: event.target.result
        };

        this.sendMessage(message);
        resolve(message);
      };

      reader.onerror = (error) => reject(error);
      reader.readAsArrayBuffer(file);
    });
  }

  sendTypingIndicator(isTyping) {
    const message = {
      type: 'typing',
      isTyping: isTyping,
      timestamp: Date.now()
    };

    this.sendMessage(message);
  }

  sendCursorUpdate(x, y) {
    const message = {
      type: 'cursor',
      x: x,
      y: y,
      timestamp: Date.now()
    };

    this.sendMessage(message);
  }

  sendCustomMessage(type, data) {
    const message = {
      type: type,
      data: data,
      timestamp: Date.now()
    };

    this.sendMessage(message);
  }

  sendMessage(message) {
    if (this.dataChannel && this.dataChannel.readyState === 'open') {
      this.dataChannel.send(JSON.stringify(message));
    } else {
      console.error('Data channel is not open');
    }
  }

  // Event handlers (can be overridden)
  onChannelOpen() {
    console.log('Data channel ready for communication');
  }

  onChannelClose() {
    console.log('Data channel communication ended');
  }

  onChannelError(error) {
    console.error('Data channel communication error:', error);
  }

  onChatMessage(message) {
    console.log('Chat message received:', message);
  }

  onFileTransfer(message) {
    console.log('File transfer received:', message);
  }

  onTypingIndicator(message) {
    console.log('Typing indicator:', message);
  }

  onCursorUpdate(message) {
    console.log('Cursor update:', message);
  }

  // Register custom message handlers
  onMessageType(type, callback) {
    this.messageCallbacks.set(type, callback);
  }

  // Data channel status
  isOpen() {
    return this.dataChannel && this.dataChannel.readyState === 'open';
  }

  close() {
    if (this.dataChannel) {
      this.dataChannel.close();
      this.dataChannel = null;
    }
  }
}

// File transfer helper class
class FileTransferHandler {
  constructor(dataChannel) {
    this.dataChannel = dataChannel;
    this.receivingFiles = new Map();
  }

  async receiveFile(fileInfo, onProgress) {
    const chunks = [];
    let receivedBytes = 0;

    return new Promise((resolve, reject) => {
      const handleChunk = (chunk) => {
        chunks.push(chunk);
        receivedBytes += chunk.byteLength;

        if (onProgress) {
          onProgress(receivedBytes, fileInfo.size);
        }

        if (receivedBytes >= fileInfo.size) {
          const blob = new Blob(chunks, { type: fileInfo.type });
          const file = new File([blob], fileInfo.name, { type: fileInfo.type });
          resolve(file);
        }
      };

      this.dataChannel.onMessageType('file-chunk', (message) => {
        if (message.fileId === fileInfo.id) {
          handleChunk(message.data);
        }
      });
    });
  }

  async sendFile(file, onProgress) {
    const chunkSize = 16384; // 16KB chunks
    const fileId = Date.now().toString();

    // Send file metadata
    this.dataChannel.sendCustomMessage('file-info', {
      id: fileId,
      name: file.name,
      size: file.size,
      type: file.type
    });

    // Send file in chunks
    const reader = new FileReader();
    let offset = 0;

    const sendNextChunk = () => {
      const chunk = file.slice(offset, offset + chunkSize);
      reader.readAsArrayBuffer(chunk);
    };

    reader.onload = (event) => {
      this.dataChannel.sendCustomMessage('file-chunk', {
        fileId: fileId,
        data: event.target.result
      });

      offset += chunkSize;

      if (onProgress) {
        onProgress(offset, file.size);
      }

      if (offset < file.size) {
        setTimeout(sendNextChunk, 10); // Small delay to prevent blocking
      }
    };

    sendNextChunk();
  }
}

// Usage example
const webrtcDataChannel = new WebRTCDataChannel(peerConnection);

// Set up message handlers
webrtcDataChannel.onMessageType('game-state', (message) => {
  updateGameDisplay(message.data);
});

// Send messages
webrtcDataChannel.sendChatMessage('Hello from peer!');
webrtcDataChannel.sendTypingIndicator(true);
format
JavaScript
apis
["RTCDataChannel","FileReader","Blob"]
features
["reliable-messaging","file-transfer","custom-protocols"]
protocols
["JSON","Binary"]
CódigoIntermedio

Implementación Compartir Pantalla

Configuración completa de compartir pantalla con soporte para pestañas y audio

Dificultad

6/10

Tiempo estimado

20 min

Etiquetas

screen-share, desktop-capture, tab-sharing, picture-in-picture, recording

plaintext
// WebRTC Screen Sharing Implementation
// Screen capture, tab sharing, and audio support

class ScreenShareManager {
  constructor(peerConnection) {
    this.pc = peerConnection;
    this.screenStream = null;
    this.localVideo = document.getElementById('localScreen');
    this.isSharing = false;
  }

  async startScreenShare(options = {}) {
    try {
      // Get screen share stream
      this.screenStream = await navigator.mediaDevices.getDisplayMedia({
        video: {
          cursor: options.cursor || 'always',
          displaySurface: options.displaySurface || 'monitor'
        },
        audio: options.audio || false,
        preferCurrentTab: options.preferCurrentTab || false
      });

      // Replace local video with screen share
      this.localVideo.srcObject = this.screenStream;

      // Add or replace video tracks in peer connection
      const videoTracks = this.screenStream.getVideoTracks();
      const sender = this.pc.getSenders().find(s =>
        s.track && s.track.kind === 'video'
      );

      if (sender && videoTracks.length > 0) {
        // Replace existing video track
        await sender.replaceTrack(videoTracks[0]);
      } else if (videoTracks.length > 0) {
        // Add new video track
        this.pc.addTrack(videoTracks[0], this.screenStream);
      }

      // Add audio tracks if available
      const audioTracks = this.screenStream.getAudioTracks();
      audioTracks.forEach(track => {
        const audioSender = this.pc.getSenders().find(s =>
          s.track && s.track.kind === 'audio'
        );

        if (audioSender) {
          audioSender.replaceTrack(track);
        } else {
          this.pc.addTrack(track, this.screenStream);
        }
      });

      this.isSharing = true;

      // Listen for stream end
      this.screenStream.getVideoTracks()[0].onended = () => {
        this.stopScreenShare();
      };

      return this.screenStream;

    } catch (error) {
      console.error('Error starting screen share:', error);
      throw error;
    }
  }

  async startTabShare() {
    try {
      // Use Chrome extension API for tab sharing (if available)
      if (chrome && chrome.desktopCapture) {
        return new Promise((resolve, reject) => {
          chrome.desktopCapture.chooseDesktopMedia(
            ['screen', 'window', 'tab'],
            (streamId) => {
              if (streamId) {
                navigator.mediaDevices.getUserMedia({
                  video: {
                    mandatory: {
                      chromeMediaSource: 'desktop',
                      chromeMediaSourceId: streamId
                    }
                  }
                }).then(resolve).catch(reject);
              } else {
                reject(new Error('No stream selected'));
              }
            }
          );
        });
      } else {
        // Fallback to standard screen capture with tab preference
        return this.startScreenShare({
          preferCurrentTab: true,
          displaySurface: 'browser'
        });
      }
    } catch (error) {
      console.error('Error starting tab share:', error);
      throw error;
    }
  }

  async stopScreenShare() {
    try {
      // Stop all tracks in screen stream
      if (this.screenStream) {
        this.screenStream.getTracks().forEach(track => track.stop());
        this.screenStream = null;
      }

      // Clear local video
      this.localVideo.srcObject = null;

      // Try to restore camera stream if available
      await this.restoreCameraStream();

      this.isSharing = false;

    } catch (error) {
      console.error('Error stopping screen share:', error);
    }
  }

  async restoreCameraStream() {
    try {
      const cameraStream = await navigator.mediaDevices.getUserMedia({
        video: true,
        audio: true
      });

      this.localVideo.srcObject = cameraStream;

      // Replace tracks in peer connection
      const videoTracks = cameraStream.getVideoTracks();
      const sender = this.pc.getSenders().find(s =>
        s.track && s.track.kind === 'video'
      );

      if (sender && videoTracks.length > 0) {
        await sender.replaceTrack(videoTracks[0]);
      }

      // Add audio tracks
      const audioTracks = cameraStream.getAudioTracks();
      audioTracks.forEach(track => {
        const audioSender = this.pc.getSenders().find(s =>
          s.track && s.track.kind === 'audio'
        );

        if (audioSender) {
          audioSender.replaceTrack(track);
        }
      });

    } catch (error) {
      console.error('Error restoring camera stream:', error);
    }
  }

  async toggleScreenShare() {
    if (this.isSharing) {
      await this.stopScreenShare();
    } else {
      await this.startScreenShare();
    }
  }

  // Get available screen sources
  async getScreenSources() {
    try {
      if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
        const devices = await navigator.mediaDevices.enumerateDevices();
        return devices.filter(device => device.kind === 'videoinput');
      }
      return [];
    } catch (error) {
      console.error('Error getting screen sources:', error);
      return [];
    }
  }

  // Check screen sharing capabilities
  getCapabilities() {
    return {
      screenCapture: !!(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia),
      tabCapture: !!(chrome && chrome.desktopCapture),
      audioCapture: !!(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia),
      cursorSupport: true,
      multipleSources: true
    };
  }

  // Add picture-in-picture support
  async enablePictureInPicture() {
    try {
      if (this.localVideo && 'requestPictureInPicture' in this.localVideo) {
        await this.localVideo.requestPictureInPicture();
      }
    } catch (error) {
      console.error('Error enabling picture-in-picture:', error);
    }
  }

  disablePictureInPicture() {
    if (this.localVideo && 'exitPictureInPicture' in this.localVideo) {
      this.localVideo.exitPictureInPicture();
    }
  }

  // Recording functionality
  startRecording() {
    if (!this.screenStream) return null;

    const chunks = [];
    const mediaRecorder = new MediaRecorder(this.screenStream, {
      mimeType: MediaRecorder.isTypeSupported('video/webm;codecs=vp9')
        ? 'video/webm;codecs=vp9'
        : 'video/webm'
    });

    mediaRecorder.ondataavailable = (event) => {
      if (event.data.size > 0) {
        chunks.push(event.data);
      }
    };

    mediaRecorder.onstop = () => {
      const blob = new Blob(chunks, { type: 'video/webm' });
      this.onRecordingComplete(blob);
    };

    mediaRecorder.start();
    return mediaRecorder;
  }

  onRecordingComplete(blob) {
    // Handle recording completion
    console.log('Screen share recording completed:', blob);

    // Create download link
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `screen-share-${Date.now()}.webm`;
    a.click();
    URL.revokeObjectURL(url);
  }

  // Get current status
  getStatus() {
    return {
      isSharing: this.isSharing,
      hasVideo: !!(this.screenStream && this.screenStream.getVideoTracks().length),
      hasAudio: !!(this.screenStream && this.screenStream.getAudioTracks().length),
      trackCount: this.screenStream ? this.screenStream.getTracks().length : 0
    };
  }
}

// UI Integration Example
class ScreenShareUI {
  constructor(screenShareManager) {
    this.screenShare = screenShareManager;
    this.setupUI();
  }

  setupUI() {
    // Create control buttons
    this.createShareButton();
    this.createTabShareButton();
    this.createStopButton();
    this.createRecordButton();

    // Create status display
    this.createStatusDisplay();
  }

  createShareButton() {
    const button = document.createElement('button');
    button.textContent = 'Share Screen';
    button.onclick = async () => {
      try {
        await this.screenShare.startScreenShare();
        this.updateStatus();
      } catch (error) {
        this.showError('Failed to start screen share: ' + error.message);
      }
    };
    document.body.appendChild(button);
  }

  createTabShareButton() {
    const button = document.createElement('button');
    button.textContent = 'Share Tab';
    button.onclick = async () => {
      try {
        await this.screenShare.startTabShare();
        this.updateStatus();
      } catch (error) {
        this.showError('Failed to start tab share: ' + error.message);
      }
    };
    document.body.appendChild(button);
  }

  createStopButton() {
    const button = document.createElement('button');
    button.textContent = 'Stop Sharing';
    button.onclick = async () => {
      await this.screenShare.stopScreenShare();
      this.updateStatus();
    };
    document.body.appendChild(button);
  }

  createRecordButton() {
    const button = document.createElement('button');
    button.textContent = 'Start Recording';

    button.onclick = () => {
      if (!this.recording) {
        this.recording = this.screenShare.startRecording();
        button.textContent = 'Stop Recording';
      } else {
        this.recording.stop();
        this.recording = null;
        button.textContent = 'Start Recording';
      }
    };

    document.body.appendChild(button);
  }

  createStatusDisplay() {
    this.statusDiv = document.createElement('div');
    this.statusDiv.style.marginTop = '10px';
    this.updateStatus();
    document.body.appendChild(this.statusDiv);
  }

  updateStatus() {
    const status = this.screenShare.getStatus();
    this.statusDiv.innerHTML = `
      <div>Sharing: ${status.isSharing ? 'Yes' : 'No'}</div>
      <div>Video: ${status.hasVideo ? 'Yes' : 'No'}</div>
      <div>Audio: ${status.hasAudio ? 'Yes' : 'No'}</div>
      <div>Tracks: ${status.trackCount}</div>
    `;
  }

  showError(message) {
    alert(message);
  }
}

// Usage example
const screenShareManager = new ScreenShareManager(peerConnection);
const screenShareUI = new ScreenShareUI(screenShareManager);
format
JavaScript
apis
["getDisplayMedia","MediaRecorder","PictureInPicture"]
features
["screen-capture","tab-capture","audio-capture","recording"]
browsers
["Chrome","Firefox","Edge"]
CódigoComplejo

Servidor de Señalización WebRTC

Servidor WebSocket para establecimiento de conexiones P2P y negociación de medios

Dificultad

8/10

Tiempo estimado

35 min

Etiquetas

signaling, websocket, peer-connection, room-management, server

plaintext
// WebRTC Signaling Server Implementation
// Node.js WebSocket server for peer connection coordination

const WebSocket = require('ws');
const http = require('http');
const express = require('express');
const cors = require('cors');

class WebRTCSignalingServer {
  constructor(port = 8080) {
    this.port = port;
    this.app = express();
    this.server = http.createServer(this.app);
    this.wss = new WebSocket.Server({ server: this.server });

    this.rooms = new Map(); // room_id -> Set of peers
    this.peers = new Map(); // peer_id -> { ws, room_id, metadata }

    this.setupExpress();
    this.setupWebSocket();
  }

  setupExpress() {
    this.app.use(cors());
    this.app.use(express.json());

    // Static files for client
    this.app.use(express.static('public'));

    // API endpoints
    this.app.get('/api/rooms', (req, res) => {
      const rooms = Array.from(this.rooms.keys()).map(roomId => ({
        id: roomId,
        peerCount: this.rooms.get(roomId).size
      }));
      res.json(rooms);
    });

    this.app.post('/api/rooms', (req, res) => {
      const roomId = this.generateRoomId();
      this.rooms.set(roomId, new Set());
      res.json({ roomId });
    });

    this.app.get('/api/rooms/:roomId/peers', (req, res) => {
      const roomId = req.params.roomId;
      const roomPeers = this.rooms.get(roomId);

      if (!roomPeers) {
        return res.status(404).json({ error: 'Room not found' });
      }

      const peers = Array.from(roomPeers).map(peerId => ({
        id: peerId,
        metadata: this.peers.get(peerId)?.metadata
      }));

      res.json(peers);
    });
  }

  setupWebSocket() {
    this.wss.on('connection', (ws, req) => {
      const peerId = this.generatePeerId();

      console.log(`Peer connected: ${peerId}`);

      // Store peer connection
      this.peers.set(peerId, {
        ws: ws,
        room_id: null,
        metadata: {
          userAgent: req.headers['user-agent'],
          ip: req.socket.remoteAddress,
          connectedAt: Date.now()
        }
      });

      // Send peer ID to client
      this.sendToPeer(peerId, {
        type: 'peer-id',
        peerId: peerId
      });

      // Handle messages
      ws.on('message', (message) => {
        try {
          const data = JSON.parse(message);
          this.handleMessage(peerId, data);
        } catch (error) {
          console.error(`Invalid message from peer ${peerId}:`, error);
          this.sendError(peerId, 'Invalid message format');
        }
      });

      // Handle disconnection
      ws.on('close', () => {
        console.log(`Peer disconnected: ${peerId}`);
        this.handlePeerDisconnect(peerId);
      });

      // Handle errors
      ws.on('error', (error) => {
        console.error(`WebSocket error for peer ${peerId}:`, error);
      });
    });
  }

  handleMessage(peerId, message) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    switch (message.type) {
      case 'join-room':
        this.handleJoinRoom(peerId, message.roomId);
        break;

      case 'leave-room':
        this.handleLeaveRoom(peerId);
        break;

      case 'offer':
        this.handleRelayMessage(peerId, message, 'offer');
        break;

      case 'answer':
        this.handleRelayMessage(peerId, message, 'answer');
        break;

      case 'ice-candidate':
        this.handleRelayMessage(peerId, message, 'ice-candidate');
        break;

      case 'room-info':
        this.sendRoomInfo(peerId);
        break;

      case 'peer-list':
        this.sendPeerList(peerId);
        break;

      case 'direct-message':
        this.handleDirectMessage(peerId, message);
        break;

      default:
        console.log(`Unknown message type: ${message.type}`);
    }
  }

  handleJoinRoom(peerId, roomId) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    // Leave current room if in one
    if (peer.room_id) {
      this.handleLeaveRoom(peerId);
    }

    // Join new room
    if (!this.rooms.has(roomId)) {
      this.rooms.set(roomId, new Set());
    }

    this.rooms.get(roomId).add(peerId);
    peer.room_id = roomId;

    console.log(`Peer ${peerId} joined room ${roomId}`);

    // Notify room about new peer
    this.broadcastToRoom(roomId, {
      type: 'peer-joined',
      peerId: peerId,
      metadata: peer.metadata
    }, peerId);

    // Send room info to new peer
    this.sendRoomInfo(peerId);
  }

  handleLeaveRoom(peerId) {
    const peer = this.peers.get(peerId);
    if (!peer || !peer.room_id) return;

    const roomId = peer.room_id;
    this.rooms.get(roomId).delete(peerId);

    // Clean up empty rooms
    if (this.rooms.get(roomId).size === 0) {
      this.rooms.delete(roomId);
    }

    console.log(`Peer ${peerId} left room ${roomId}`);

    // Notify room about peer leaving
    this.broadcastToRoom(roomId, {
      type: 'peer-left',
      peerId: peerId
    });

    peer.room_id = null;
  }

  handleRelayMessage(senderId, message, messageType) {
    const peer = this.peers.get(senderId);
    if (!peer || !peer.room_id) return;

    // Add sender info to message
    message.senderId = senderId;

    // Relay to all peers in room except sender
    this.broadcastToRoom(peer.room_id, message, senderId);
  }

  handleDirectMessage(senderId, message) {
    const recipientId = message.recipientId;
    const recipient = this.peers.get(recipientId);

    if (recipient) {
      message.senderId = senderId;
      this.sendToPeer(recipientId, message);
    } else {
      this.sendError(senderId, `Recipient ${recipientId} not found`);
    }
  }

  handlePeerDisconnect(peerId) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    // Leave room if in one
    if (peer.room_id) {
      this.handleLeaveRoom(peerId);
    }

    // Clean up peer data
    this.peers.delete(peerId);
  }

  sendToPeer(peerId, message) {
    const peer = this.peers.get(peerId);
    if (peer && peer.ws.readyState === WebSocket.OPEN) {
      peer.ws.send(JSON.stringify(message));
    }
  }

  sendError(peerId, error) {
    this.sendToPeer(peerId, {
      type: 'error',
      error: error
    });
  }

  broadcastToRoom(roomId, message, excludePeerId = null) {
    const room = this.rooms.get(roomId);
    if (!room) return;

    room.forEach(peerId => {
      if (peerId !== excludePeerId) {
        this.sendToPeer(peerId, message);
      }
    });
  }

  sendRoomInfo(peerId) {
    const peer = this.peers.get(peerId);
    if (!peer || !peer.room_id) return;

    const room = this.rooms.get(peer.room_id);
    const peers = Array.from(room || []).filter(id => id !== peerId);

    this.sendToPeer(peerId, {
      type: 'room-info',
      roomId: peer.room_id,
      peers: peers,
      peerCount: peers.length
    });
  }

  sendPeerList(peerId) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    const allPeers = Array.from(this.peers.keys())
      .filter(id => id !== peerId)
      .map(id => ({
        id: id,
        metadata: this.peers.get(id)?.metadata
      }));

    this.sendToPeer(peerId, {
      type: 'peer-list',
      peers: allPeers
    });
  }

  generatePeerId() {
    return `peer_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  generateRoomId() {
    return Math.random().toString(36).substr(2, 8).toUpperCase();
  }

  start() {
    this.server.listen(this.port, () => {
      console.log(`Signaling server running on port ${this.port}`);
    });
  }

  stop() {
    this.wss.close();
    this.server.close();
  }

  // Server statistics
  getStats() {
    return {
      totalPeers: this.peers.size,
      totalRooms: this.rooms.size,
      rooms: Array.from(this.rooms.entries()).map(([id, peers]) => ({
        id,
        peerCount: peers.size
      }))
    };
  }
}

// Room management extension
class RoomManager {
  constructor(signalingServer) {
    this.server = signalingServer;

    // Add room management endpoints
    this.server.app.delete('/api/rooms/:roomId', (req, res) => {
      const roomId = req.params.roomId;
      const room = this.server.rooms.get(roomId);

      if (!room) {
        return res.status(404).json({ error: 'Room not found' });
      }

      // Disconnect all peers in room
      room.forEach(peerId => {
        this.server.sendToPeer(peerId, {
          type: 'room-closed',
          roomId: roomId,
          reason: 'Room deleted by admin'
        });

        const peer = this.server.peers.get(peerId);
        if (peer) {
          peer.room_id = null;
        }
      });

      this.server.rooms.delete(roomId);
      res.json({ message: 'Room deleted' });
    });

    // Add room settings
    this.server.app.patch('/api/rooms/:roomId/settings', (req, res) => {
      const roomId = req.params.roomId;
      const settings = req.body;

      // Store room settings (implement room settings storage)
      console.log(`Room ${roomId} settings updated:`, settings);

      // Broadcast settings update to room
      this.server.broadcastToRoom(roomId, {
        type: 'room-settings-updated',
        roomId: roomId,
        settings: settings
      });

      res.json({ message: 'Settings updated' });
    });
  }
}

// Usage example
const signalingServer = new WebRTCSignalingServer(8080);
const roomManager = new RoomManager(signalingServer);

// Start server
signalingServer.start();

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('Shutting down signaling server...');
  signalingServer.stop();
  process.exit(0);
});

// Health check endpoint
signalingServer.app.get('/health', (req, res) => {
  const stats = signalingServer.getStats();
  res.json({
    status: 'healthy',
    ...stats
  });
});
format
JavaScript
frameworks
["Node.js","WebSocket","Express"]
features
["room-management","peer-discovery","message-relay"]
protocols
["WebSocket","HTTP"]
CódigoComplejo

Implementación Servidor de Medios SFU

Arquitectura SFU para videoconferencias escalables con múltiples participantes

Dificultad

9/10

Tiempo estimado

45 min

Etiquetas

sfu, media-server, scalable, video-conference, simulcast, quality-control

plaintext
// WebRTC SFU (Selective Forwarding Unit) Implementation
// Scalable media server for multi-participant conferences

const WebSocket = require('ws');
const { RTCPeerConnection, RTCSessionDescription } = require('wrtc');
const uuid = require('uuid');

class SFUMediaServer {
  constructor() {
    this.rooms = new Map(); // roomId -> Room instance
    this.peers = new Map(); // peerId -> Peer instance
    this.wss = new WebSocket.Server({ port: 3000 });

    this.setupWebSocket();
  }

  setupWebSocket() {
    this.wss.on('connection', (ws, req) => {
      console.log('New SFU client connected');

      ws.on('message', async (data) => {
        try {
          const message = JSON.parse(data);
          await this.handleMessage(ws, message);
        } catch (error) {
          console.error('Error handling message:', error);
        }
      });

      ws.on('close', () => {
        console.log('SFU client disconnected');
        this.handleDisconnect(ws);
      });
    });
  }

  async handleMessage(ws, message) {
    const { type, roomId, peerId, data } = message;

    switch (type) {
      case 'join-room':
        await this.handleJoinRoom(ws, roomId, peerId, data);
        break;

      case 'leave-room':
        this.handleLeaveRoom(peerId);
        break;

      case 'offer':
        await this.handleOffer(peerId, data);
        break;

      case 'ice-candidate':
        await this.handleIceCandidate(peerId, data);
        break;

      case 'request-tracks':
        await this.handleTrackRequest(peerId, data);
        break;

      case 'update-layout':
        this.handleLayoutUpdate(roomId, data);
        break;

      default:
        console.log(`Unknown message type: ${type}`);
    }
  }

  async handleJoinRoom(ws, roomId, peerId, metadata = {}) {
    try {
      // Create room if it doesn't exist
      if (!this.rooms.has(roomId)) {
        this.rooms.set(roomId, new SFURoom(roomId));
      }

      const room = this.rooms.get(roomId);

      // Create peer instance
      const peer = new SFUPeer(peerId, ws, room, metadata);
      this.peers.set(peerId, peer);

      // Add peer to room
      await room.addPeer(peer);

      console.log(`Peer ${peerId} joined room ${roomId}`);

    } catch (error) {
      console.error('Error joining room:', error);
      ws.send(JSON.stringify({
        type: 'error',
        error: error.message
      }));
    }
  }

  handleLeaveRoom(peerId) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    peer.leave();
    this.peers.delete(peerId);
  }

  async handleOffer(peerId, offerData) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    await peer.handleOffer(offerData);
  }

  async handleIceCandidate(peerId, candidateData) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    await peer.handleIceCandidate(candidateData);
  }

  async handleTrackRequest(peerId, request) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    await peer.handleTrackRequest(request);
  }

  handleLayoutUpdate(roomId, layout) {
    const room = this.rooms.get(roomId);
    if (!room) return;

    room.updateLayout(layout);
  }

  handleDisconnect(ws) {
    // Find and disconnect peer
    for (const [peerId, peer] of this.peers.entries()) {
      if (peer.ws === ws) {
        this.handleLeaveRoom(peerId);
        break;
      }
    }
  }
}

class SFURoom {
  constructor(roomId) {
    this.roomId = roomId;
    this.peers = new Map(); // peerId -> Peer instance
    this.tracks = new Map(); // trackId -> Track instance
    this.layout = 'grid'; // grid, speaker, custom
    this.maxVideoQuality = '1080p';
  }

  async addPeer(peer) {
    this.peers.set(peer.id, peer);

    // Send existing tracks to new peer
    for (const [trackId, track] of this.tracks.entries()) {
      await this.sendTrackToPeer(peer, track);
    }

    // Notify other peers about new peer
    this.broadcastToPeers({
      type: 'peer-joined',
      peerId: peer.id,
      metadata: peer.metadata
    }, peer.id);
  }

  removePeer(peerId) {
    const peer = this.peers.get(peerId);
    if (!peer) return;

    // Clean up peer's tracks
    for (const trackId of peer.trackIds) {
      this.tracks.delete(trackId);
    }

    // Remove peer from room
    this.peers.delete(peerId);

    // Notify other peers
    this.broadcastToPeers({
      type: 'peer-left',
      peerId: peerId
    });

    console.log(`Peer ${peerId} left room ${this.roomId}`);
  }

  async sendTrackToPeer(peer, track) {
    try {
      // Create transceiver for the track
      const transceiver = peer.pc.addTransceiver(track.track, {
        direction: 'sendonly'
      });

      // Store track mapping
      peer.trackMappings.set(track.id, {
        transceiver: transceiver,
        trackId: track.id
      });

      console.log(`Sent track ${track.id} to peer ${peer.id}`);

    } catch (error) {
      console.error(`Error sending track to peer ${peer.id}:`, error);
    }
  }

  addTrack(peerId, track) {
    const trackId = uuid.v4();
    const sfuTrack = new SFUTrack(trackId, peerId, track);

    this.tracks.set(trackId, sfuTrack);

    // Send track to all other peers
    this.broadcastToPeers({
      type: 'track-added',
      trackId: trackId,
      peerId: peerId,
      metadata: sfuTrack.metadata
    }, peerId);

    return sfuTrack;
  }

  removeTrack(trackId) {
    const track = this.tracks.get(trackId);
    if (!track) return;

    this.tracks.delete(trackId);

    // Notify all peers
    this.broadcastToPeers({
      type: 'track-removed',
      trackId: trackId
    });
  }

  updateLayout(layout) {
    this.layout = layout;

    // Recalculate track quality and simulcast layers
    this.optimizeTracks();

    // Notify peers about layout change
    this.broadcastToPeers({
      type: 'layout-updated',
      layout: layout
    });
  }

  optimizeTracks() {
    // Implement track optimization based on layout
    // Adjust quality levels, enable/disable simulcast layers
    console.log(`Optimizing tracks for layout: ${this.layout}`);
  }

  broadcastToPeers(message, excludePeerId = null) {
    for (const [peerId, peer] of this.peers.entries()) {
      if (peerId !== excludePeerId) {
        peer.send(message);
      }
    }
  }
}

class SFUPeer {
  constructor(id, ws, room, metadata = {}) {
    this.id = id;
    this.ws = ws;
    this.room = room;
    this.metadata = metadata;

    this.pc = new RTCPeerConnection({
      iceServers: [
        { urls: 'stun:stun.l.google.com:19302' }
      ]
    });

    this.trackIds = new Set();
    this.trackMappings = new Map();
    this.requestedTracks = new Set();

    this.setupPeerConnection();
    this.setupWebSocket();
  }

  setupPeerConnection() {
    // Handle incoming tracks
    this.pc.ontrack = (event) => {
      console.log(`Received track from peer ${this.id}:`, event.track);

      const trackId = uuid.v4();
      this.trackIds.add(trackId);

      // Add track to room
      this.room.addTrack(this.id, event.track);
    };

    // Handle ICE candidates
    this.pc.onicecandidate = (event) => {
      if (event.candidate) {
        this.send({
          type: 'ice-candidate',
          candidate: event.candidate
        });
      }
    };

    // Handle connection state
    this.pc.onconnectionstatechange = () => {
      console.log(`Peer ${this.id} connection state: ${this.pc.connectionState}`);
    };
  }

  setupWebSocket() {
    this.ws.on('close', () => {
      this.leave();
    });

    this.ws.on('error', (error) => {
      console.error(`WebSocket error for peer ${this.id}:`, error);
    });
  }

  async handleOffer(offerData) {
    try {
      const offer = new RTCSessionDescription(offerData);
      await this.pc.setRemoteDescription(offer);

      // Create answer
      const answer = await this.pc.createAnswer();
      await this.pc.setLocalDescription(answer);

      // Send answer back
      this.send({
        type: 'answer',
        answer: answer
      });

    } catch (error) {
      console.error(`Error handling offer for peer ${this.id}:`, error);
    }
  }

  async handleIceCandidate(candidateData) {
    try {
      const candidate = new RTCIceCandidate(candidateData);
      await this.pc.addIceCandidate(candidate);
    } catch (error) {
      console.error(`Error adding ICE candidate for peer ${this.id}:`, error);
    }
  }

  async handleTrackRequest(request) {
    const { trackId, quality } = request;

    if (!this.room.tracks.has(trackId)) {
      console.warn(`Track ${trackId} not found`);
      return;
    }

    // Adjust track quality based on request
    await this.adjustTrackQuality(trackId, quality);

    console.log(`Peer ${this.id} requested track ${trackId} with quality ${quality}`);
  }

  async adjustTrackQuality(trackId, quality) {
    const track = this.room.tracks.get(trackId);
    if (!track) return;

    // Implement quality adjustment logic
    // This could involve simulcast layer selection or bitrate adjustment
    console.log(`Adjusting track ${trackId} quality to ${quality}`);
  }

  send(message) {
    if (this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(message));
    }
  }

  leave() {
    console.log(`Peer ${this.id} leaving`);

    // Clean up peer connection
    if (this.pc) {
      this.pc.close();
    }

    // Remove from room
    this.room.removePeer(this.id);
  }
}

class SFUTrack {
  constructor(id, peerId, track) {
    this.id = id;
    this.peerId = peerId;
    this.track = track;
    this.metadata = {
      kind: track.kind,
      enabled: track.enabled,
      readyState: track.readyState
    };
    this.quality = 'auto'; // auto, high, medium, low
    this.simulcastLayers = [];
  }

  setQuality(quality) {
    this.quality = quality;
    // Implement actual quality adjustment
  }

  enable() {
    this.track.enabled = true;
    this.metadata.enabled = true;
  }

  disable() {
    this.track.enabled = false;
    this.metadata.enabled = false;
  }
}

// Usage example
const sfuServer = new SFUMediaServer();

console.log('SFU Media Server running on ws://localhost:3000');

// Add graceful shutdown
process.on('SIGINT', () => {
  console.log('Shutting down SFU server...');
  process.exit(0);
});

// Monitoring and statistics
class SFUMonitor {
  constructor(sfuServer) {
    this.sfu = sfuServer;

    // Add monitoring endpoint
    setInterval(() => {
      this.logStats();
    }, 30000); // Log stats every 30 seconds
  }

  logStats() {
    const stats = this.getStats();
    console.log('SFU Statistics:', stats);
  }

  getStats() {
    let totalPeers = 0;
    let totalTracks = 0;

    for (const room of this.sfu.rooms.values()) {
      totalPeers += room.peers.size;
      totalTracks += room.tracks.size;
    }

    return {
      rooms: this.sfu.rooms.size,
      peers: totalPeers,
      tracks: totalTracks,
      timestamp: new Date().toISOString()
    };
  }
}

const monitor = new SFUMonitor(sfuServer);
format
JavaScript
frameworks
["Node.js","WebSocket","WebRTC"]
features
["track-forwarding","quality-control","simulcast","room-management"]
protocols
["WebRTC","WebSocket"]

Herramientas

Herramientas que suelen usarse con este ejemplo

Relacionado

Colecciones de ejemplos relacionadas

Relacionado

Temas relacionados

Guía de uso de la herramienta

Contexto para entender esta colección de ejemplos

Título

Muestras de Comunicación en Tiempo Real WebRTC

Descripción

Muestras completas de WebRTC para comunicación de audio/video P2P, canales de datos, compartir pantalla y servidor de señalización

Resumen

Muestras completas de WebRTC para comunicación de audio/video P2P, canales de datos, compartir pantalla y servidor de señalización

Cuándo usarlo

  • Use this sample set when you want reference inputs for Muestras de Comunicación en Tiempo Real WebRTC.
  • Use it to preview output formats and compare related sample variants.

Cómo funciona

  1. 1Open a sample entry to inspect the prepared example payload or asset.
  2. 2Compare it with related tools to understand how the workflow connects.

Casos de uso

  • Implementación de Videollamada P2P
  • Comunicación Canal Datos WebRTC
  • Implementación Compartir Pantalla
  • Servidor de Señalización WebRTC
  • Implementación Servidor de Medios SFU

Preguntas frecuentes

What is included in Muestras de Comunicación en Tiempo Real WebRTC?

This page includes curated sample entries, related tools, and neighboring sample collections for Muestras de Comunicación en Tiempo Real WebRTC.

Tema

Captura autorizada de webhooks y revisión de confianza del endpoint

Captura y repite solicitudes webhook controladas, revisa la evidencia de entrega y comprueba el certificado HTTPS del receptor sin confundir un certificado válido con confianza empresarial.

Tema

Conversión de formatos para entrega de video: audio, streams, GIF, WebM y MP4

Define el destino de entrega, extrae solo el stream necesario y convierte animaciones breves, dejando constancia de la compatibilidad, la calidad y el tamaño resultantes.

Tema

Inferir JSON Schema desde muestras y hojas de cálculo

Obtén un borrador revisable de JSON Schema a partir de muestras JSON representativas o filas XLSX, y revisa tipos, obligatoriedad, nulos y valores atípicos antes de usarlo como contrato.

Tema

Conversión de contenedor de vídeo con control deliberado de transcodificación

Convierte vídeos entre los contenedores MP4, AVI, FLV, MKV, MOV y WebM, mantén bajo control el CRF y el bitrate durante la recodificación y procesa toda la videoteca por lotes con una sola configuración documentada.

Inicio
Herramientas3201
Ejemplos593
Visualizaciones465
Visores de archivos103
Temas233
Etiquetas333
Imágenes, audio y video657

Herramientas de procesamiento, conversión y optimización de imágenes, audio y video

Matemáticas, fechas y finanzas644

Calculadoras, herramientas numéricas, lógica de fechas, estadística y finanzas

Diseño y color297

Herramientas de color, maquetación, gráficos, estilo visual y apoyo al diseño

Texto y redacción209

Herramientas de redacción, limpieza, formato, extracción y análisis de texto

Desarrollo y Web193

Utilidades para desarrolladores, red, depuración web y automatización

Conversión y codificación175

Herramientas de conversión de formatos, archivos, unidades, archivos comprimidos y codificación

Revisión de fuentes FLAC/WAV y entrega de evidencias
Revisión e integración de un componente de sorteos autoalojado
Revisión de lotes de códigos Stripe y comprobación previa a la importación
Revisión del descarte fotográfico y entrega de candidatos para edición
Estructurar una captura de interfaz, revisarla y entregarla a desarrollo

© 2026 Elysia Tools. Todos los derechos reservados.

DocumentaciónPreciosAcerca deTérminos de servicioPolítica de privacidadCookiesContacto

Data Analysis

Detector de anomalias en series temporales

Sube datos de series temporales en CSV o JSON, detecta anomalías con Z-Score e IQR y devuelve un informe con grafico

Data Analysis

Analizador de pronostico y estacionalidad

Pronostica periodos futuros desde series temporales CSV o JSON y muestra tendencia, componente estacional y residual

Document Tools

Generador PDF de entrenamiento fitness

Genera un PDF imprimible de entrenamiento desde JSON o CSV con tablas, casillas de progreso y QR opcionales

Data Processing

Fusionador de JSON

Combina múltiples archivos JSON con varias estrategias de fusión (fusión profunda, sobrescribir, etc.)

Security

Generador JWT

Genera tokens JWT con reclamos y algoritmos personalizados

Format Conversion

Text to Excel Converter

Convert various text formats to Excel with customizable parsing and formatting options

Format Conversion

Convertidor de Texto a JSON

Convierte varios formatos de texto a JSON con opciones de análisis personalizables

Document Tools

Panel de Ventas PDF

Crea un informe visual PDF con graficos, datos y conclusiones

Medios

Ejemplos de Códigos QR

Imágenes de códigos QR de muestra para probar herramientas de lectura y generación de códigos QR

Documentos

Muestras PDF

Muestras PDF generadas por herramientas 2026-02-01 a 2026-02-10

DevOps

Muestras de Análisis de Logs ELK Stack

Muestras completas de ELK Stack para agregación, procesamiento y visualización de logs en sistemas distribuidos

C++

Ejemplos de Serialización C++ Windows

Ejemplos de serialización de datos Windows C++ incluyendo serialización/deserialización JSON y análisis XML

Medios

Ejemplos SVG

Ejemplos de Gráficos Vectoriales Escalables (SVG) que demuestran varias características y técnicas SVG

Tecnologías web

Aplicaciones Web Progresivas (PWA)

Construye aplicaciones web progresivas modernas, confiables y atractivas con capacidades offline