Video Connector for Node.js
The Vonage Video Connector Node.js library enables you to programmatically participate in Vonage Video API sessions as a server-side participant. This library allows you to connect to video sessions, publish and subscribe to streams, and process real-time audio and video data.
Important The Vonage Video Connector Node.js library is designed for server-side applications and requires valid Vonage Video API credentials and tokens with appropriate permissions.
This page covers the Node.js API. For the concepts, media formats, and runtime behavior shared by all Video Connector libraries, see the Video Connector guide. If you are working in Python, see Video Connector for Python.
This topic includes the following sections:
- Getting started
- Requirements
- The promise-based API
- Data structures
- Connecting to a session
- Session settings
- Publishing streams
- Subscribing to streams
- Audio data handling
- Video data handling
- Media buffer management
- Event callbacks
- Error handling
Getting started
The Vonage Video Connector for Node.js is distributed as the @vonage/video-connector package.
To install the library, run:
The package ships prebuilt native binaries and requires no compilation step. TypeScript type declarations are
included, so no separate @types package is needed.
Requirements
This library requires Node.js 18 or later running on Linux x64 or ARM64 platforms. We recommend using Debian Bookworm as it is the distribution where this has been most thoroughly tested.
Loading the library on an unsupported platform throws at import time, because the native module for that platform and architecture combination does not exist.
The promise-based API
The Node.js library exposes lifecycle operations as promises rather than completion callbacks. This shapes the whole API, so it is worth understanding before reading the rest of this page.
Each of connect(), publish(), subscribe(), disconnect(), unpublish(), and unsubscribe() returns a
promise:
| Method | Resolves with | Resolves when |
|---|---|---|
connect() |
Session |
The session connection is established |
publish() |
Publisher |
Your published stream has been created |
subscribe() |
Subscriber |
The subscription is established |
disconnect() |
void |
Disconnection completes |
unpublish() |
void |
Unpublishing completes |
unsubscribe() |
void |
Unsubscription completes |
As a consequence, the one-shot lifecycle events have no corresponding callback in this library — the promise
is the notification. There is no onConnected, no publisher onStreamCreated, and no subscriber
onConnected to register. Every other event is a callback you pass in the arguments object.
Error handling follows the same split. If an error occurs before an operation settles, the promise rejects and
your onError callback is not invoked; the rejection is the error notification. If an error occurs after the
operation has settled, your onError callback is invoked as normal.
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
Create only one VonageVideoClient instance per process. The underlying SDK supports a single active connection, and creating multiple instances results in undefined behavior.
Data structures
All types below are exported from the package and can be imported as types.
Session
Represents a Vonage Video API session that clients can connect to:
interface Session {
sessionId: string; // Unique identifier for the session
}
The Session object is passed to session-level callbacks to identify which session triggered the event.
Connection
Represents a participant's connection to a session:
interface Connection {
connectionId: string; // Unique identifier for the connection
creationTime: number; // Unix timestamp in milliseconds
data: string; // Connection data (encoded in the token)
}
Connection data can be used to store custom metadata about participants, such as user IDs or roles.
Note that creationTime is a numeric Unix timestamp in milliseconds, so it can be passed straight to
new Date(connection.creationTime).
Stream
Represents a media stream (audio/video) published by a participant:
interface Stream {
streamId: string; // Unique identifier for the stream
connection: Connection; // The connection that published this stream
}
Publisher
Represents your published stream in the session:
interface Publisher {
stream: Stream; // The underlying stream for this publisher
}
Subscriber
Represents a subscription to another participant's stream:
interface Subscriber {
stream: Stream; // The stream being subscribed to
}
AudioData
Represents audio data being transmitted or received:
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
}
Audio format requirements:
datamust be aBuffercontaining 16-bit signed little-endian samples- Valid sample rates: 8000, 12000, 16000, 24000, 32000, 44100, 48000 Hz
- Channels: 1 (mono) or 2 (stereo)
data.byteLengthmust be at leastnumberOfFrames * numberOfChannels * 2bytes
VideoFrame
Represents video frame data being transmitted or received:
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'
}
Video format requirements:
datamust be aBufferof 8-bit unsigned valueswidthandheightare flat properties on the frame; there is no separate resolution object- Maximum resolution: 1920x1080 pixels (2,073,600 total pixels)
- Required buffer size depends on the format:
| Format | Required data.byteLength |
|---|---|
YUV420P |
width * height * 3 / 2 |
RGB24 |
width * height * 3 |
ARGB32 |
width * height * 4 |
VideoResolution
Represents the dimensions of a video frame:
interface VideoResolution {
width: number; // Width in pixels. Must be a positive integer
height: number; // Height in pixels. Must be a positive integer
}
Total pixel count (width * height) must not exceed 1920 * 1080 (2,073,600). This type is used by
SubscriberVideoSettings.preferredResolution; the publisher geometry in SessionVideoPublisherSettings and on
VideoFrame uses flat width and height properties instead.
CaptionsData
Represents caption text data received from a subscribed stream:
interface CaptionsData {
text: string; // The caption text content
isFinal: boolean; // true for final captions, false for interim/partial captions
}
MediaBufferStats
Provides statistics about the publishing media buffers:
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
}
Durations are plain numbers in milliseconds.
Validation types
The library exports union types for the values it validates, so invalid literals are caught at compile time:
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';
Configuration structures
SessionSettings
Configures session-level behavior:
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
Configures audio and video settings for the 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: Defines the format for audio data you provide via
addAudio(). The audio data you send must match this configuration's sample rate and channel count. - audioSubscribersMix: Defines the format for the mixed audio you receive from all subscribed streams via the session-level
onAudioDatacallback. The library automatically handles mixing multiple subscribers' audio and resampling/channel conversion to match your specified format.
For guidance on choosing these formats, see Audio for publishing versus subscribing.
SessionAudioSettings
Configures audio format for publishing or receiving audio data:
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
};
Note that the property is named channels here, while AudioData uses numberOfChannels.
SessionVideoPublisherSettings
Configures video settings for publishing:
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'
};
Width and height are flat properties rather than a nested resolution object.
LoggingSettings
Controls logging verbosity:
import type { LoggingSettings } from '@vonage/video-connector';
const loggingSettings: LoggingSettings = {
level: 'INFO' // Required: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE'
};
PublisherSettings
Configures your published stream:
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
}
};
Note: At least one of hasAudio or hasVideo must be true. Passing both as false throws a
ValidationError synchronously from publish().
Note: Set enableCaptions: true to allow subscribers to receive live caption text from this stream via the
subscriber onCaptionText callback. Captions are disabled by default.
PublisherAudioSettings
Configures audio settings for your published stream:
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
};
Discontinuous transmission (DTX) stops sending audio packets during silence, saving bandwidth.
SubscriberSettings
Configures subscriber behavior:
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
}
};
Captions must be enabled on the publishing side and opted into per subscriber via subscribeToCaptions.
SubscriberVideoSettings
Configures video preferences for subscribers:
import type { SubscriberVideoSettings } from '@vonage/video-connector';
const videoSettings: SubscriberVideoSettings = {
preferredResolution: { width: 640, height: 480 }, // Optional
preferredFrameRate: 15 // Optional, 1-30
};
These settings request a specific simulcast quality layer from the Vonage Video API SFU. For an explanation of how the SFU honors them, see Preferred subscriber resolution and frame rate.
Connecting to a session
Basic connection
To connect to a Vonage Video API session, you need your application ID (API key if using Tokbox), session ID, and a valid token:
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}`);
Connection with all callbacks
For full session management, register all available ongoing callbacks:
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
}
);
There is no onConnected callback — the awaited promise serves that purpose.
Disconnecting from a session
Disconnect from the session when done:
await client.disconnect();
Checking connection state
if (client.isConnected()) {
console.log('Still connected');
}
Session settings
Audio and video configuration
Configure audio and video settings for the session to control the format of media data:
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 }
});
Invalid values in these settings throw a TypeError synchronously from connect(), before any network activity.
Logging configuration
Control the verbosity of console logging:
await client.connect(applicationId, sessionId, token, {
sessionSettings: {
logging: { level: 'DEBUG' } // Valid: ERROR, WARN, INFO, DEBUG, TRACE
}
});
Session migration
Enable automatic session migration in case of SFU rotation:
await client.connect(applicationId, sessionId, token, {
sessionSettings: {
enableMigration: true,
av: avSettings,
logging: { level: 'INFO' }
}
});
Publishing streams
Publisher configuration
Configure publisher settings before starting to publish:
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
};
Start publishing
Begin publishing a stream to the session:
const publisher = await client.publish({
settings: publisherSettings,
onError: onPublisherError,
onStreamDestroyed: onStreamDestroyed
});
console.log(`Publishing stream: ${publisher.stream.streamId}`);
The promise resolves once the stream has been created, so there is no onStreamCreated callback. The
onStreamDestroyed callback fires only when the stream is destroyed unexpectedly, such as a server-side forced
unpublish — it does not fire when you call unpublish() yourself.
Important
If you're publishing audio (hasAudio: true), you must wait for the onReadyForAudio callback before calling addAudio(). This callback indicates that the audio system is initialized and ready to accept audio data. Frames submitted beforehand are silently dropped by the native layer. This requirement does not apply to video-only publishing scenarios.
// 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);
Video has no equivalent readiness signal: frames can be submitted as soon as publish() resolves.
Adding audio data
Send audio data to your published stream:
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() is synchronous. It returns true if the frame was accepted, and false if the controller is not
initialized or the native call fails. It throws a TypeError if the frame is malformed — for example if the
buffer is too small for the declared frame geometry.
Stop publishing
Stop publishing when done:
await client.unpublish();
Checking publishing state
if (client.isPublishing()) {
console.log('Still publishing');
}
Subscribing to streams
Subscribe to streams
When a new stream is received, subscribe to it to receive audio and/or video data:
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);
}
}
Receiving subscribed media
The library delivers audio and video through different callbacks. For the reasoning behind this design, see Subscribing to streams in the Video Connector guide.
Important
The Buffer inside an AudioData or VideoFrame delivered to a callback is only valid for the duration of that callback. If you need to retain the media beyond the callback — to queue it, or process it asynchronously — copy it first, for example with Buffer.from(frame.data).
Video data: Video frames are delivered individually per subscribed stream through the onRenderFrame
callback registered at subscription time. Each invocation includes the subscriber that identifies which stream
the frame belongs to, so you can process video from different participants separately.
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);
}
Audio data: Mixed audio is delivered through the onAudioData callback registered during connect(),
approximately every 10 ms. The library automatically mixes audio from all subscribed streams into a single
stream. You cannot distinguish between individual participants' audio in this 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)`
);
}
Caption data: This feature is currently available as a beta feature. Caption text is delivered per
subscribed stream through the onCaptionText callback, with the subscriber identifying the source stream and a
CaptionsData object containing the text and whether the result is final or interim.
Note
For the onCaptionText callback to receive caption data, live captions must be enabled in the underlying Vonage Video API session configuration (outside of this library; see the Vonage Video API Live Captions documentation) and for the specific publisher stream that is sending audio. Set subscribeToCaptions: true in the subscriber settings to receive them.
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}`);
}
Individual audio data
This capability is currently available as a beta feature. Individual stream audio is delivered via the
onAudioData callback registered at subscription time through subscribe() — distinct from the session-level
onAudioData, which carries the mix. Audio arrives in the format received from the stream, Linear PCM 16-bit,
and neither the sample rate nor the channel count can be configured prior to reception.
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)`
);
}
Unsubscribe from streams
Stop receiving media from a specific stream:
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);
});
}
Audio data handling
Audio format
Audio data is exchanged as Linear PCM 16-bit signed little-endian samples in a Buffer:
- Sample rates: 8000, 12000, 16000, 24000, 32000, 44100, or 48000 Hz
- Channels: 1 (mono) or 2 (stereo)
- Format: 16-bit signed little-endian in a
Buffer - Frame size: Typically 20ms chunks (varies by sample rate)
Processing audio data
Handle incoming audio in the session audio data callback:
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);
}
Creating audio data
When adding audio, build a Buffer of 16-bit samples:
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 });
Audio data continuity
When you publish audio, the library sends silence until your first addAudio() call, tolerates brief gaps
without sending packets, then falls back to explicit silence frames, and pads partial periods to prevent drift.
See Audio continuity for the full behavior.
Best practices:
- Maintain a consistent audio rate by calling
addAudio()at regular intervals matching your configured sample rate - Monitor buffer statistics using
getMediaBufferStats()to ensure adequate audio data - Handle the
onMediaBufferDrainedcallback to detect when your audio buffer is depleted - Consider implementing an audio generation strategy that adapts to varying processing loads
Video data handling
Video format
Video data is exchanged as 8-bit unsigned values in a Buffer, in one of three formats:
- YUV420P: Planar YUV format with 4:2:0 chroma subsampling
- RGB24: Packed RGB, 8 bits per channel
- ARGB32: Packed ARGB, 8 bits per channel including alpha
Video specifications:
- Resolutions: Up to 1920x1080 (Full HD)
- Frame rates: 1-30 FPS
- Format: 8-bit unsigned values in a
Buffer
Processing video frames
Handle incoming video frames in the render frame callback:
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);
}
Creating video frames
When publishing video, build a Buffer sized for the format and resolution:
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() is synchronous. It returns true if the frame was accepted, and false if the controller is not
initialized or the native call fails. It throws a TypeError if the frame is malformed — an unknown format, a
non-positive dimension, a pixel count above 1920x1080, or a buffer too small for the declared resolution.
Video frame continuity
When you publish video, the library sends black frames until your first addVideo() call, repeats your last
frame for up to 2 seconds if you stop supplying frames, then falls back to black frames. See
Video continuity for the full behavior.
Best practices:
- Maintain a consistent frame rate by calling
addVideo()at regular intervals matching your configured FPS - Monitor buffer statistics using
getMediaBufferStats()to ensure adequate video data - Handle the
onMediaBufferDrainedcallback to detect when your video buffer is depleted - Consider implementing a frame generation strategy that adapts to varying processing loads
Media buffer management
Checking buffer stats
Monitor the state of your media buffers:
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`);
}
Each field is null when no publisher of that media type is active.
Clearing media buffers
Clear both audio and video buffers when needed, for example to interrupt playback mid-utterance:
if (client.clearMediaBuffers()) {
console.log('Media buffers cleared successfully');
}
Buffer drained callback
Handle buffer drain events:
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`);
}
}
The onMediaBufferDrained callback is registered in connect(). It is invoked when the internal audio or video
buffers are depleted, and implements hysteresis so it does not fire repeatedly while the buffer remains empty.
See Buffer drain events for details.
Getting connection info
Retrieve your local connection information:
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() returns null when the client is not connected.
Event callbacks
Callbacks are grouped by the call that registers them. Remember that the one-shot lifecycle events —
session connected, publisher stream created, and subscriber connected — are delivered by the promises returned
from connect(), publish(), and subscribe() rather than as callbacks.
Session callbacks
Registered in connect():
| Callback | 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
}
Publisher callbacks
Registered in publish():
| Callback | 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}`);
}
Subscriber callbacks
Registered in subscribe():
| Callback | 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}`);
}
Error handling
The library reports problems in three distinct ways.
Synchronous throws for invalid input. Arguments are validated before crossing into the native layer.
addAudio(), addVideo(), and the audio/video settings passed to connect() throw a TypeError when a value
has the wrong type or violates a documented constraint. publish() throws a ValidationError when the settings
are well-typed but break a rule, such as hasAudio and hasVideo both being 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 is a class exported by the package, so it can be caught with instanceof.
Promise rejections for failed operations. If an operation fails before it settles, its promise rejects with
an Error whose message includes the underlying description and code.
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 for errors after settlement. Once an operation has succeeded, later failures are reported to the
onError callback registered for that scope. An onError callback is therefore never invoked for a failure that
already rejected the promise.
Boolean returns for media submission. addAudio(), addVideo(), and clearMediaBuffers() return false
rather than throwing when the controller is not initialized or the native call fails.
Resource cleanup
Always clean up resources properly:
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'));
}
}