Conector de vídeo para Node.js

La biblioteca Vonage Video Connector para Node.js te permite participar mediante programación en sesiones de la Video API de Vonage como participante del lado del servidor. Esta biblioteca te permite conectarte a sesiones de vídeo, publicar y suscribirte a transmisiones, y procesar datos de audio y vídeo en tiempo real.

Importante La biblioteca Vonage Video Connector para Node.js está diseñada para aplicaciones del lado del servidor y requiere credenciales y tokens válidos de la Video API de Vonage con los permisos adecuados.

Esta página trata sobre la API de Node.js. Para conocer los Concepts, los formatos multimedia y el comportamiento en tiempo de ejecución comunes a todas las bibliotecas de Video Connector, consulta la Conector de vídeo guía. Si trabajas con Python, consulta Conector de vídeo para Python.

Este tema incluye las siguientes secciones:

Primeros pasos

El Vonage Video Connector para Node.js se distribuye como el @vonage/video-connector paquete.

Para instalar la biblioteca, ejecuta:

npm install @vonage/video-connector

El paquete incluye binarios nativos ya compilados y no requiere ningún paso de compilación. Las declaraciones de tipos de TypeScript están incluidas, por lo que no es necesario @types Se necesita el paquete.

Requisitos

Esta biblioteca requiere Node.js 18 o una versión posterior, ejecutándose en plataformas Linux x64 o ARM64. Recomendamos utilizar Debian Bookworm, ya que es la distribución en la que se ha probado más a fondo.

Al cargar la biblioteca en una plataforma no compatible se produce un error en el momento de la importación, ya que no existe el módulo nativo para esa combinación de plataforma y arquitectura.

La API basada en promesas

La biblioteca Node.js presenta las operaciones del ciclo de vida como promesas en lugar de como funciones de devolución de llamada de finalización. Esto define la API en su conjunto, por lo que conviene comprenderlo antes de seguir leyendo el resto de esta página.

Cada uno de connect(), publish(), subscribe(), disconnect(), unpublish()y unsubscribe() devuelve una promesa:

Método Se aprueba lo siguiente: Se resuelve cuando
connect() Session Se ha establecido la conexión de la sesión
publish() Publisher Se ha creado tu transmisión publicada
subscribe() Subscriber La suscripción ya está activada
disconnect() void La desconexión se ha completado
unpublish() void Se ha completado la retirada de la publicación
unsubscribe() void Se ha completado la baja

Por consiguiente, los eventos del ciclo de vida que se producen una sola vez no tienen una llamada de retorno correspondiente en esta biblioteca: la promesa es la notificación. No hay onConnected, sin editorial onStreamCreated, y ningún suscriptor onConnected para registrarse. El resto de eventos son funciones de devolución de llamada que se pasan en el objeto de argumentos.

La gestión de errores sigue la misma estructura. Si se produce un error antes de una operación se liquida, la promesa se rechaza y tu onError No se ejecuta la función de devolución de llamada; el rechazo constituye la notificación de error. Si se produce un error después de Una vez que la operación se haya completado, tu onError La función de devolución de llamada se ejecuta con normalidad.

import { VonageVideoClient } from '@vonage/video-connector';

const client = new VonageVideoClient();

try {
  const session = await client.connect(applicationId, sessionId, token, {
    // Fires only for errors after the connection is established
    onError: (session, description, code) => {
      console.error(`Session error [${code}]: ${description}`);
    },
  });
  console.log(`Connected to session ${session.sessionId}`);
} catch (error) {
  // Failure to connect surfaces here, not in onError
  console.error('Failed to connect:', error);
}

Importante Crea solo uno VonageVideoClient una instancia por proceso. El SDK subyacente admite una única conexión activa, y la creación de varias instancias da lugar a un comportamiento indefinido.

Estructuras de datos

Todos los tipos que se indican a continuación se exportan desde el paquete y pueden importarse como tipos.

Sesión

Representa una sesión de la Video API de Vonage a la que se pueden conectar los clientes:

interface Session {
  sessionId: string;  // Unique identifier for the session
}

El Session El objeto se pasa a las funciones de devolución de llamada a nivel de sesión para identificar qué sesión ha desencadenado el evento.

Conexión

Representa la conexión de un participante a una sesión:

interface Connection {
  connectionId: string;  // Unique identifier for the connection
  creationTime: number;  // Unix timestamp in milliseconds
  data: string;          // Connection data (encoded in the token)
}

Los datos de conexión pueden utilizarse para almacenar metadatos personalizados sobre los participantes, como los identificadores de usuario o sus funciones.

Ten en cuenta que creationTime es una marca de tiempo numérica de Unix expresada en milisegundos, por lo que se puede pasar directamente a new Date(connection.creationTime).

Transmisión

Representa una transmisión multimedia (audio/vídeo) publicada por un participante:

