Connecteur vidéo pour Node.js

La bibliothèque Node.js « Vonage Video Connector » vous permet de participer par programmation à des sessions de la Video API Vonage en tant que participant côté serveur. Cette bibliothèque vous permet de vous connecter à des sessions vidéo, de publier et de vous abonner à des flux, ainsi que de traiter des données audio et vidéo en temps réel.

Important La bibliothèque Node.js « Vonage Video Connector » est conçue pour les applications côté serveur et nécessite des identifiants et des jetons valides de la Video API Vonage, dotés des autorisations appropriées.

Cette page présente l'API Node.js. Pour en savoir plus sur les concepts, les formats multimédias et le comportement d'exécution communs à toutes les bibliothèques Video Connector, consultez la Connecteur vidéo guide. Si vous travaillez sous Python, consultez Connecteur vidéo pour Python.

Cette rubrique comprend les sections suivantes :

Pour commencer

Le Vonage Video Connector pour Node.js est distribué sous la forme d'un @vonage/video-connector l'emballage.

Pour installer la bibliothèque, exécutez :

npm install @vonage/video-connector

Le paquet est fourni avec des binaires natifs précompilés et ne nécessite aucune étape de compilation. Les déclarations de types TypeScript sont incluses ; il n'est donc pas nécessaire de @types Le paquet est nécessaire.

Exigences

Cette bibliothèque nécessite Node.js 18 ou une version ultérieure, fonctionnant sur des plateformes Linux x64 ou ARM64. Nous vous recommandons d'utiliser Debian Bookworm, car c'est la distribution sur laquelle cette bibliothèque a été testée de manière la plus approfondie.

Le chargement de la bibliothèque sur une plateforme non prise en charge génère une exception au moment de l'importation, car le module natif correspondant à cette combinaison de plateforme et d'architecture n'existe pas.

L'API basée sur les « promesses »

La bibliothèque Node.js expose les opérations liées au cycle de vie sous forme de « promises » plutôt que de callbacks de fin d'exécution. Cela détermine la structure de l'ensemble de l'API ; il est donc utile de bien comprendre ce principe avant de poursuivre la lecture de cette page.

Chacun des connect(), publish(), subscribe(), disconnect(), unpublish()et unsubscribe() renvoie une promesse :

Méthode Se termine par Se résout lorsque
connect() Session La connexion à la session est établie
publish() Publisher Votre flux publié a été créé
subscribe() Subscriber L'abonnement est activé
disconnect() void La déconnexion est terminée
unpublish() void La suppression de la publication est terminée
unsubscribe() void La désinscription est terminée

Par conséquent, les événements ponctuels du cycle de vie n'ont pas de fonction de rappel correspondante dans cette bibliothèque — la promesse est la notification. Il n'y a pas de onConnected, sans éditeur onStreamCreated, et aucun abonné onConnected pour s'inscrire. Tous les autres événements sont des fonctions de rappel que vous transmettez via l'objet `arguments`.

La gestion des erreurs suit la même structure. Si une erreur se produit avant une opération est finalisée, la promesse est rejetée et votre onError la fonction de rappel n'est pas appelée ; le rejet constitue la notification d'erreur. Si une erreur se produit après Une fois que l'opération s'est stabilisée, votre onError La fonction de rappel est appelée comme d'habitude.

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

Important N'en créez qu'un seul VonageVideoClient une instance par processus. Le SDK sous-jacent ne prend en charge qu'une seule connexion active ; la création de plusieurs instances entraîne un comportement indéfini.

Structures de données

Tous les types ci-dessous sont exportés depuis le package et peuvent être importés en tant que types.

Session

Représente une session Video API de Vonage à laquelle les clients peuvent se connecter :

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

Les Session L'objet est transmis aux fonctions de rappel au niveau de la session afin d'identifier la session qui a déclenché l'événement.

Connexion

Représente la connexion d'un participant à une session :

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

Les données de connexion peuvent être utilisées pour stocker des métadonnées personnalisées sur les participants, telles que les identifiants ou les rôles des utilisateurs.

Il convient de noter que creationTime Il s'agit d'un horodatage Unix numérique exprimé en millisecondes ; il peut donc être transmis directement à new Date(connection.creationTime).

