Conector de vídeo para Node.js

A biblioteca Vonage Video Connector para Node.js permite que você participe programaticamente de sessões da Video API do Vonage como participante do lado do servidor. Essa biblioteca permite que você se conecte a sessões de vídeo, publique e assine fluxos e processe dados de áudio e vídeo em tempo real.

Importante A biblioteca Vonage Video Connector para Node.js foi projetada para aplicativos do lado do servidor e requer credenciais e tokens válidos da Video API do Vonage com as permissões adequadas.

Esta página aborda a API do Node.js. Para conhecer os Concepts, formatos de mídia e comportamento em tempo de execução comuns a todas as bibliotecas do Video Connector, consulte o Conector de vídeo guia. Se você estiver trabalhando em Python, consulte Conector de vídeo para Python.

Este tópico inclui as seguintes seções:

Introdução

O Vonage Video Connector para Node.js é distribuído como o @vonage/video-connector pacote.

Para instalar a biblioteca, execute:

npm install @vonage/video-connector

O pacote é fornecido com binários nativos pré-compilados e não requer nenhuma etapa de compilação. As declarações de tipos do TypeScript estão incluídas, portanto, não é necessário nenhum @types É necessário o pacote.

Requisitos

Esta biblioteca requer o Node.js 18 ou versão posterior, rodando em plataformas Linux x64 ou ARM64. Recomendamos o uso do Debian Bookworm, pois é a distribuição na qual ela foi testada de forma mais completa.

Carregar a biblioteca em uma plataforma não suportada gera um erro no momento da importação, pois o módulo nativo para essa combinação de plataforma e arquitetura não existe.

A API baseada em promessas

A biblioteca Node.js expõe as operações do ciclo de vida como promessas, em vez de callbacks de conclusão. Isso define toda a API; portanto, vale a pena compreender esse conceito antes de ler o restante desta página.

Cada um dos connect(), publish(), subscribe(), disconnect(), unpublish(), e unsubscribe() retorna uma promessa:

Método Resolve com Resolve quando
connect() Session A conexão da sessão foi estabelecida
publish() Publisher Sua transmissão publicada foi criada
subscribe() Subscriber A assinatura foi ativada
disconnect() void A desconexão foi concluída
unpublish() void A retirada da publicação foi concluída
unsubscribe() void A cancelamento da inscrição foi concluída

Consequentemente, os eventos de ciclo de vida que ocorrem uma única vez não têm um callback correspondente nesta biblioteca — a promessa é a notificação. Não há onConnected, sem editora onStreamCreated, e nenhum assinante onConnected para se registrar. Todos os demais eventos são callbacks que você passa no objeto de argumentos.

O tratamento de erros segue a mesma divisão. Se ocorrer um erro antes quando uma operação é concluída, a promessa rejeita e sua onError a função de retorno não é chamada; a rejeição constitui a notificação de erro. Se ocorrer um erro depois de A transação foi concluída, seu onError A função de retorno de chamada é chamada normalmente.

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 Crie apenas um VonageVideoClient uma instância por processo. O SDK subjacente suporta uma única conexão ativa, e a criação de múltiplas instâncias resulta em comportamento indefinido.

Estruturas de dados

Todos os tipos abaixo são exportados do pacote e podem ser importados como tipos.

Sessão

Representa uma sessão da Video API da Vonage à qual os clientes podem se conectar:

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

O Session O objeto é passado para os callbacks no nível da sessão a fim de identificar qual sessão desencadeou o evento.

Conexão

Representa a conexão de um participante a uma sessão:

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

Os dados de conexão podem ser usados para armazenar metadados personalizados sobre os participantes, como IDs de usuário ou funções.

Observe que creationTime é um timestamp numérico do Unix em milissegundos; portanto, pode ser passado diretamente para new Date(connection.creationTime).

Transmissão

Representa um fluxo de mídia (áudio/vídeo) publicado por um participante:

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

Editora

Representa seu stream publicado na sessão:

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

Assinante