interface Stream {
  streamId: string;       // Unique identifier for the stream
  connection: Connection; // The connection that published this stream
}

Editorial

Representa su flujo publicado en la sesión:

interface Publisher {
  stream: Stream;  // The underlying stream for this publisher
}

Suscriptor

Representa una suscripción al flujo de otro participante:

interface Subscriber {
  stream: Stream;  // The stream being subscribed to
}

AudioData

Representa los datos de audio que se transmiten o se reciben:

interface AudioData {
  data: Buffer;                        // Raw PCM 16-bit LE samples
  sampleRate: SampleRate;              // 8000 | 12000 | 16000 | 24000 | 32000 | 44100 | 48000
  numberOfChannels: NumberOfChannels;  // 1 (mono) or 2 (stereo)
  numberOfFrames: number;              // One frame = one sample per channel
}

Requisitos de formato de audio:

  • data debe ser un Buffer que contiene muestras de 16 bits con signo en formato «little-endian»
  • Frecuencias de muestreo válidas: 8000, 12000, 16000, 24000, 32000, 44100, 48000 Hz
  • Canales: 1 (mono) o 2 (estéreo)
  • data.byteLength debe ser como mínimo numberOfFrames * numberOfChannels * 2 bytes

VideoFrame

Representa los datos de un fotograma de vídeo que se están transmitiendo o recibiendo:

interface VideoFrame {
  data: Buffer;               // Raw pixel data
  width: number;              // Frame width in pixels
  height: number;             // Frame height in pixels
  format?: VideoFrameFormat;  // 'YUV420P' | 'RGB24' | 'ARGB32', defaults to 'YUV420P'
}

Requisitos de formato de vídeo:

  • data debe ser un Buffer de valores sin signo de 8 bits
  • width y height son propiedades planas del marco; no hay ningún objeto de resolución independiente
  • Resolución máxima: 1920x1080 píxeles (2.073.600 píxeles en total)
  • El tamaño de búfer necesario depende del formato:
Formato Obligatorio data.byteLength
YUV420P width * height * 3 / 2
RGB24 width * height * 3
ARGB32 width * height * 4

Resolución de vídeo

Representa las dimensiones de un fotograma de vídeo:

interface VideoResolution {
  width: number;   // Width in pixels. Must be a positive integer
  height: number;  // Height in pixels. Must be a positive integer
}

Número total de píxeles (width * height) no debe superar los 1920 × 1080 (2 073 600). Este tipo lo utiliza SubscriberVideoSettings.preferredResolution; la geometría del editor en SessionVideoPublisherSettings y además VideoFrame utiliza planos width y height propiedades en su lugar.

Datos de los subtítulos

Representa los datos del texto de los subtítulos recibidos de una transmisión a la que se está suscrito:

interface CaptionsData {
  text: string;     // The caption text content
  isFinal: boolean; // true for final captions, false for interim/partial captions
}

MediaBufferStats

Proporciona estadísticas sobre los búferes de los medios de publicación:

interface BufferStats {
  durationMs: number;  // Duration of queued media in milliseconds
}

interface MediaBufferStats {
  audio: BufferStats | null;  // null if no audio publisher is active
  video: BufferStats | null;  // null if no video publisher is active
}

Las duraciones son números sin formato, expresados en milisegundos.

Tipos de validación

La biblioteca exporta tipos de unión para los valores que valida, por lo que los literales no válidos se detectan en tiempo de compilación:

type SampleRate = 8000 | 12000 | 16000 | 24000 | 32000 | 44100 | 48000;
type NumberOfChannels = 1 | 2;
type LogLevel = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
type VideoFrameFormat = 'YUV420P' | 'RGB24' | 'ARGB32';

Estructuras de configuración

Configuración de sesión

Configura el comportamiento a nivel de sesión:

import type { SessionSettings } from '@vonage/video-connector';

const sessionSettings: SessionSettings = {
  enableMigration: false,   // Enable automatic session migration. Defaults to false
  av: avSettings,           // Optional audio/video configuration
  logging: { level: 'INFO' } // Optional logging configuration
};

SesiónAVSettings

Configura los ajustes de audio y vídeo para la sesión:

import type { SessionAVSettings } from '@vonage/video-connector';

const avSettings: SessionAVSettings = {
  audioPublisher: { sampleRate: 48000, channels: 2 },
  audioSubscribersMix: { sampleRate: 48000, channels: 1 },
  videoPublisher: { width: 1280, height: 720, fps: 30, format: 'YUV420P' }
};
  • audioPublisher: Define el formato de los datos de audio que se facilitan a través de addAudio(). Los datos de audio que envíes deben coincidir con la frecuencia de muestreo y el número de canales de esta configuración.
  • audioSuscriptoresMix: Define el formato del audio mezclado que recibes de todas las transmisiones a las que estás suscrito a través del nivel de sesión onAudioData callback. La biblioteca se encarga automáticamente de mezclar el audio de varios abonados y de remuestrear/convertir los canales para que coincidan con el formato especificado.

