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
- Requisitos
- A API baseada em promessas
- Estruturas de dados
- Conectando-se a uma sessão
- Configurações da sessão
- Transmissões de publicação
- Inscrever-se em canais
- Tratamento de dados de áudio
- Tratamento de dados de vídeo
- Gerenciamento do buffer de mídia
- Callbacks de eventos
- Tratamento de erros
Introdução
O Vonage Video Connector para Node.js é distribuído como o @vonage/video-connector pacote.
Para instalar a biblioteca, execute:
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:
datadeve ser umBuffercontendo 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.byteLengthdeve ser de pelo menosnumberOfFrames * numberOfChannels * 2bytes
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:
datadeve ser umBufferde valores sem sinal de 8 bitswidtheheightsã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
onAudioDatacallback. 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
onMediaBufferDrainedfunçã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
onMediaBufferDrainedfunçã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'));
}
}