Representa uma assinatura do stream de outro participante:

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

Dados de áudio

Representa dados de áudio que estão sendo transmitidos ou recebidos:

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 áudio:

  • data deve ser um Buffer contendo amostras de 16 bits com sinal, no formato little-endian
  • Taxas de amostragem válidas: 8.000, 12.000, 16.000, 24.000, 32.000, 44.100, 48.000 Hz
  • Canais: 1 (mono) ou 2 (estéreo)
  • data.byteLength deve ser de pelo menos numberOfFrames * numberOfChannels * 2 bytes

VideoFrame

Representa os dados de um quadro de vídeo que estão sendo transmitidos ou recebidos:

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 deve ser um Buffer de valores sem sinal de 8 bits
  • width e height são propriedades planas na estrutura; não há um objeto de resolução separado
  • Resolução máxima: 1920 x 1080 pixels (2.073.600 pixels no total)
  • O tamanho necessário do buffer depende do formato:
Formato Obrigatório data.byteLength
YUV420P width * height * 3 / 2
RGB24 width * height * 3
ARGB32 width * height * 4

Resolução do vídeo

Representa as dimensões de um quadro 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 pixels (width * height) não deve exceder 1920 × 1080 (2.073.600). Esse tipo é utilizado por SubscriberVideoSettings.preferredResolution; a geometria do editor em SessionVideoPublisherSettings e em VideoFrame usa plano width e height propriedades, em vez disso.

Dados das legendas

Representa os dados de texto das legendas recebidos de um fluxo ao qual se está inscrito:

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

MediaBufferStats

Fornece estatísticas sobre os buffers de mídia de publicação:

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
}

As durações são números puros em milissegundos.

Tipos de validação

A biblioteca exporta tipos de união para os valores que valida, de modo que literais inválidos são detectados em tempo de compilação:

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';

Estruturas de configuração

Configurações da sessão

Configura o comportamento no nível da sessão:

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
};

Configurações da sessão AV

Configura as opções de áudio e vídeo para a sessão:

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 o formato dos dados de áudio que você fornece por meio de addAudio(). Os dados de áudio que você enviar devem corresponder à taxa de amostragem e ao número de canais desta configuração.
  • mix de assinantes de áudio: Define o formato do áudio mixado que você recebe de todos os fluxos assinados por meio do nível de sessão onAudioData callback. A biblioteca lida automaticamente com a mixagem do áudio de vários assinantes e com a reamostragem e conversão de canais para se adequar ao formato especificado por você.

Para obter orientações sobre como escolher esses formatos, consulte Áudio para publicação versus áudio para assinatura.

Configurações de Áudio da Sessão

Configura o formato de áudio para publicar ou receber dados de áudio:

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
};

Observe que a propriedade se chama channels aqui, enquanto AudioData usos numberOfChannels.

Configurações do Editor de Vídeos da Sessão

Configura as opções de vídeo para publicação:

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'
};

A largura e a altura são propriedades diretas, e não um objeto de resolução aninhado.

Configurações de registro

Controla o nível de detalhamento do registro:

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

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

Configurações do editor

Configura seu stream 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
  }
};

Observação: Pelo menos um dos hasAudio ou hasVideo deve ser true. Passando ambos como false gera um ValidationError sincronamente a partir de publish().

Observação: Conjunto enableCaptions: true para permitir que os assinantes recebam legendas em tempo real desta transmissão por meio do assinante onCaptionText callback. As legendas estão desativadas por padrão.

Configurações de Áudio do Editor

Configura as opções de áudio da sua transmissão 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
};

Transmissão descontínua (DTX) deixa de enviar pacotes de áudio durante os momentos de silêncio, economizando largura de banda.

Configurações do assinante

Configura o comportamento do assinante:

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
  }
};

As legendas devem estar habilitadas no lado da publicação e cada assinante deve optar por recebê-las por meio de subscribeToCaptions.

Configurações de vídeo do assinante

Configura as preferências de vídeo para os assinantes:

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

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