Para obtener orientación sobre cómo elegir estos formatos, consulta El audio para la publicación frente al audio para la suscripción.

SessionAudioSettings

Configura el formato de audio para publicar o recibir datos de audio:

import type { SessionAudioSettings } from '@vonage/video-connector';

const audioSettings: SessionAudioSettings = {
  sampleRate: 48000,  // Optional, defaults to 48000
  channels: 1         // Optional, 1 (mono) or 2 (stereo), defaults to 2
};

Ten en cuenta que la propiedad se llama channels aquí, mientras que AudioData usos numberOfChannels.

SessionVideoPublisherSettings

Configura los ajustes de vídeo para la publicación:

import type { SessionVideoPublisherSettings } from '@vonage/video-connector';

const videoSettings: SessionVideoPublisherSettings = {
  width: 1280,        // Optional, defaults to 640
  height: 720,        // Optional, defaults to 480
  fps: 30,            // Optional, 1-30, defaults to 30
  format: 'YUV420P'   // Optional, defaults to 'YUV420P'
};

El ancho y la altura son propiedades planas, en lugar de un objeto de resolución anidado.

Configuración de registro

Controla la verbosidad del registro:

import type { LoggingSettings } from '@vonage/video-connector';

const loggingSettings: LoggingSettings = {
  level: 'INFO'  // Required: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE'
};

Configuración del editor

Configura el flujo publicado:

import type { PublisherSettings } from '@vonage/video-connector';

const publisherSettings: PublisherSettings = {
  name: 'My Application',  // Required: name for your published stream
  hasAudio: true,          // Required: whether to publish audio
  hasVideo: true,          // Required: whether to publish video
  enableCaptions: false,   // Optional: enable live captions for this stream. Defaults to false
  audioSettings: {         // Optional
    enableStereoMode: true,
    enableOpusDtx: false
  }
};

Nota: Al menos uno de hasAudio o hasVideo debe ser true. Pasando ambos como false lanza un ValidationError de forma sincrónica desde publish().

Nota: Establecer enableCaptions: true para que los suscriptores puedan recibir el texto de los subtítulos en directo de esta retransmisión a través del suscriptor onCaptionText callback. Los subtítulos están desactivados por defecto.

Configuración de audio del editor

Configura los ajustes de audio de tu transmisión publicada:

import type { PublisherAudioSettings } from '@vonage/video-connector';

const audioSettings: PublisherAudioSettings = {
  enableStereoMode: true,  // Optional: publish in stereo. Defaults to true
  enableOpusDtx: false     // Optional: enable discontinuous transmission. Defaults to false
};

Transmisión discontinua (DTX) deja de enviar paquetes de audio durante el silencio, ahorrando ancho de banda.

Configuración del suscriptor

Configura el comportamiento de los suscriptores:

import type { SubscriberSettings } from '@vonage/video-connector';

const subscriberSettings: SubscriberSettings = {
  subscribeToAudio: true,     // Optional, defaults to true
  subscribeToVideo: true,     // Optional, defaults to true
  subscribeToCaptions: false, // Optional, defaults to false
  videoSettings: {
    preferredResolution: { width: 640, height: 480 },
    preferredFrameRate: 15
  }
};

Las leyendas deben estar activadas en el lado de la publicación y cada suscriptor debe dar su consentimiento a través de subscribeToCaptions.

SubscriberVideoSettings

Configura las preferencias de vídeo de los abonados:

import type { SubscriberVideoSettings } from '@vonage/video-connector';

const videoSettings: SubscriberVideoSettings = {
  preferredResolution: { width: 640, height: 480 },  // Optional
  preferredFrameRate: 15                             // Optional, 1-30
};

Estos ajustes solicitan un nivel específico de calidad de retransmisión simultánea a la SFU de la Video API de Vonage. Para obtener una explicación de cómo los tiene en cuenta la SFU, consulta Resolución y frecuencia de fotogramas recomendadas para los suscriptores.

Conectarse a una sesión

Conexión básica

Para conectarte a una sesión de la Video API de Vonage, necesitas tu ID de aplicación (clave API si utilizas Tokbox), el ID de sesión y un token válido:

import { VonageVideoClient, type SessionSettings } from '@vonage/video-connector';

const client = new VonageVideoClient();

const sessionSettings: SessionSettings = {
  enableMigration: false,
  av: {
    audioSubscribersMix: { sampleRate: 48000, channels: 1 }
  },
  logging: { level: 'INFO' }
};

const session = await client.connect(
  'your_application_id',
  'your_session_id',
  'your_token',
  { sessionSettings }
);

console.log(`Connected to session: ${session.sessionId}`);

Conexión con todas las funciones de devolución de llamada

Para una gestión completa de la sesión, registra todas las llamadas de retorno en curso disponibles:

const session = await client.connect(
  'your_application_id',
  'your_session_id',
  'your_token',
  {
    sessionSettings,
    onError: onSessionError,
    onDisconnected: onSessionDisconnected,
    onConnectionCreated: onConnectionCreated,
    onConnectionDropped: onConnectionDropped,
    onStreamReceived: onStreamReceived,
    onStreamDropped: onStreamDropped,
    onAudioData: onAudioData,
    onReadyForAudio: onReadyForAudio,
    onMediaBufferDrained: onMediaBufferDrained
  }
);

No hay onConnected callback: la promesa esperada cumple esa función.

Desconectarse de una sesión

Desconéctese de la sesión cuando haya terminado:

await client.disconnect();

Comprobación del estado de la conexión

if (client.isConnected()) {
  console.log('Still connected');
}

Configuración de la sesión

Configuración de audio y vídeo

Configura los ajustes de audio y vídeo de la sesión para controlar el formato de los datos multimedia:

import type { SessionAVSettings } from '@vonage/video-connector';

const avSettings: SessionAVSettings = {
  // Format of the audio you supply via addAudio()
  audioPublisher: {
    sampleRate: 48000,  // Valid: 8000, 12000, 16000, 24000, 32000, 44100, 48000
    channels: 2         // 1 for mono, 2 for stereo
  },
  // Format of the mixed audio delivered to your session onAudioData callback
  audioSubscribersMix: {
    sampleRate: 48000,
    channels: 1
  },
  // Geometry and format of the video you supply via addVideo()
  videoPublisher: {
    width: 1280,
    height: 720,
    fps: 30,
    format: 'YUV420P'  // Valid: YUV420P, RGB24, ARGB32
  }
};

await client.connect(applicationId, sessionId, token, {
  sessionSettings: { av: avSettings }
});

Los valores no válidos en estos ajustes provocan un TypeError de forma sincrónica desde connect(), antes de cualquier actividad de red.

Configuración del registro

Controla el nivel de detalle de los mensajes de registro en la consola:

await client.connect(applicationId, sessionId, token, {
  sessionSettings: {
    logging: { level: 'DEBUG' }  // Valid: ERROR, WARN, INFO, DEBUG, TRACE
  }
});

Migración de sesiones

Activar la migración automática de sesiones en caso de rotación de SFU:

await client.connect(applicationId, sessionId, token, {
  sessionSettings: {
    enableMigration: true,
    av: avSettings,
    logging: { level: 'INFO' }
  }
});

Flujos de publicación

Configuración del editor

Configura los ajustes del editor antes de empezar a publicar:

import type { PublisherSettings } from '@vonage/video-connector';

// Audio and video publisher
const publisherSettings: PublisherSettings = {
  name: 'AI Assistant Bot',
  hasAudio: true,
  hasVideo: true,
  enableCaptions: true,
  audioSettings: {
    enableStereoMode: true,
    enableOpusDtx: false
  }
};

// Or audio-only publisher
const audioOnlySettings: PublisherSettings = {
  name: 'Audio Bot',
  hasAudio: true,
  hasVideo: false,
  enableCaptions: false
};

Empezar a publicar

Empieza a publicar un flujo en la sesión:

const publisher = await client.publish({
  settings: publisherSettings,
  onError: onPublisherError,
  onStreamDestroyed: onStreamDestroyed
});

console.log(`Publishing stream: ${publisher.stream.streamId}`);

La promesa se resuelve una vez que se ha creado el flujo, por lo que no hay onStreamCreated función de devolución de llamada. La onStreamDestroyed La función de devolución de llamada solo se activa cuando el flujo se destruye de forma inesperada, como en el caso de una retirada forzada por parte del servidor; no se activa cuando se llama a unpublish() tú mismo.

Importante Si vas a publicar audio (hasAudio: true), debes esperar a que el onReadyForAudio función de llamada de retorno antes de llamar a addAudio(). Esta función de devolución de llamada indica que el sistema de audio está inicializado y listo para aceptar datos de audio. La capa nativa descarta de forma silenciosa los fotogramas enviados con anterioridad. Este requisito no se aplica a los casos de publicación exclusivamente de vídeo.

// onReadyForAudio is registered at connect() time, so capture its resolver first
let signalAudioReady: () => void = () => {};
const audioReady = new Promise<void>((resolve) => {
  signalAudioReady = resolve;
});

await client.connect(applicationId, sessionId, token, {
  sessionSettings,
  onReadyForAudio: () => {
    console.log('Audio system ready - can now add audio');
    signalAudioReady();
  }
});

await client.publish({ settings: publisherSettings });
await audioReady;

// Now safe to add audio
client.addAudio(audioData);

El vídeo no tiene una señal de disponibilidad equivalente: los fotogramas se pueden enviar tan pronto como publish() resuelve.

Añadir datos de audio