Flux

Représente un flux média (audio/vidéo) publié par un participant :

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

Éditeur

Représente le flux publié dans la session :

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

Abonné

Représente un abonnement au flux d'un autre participant :

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

Données audio

Représente les données audio transmises ou reçues :

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
}

Exigences en matière de format audio :

  • data ça doit être un Buffer contenant des échantillons signés de 16 bits au format « little-endian »
  • Taux d'échantillonnage valables : 8000, 12000, 16000, 24000, 32000, 44100, 48000 Hz
  • Canaux : 1 (mono) ou 2 (stéréo)
  • data.byteLength doit être d'au moins numberOfFrames * numberOfChannels * 2 octets

VideoFrame

Représente les données de la trame vidéo en cours de transmission ou de réception :

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

Exigences en matière de format vidéo :

  • data ça doit être un Buffer de valeurs non signées de 8 bits
  • width et height il s'agit de propriétés « plates » associées au cadre ; il n'y a pas d'objet de résolution distinct
  • Résolution maximale : 1920x1080 pixels (2 073 600 pixels au total)
  • La taille de la mémoire tampon requise dépend du format :
Format Obligatoire data.byteLength
YUV420P width * height * 3 / 2
RGB24 width * height * 3
ARGB32 width * height * 4

Résolution vidéo

Représente les dimensions d'une image vidéo :

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

Nombre total de pixels (width * height) ne doit pas dépasser 1 920 × 1 080 (2 073 600). Ce type est utilisé par SubscriberVideoSettings.preferredResolution; la géométrie de l'éditeur dans SessionVideoPublisherSettings et sur VideoFrame utilise des caractères plats width et height à la place.

CaptionsData

Représente les données textuelles de la légende reçues d'un flux abonné :

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

MediaBufferStats

Fournit des statistiques sur les tampons des supports de publication :

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
}

Les durées sont exprimées en nombres bruts, en millisecondes.

Types de validation

La bibliothèque exporte des types union pour les valeurs qu'elle valide, ce qui permet de détecter les littéraux non valides dès la phase de compilation :

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

Structures de configuration

Paramètres de session

Configure le comportement au niveau de la session :

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

SessionAVSettings

Configure les paramètres audio et vidéo de la session :

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: Définit le format des données audio que vous fournissez par l'intermédiaire de l'application addAudio(). Les données audio que vous envoyez doivent correspondre à la fréquence d'échantillonnage et au nombre de canaux de cette configuration.
  • audioAbonnésMix: Définit le format du flux audio mixé que vous recevez de tous les flux auxquels vous êtes abonné via le niveau de session onAudioData callback. La bibliothèque prend automatiquement en charge le mixage de l'audio de plusieurs abonnés et le rééchantillonnage/la conversion des canaux pour correspondre au format que vous avez spécifié.

Pour obtenir des conseils sur le choix de ces formats, consultez Contenu audio destiné à la publication ou à l'abonnement.

SessionAudioSettings

Configure le format audio pour la publication ou la réception de données 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
};

Notez que la propriété s’appelle channels ici, tandis que AudioData utilisations numberOfChannels.

Paramètres de l'éditeur vidéo de session

Configure les paramètres vidéo pour la publication :

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

La largeur et la hauteur sont des propriétés « flat » et non un objet de résolution imbriqué.

Paramètres de journalisation

Contrôle la verbosité de la journalisation :

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

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

Paramètres de l'éditeur

Configure votre flux publié :

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

Remarque : Au moins un des éléments suivants hasAudio ou hasVideo doit être true. En les transmettant tous les deux sous la forme false lance un ValidationError de manière synchrone à partir de publish().

Remarque : Set (jeu de mots) enableCaptions: true pour permettre aux abonnés de recevoir le texte des sous-titres en direct de ce flux via l' abonné onCaptionText callback. Les légendes sont désactivées par défaut.

Paramètres audio de l'éditeur

Configure les paramètres audio pour votre flux publié :

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

Transmission discontinue (DTX) arrête l'envoi de paquets audio pendant les silences, ce qui permet d'économiser de la bande passante.

Paramètres de l'abonné

Configure le comportement des abonnés :

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