Essas configurações solicitam um nível específico de qualidade de transmissão simultânea da SFU da Video API da Vonage. Para saber como a SFU as processa, consulte Resolução e taxa de quadros preferenciais do assinante.

Conectando-se a uma sessão

Conexão básica

Para se conectar a uma sessão da Video API da Vonage, você precisa do ID do seu aplicativo (chave de API, se estiver usando o Tokbox), do ID da sessão e de um 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}`);

Conexão com todos os callbacks

Para um gerenciamento completo da sessão, registre todos os callbacks em andamento disponíveis:

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
  }
);

Não há onConnected callback — a promessa aguardada cumpre essa função.

Desconectando-se de uma sessão

Desconecte-se da sessão quando terminar:

await client.disconnect();

Verificando o estado da conexão

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

Configurações da sessão

Configuração de áudio e vídeo

Configure as definições de áudio e vídeo da sessão para controlar o formato dos dados de mídia:

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 }
});

Valores inválidos nessas configurações geram uma TypeError sincronamente a partir de connect(), antes de qualquer atividade de rede.

Configuração de registro

Controle o nível de detalhamento dos registros no console:

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

Migração de sessão

Ativar a migração automática de sessões em caso de rotação da SFU:

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

Transmissões de publicação

Configuração do editor

Configure as configurações do editor antes de começar 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
};

Comece a publicar

Comece a publicar um fluxo na sessão:

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

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

A promessa é resolvida assim que o fluxo é criado, portanto, não há onStreamCreated callback. O onStreamDestroyed A callback é acionada apenas quando o stream é destruído inesperadamente, como no caso de uma retirada forçada do ar do lado do servidor — ela não é acionada quando você chama unpublish() você mesmo.

Importante Se você estiver publicando áudio (hasAudio: true), é preciso aguardar até que o onReadyForAudio chamada de retorno antes de chamar addAudio(). Essa função de retorno indica que o sistema de áudio está inicializado e pronto para receber dados de áudio. Os quadros enviados anteriormente são descartados silenciosamente pela camada nativa. Esse requisito não se aplica a cenários de publicação 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);

O vídeo não possui um sinal de prontidão equivalente: os quadros podem ser enviados assim que publish() decide.

Inserção de dados de áudio

Envie dados de áudio para sua transmissão 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() é síncrona. Ela retorna true se o quadro foi aceito, e false se o controlador não estiver inicializado ou se a chamada nativa falhar. Isso gera uma TypeError se o quadro estiver com formato incorreto — por exemplo, se o buffer for pequeno demais para a geometria declarada do quadro.

Interromper a publicação

Interromper a publicação quando terminar:

await client.unpublish();

Verificando o status de publicação

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

Inscrever-se em canais

Inscreva-se nos canais

Quando um novo stream for recebido, inscreva-se nele para receber dados de áudio e/ou 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);
  }
}

Recebimento de mídias assinadas

A biblioteca fornece áudio e vídeo por meio de diferentes callbacks. Para saber o motivo por trás desse design, consulte Inscrever-se em canais no guia do Video Connector.

Importante O Buffer dentro de um AudioData ou VideoFrame A mídia entregue a uma função de retorno de chamada é válida apenas enquanto essa função estiver em execução. Se você precisar manter a mídia após o término da função de retorno de chamada — para colocá-la na fila ou processá-la de forma assíncrona —, copie-a primeiro, por exemplo, com Buffer.from(frame.data).

Dados de vídeo: Os quadros de vídeo são transmitidos individualmente por cada fluxo assinado por meio do onRenderFrame função de retorno registrada no momento da assinatura. Cada invocação inclui a subscriber que identifica a qual fluxo o quadro pertence, para que você possa processar separadamente os vídeos de diferentes 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);
}

Dados de áudio: O áudio mixado é transmitido pelo onAudioData callback registrado durante connect(), aproximadamente a cada 10 ms. A biblioteca combina automaticamente o áudio de todos os fluxos assinados em um único fluxo. Não é possível distinguir o áudio de participantes individuais neste callback.

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)`
  );
}