Envía datos de audio a tu transmisión publicada:

import type { AudioData } from '@vonage/video-connector';

const sampleRate = 48000;
const numberOfChannels = 1;
const numberOfFrames = 960;  // 20ms at 48kHz

// PCM16 LE: 2 bytes per sample per channel
const data = Buffer.alloc(numberOfFrames * numberOfChannels * 2);

const audioData: AudioData = {
  data,
  sampleRate,
  numberOfChannels,
  numberOfFrames
};

const accepted = client.addAudio(audioData);

addAudio() es síncrona. Devuelve true si se aceptó el fotograma, y false si el controlador no está inicializado o si la llamada nativa falla. Lanza un TypeError si la trama presenta un error de formato —por ejemplo, si el búfer es demasiado pequeño para la geometría de la trama declarada—.

Dejar de publicar

Deje de publicar cuando haya terminado:

await client.unpublish();

Comprobación del estado de publicación

if (client.isPublishing()) {
  console.log('Still publishing');
}

Suscripción a flujos

Suscribirse a los flujos

Cuando se reciba una nueva transmisión, suscríbete a ella para recibir datos de audio y/o vídeo:

import type { Session, Stream } from '@vonage/video-connector';

async function onStreamReceived(session: Session, stream: Stream): Promise<void> {
  console.log(`New stream received: ${stream.streamId}`);
  console.log(`From connection: ${stream.connection.connectionId}`);

  try {
    const subscriber = await client.subscribe(stream, {
      settings: {
        subscribeToAudio: true,
        subscribeToVideo: true,
        subscribeToCaptions: true,
        videoSettings: {
          preferredResolution: { width: 640, height: 480 },
          preferredFrameRate: 15
        }
      },
      onError: onSubscriberError,
      onDisconnected: onSubscriberDisconnected,
      onRenderFrame: onRenderFrame,
      onAudioData: onSubscriberAudioData,
      onCaptionText: onCaptionText
    });

    console.log(`Subscribed to stream: ${subscriber.stream.streamId}`);
  } catch (error) {
    // onStreamReceived is a void callback, so handle failures here rather than
    // letting them become an unhandled rejection.
    console.error(`Failed to subscribe to ${stream.streamId}:`, error);
  }
}

Recepción de medios suscritos

La biblioteca proporciona audio y vídeo a través de diferentes callbacks. Para conocer los motivos que justifican este diseño, consulta Suscripción a flujos en la guía de Video Connector.

Importante En Buffer dentro de un AudioData o VideoFrame El contenido entregado a una función de devolución de llamada solo es válido mientras dura dicha función. Si necesitas conservar el contenido multimedia más allá de la función de devolución de llamada —para ponerlo en cola o procesarlo de forma asíncrona—, cópialo primero, por ejemplo, con Buffer.from(frame.data).

Datos de vídeo: Los fotogramas de vídeo se entregan individualmente por flujo suscrito a través del onRenderFrame función de llamada registrada en el momento de la suscripción. Cada invocación incluye la subscriber que identifica a qué flujo pertenece el fotograma, para que puedas procesar por separado el vídeo de los distintos participantes.

import type { Subscriber, VideoFrame } from '@vonage/video-connector';

function onRenderFrame(subscriber: Subscriber, frame: VideoFrame): void {
  const streamId = subscriber.stream.streamId;
  console.log(`Video frame from stream ${streamId}: ${frame.width}x${frame.height}`);
  // Copy before retaining beyond this callback
  const owned = Buffer.from(frame.data);
}

Datos de audio: El audio mezclado se transmite a través del onAudioData registrada durante connect(), aproximadamente cada 10 ms. La biblioteca mezcla automáticamente el audio de todas las transmisiones a las que se está suscrito en una única transmisión. En esta llamada de retorno no es posible distinguir el audio de cada uno de los participantes.

import type { Session, AudioData } from '@vonage/video-connector';

function onAudioData(session: Session, audioData: AudioData): void {
  console.log(
    `Mixed audio from all subscribers: ${audioData.sampleRate}Hz, ` +
    `${audioData.numberOfChannels} channel(s)`
  );
}

Datos del pie de foto: Esta función está disponible actualmente en versión beta. El texto de los subtítulos se transmite por cada canal al que se esté suscrito a través de la onCaptionText función de devolución de llamada, con el subscriber identificar el flujo de origen y un CaptionsData objeto que contiene el texto y si el resultado es definitivo o provisional.

Nota Para el onCaptionText Para que la función de devolución de llamada reciba los datos de los subtítulos, es necesario que los subtítulos en directo estén habilitados en la configuración de la sesión de la Video API de Vonage subyacente (fuera de esta biblioteca; consulta la Video API de Vonage Subtítulos en directo documentación) y para el flujo específico del emisor que está enviando audio. Establecer subscribeToCaptions: true en la configuración de suscriptor para recibirlas.