Les sous-titres doivent être activés côté publication et chaque abonné doit donner son accord via subscribeToCaptions.

Paramètres vidéo de l'abonné

Configure les préférences vidéo pour les abonnés :

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

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

Ces paramètres demandent au SFU de la Video API une couche de qualité de diffusion simultanée spécifique. Pour savoir comment le SFU les prend en compte, consultez Résolution et fréquence d'images recommandées pour les abonnés.

Connexion à une session

Connexion de base

Pour vous connecter à une session de la Video API Vonage, vous devez disposer de l'identifiant de votre application (clé API si vous utilisez Tokbox), de l'identifiant de session et d'un jeton valide :

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

Connexion avec tous les rappels

Pour une gestion complète des sessions, enregistrez tous les callbacks actifs 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
  }
);

Il n'y a pas de onConnected callback — la promesse attendue remplit cette fonction.

Déconnexion d'une session

Déconnectez-vous de la session lorsque vous avez terminé :

await client.disconnect();

Vérification de l'état de la connexion

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

Paramètres de la session

Configuration audio et vidéo

Configurer les paramètres audio et vidéo de la session pour contrôler le format des données multimédias :

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

Des valeurs non valides dans ces paramètres provoquent une TypeError de manière synchrone à partir de connect(), avant toute activité réseau.

Configuration de l'enregistrement

Contrôle la verbosité de la journalisation de la console :

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

Migration de session

Activer la migration automatique des sessions en cas de rotation du SFU :

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

Flux de publication

Configuration de l'éditeur

Configurez les paramètres de l'éditeur avant de commencer à publier :

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

Commencer à publier

Commencer à publier un flux dans la session :

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

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

La promesse est résolue dès que le flux a été créé ; il n'y a donc pas onStreamCreated rappel. Le onStreamDestroyed Le callback ne se déclenche que lorsque le flux est détruit de manière inattendue, par exemple lors d'une suppression forcée côté serveur ; il ne se déclenche pas lorsque vous appelez unpublish() vous-même.

Important Si vous publiez un fichier audio (hasAudio: true), vous devez attendre que le onReadyForAudio appel de retour avant l'appel addAudio(). Cette fonction de rappel indique que le système audio est initialisé et prêt à recevoir des données audio. Les trames envoyées auparavant sont ignorées par la couche native sans notification. Cette exigence ne s'applique pas aux scénarios de diffusion exclusivement vidéo.

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

La vidéo ne dispose d'aucun signal d'éligibilité équivalent : les images peuvent être transmises dès que publish() décide.

Ajout de données audio

Envoyez des données audio à votre flux publié :

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() est synchrone. Elle renvoie true si la trame a été acceptée, et false si le contrôleur n'est pas initialisé ou si l'appel natif échoue. Il déclenche une TypeError si la trame est mal formée — par exemple si le tampon est trop petit pour la géométrie de trame déclarée.

Cesser de publier

Arrêtez la publication lorsque vous avez terminé :

await client.unpublish();

Vérification de l'état de publication

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

S'abonner à des flux

S'abonner aux flux

Lorsqu'un nouveau flux est reçu, vous pouvez vous y abonner pour recevoir des données audio et/ou vidéo :

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

Réception des médias souscrits

La bibliothèque fournit des données audio et vidéo via différents callbacks. Pour en savoir plus sur les raisons qui ont motivé cette conception, consultez S'abonner à des flux dans le guide « Video Connector ».

Important Le Buffer à l'intérieur d'un AudioData ou VideoFrame Le contenu transmis à une fonction de rappel n'est valide que pendant la durée de cette fonction. Si vous devez conserver le média au-delà de la fonction de rappel — pour le mettre en file d'attente ou le traiter de manière asynchrone —, copiez-le d'abord, par exemple à l'aide de Buffer.from(frame.data).

Données vidéo: Les images vidéo sont fournies individuellement pour chaque flux souscrit par l'intermédiaire de l'interface utilisateur. onRenderFrame fonction de rappel enregistrée au moment de l'abonnement. Chaque appel inclut la subscriber qui identifie le flux auquel appartient l'image, ce qui vous permet de traiter séparément les vidéos des différents participants.

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