Dados da legenda: Atualmente, esse recurso está disponível na versão beta. O texto da legenda é transmitido por cada stream assinado por meio do onCaptionText callback, com o subscriber identificando o fluxo de origem e um CaptionsData objeto que contém o texto e se o resultado é definitivo ou provisório.

Nota Para o onCaptionText Para que a função de retorno de chamada receba os dados das legendas, as legendas em tempo real devem estar habilitadas na configuração da sessão da Video API do Vonage (fora desta biblioteca; consulte a Video API do Vonage Legendas em tempo real (documentação) e para o fluxo específico do transmissor que está enviando áudio. Defina subscribeToCaptions: true nas configurações do assinante para recebê-las.

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}`);
}

Dados de áudio individuais

Atualmente, esse recurso está disponível na versão beta. O áudio de cada transmissão é transmitido por meio do onAudioData callback registrado no momento da assinatura por meio de subscribe() — diferente do nível da sessão onAudioData, que transmite a mixagem. O áudio chega no formato recebido da transmissão, PCM linear de 16 bits, e nem a taxa de amostragem nem o número de canais podem ser configurados antes da recepção.

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)`
  );
}

Cancelar a inscrição em canais

Para deixar de receber conteúdo de um stream específico:

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);
  });
}

Tratamento de dados de áudio

Formato de áudio

Os dados de áudio são trocados na forma de amostras PCM linear de 16 bits com sinal, no formato little-endian, em um Buffer:

  • Taxas de amostragem: 8.000, 12.000, 16.000, 24.000, 32.000, 44.100 ou 48.000 Hz
  • Canais: 1 (mono) ou 2 (estéreo)
  • Formato: 16 bits com sinal, no formato little-endian, em um Buffer
  • Tamanho do quadro: Normalmente, blocos de 20 ms (varia de acordo com a taxa de amostragem)

Processamento de dados de áudio

Processe o áudio recebido na função de retorno de chamada de dados de áudio da sessão:

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);
}

Criação de dados de áudio

Ao adicionar áudio, crie um Buffer de amostras 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 });

Continuidade dos dados de áudio

Quando você publica um arquivo de áudio, a biblioteca envia silêncio até o seu primeiro addAudio() chamada, tolera breves intervalos sem o envio de pacotes; em seguida, recorre a quadros de silêncio explícitos e preenche períodos parciais para evitar desvios. Veja Continuidade de áudio para conhecer o comportamento completo.

Melhores práticas:

  • Mantenha uma taxa de áudio consistente chamando addAudio() em intervalos regulares, de acordo com a taxa de amostragem configurada
  • Monitore as estatísticas do buffer usando getMediaBufferStats() para garantir dados de áudio adequados
  • Lidar com o onMediaBufferDrained função de retorno para detectar quando o buffer de áudio estiver vazio
  • Considere implementar uma estratégia de geração de áudio que se adapte às variações na carga de processamento

Tratamento de dados de vídeo

Formato de vídeo

Os dados de vídeo são trocados como valores sem sinal de 8 bits em um Buffer, em um dos três formatos:

  • YUV420P: Formato YUV planar com subamostragem de crominância 4:2:0
  • RGB24: RGB compactado, 8 bits por canal
  • ARGB32: ARGB compactado, 8 bits por canal, incluindo alfa

Especificações do vídeo:

  • Resoluções: Até 1920x1080 (Full HD)
  • Taxas de quadros: 1-30 FPS
  • Formato: valores sem sinal de 8 bits em um Buffer

Processamento de quadros de vídeo

Processe os quadros de vídeo recebidos na função de retorno de chamada do quadro de renderização:

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);
}

Criação de quadros de vídeo