import type { Subscriber, CaptionsData } from '@vonage/video-connector';

function onCaptionText(subscriber: Subscriber, captions: CaptionsData): void {
  const status = captions.isFinal ? 'final' : 'interim';
  console.log(`Caption (${status}) from stream ${subscriber.stream.streamId}: ${captions.text}`);
}

Datos de audio individuales

Esta función está disponible actualmente en versión beta. El audio de cada transmisión se transmite a través del onAudioData función de devolución de llamada registrada en el momento de la suscripción a través de subscribe() — a diferencia del nivel de sesión onAudioData, que transmite la mezcla. El audio llega en el formato en el que se recibe de la transmisión, PCM lineal de 16 bits, y no es posible configurar ni la frecuencia de muestreo ni el número de canales antes de la recepción.

import type { Subscriber, AudioData } from '@vonage/video-connector';

function onSubscriberAudioData(subscriber: Subscriber, audioData: AudioData): void {
  console.log(
    `Individual audio from stream ${subscriber.stream.streamId}: ` +
    `${audioData.sampleRate}Hz, ${audioData.numberOfChannels} channel(s)`
  );
}

Darse de baja de streams

Dejar de recibir contenido multimedia de una fuente concreta:

import type { Session, Stream } from '@vonage/video-connector';

function onStreamDropped(session: Session, stream: Stream): void {
  console.log(`Stream dropped: ${stream.streamId}`);
  client.unsubscribe(stream).catch((error) => {
    console.error('Failed to unsubscribe:', error);
  });
}

Tratamiento de datos de audio

Formato de audio

Los datos de audio se intercambian como muestras PCM lineal de 16 bits con signo y en formato «little-endian» en un Buffer:

  • Tasas de muestreo: 8000, 12000, 16000, 24000, 32000, 44100 o 48000 Hz
  • Canales: 1 (mono) o 2 (estéreo)
  • Formato: 16 bits con signo, en formato «little-endian», en un Buffer
  • Tamaño del marco: Típicamente 20ms trozos (varía según la frecuencia de muestreo)

Procesamiento de datos de audio

Gestiona el audio entrante en la función de devolución de datos de audio de la sesión:

import type { Session, AudioData } from '@vonage/video-connector';

function onAudioData(session: Session, audioData: AudioData): void {
  const { sampleRate, numberOfChannels, numberOfFrames, data } = audioData;

  console.log(`Received ${numberOfFrames} frames at ${sampleRate}Hz, ${numberOfChannels} channel(s)`);

  // Read individual samples as 16-bit signed little-endian
  const firstSample = data.readInt16LE(0);

  // Copy if the audio is needed after this callback returns
  const owned = Buffer.from(data);
}

Creación de datos de audio

Al añadir audio, crea un Buffer de muestras de 16 bits:

const sampleRate = 48000;
const durationMs = 20;
const numberOfChannels = 1;
const numberOfFrames = (sampleRate * durationMs) / 1000;  // 960 frames

const data = Buffer.alloc(numberOfFrames * numberOfChannels * 2);

// Example: generate a 440Hz sine wave
for (let i = 0; i < numberOfFrames; i++) {
  const sample = Math.round(32767 * 0.5 * Math.sin((2 * Math.PI * 440 * i) / sampleRate));
  data.writeInt16LE(sample, i * 2);
}

client.addAudio({ data, sampleRate, numberOfChannels: 1, numberOfFrames });

Continuidad de los datos de audio

Cuando publicas audio, la biblioteca envía silencio hasta tu primer addAudio() llamada, tolera breves intervalos sin enviar paquetes, tras lo cual recurre a tramas de silencio explícitas y rellena los periodos parciales para evitar la deriva. Véase Continuidad del audio para conocer el comportamiento completo.

Buenas prácticas:

  • Mantener una tasa de audio constante llamando addAudio() a intervalos regulares que coincidan con la frecuencia de muestreo configurada.
  • Supervisa las estadísticas del búfer mediante getMediaBufferStats() para garantizar que los datos de audio sean adecuados
  • Manejar el onMediaBufferDrained función de devolución de llamada para detectar cuándo se agota el búfer de audio
  • Plantéate implementar una estrategia de generación de audio que se adapte a las diferentes cargas de procesamiento.

Tratamiento de datos de vídeo

Formato de vídeo

Los datos de vídeo se intercambian como valores sin signo de 8 bits en un Buffer, en uno de estos tres formatos:

  • YUV420P: Formato YUV planar con submuestreo de croma 4:2:0
  • RGB24: RGB comprimido, 8 bits por canal
  • ARGB32: ARGB comprimido, 8 bits por canal, incluido el canal alfa

Especificaciones de vídeo:

  • Resoluciones: Hasta 1920x1080 (Full HD)
  • Frecuencia de imagen: 1-30 FPS
  • Formato: Valores sin signo de 8 bits en un Buffer