Données audio: Le son mixé est diffusé via le onAudioData enregistré lors de la connect(), environ toutes les 10 ms. La bibliothèque fusionne automatiquement le son de tous les flux auxquels vous êtes abonné en un seul flux. Dans cette fonction de rappel, il est impossible de distinguer le son de chaque participant.

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

Données de légende: Cette fonctionnalité est actuellement disponible en version bêta. Le texte des sous-titres est diffusé via chaque flux souscrit par le biais du onCaptionText rappel, avec le subscriber identifier le flux source et un CaptionsData objet contenant le texte et précisant si le résultat est définitif ou provisoire.

Note Pour les onCaptionText Pour que la fonction de rappel puisse recevoir les données de sous-titrage, les sous-titres en direct doivent être activés dans la configuration de la session de la Video API Vonage sous-jacente (en dehors de cette bibliothèque ; voir la Video API Vonage Sous-titres en direct (documentation) et pour le flux de diffusion spécifique qui transmet le son. Définir subscribeToCaptions: true dans les paramètres d'abonnement pour les recevoir.

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

Données audio individuelles

Cette fonctionnalité est actuellement disponible en version bêta. Le flux audio individuel est diffusé via le onAudioData enregistré au moment de l'abonnement par l'intermédiaire de subscribe() — à distinguer du niveau de session onAudioData, qui achemine le mixage. Le signal audio est transmis dans le format reçu depuis le flux, à savoir PCM linéaire 16 bits, et ni la fréquence d'échantillonnage ni le nombre de canaux ne peuvent être configurés avant la réception.

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

Se désabonner des flux

Arrêter la réception de médias provenant d'un flux spécifique :

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

Traitement des données audio

Format audio

Les données audio sont échangées sous forme d'échantillons PCM linéaires 16 bits signés en format « little-endian » dans un Buffer:

  • Taux d'échantillonnage: 8000, 12000, 16000, 24000, 32000, 44100, ou 48000 Hz
  • Canaux: 1 (mono) ou 2 (stéréo)
  • Format: 16 bits signés, format « little-endian », dans un Buffer
  • Taille du cadre: En général, tranches de 20 ms (varie en fonction de la fréquence d'échantillonnage)

Traitement des données audio

Gérer le flux audio entrant dans la fonction de rappel des données audio de la session :

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

Création de données audio

Lorsque vous ajoutez un fichier audio, créez un Buffer d'échantillons 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 });

Continuité des données audio

Lorsque vous publiez un fichier audio, la bibliothèque diffuse un silence jusqu'à votre premier addAudio() appel, tolère de brèves interruptions sans envoyer de paquets, puis revient à des trames de silence explicites et complète les périodes partielles pour éviter toute dérive. Voir Continuité audio pour connaître le fonctionnement complet.

Meilleures pratiques :

  • Maintenir un débit audio constant en appelant addAudio() à intervalles réguliers correspondant à la fréquence d'échantillonnage configurée
  • Surveiller les statistiques de la mémoire tampon à l'aide de getMediaBufferStats() pour garantir des données audio adéquates
  • Manipuler les onMediaBufferDrained callback pour détecter l'épuisement de la mémoire tampon audio
  • Envisager la mise en œuvre d'une stratégie de génération audio qui s'adapte à des charges de traitement variables.

Traitement des données vidéo

Format vidéo

Les données vidéo sont échangées sous forme de valeurs non signées de 8 bits dans un Buffer, dans l'un des trois formats suivants :

  • YUV420P: Format YUV planaire avec sous-échantillonnage chromatique 4:2:0
  • RGB24: RVB compressé, 8 bits par canal
  • ARGB32: ARGB compressé, 8 bits par canal, alpha compris

Spécifications vidéo :

  • Résolutions: Jusqu'à 1920x1080 (Full HD)
  • Taux de rafraîchissement: 1-30 FPS
  • Format: valeurs non signées de 8 bits dans un Buffer

Traitement des images vidéo

Traite les images vidéo entrantes dans le rappel de l'image de rendu :

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

Création d'images vidéo

Lorsque vous publiez une vidéo, créez un Buffer dimensionné en fonction du format et de la résolution :

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() est synchrone. Elle renvoie true si la trame a été acceptée, et false si le contrôleur n'est pas initialisé ou si l'appel natif échoue. Il déclenche une TypeError si la trame présente une anomalie : un format inconnu, une dimension non positive, un nombre de pixels supérieur à 1920 × 1080 ou une mémoire tampon trop petite pour la résolution déclarée.

Continuité de l'image vidéo

Lorsque vous publiez une vidéo, la bibliothèque envoie des images noires jusqu'à ce que votre première addVideo() La fonction « call » répète votre dernière image pendant 2 secondes maximum si vous cessez de fournir des images, puis affiche des images noires. Voir Continuité vidéo pour connaître le fonctionnement complet.

Meilleures pratiques :

  • Maintenir une fréquence d'images constante en appelant addVideo() à intervalles réguliers en fonction de votre FPS configuré
  • Surveiller les statistiques de la mémoire tampon à l'aide de getMediaBufferStats() pour garantir des données vidéo adéquates
  • Manipuler les onMediaBufferDrained callback pour détecter l'épuisement de la mémoire tampon vidéo
  • Envisager la mise en œuvre d'une stratégie de génération de trames qui s'adapte à des charges de traitement variables.

Gestion de la mémoire tampon des médias

Vérification des statistiques de la mémoire tampon

Surveillez l'état de vos mémoires tampons :

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

Chaque champ est null lorsqu'aucun éditeur de ce type de média n'est actif.

Effacer les mémoires tampons des médias

Videz les tampons audio et vidéo lorsque cela est nécessaire, par exemple pour interrompre la lecture en cours de lecture :

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

Buffer drained callback

Gérer les événements de vidange de la mémoire tampon :

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

Les onMediaBufferDrained la fonction de rappel est enregistrée dans connect(). Elle est appelée lorsque les tampons audio ou vidéo internes sont vides, et intègre un mécanisme d'hystérésis afin de ne pas se déclencher de manière répétée tant que le tampon reste vide. Voir Événements de vidange du tampon pour plus de détails.

Obtenir des informations sur la connexion

Récupérez vos informations de connexion locale :

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() retours null lorsque le client n'est pas connecté.

Rappels d'événements

Les callbacks sont regroupés en fonction de l'appel qui les enregistre. N'oubliez pas que les événements du cycle de vie qui ne se produisent qu'une seule fois — connexion à la session, création du flux de l'éditeur et connexion de l'abonné — sont transmis par les promesses renvoyées par connect(), publish()et subscribe() plutôt que sous forme de callbacks.

Rappels de session

Enregistré à connect():

Rappel Signature
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
}

Rappels de l'éditeur

Enregistré à publish():

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

Rappels de l'abonné

Enregistré à subscribe():

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

Gestion des erreurs

La bibliothèque signale les problèmes de trois manières différentes.

Exceptions synchrones en cas de saisie non valide. Les arguments sont validés avant de passer à la couche native. addAudio(), addVideo(), et les paramètres audio/vidéo transmis à connect() lancer un TypeError lorsqu'une valeur est d'un type incorrect ou enfreint une contrainte documentée. publish() lance un ValidationError lorsque les paramètres sont correctement typés mais enfreignent une règle, par exemple hasAudio et hasVideo les deux étant 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 est une classe exportée par le paquet ; elle peut donc être interceptée avec instanceof.

Prévoir des rejets en cas d'échec des opérations. Si une opération échoue avant d'être validée, sa promesse est rejetée avec un Error dont le message contient la description et le code sous-jacents.

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

Fonctions de rappel en cas d'erreurs après le règlement. Une fois qu'une opération a abouti, les échecs ultérieurs sont signalés au onError rappel enregistré pour cette portée. Un onError La fonction de rappel n'est donc jamais appelée en cas d'échec ayant déjà entraîné le rejet de la promesse.

Valeurs booléennes renvoyées lors de l'envoi de fichiers multimédias. addAudio(), addVideo()et clearMediaBuffers() retour false plutôt que de générer une exception lorsque le contrôleur n'est pas initialisé ou que l'appel natif échoue.

Nettoyage des ressources

Nettoyez toujours les ressources de manière appropriée :

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