Ao publicar um vídeo, crie um Buffer dimensionada para o formato e a resolução:

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() é síncrona. Ela retorna true se o quadro foi aceito, e false se o controlador não estiver inicializado ou se a chamada nativa falhar. Isso gera uma TypeError se o quadro estiver com formato incorreto — um formato desconhecido, uma dimensão negativa, uma contagem de pixels superior a 1920x1080 ou um buffer muito pequeno para a resolução declarada.

Continuidade dos quadros de vídeo

Quando você publica um vídeo, a biblioteca envia quadros pretos até o seu primeiro addVideo() A função `call` repete o seu último quadro por até 2 segundos; caso você pare de fornecer quadros, ela volta a exibir quadros pretos. Veja Continuidade do vídeo para conhecer o comportamento completo.

Melhores práticas:

  • Mantenha uma taxa de quadros consistente chamando addVideo() em intervalos regulares, de acordo com o FPS configurado por você
  • Monitore as estatísticas do buffer usando getMediaBufferStats() para garantir dados de vídeo adequados
  • Lidar com o onMediaBufferDrained função de retorno para detectar quando o buffer de vídeo estiver vazio
  • Considere implementar uma estratégia de geração de quadros que se adapte às variações na carga de processamento

Gerenciamento do buffer de mídia

Verificando as estatísticas do buffer

Monitore o estado dos seus buffers de mídia:

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 é null quando não houver nenhum editor ativo para esse tipo de mídia.

Limpeza dos buffers de mídia

Limpe os buffers de áudio e vídeo quando necessário, por exemplo, para interromper a reprodução no meio de uma fala:

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

Callback de esvaziamento do buffer

Tratar eventos de esvaziamento do 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`);
  }
}

O onMediaBufferDrained a função de retorno de chamada está registrada em connect(). É acionado quando os buffers internos de áudio ou vídeo estão esgotados e implementa histerese para que não seja acionado repetidamente enquanto o buffer permanecer vazio. Veja Eventos de esvaziamento do buffer para mais detalhes.

Obtendo informações de conexão

Recupere as informações da sua conexão 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() retornos null quando o cliente não está conectado.

Callbacks de eventos

Os callbacks são agrupados de acordo com a chamada que os registra. Lembre-se de que os eventos de ciclo de vida que ocorrem uma única vez — sessão conectada, fluxo do editor criado e assinante conectado — são entregues pelas promessas retornadas de connect(), publish(), e subscribe() em vez de como callbacks.

Callbacks de sessão

Registrado em connect():

Retorno de chamada Assinatura
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
}

Callbacks do editor

Registrado em publish():

Retorno de chamada Assinatura
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}`);
}

Retornos de chamada dos assinantes

Registrado em subscribe():

Retorno de chamada Assinatura
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}`);
}

Tratamento de erros

A biblioteca relata problemas de três maneiras diferentes.

Lança exceções síncronas em caso de entrada inválida. Os argumentos são validados antes de serem transferidos para a camada nativa. addAudio(), addVideo(), e as configurações de áudio/vídeo passadas para connect() lançar um TypeError quando um valor tem o tipo incorreto ou viola uma restrição documentada. publish() gera um ValidationError quando as configurações estão corretamente digitadas, mas infringem uma regra, como, por exemplo, hasAudio e hasVideo sendo 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 é uma classe exportada pelo pacote, portanto pode ser capturada com instanceof.

Garantir rejeições em caso de falhas nas operações. Se uma operação falhar antes de ser confirmada, sua promessa será rejeitada com um Error cuja mensagem inclui a descrição e o código subjacentes.

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);
}

Callbacks para erros após a liquidação. Uma vez que uma operação tenha sido bem-sucedida, as falhas posteriores são relatadas ao onError callback registrado para esse escopo. Um onError Portanto, a função de retorno nunca é chamada em caso de falha que já tenha rejeitado a promessa.

Retorna um valor booleano ao enviar mídia. addAudio(), addVideo(), e clearMediaBuffers() retornar false em vez de gerar uma exceção quando o controlador não estiver inicializado ou quando a chamada nativa falhar.

Limpeza de recursos

Sempre libere os recursos corretamente:

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'));
  }
}