Procesamiento de fotogramas de vídeo

Maneja los fotogramas de vídeo entrantes en la llamada de retorno del fotograma renderizado:

import type { Subscriber, VideoFrame } from '@vonage/video-connector';

function onRenderFrame(subscriber: Subscriber, frame: VideoFrame): void {
  const { width, height, format, data } = frame;

  console.log(`Received ${width}x${height} frame in ${format} format`);

  // Copy if the frame is needed after this callback returns
  const owned = Buffer.from(data);
}

Creación de fotogramas de vídeo

Al publicar un vídeo, crea un Buffer con el tamaño adecuado para el formato y la resolución:

import type { VideoFrame } from '@vonage/video-connector';

const width = 640;
const height = 480;

// YUV420P layout:
// Y plane: width * height
// U plane: (width / 2) * (height / 2)
// V plane: (width / 2) * (height / 2)
const ySize = width * height;
const uvSize = (width / 2) * (height / 2);
const data = Buffer.alloc(ySize + 2 * uvSize);

// Fill with mid-gray
data.fill(128);

const videoFrame: VideoFrame = { data, width, height, format: 'YUV420P' };

client.addVideo(videoFrame);

addVideo() es síncrona. Devuelve true si se aceptó el fotograma, y false si el controlador no está inicializado o si la llamada nativa falla. Lanza un TypeError si el fotograma presenta un formato incorrecto —un formato desconocido, una dimensión no positiva, un número de píxeles superior a 1920x1080 o un búfer demasiado pequeño para la resolución declarada—.

Continuidad de fotogramas de vídeo

Cuando publicas un vídeo, la biblioteca envía fotogramas negros hasta que aparece el primer addVideo() La función «call» repite el último fotograma durante un máximo de 2 segundos; si dejas de enviar fotogramas, pasa a mostrar fotogramas negros. Véase Continuidad del vídeo para conocer el comportamiento completo.

Buenas prácticas:

  • Mantener una velocidad de fotogramas constante llamando a addVideo() a intervalos regulares que se ajusten a los FPS que hayas configurado
  • Supervisa las estadísticas del búfer mediante getMediaBufferStats() para garantizar unos datos de vídeo adecuados
  • Manejar el onMediaBufferDrained función de devolución de llamada para detectar cuándo se agota el búfer de vídeo
  • Considere la posibilidad de aplicar una estrategia de generación de fotogramas que se adapte a las distintas cargas de procesamiento.

Gestión de la memoria intermedia

Comprobación de las estadísticas del búfer

Supervisa el estado de tus buffers multimedia:

const stats = client.getMediaBufferStats();

if (stats.audio) {
  console.log(`Audio buffer duration: ${stats.audio.durationMs}ms`);
}

if (stats.video) {
  console.log(`Video buffer duration: ${stats.video.durationMs}ms`);
}

Cada campo es null cuando no haya ningún editor activo de ese tipo de medio.

Borrado de memorias intermedias

Borra los búferes de audio y vídeo cuando sea necesario, por ejemplo, para interrumpir la reproducción en mitad de una frase:

if (client.clearMediaBuffers()) {
  console.log('Media buffers cleared successfully');
}

Retrollamada de vaciado del búfer

Manejar eventos de drenaje del buffer:

import type { MediaBufferStats } from '@vonage/video-connector';

function onMediaBufferDrained(stats: MediaBufferStats): void {
  console.log('Media buffers drained');

  if (stats.audio) {
    console.log(`Audio buffer: ${stats.audio.durationMs}ms remaining`);
  }

  if (stats.video) {
    console.log(`Video buffer: ${stats.video.durationMs}ms remaining`);
  }
}

El onMediaBufferDrained La función de devolución de llamada está registrada en connect(). Se invoca cuando los búferes internos de audio o vídeo se agotan, e implementa histéresis para que no se active repetidamente mientras el búfer permanezca vacío. Véase Eventos de vaciado del búfer Para más información.

Obtener información de conexión

Recupera la información de tu conexión local:

const connection = client.getConnection();

if (connection) {
  console.log(`Connection ID: ${connection.connectionId}`);
  console.log(`Connection data: ${connection.data}`);
  console.log(`Created at: ${new Date(connection.creationTime).toISOString()}`);
}

getConnection() devoluciones null cuando el cliente no está conectado.

Llamadas de retorno de eventos

Las funciones de devolución de llamada se agrupan según la llamada que las registra. Recuerda que los eventos del ciclo de vida que solo se producen una vez — conexión de sesión, creación de un flujo de editor y conexión de suscriptor— se entregan mediante las promesas devueltas por connect(), publish()y subscribe() en lugar de como funciones de devolución de llamada.

Llamadas de retorno de sesión

Inscrita en connect():

