πŸ“‘ WebRTC Events

Sistema de Notificaciones WebRTC - PujasOnline Streaming Service

← Volver al Inicio

πŸ“Š Resumen del Sistema

βœ… Completitud del Sistema: 100%

El sistema de notificaciones WebRTC estΓ‘ COMPLETO con todos los eventos implementados y documentados.

EstadΓ­sticas

  • Eventos Totales: 18 eventos
  • Eventos CrΓ­ticos: 4 (producer-closed, transport-closed, peer-left, consume-error)
  • Eventos de Alta Prioridad: 7
  • Eventos de Media Prioridad: 7

Eventos Nuevos Agregados (2025-10-31)

  • βœ… transport-closed - NotificaciΓ³n de transport cerrado
  • βœ… transport-state-changed πŸ†• - Cambios de estado del transport
  • βœ… peer-left - NotificaciΓ³n de peer desconectado
  • βœ… peer-joined - NotificaciΓ³n de peer conectado
  • βœ… producer-created - Producer creado (interno)
  • βœ… producer-paused - Producer pausado
  • βœ… producer-resumed - Producer reanudado
  • βœ… consume-error - Error al consumir producer

Vistas Actualizadas

  • βœ… bidder-asm-stream.html - Manejo completo de eventos
  • βœ… host-asm-stream.html - Monitoreo de eventos
  • βœ… viewer-stream.html - Limpieza automΓ‘tica de recursos
  • βœ… videoconference.html - Multi-party event handling

πŸ—οΈ Arquitectura del Sistema de Eventos

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ MEDIASOUP (Capa WebRTC Nativa)                                 β”‚
β”‚ - producer.on('close')                                          β”‚
β”‚ - producer.on('pause')                                          β”‚
β”‚ - transport.on('close')                                         β”‚
β”‚ - consumer.on('producerclose')                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ EVENT BUS (room.mjs)                                            β”‚
β”‚ - Centraliza todos los eventos                                 β”‚
β”‚ - room.emit('ProducerClosed', data)                            β”‚
β”‚ - room.emit('TransportClosed', data)                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ WEBSOCKET BROADCAST (ws.mjs)                                   β”‚
β”‚ - room.eventBus.onProducerClosed = (data) => broadcast(...)   β”‚
β”‚ - EnvΓ­a eventos a todos los peers conectados                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ CLIENTES (bidder-asm-stream.html, host-asm-stream.html)       β”‚
β”‚ - ws.onmessage: if (m.type === 'producer-closed') {...}       β”‚
β”‚ - Actualiza UI segΓΊn evento recibido                           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          

Event Bus

Sistema centralizado para emitir eventos WebRTC de manera consistente:

const room = {
  eventBus: {
    onProducerCreated: null,
    onProducerClosed: null,
    onProducerPaused: null,
    onProducerResumed: null,
    onTransportClosed: null,
    onDataProducerClosed: null,
  },

  emit(event, data) {
    const handler = this.eventBus[\`on\${event}\`];
    if (handler && typeof handler === 'function') {
      handler(data);
    }
  },
};

🎬 Eventos de Ciclo de Vida

welcome Alta Prioridad
πŸ“€ DirecciΓ³n: Server β†’ Client (unicast) ⏱️ CuΓ‘ndo: ConexiΓ³n inicial
Primer mensaje del servidor al cliente, informa su identidad asignada (peerId, role, roomId).
{
  "type": "welcome",
  "peerId": "uuid-del-peer",
  "roomId": "sala-100",
  "role": "bidder"
}
rtp-capabilities Alta Prioridad
πŸ“€ DirecciΓ³n: Server β†’ Client (unicast) ⏱️ CuΓ‘ndo: DespuΓ©s de welcome
EnvΓ­a las capacidades del router mediasoup para que el cliente pueda cargar el Device y negociar codecs.
{
  "type": "rtp-capabilities",
  "rtpCapabilities": {
    "codecs": [...],
    "headerExtensions": [...]
  }
}

πŸ“Ή Eventos de Producers

producer-announced Alta Prioridad
πŸ“’ DirecciΓ³n: Server β†’ All Clients (broadcast) ⏱️ CuΓ‘ndo: Nuevo producer creado
Notifica a todos los clientes que hay un nuevo producer disponible para consumir (video o audio).
{
  "type": "producer-announced",
  "roomId": "sala-100",
  "peerId": "peer-abc123",
  "producerId": "producer-xyz789",
  "kind": "video"
}
producer-closed ⭐ CRÍTICO
πŸ“’ DirecciΓ³n: Server β†’ All Clients (broadcast) ⏱️ CuΓ‘ndo: Producer se cierra
Notifica que un producer ya no estΓ‘ disponible (peer detuvo cΓ‘mara/micrΓ³fono, se desconectΓ³, etc.). Este es el evento mΓ‘s importante para limpieza de UI.
{
  "type": "producer-closed",
  "roomId": "sala-100",
  "peerId": "peer-abc123",
  "producerId": "producer-xyz789",
  "kind": "video",
  "serverTs": 1234567890
}

Ejemplo de Manejo:

if (m.type === 'producer-closed') {
  const { producerId, kind, peerId } = m;

  if (kind === 'video' && mediaEls.has(peerId)) {
    updateVideoState(peerId, 'ended', 'disconnected',
      'El auctioneer detuvo la transmisiΓ³n');
  }

  producers.delete(producerId);
}
producer-paused πŸ†• Media Prioridad
πŸ“’ DirecciΓ³n: Server β†’ All Clients (broadcast) ⏱️ CuΓ‘ndo: Producer se pausa
Indica que un producer se pausΓ³ temporalmente (ej: auctioneer mutea micrΓ³fono). Diferente de cerrar permanentemente.
{
  "type": "producer-paused",
  "roomId": "sala-100",
  "peerId": "peer-abc123",
  "producerId": "producer-xyz789",
  "kind": "audio",
  "serverTs": 1234567890
}
producer-resumed πŸ†• Media Prioridad
πŸ“’ DirecciΓ³n: Server β†’ All Clients (broadcast) ⏱️ CuΓ‘ndo: Producer se reanuda
Indica que un producer previamente pausado vuelve a estar activo.
{
  "type": "producer-resumed",
  "roomId": "sala-100",
  "peerId": "peer-abc123",
  "producerId": "producer-xyz789",
  "kind": "audio",
  "serverTs": 1234567890
}

🚚 Eventos de Transports

transport-closed ⭐ CRÍTICO
πŸ“’ DirecciΓ³n: Server β†’ All Clients (broadcast) ⏱️ CuΓ‘ndo: Transport se cierra
Notifica que un transport WebRTC se cerrΓ³ (falla de red, desconexiΓ³n, timeout). Este evento es crΓ­tico para detectar pΓ©rdida de conexiΓ³n.
{
  "type": "transport-closed",
  "roomId": "sala-100",
  "transportId": "transport-123",
  "peerId": "peer-abc123",
  "direction": "send",
  "serverTs": 1234567890
}

Ejemplo de Manejo:

if (m.type === 'transport-closed') {
  const { transportId, peerId, direction } = m;

  if (direction === 'send' && mediaEls.has(peerId)) {
    updateVideoState(peerId, 'ended', 'disconnected', 'ConexiΓ³n perdida');
  }
}
transport-created Alta Prioridad
πŸ“€ DirecciΓ³n: Server β†’ Client (unicast) ⏱️ CuΓ‘ndo: Respuesta a create-transport
EnvΓ­a parΓ‘metros para que el cliente cree el transport WebRTC (ICE candidates, DTLS parameters, etc.).
{
  "type": "transport-created",
  "direction": "send",
  "params": {
    "id": "transport-123",
    "iceParameters": {...},
    "iceCandidates": [...],
    "dtlsParameters": {...}
  }
}

πŸ‘₯ Eventos de Peers

peer-joined πŸ†• Media Prioridad
πŸ“’ DirecciΓ³n: Server β†’ All Clients (broadcast) ⏱️ CuΓ‘ndo: Peer se conecta
Notifica que un nuevo peer se ha unido a la sala (antes de que produzca video/audio).
{
  "type": "peer-joined",
  "roomId": "sala-100",
  "peerId": "peer-abc123",
  "role": "bidder",
  "serverTs": 1234567890
}
peer-left ⭐ CRÍTICO
πŸ“’ DirecciΓ³n: Server β†’ All Clients (broadcast) ⏱️ CuΓ‘ndo: Peer se desconecta
Notifica que un peer se ha desconectado (cerrΓ³ navegador, perdiΓ³ conexiΓ³n, timeout). CrΓ­tico para limpieza de recursos y UI.
{
  "type": "peer-left",
  "roomId": "sala-100",
  "peerId": "peer-abc123",
  "role": "auctioneer",
  "serverTs": 1234567890
}

Ejemplo de Manejo:

if (m.type === 'peer-left') {
  const { peerId, role } = m;

  // Limpiar producers del peer
  for (const [producerId, info] of producers.entries()) {
    if (info.peerId === peerId) {
      producers.delete(producerId);
    }
  }

  // Limpiar UI
  if (mediaEls.has(peerId)) {
    const { tile, healthCheckInterval } = mediaEls.get(peerId);
    if (healthCheckInterval) clearInterval(healthCheckInterval);
    tile.remove();
    mediaEls.delete(peerId);
  }
}

🚨 Eventos de Errores

consume-error πŸ†• Alta Prioridad
πŸ“€ DirecciΓ³n: Server β†’ Client (unicast) ⏱️ CuΓ‘ndo: Falla al consumir producer
Notifica que no se pudo crear un consumer para un producer (codec incompatible, producer no existe, etc.).
{
  "type": "consume-error",
  "producerId": "producer-xyz789",
  "error": "router cannot consume this producer",
  "code": "CONSUME_FAILED",
  "serverTs": 1234567890
}

Ejemplo de Manejo:

if (m.type === 'consume-error') {
  const { producerId, error, code } = m;
  console.error('❌ Error al consumir:', producerId, error);
  showErrorNotification(\`No se puede reproducir: \${error}\`);
}

πŸ’» Ejemplos de ImplementaciΓ³n

Cliente: Manejo Completo de Eventos

ws.onmessage = async (ev) => {
  const m = JSON.parse(ev.data);

  // ═══════════════════════════════════════════════════════════
  // CICLO DE VIDA
  // ═══════════════════════════════════════════════════════════

  if (m.type === 'welcome') {
    const { peerId, roomId, role } = m;
    myPeerId = peerId;
    log(\`Conectado como \${role} en \${roomId}\`);
    return;
  }

  if (m.type === 'rtp-capabilities') {
    device = new mediasoupClient.Device();
    await device.load({ routerRtpCapabilities: m.rtpCapabilities });
    return;
  }

  // ═══════════════════════════════════════════════════════════
  // PRODUCERS
  // ═══════════════════════════════════════════════════════════

  if (m.type === 'producer-announced') {
    const { producerId, kind, peerId } = m;
    producers.set(producerId, { kind, peerId });
    consume(producerId);
    return;
  }

  if (m.type === 'producer-closed') {
    const { producerId, kind, peerId } = m;
    if (kind === 'video' && mediaEls.has(peerId)) {
      updateVideoState(peerId, 'ended', 'disconnected',
        'El auctioneer detuvo la transmisiΓ³n');
    }
    producers.delete(producerId);
    return;
  }

  if (m.type === 'producer-paused') {
    const { producerId, kind, peerId } = m;
    if (kind === 'video') {
      updateVideoState(peerId, 'paused', null, 'Video pausado');
    }
    return;
  }

  if (m.type === 'producer-resumed') {
    const { producerId, kind, peerId } = m;
    if (kind === 'video') {
      checkVideoPlaying(peerId);
    }
    return;
  }

  // ═══════════════════════════════════════════════════════════
  // TRANSPORTS
  // ═══════════════════════════════════════════════════════════

  if (m.type === 'transport-closed') {
    const { transportId, peerId, direction } = m;
    if (direction === 'send' && mediaEls.has(peerId)) {
      updateVideoState(peerId, 'ended', 'disconnected', 'ConexiΓ³n perdida');
    }
    return;
  }

  // ═══════════════════════════════════════════════════════════
  // PEERS
  // ═══════════════════════════════════════════════════════════

  if (m.type === 'peer-joined') {
    const { peerId, role } = m;
    log('πŸ‘‹ Peer conectado:', peerId, role);
    return;
  }

  if (m.type === 'peer-left') {
    const { peerId, role } = m;
    log('πŸ‘‹ Peer desconectado:', peerId, role);

    // Limpiar producers
    for (const [producerId, info] of producers.entries()) {
      if (info.peerId === peerId) {
        producers.delete(producerId);
      }
    }

    // Limpiar UI
    if (mediaEls.has(peerId)) {
      const { tile, healthCheckInterval } = mediaEls.get(peerId);
      if (healthCheckInterval) clearInterval(healthCheckInterval);
      tile.remove();
      mediaEls.delete(peerId);
    }
    return;
  }

  // ═══════════════════════════════════════════════════════════
  // ERRORES
  // ═══════════════════════════════════════════════════════════

  if (m.type === 'consume-error') {
    const { producerId, error } = m;
    log('❌ Error al consumir:', producerId, error);
    return;
  }
};

Flujo Completo: Auctioneer Inicia Streaming

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  AUCTIONEER β”‚                 β”‚   SERVER    β”‚                 β”‚   BIDDER    β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚                               β”‚                               β”‚
       β”‚ 1. WebSocket connect          β”‚                               β”‚
       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚                               β”‚
       β”‚                               β”‚                               β”‚
       β”‚ 2. welcome, rtp-capabilities  β”‚                               β”‚
       β”‚<───────────────────────────────                               β”‚
       β”‚                               β”‚                               β”‚
       β”‚ 3. create-transport (send)    β”‚                               β”‚
       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚                               β”‚
       β”‚                               β”‚                               β”‚
       β”‚ 4. transport-created          β”‚                               β”‚
       β”‚<───────────────────────────────                               β”‚
       β”‚                               β”‚                               β”‚
       β”‚ 5. connect-transport          β”‚                               β”‚
       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚                               β”‚
       β”‚                               β”‚                               β”‚
       β”‚ 6. transport-connected        β”‚                               β”‚
       β”‚<───────────────────────────────                               β”‚
       β”‚                               β”‚                               β”‚
       β”‚ 7. produce (video)            β”‚                               β”‚
       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚                               β”‚
       β”‚                               β”‚                               β”‚
       β”‚ 8. produced                   β”‚ 9. producer-announced (broadcast)
       β”‚<─────────────────────────────────────────────────────────────>β”‚
       β”‚                               β”‚                               β”‚
       β”‚                               β”‚ 10. (Bidder consume video...) β”‚
       β”‚                               β”‚<───────────────────────────────
       β”‚                               β”‚                               β”‚
       β”‚ 11. Detener cΓ‘mara            β”‚                               β”‚
       β”‚ (producer.close())            β”‚                               β”‚
       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚                               β”‚
       β”‚                               β”‚                               β”‚
       β”‚                               β”‚ 12. producer-closed (broadcast)
       β”‚                               β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€>β”‚
       β”‚                               β”‚                               β”‚
       β”‚                               β”‚ 13. UI muestra "TransmisiΓ³n   β”‚
       β”‚                               β”‚     finalizada"               β”‚
       β”‚                               β”‚                               β”‚
          

πŸ“Š Tabla Resumen de Eventos

Evento DirecciΓ³n Prioridad CuΓ‘ndo
welcome Server β†’ Client Alta ConexiΓ³n inicial
rtp-capabilities Server β†’ Client Alta DespuΓ©s de welcome
producer-announced Server β†’ All Alta Nuevo producer
producer-closed Server β†’ All CRÍTICA Producer cierra
producer-paused πŸ†• Server β†’ All Media Producer pausa
producer-resumed πŸ†• Server β†’ All Media Producer reanuda
transport-created Server β†’ Client Alta Crear transport
transport-closed πŸ†• Server β†’ All CRÍTICA Transport cierra
transport-state-changed πŸ†• Server β†’ All Media Cambio estado transport
peer-joined πŸ†• Server β†’ All Media Peer conecta
peer-left πŸ†• Server β†’ All CRÍTICA Peer desconecta
consume-error πŸ†• Server β†’ Client Alta Error al consumir
producers (lista) Server β†’ Client Alta Late-joiner sync
consumable Server β†’ Client Alta Params de consumer

🎯 Eventos Críticos (4)

Estos eventos son esenciales para el correcto funcionamiento del sistema:

  • producer-closed - Limpieza de UI cuando se detiene video/audio
  • transport-closed - DetecciΓ³n de fallas de conexiΓ³n
  • peer-left - Limpieza de recursos al desconectarse
  • consume-error - Manejo de errores de reproducciΓ³n