Devolución de llamada Firma
onError (session: Session, description: string, code: number) => void
onDisconnected (session: Session) => void
onConnectionCreated (session: Session, connection: Connection) => void
onConnectionDropped (session: Session, connection: Connection) => void
onStreamReceived (session: Session, stream: Stream) => void
onStreamDropped (session: Session, stream: Stream) => void
onAudioData (session: Session, audioData: AudioData) => void
onReadyForAudio (session: Session) => void
onMediaBufferDrained (stats: MediaBufferStats) => void
import type { Session, Connection, Stream } from '@vonage/video-connector';

function onSessionError(session: Session, description: string, code: number): void {
  console.error(`Session error: ${description} (Code: ${code})`);
}

function onSessionDisconnected(session: Session): void {
  console.log(`Disconnected from session: ${session.sessionId}`);
}

function onConnectionCreated(session: Session, connection: Connection): void {
  console.log(`Participant joined: ${connection.connectionId}`);
  console.log(`Connection data: ${connection.data}`);
}

function onConnectionDropped(session: Session, connection: Connection): void {
  console.log(`Participant left: ${connection.connectionId}`);
}

function onStreamReceived(session: Session, stream: Stream): void {
  console.log(`Stream received: ${stream.streamId} from ${stream.connection.connectionId}`);
  // Decide whether to subscribe based on your application logic
}

Llamadas de retorno de editores

Inscrita en publish():

Devolución de llamada Firma
onError (publisher: Publisher, description: string, code: number) => void
onStreamDestroyed (publisher: Publisher) => void
import type { Publisher } from '@vonage/video-connector';

function onPublisherError(publisher: Publisher, description: string, code: number): void {
  console.error(`Publisher error: ${description} (Code: ${code})`);
}

function onStreamDestroyed(publisher: Publisher): void {
  console.log(`Published stream destroyed: ${publisher.stream.streamId}`);
}

Llamadas de retorno a abonados

Inscrita en subscribe():

Devolución de llamada Firma
onError (subscriber: Subscriber, description: string, code: number) => void
onDisconnected (subscriber: Subscriber) => void
onRenderFrame (subscriber: Subscriber, frame: VideoFrame) => void
onAudioData (subscriber: Subscriber, audioData: AudioData) => void
onCaptionText (subscriber: Subscriber, captions: CaptionsData) => void
import type { Subscriber } from '@vonage/video-connector';

function onSubscriberError(subscriber: Subscriber, description: string, code: number): void {
  console.error(`Subscriber error: ${description} (Code: ${code})`);
}

function onSubscriberDisconnected(subscriber: Subscriber): void {
  console.log(`Unsubscribed from stream: ${subscriber.stream.streamId}`);
}

Gestión de errores

La biblioteca notifica los problemas de tres formas distintas.

Lanza sincrónicas en caso de entrada no válida. Los argumentos se validan antes de pasar a la capa nativa. addAudio(), addVideo(), y los ajustes de audio y vídeo que se pasan a connect() lanzar un TypeError cuando un valor tiene un tipo incorrecto o incumple una restricción documentada. publish() lanza un ValidationError cuando las configuraciones están bien escritas pero incumplen una regla, como por ejemplo hasAudio y hasVideo siendo ambos false.

import { ValidationError } from '@vonage/video-connector';

try {
  await client.publish({ settings: { name: 'Bot', hasAudio: false, hasVideo: false } });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Invalid publisher settings:', error.message);
  }
}

ValidationError es una clase exportada por el paquete, por lo que se puede interceptar con instanceof.

Establecer mensajes de error para las operaciones fallidas. Si una operación falla antes de completarse, su promesa se rechaza con un Error cuyo mensaje incluye la descripción y el código subyacentes.

try {
  await client.connect(applicationId, sessionId, token, { sessionSettings });
} catch (error) {
  // e.g. "Connection error [1004]: Authentication error"
  // or "Failed to initiate connection. Client may already be connected."
  console.error(error);
}

Funciones de devolución de llamada para errores tras la liquidación. Una vez que una operación se ha realizado con éxito, los errores posteriores se notifican al onError función de devolución de llamada registrada para ese ámbito. Una onError Por lo tanto, la función de devolución de llamada nunca se invoca en caso de un error que ya haya rechazado la promesa.

Valores booleanos de respuesta para el envío de archivos multimedia. addAudio(), addVideo()y clearMediaBuffers() volver false en lugar de lanzar una excepción cuando el controlador no está inicializado o la llamada nativa falla.

Limpieza de recursos

Limpie siempre los recursos adecuadamente:

try {
  await client.connect(applicationId, sessionId, token, { sessionSettings });
  await client.publish({ settings: publisherSettings });
  // ... do work ...
} catch (error) {
  console.error('Application error:', error);
} finally {
  if (client.isPublishing()) {
    await client.unpublish().catch(() => console.warn('Failed to stop publishing'));
  }

  if (client.isConnected()) {
    await client.disconnect().catch(() => console.warn('Failed to disconnect'));
  }
}