Video Connector for Python

The Vonage Video Connector Python 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 Python library is designed for server-side applications and requires valid Vonage Video API credentials and tokens with appropriate permissions.

This page covers the Python API. For the concepts, media formats, and runtime behavior shared by all Video Connector libraries, see the Video Connector guide.

This topic includes the following sections:

Getting Started

Vonage Video Connector Server SDK is available on PyPI as vonage-video-connector.

To install the library, run:

pip install vonage-video-connector

Requirements

This library requires Python 3.13 running Linux AMD64 and ARM64 platforms. We recommend using Debian Bookworm as it is the distribution where this has been most thoroughly tested.

Data structures

The Vonage Video Connector Python library uses several key data structures to represent sessions, connections, streams, and audio data. Understanding these structures is essential for working with the library effectively.

Session

Represents a Vonage Video API session that clients can connect to:

from vonage_video_connector.models import Session

# Session object properties
session.id  # str: Unique identifier for the session

The Session object is passed to various callback functions to identify which session triggered the event.

Connection

Represents a participant's connection to a session:

from vonage_video_connector.models import Connection

# Connection object properties
connection.id             # str: Unique identifier for the connection
connection.creation_time  # datetime: When the connection was established
connection.data          # str: Connection data (encoded in the token)

Connection data can be used to store custom metadata about participants, such as user IDs or roles.

Stream

Represents a media stream (audio/video) published by a participant:

from vonage_video_connector.models import Stream

# Stream object properties
stream.id          # str: Unique identifier for the stream
stream.connection  # Connection: The underlying connection that published this stream

Streams are created when participants publish media and are used for subscribing to receive their audio/video data.

Publisher

Represents your published stream in the session:

from vonage_video_connector.models import Publisher

# Publisher object properties
publisher.stream  # Stream: The underlying stream for this publisher

The Publisher object is used in publisher-related callbacks and represents your own published media stream.

Subscriber

Represents a subscription to another participant's stream:

from vonage_video_connector.models import Subscriber

# Subscriber object properties
subscriber.stream  # Stream: The underlying stream for this subscriber

The Subscriber object is used in subscriber-related callbacks and represents your subscription to receive another participant's media.

AudioData

Represents audio data being transmitted or received:

from vonage_video_connector.models import AudioData

# AudioData object properties
audio_data.sample_buffer      # memoryview: 16-bit signed integer audio samples
audio_data.sample_rate        # int: Sample rate (8000-48000 Hz)
audio_data.number_of_channels # int: 1 (mono) or 2 (stereo)
audio_data.number_of_frames   # int: Number of audio frames

Audio format requirements:

  • Sample buffer must contain 16-bit signed integers
  • Valid sample rates: 8000, 12000, 16000, 24000, 32000, 44100, 48000 Hz
  • Channels: 1 (mono) or 2 (stereo)
  • Buffer size must accommodate: number_of_frames * number_of_channels samples

VideoFrame

Represents video frame data being transmitted or received:

from vonage_video_connector.models import VideoFrame, VideoResolution

# VideoFrame object properties
video_frame.frame_buffer  # memoryview: 8-bit unsigned char video frame data
video_frame.resolution    # VideoResolution: Width and height in pixels
video_frame.format        # str: Video format (YUV420P, RGB24, or ARGB32)

Video format requirements:

  • Frame buffer must contain 8-bit unsigned chars
  • Valid formats: YUV420P, RGB24, ARGB32
  • Maximum resolution: 1920x1080 pixels (2,073,600 total pixels)
  • Buffer size varies by format and resolution

VideoResolution

Represents the dimensions of a video frame:

from vonage_video_connector.models import VideoResolution

# VideoResolution object properties
resolution = VideoResolution(
    width=640,   # int: Width in pixels, defaults to 640
    height=480   # int: Height in pixels, defaults to 480
)

Both must be positive integers, and the total pixel count must not exceed 1920 * 1080 (2,073,600).

CaptionsData

Represents caption text data received from a subscribed stream:

from vonage_video_connector.models import CaptionsData

# CaptionsData object properties
captions_data.text      # str: The caption text content
captions_data.is_final  # bool: True for final captions, False for interim/partial captions

MediaBufferStats

Provides statistics about media buffers:

from vonage_video_connector.models import MediaBufferStats, AudioBufferStats, VideoBufferStats

# MediaBufferStats object properties
stats.audio  # Optional[AudioBufferStats]: Audio buffer statistics
stats.video  # Optional[VideoBufferStats]: Video buffer statistics

# AudioBufferStats properties
stats.audio.duration  # timedelta: Duration of queued audio

# VideoBufferStats properties
stats.video.duration  # timedelta: Duration of queued video

Configuration structures

SessionSettings

Configures session-level behavior:

from vonage_video_connector.models import SessionSettings

session_settings = SessionSettings(
    enable_migration=False,  # bool: Enable automatic session migration
    av=av_settings,          # Optional[SessionAVSettings]: Audio/video configuration
    logging=logging_settings # Optional[LoggingSettings]: Logging configuration
)

SessionAVSettings

Configures audio and video settings for the session:

from vonage_video_connector.models import (
    SessionAVSettings,
    SessionAudioSettings,
    SessionVideoPublisherSettings,
    VideoResolution,
)

av_settings = SessionAVSettings(
    audio_publisher=SessionAudioSettings(sample_rate=48000, number_of_channels=2),
    audio_subscribers_mix=SessionAudioSettings(sample_rate=48000, number_of_channels=1),
    video_publisher=SessionVideoPublisherSettings(
        resolution=VideoResolution(width=1280, height=720),
        fps=30,
        format="YUV420P"
    )
)
  • audio_publisher: Defines the format for audio data you provide via add_audio(). The audio data you send must match this configuration's sample rate and number of channels.
  • audio_subscribers_mix: Defines the format for the mixed audio you receive from all subscribed streams via the on_audio_data_cb callback. 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:

from vonage_video_connector.models import SessionAudioSettings

audio_settings = SessionAudioSettings(
    sample_rate=48000,       # int: Optional, defaults to 48000
    number_of_channels=1     # int: Optional, 1 (mono) or 2 (stereo), defaults to 2
)

SessionVideoPublisherSettings

Configures video settings for publishing:

from vonage_video_connector.models import SessionVideoPublisherSettings, VideoResolution

video_settings = SessionVideoPublisherSettings(
    resolution=VideoResolution(width=1280, height=720),  # Required: resolution in pixels
    fps=30,              # int: Optional, 1-30, defaults to 30
    format="YUV420P"     # str: Optional, defaults to "YUV420P"
)

Unlike the other settings models, resolution is required. VideoResolution itself defaults to 640x480.

LoggingSettings

Controls logging verbosity:

from vonage_video_connector.models import LoggingSettings

logging_settings = LoggingSettings(
    level="INFO"  # str: Optional, ERROR, WARN, INFO, DEBUG, or TRACE, defaults to WARN
)

PublisherSettings

Configures your published stream:

from vonage_video_connector.models import PublisherSettings, PublisherAudioSettings

publisher_settings = PublisherSettings(
    name="My Application",           # str: Name for your published stream (required, min 1 char)
    has_audio=True,                  # bool: Whether to publish audio
    has_video=True,                  # bool: Whether to publish video
    enable_captions=False,           # bool: Whether to enable live captions for this stream (default: False)
    audio_settings=audio_settings    # Optional[PublisherAudioSettings]: Audio configuration
)

Note: At least one of has_audio or has_video must be True.

Note: Set enable_captions=True to allow subscribers to receive live caption text from this stream via the on_caption_text_cb callback. Captions are disabled by default.

PublisherAudioSettings

Configures audio settings for your published stream:

from vonage_video_connector.models import PublisherAudioSettings

audio_settings = PublisherAudioSettings(
    enable_stereo_mode=True,  # bool: Publish in stereo (True) or mono (False)
    enable_opus_dtx=False     # bool: Enable discontinuous transmission
)

Discontinuous transmission (DTX) stops sending audio packets during silence, saving bandwidth.

SubscriberSettings

Configures subscriber behavior:

from vonage_video_connector.models import SubscriberSettings, SubscriberVideoSettings, VideoResolution

subscriber_settings = SubscriberSettings(
    subscribe_to_audio=True,     # bool: Whether to subscribe to audio
    subscribe_to_video=True,     # bool: Whether to subscribe to video
    subscribe_to_captions=False, # bool: Whether to subscribe to live captions (default: False)
    video_settings=SubscriberVideoSettings(
        preferred_resolution=VideoResolution(width=640, height=480),
        preferred_framerate=15
    )
)

Note: At least one of subscribe_to_audio or subscribe_to_video must be True.

Note: Set subscribe_to_captions=True to receive caption text from this stream via the on_caption_text_cb callback. Captions must also be enabled on the publishing side.

SubscriberVideoSettings

Configures video preferences for subscribers:

from vonage_video_connector.models import SubscriberVideoSettings, VideoResolution

video_settings = SubscriberVideoSettings(
    preferred_resolution=VideoResolution(width=640, height=480),  # Optional
    preferred_framerate=15  # Optional: Preferred FPS (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:

from vonage_video_connector import VonageVideoClient
from vonage_video_connector.models import (
    SessionSettings,
    SessionAVSettings,
    SessionAudioSettings,
    LoggingSettings,
)

# Create client instance
client = VonageVideoClient()

# Configure session settings
session_settings = SessionSettings(
    enable_migration=False,
    av=SessionAVSettings(
        audio_subscribers_mix=SessionAudioSettings(
            sample_rate=48000,
            number_of_channels=1
        )
    ),
    logging=LoggingSettings(level="INFO")
)

# Connect to session
connecting = client.connect(
    application_id="your_application_id",
    session_id="your_session_id",
    token="your_token",
    session_settings=session_settings,
    on_connected_cb=on_session_connected,
    on_error_cb=on_session_error
)

connect() is asynchronous and returns as soon as the connection attempt has been started. A return value of True means the request was accepted and dispatched, not that the client is connected to the session. Wait for on_connected_cb to be invoked before treating the session as connected, and before publishing or subscribing.

A return value of False means the request was rejected before it was dispatched, for example because the client is already connected to a session or the session information is incomplete. Failures that occur after the request has been dispatched, such as an expired or invalid token, are reported to on_error_cb rather than through the return value.

Connection with all callbacks

For full session management, implement all available callbacks:

connecting = client.connect(
    application_id="your_application_id",
    session_id="your_session_id",
    token="your_token",
    session_settings=session_settings,
    on_error_cb=on_session_error,
    on_connected_cb=on_session_connected,
    on_disconnected_cb=on_session_disconnected,
    on_connection_created_cb=on_connection_created,
    on_connection_dropped_cb=on_connection_dropped,
    on_stream_received_cb=on_stream_received,
    on_stream_dropped_cb=on_stream_dropped,
    on_audio_data_cb=on_audio_data,
    on_ready_for_audio_cb=on_ready_for_audio,
    on_media_buffer_drained_cb=on_media_buffer_drained
)

Disconnecting from a session

Disconnect from the session when done:

success = client.disconnect()

disconnect() returns True once everything has been torn down, and also returns True if the client was not connected to a session in the first place.

Checking connection state

if client.is_connected():
    print("Still connected")

Session settings

Audio and video configuration

Configure audio and video settings for the session to control the format of media data:

from vonage_video_connector.models import (
    SessionAVSettings, 
    SessionAudioSettings, 
    SessionVideoPublisherSettings,
    VideoResolution
)

# Configure audio for publisher and subscriber mix
audio_publisher = SessionAudioSettings(
    sample_rate=48000,      # Valid: 8000, 12000, 16000, 24000, 32000, 44100, 48000
    number_of_channels=2    # 1 for mono, 2 for stereo
)

audio_subscribers_mix = SessionAudioSettings(
    sample_rate=48000,
    number_of_channels=1
)

# Configure video publisher settings
video_publisher = SessionVideoPublisherSettings(
    resolution=VideoResolution(width=1280, height=720),
    fps=30,
    format="YUV420P"  # Valid: YUV420P, RGB24, ARGB32
)

# Combine into session AV settings
av_settings = SessionAVSettings(
    audio_publisher=audio_publisher,
    audio_subscribers_mix=audio_subscribers_mix,
    video_publisher=video_publisher
)

Logging configuration

Control the verbosity of console logging:

from vonage_video_connector.models import LoggingSettings

# Configure logging level
logging_settings = LoggingSettings(
    level="DEBUG"  # Valid: ERROR, WARN, INFO, DEBUG, TRACE
)

Session migration

Enable automatic session migration in case of SFU rotation:

from vonage_video_connector.models import SessionSettings

session_settings = SessionSettings(
    enable_migration=True,  # Enable automatic migration
    av=av_settings,
    logging=logging_settings
)

Publishing streams

Publisher configuration

Configure publisher settings before starting to publish:

from vonage_video_connector.models import PublisherSettings, PublisherAudioSettings

# Configure publisher audio settings
audio_settings = PublisherAudioSettings(
    enable_stereo_mode=True,   # Publish in stereo
    enable_opus_dtx=False      # Enable discontinuous transmission for bandwidth savings
)

# Create publisher settings for audio and video
publisher_settings = PublisherSettings(
    name="AI Assistant Bot",
    has_audio=True,
    has_video=True,
    enable_captions=True,
    audio_settings=audio_settings
)

# Or audio-only publisher
audio_only_settings = PublisherSettings(
    name="Audio Bot",
    has_audio=True,
    has_video=False,
    enable_captions=False,
    audio_settings=audio_settings
)

Start publishing

Begin publishing a stream to the session:

success = client.publish(
    settings=publisher_settings,
    on_error_cb=on_publisher_error,
    on_stream_created_cb=on_stream_created,
    on_stream_destroyed_cb=on_stream_destroyed
)

Important If you're publishing audio (has_audio=True), you must wait for the on_ready_for_audio_cb callback to be invoked before calling add_audio(). This callback indicates that the audio system is initialized and ready to accept audio data. This requirement does not apply to video-only publishing scenarios.

import time

# Example: Wait for audio system to be ready
audio_ready = False

def on_ready_for_audio(session):
    global audio_ready
    audio_ready = True
    print("Audio system ready - can now add audio")

# Connect with the callback
client.connect(
    application_id="your_application_id",
    session_id="your_session_id",
    token="your_token",
    session_settings=session_settings,
    on_ready_for_audio_cb=on_ready_for_audio
)

# Publish
client.publish(settings=publisher_settings)

# Wait for audio to be ready before adding audio
while not audio_ready:
    time.sleep(0.01)

# Now safe to add audio
client.add_audio(audio_data)

Adding audio data

Send audio data to your published stream:

from vonage_video_connector.models import AudioData

# Create audio data (example with 16-bit PCM samples)
audio_buffer = memoryview(your_audio_samples)  # Must be 16-bit signed integers

audio_data = AudioData(
    sample_buffer=audio_buffer,
    sample_rate=48000,
    number_of_channels=1,
    number_of_frames=960  # 20ms at 48kHz
)

# Add audio to the published stream
success = client.add_audio(audio_data)

add_audio() returns True if the frame was accepted, and False if the publishing pipeline is not ready or the native call fails. Constructing the AudioData raises a ValidationError if the frame is malformed — for example if the buffer is too small for the declared frame geometry.

Stop publishing

Stop publishing when done:

success = client.unpublish()

Checking publishing state

if client.is_publishing():
    print("Still publishing")

Subscribing to streams

Subscribe to streams

When a new stream is received, subscribe to it to receive audio and/or video data:

from vonage_video_connector.models import SubscriberSettings, SubscriberVideoSettings, VideoResolution

def on_stream_received(session, stream):
    print(f"New stream received: {stream.id}")
    print(f"From connection: {stream.connection.id}")
    
    # Configure subscriber settings (optional)
    subscriber_settings = SubscriberSettings(
        subscribe_to_audio=True,
        subscribe_to_video=True,
        subscribe_to_captions=True,
        video_settings=SubscriberVideoSettings(
            preferred_resolution=VideoResolution(width=640, height=480),
            preferred_framerate=15
        )
    )
    
    # Subscribe to the stream
    success = client.subscribe(
        stream=stream,
        settings=subscriber_settings,
        on_error_cb=on_subscriber_error,
        on_connected_cb=on_subscriber_connected,
        on_disconnected_cb=on_subscriber_disconnected,
        on_render_frame_cb=on_render_frame,
        on_audio_data_cb=on_subscriber_audio_data,
        on_caption_text_cb=on_caption_text
    )

Receiving subscribed media

When you subscribe to streams, the library delivers audio and video data through different callbacks. For the reasoning behind this design, see Subscribing to streams in the Video Connector guide.

Important The memoryview 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 audio_data.sample_buffer.tobytes() or bytes(video_frame.frame_buffer).

Video data: Video frames are delivered individually per subscribed stream through the on_render_frame_cb callback. Each callback invocation includes the subscriber object that identifies which stream the video frame belongs to. This allows you to process video from different participants separately.

def on_render_frame(subscriber, video_frame):
    """Called for each subscribed stream's video frames"""
    stream_id = subscriber.stream.id
    width = video_frame.resolution.width
    height = video_frame.resolution.height
    print(f"Video frame from stream {stream_id}: {width}x{height}")
    # Process video for this specific stream

Audio data: Audio is delivered as a single mixed stream through the on_audio_data_cb callback registered during connect(). The library automatically mixes audio from all subscribed streams together into a single audio stream. You cannot distinguish between individual participants' audio in this callback.

def on_audio_data(session, audio_data):
    """Called with mixed audio from all subscribed streams"""
    sample_rate = audio_data.sample_rate
    channels = audio_data.number_of_channels
    print(f"Mixed audio from all subscribers: {sample_rate}Hz, {channels} channel(s)")
    # Process the combined audio from all participants

Caption data: This feature is currently available as a beta feature. Caption text is delivered individually per subscribed stream through the on_caption_text_cb callback. Each invocation includes the subscriber object identifying the source stream and a CaptionsData object containing the text and whether it is a final or interim result.

Note For the on_caption_text_cb 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 subscribe_to_captions=True in the subscriber settings to receive them.

def on_caption_text(subscriber, captions_data):
    """Called when caption text is received from a subscribed stream"""
    stream_id = subscriber.stream.id
    status = "final" if captions_data.is_final else "interim"
    print(f"Caption ({status}) from stream {stream_id}: {captions_data.text}")
    # Process interim captions for live display, final captions for storage or further processing

Individual audio data

This capability is currently available as a beta feature. Individual stream audio may be retrieved via the on_audio_data_cb callback registered at subscription time through subscribe(). Audio is delivered in the format received from the stream — Linear PCM 16-bit — and neither the sample rate nor the number of channels can be configured prior to reception.

def on_subscriber_audio_data(subscriber, audio_data):
    """Called with individual audio from the stream subscribed to"""
    sample_rate = audio_data.sample_rate
    channels = audio_data.number_of_channels
    print(f"Individual audio from stream {subscriber.stream.id}: {sample_rate}Hz, {channels} channel(s)")
    # Process the individual audio from this stream

Unsubscribe from streams

Stop receiving media from a specific stream:

def on_stream_dropped(session, stream):
    print(f"Stream dropped: {stream.id}")
    
    # Unsubscribe from the stream
    success = client.unsubscribe(stream)

Audio data handling

Audio format

Audio data is delivered as Linear PCM 16-bit signed integers with the following characteristics:

  • Sample rates: 8000, 12000, 16000, 24000, 32000, 44100, or 48000 Hz
  • Channels: 1 (mono) or 2 (stereo)
  • Format: 16-bit signed integers in a memoryview buffer
  • Frame size: Typically 20ms chunks (varies by sample rate)

Processing audio data

Handle incoming audio in the audio data callback:

def on_audio_data(session, audio_data):
    """Process incoming audio data from subscribed streams"""
    
    # Access audio properties
    sample_rate = audio_data.sample_rate
    channels = audio_data.number_of_channels
    frames = audio_data.number_of_frames
    
    # Access the audio buffer (memoryview of 16-bit signed integers)
    audio_buffer = audio_data.sample_buffer
    
    print(f"Received {frames} frames at {sample_rate}Hz, {channels} channel(s)")
    
    # Convert to bytes if needed
    audio_bytes = audio_buffer.tobytes()
    
    # Process the audio (e.g., transcription, analysis, etc.)
    process_audio(audio_buffer, sample_rate, channels)
    
    # Generate response audio and add it back
    response_audio = generate_response(audio_buffer)
    if response_audio:
        client.add_audio(response_audio)

Creating audio data

When adding audio, create properly formatted AudioData objects:

import array
import math

from vonage_video_connector.models import AudioData

# Create 16-bit PCM audio samples (example: sine wave)
sample_rate = 48000
duration_ms = 20  # 20ms frame
num_samples = int(sample_rate * duration_ms / 1000)

# Generate audio samples as 16-bit signed integers
samples = array.array('h')  # 'h' = signed short (16-bit)
for i in range(num_samples):
    # Example: generate a 440Hz sine wave
    sample = int(32767 * 0.5 * math.sin(2 * math.pi * 440 * i / sample_rate))
    samples.append(sample)

# Create AudioData object
audio_data = AudioData(
    sample_buffer=memoryview(samples),
    sample_rate=sample_rate,
    number_of_channels=1,
    number_of_frames=num_samples
)

# Add the audio
client.add_audio(audio_data)

Audio data continuity

When you publish audio, the library sends silence until your first add_audio() 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 add_audio() at regular intervals matching your configured sample rate
  • Monitor buffer statistics using get_media_buffer_stats() to ensure adequate audio data
  • Handle the on_media_buffer_drained_cb callback 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 delivered as 8-bit unsigned chars 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 chars in a memoryview buffer

Processing video frames

Handle incoming video frames in the render frame callback:

def on_render_frame(subscriber, video_frame):
    """Process incoming video frames from subscribed streams"""
    
    # Access video properties
    width = video_frame.resolution.width
    height = video_frame.resolution.height
    format = video_frame.format
    
    # Access the video buffer (memoryview of 8-bit unsigned chars)
    frame_buffer = video_frame.frame_buffer
    
    print(f"Received {width}x{height} frame in {format} format")
    
    # Convert to bytes if needed
    frame_bytes = frame_buffer.tobytes()
    
    # Process the video frame (e.g., computer vision, recording, etc.)
    process_video(frame_buffer, width, height, format)

Creating video frames

When publishing video, create properly formatted VideoFrame objects:

import array
from vonage_video_connector.models import VideoFrame, VideoResolution

# Create a video frame (example: solid color in YUV420P format)
width = 640
height = 480

# YUV420P format calculation:
# Y plane: width * height
# U plane: (width/2) * (height/2)
# V plane: (width/2) * (height/2)
y_size = width * height
uv_size = (width // 2) * (height // 2)
total_size = y_size + 2 * uv_size

# Create frame buffer as 8-bit unsigned chars
frame_data = array.array('B', [128] * total_size)  # 'B' = unsigned char (8-bit)

# Create VideoFrame object
video_frame = VideoFrame(
    frame_buffer=memoryview(frame_data),
    resolution=VideoResolution(width=width, height=height),
    format="YUV420P"
)

# Add the video frame
success = client.add_video(video_frame)

add_video() returns True if the frame was accepted, and False if the publishing pipeline is not ready or the native call fails. Constructing the VideoFrame raises a ValidationError 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 add_video() 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 add_video() at regular intervals matching your configured FPS
  • Monitor buffer statistics using get_media_buffer_stats() to ensure adequate video data
  • Handle the on_media_buffer_drained_cb callback 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:

# Get current buffer statistics
stats = client.get_media_buffer_stats()

if stats.audio:
    print(f"Audio buffer duration: {stats.audio.duration.total_seconds()}s")

if stats.video:
    print(f"Video buffer duration: {stats.video.duration.total_seconds()}s")

Each field is None when no publisher of that media type is active.

Clearing media buffers

Clear both audio and video buffers when needed:

# Clear all media buffers
success = client.clear_media_buffers()

if success:
    print("Media buffers cleared successfully")

Buffer drained callback

Handle buffer drain events:

def on_media_buffer_drained(stats):
    """Called when media buffers are drained"""
    print("Media buffers drained")
    
    if stats.audio:
        print(f"Audio buffer: {stats.audio.duration.total_seconds()}s remaining")
    
    if stats.video:
        print(f"Video buffer: {stats.video.duration.total_seconds()}s remaining")

The on_media_buffer_drained_cb callback 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:

# Get the local connection
connection = client.get_connection()

if connection:
    print(f"Connection ID: {connection.id}")
    print(f"Connection data: {connection.data}")
    print(f"Created at: {connection.creation_time}")

get_connection() returns None when the client is not connected.

Event callbacks

Session callbacks

Registered in connect():

Callback Signature
on_error_cb (session: Session, description: str, code: int) -> None
on_connected_cb (session: Session) -> None
on_disconnected_cb (session: Session) -> None
on_connection_created_cb (session: Session, connection: Connection) -> None
on_connection_dropped_cb (session: Session, connection: Connection) -> None
on_stream_received_cb (session: Session, stream: Stream) -> None
on_stream_dropped_cb (session: Session, stream: Stream) -> None
on_audio_data_cb (session: Session, audio_data: AudioData) -> None
on_ready_for_audio_cb (session: Session) -> None
on_media_buffer_drained_cb (stats: MediaBufferStats) -> None

Handle session-level events:

def on_session_error(session, error_description, error_code):
    """Handle session errors"""
    print(f"Session error: {error_description} (Code: {error_code})")

def on_session_connected(session):
    """Handle successful session connection"""
    print(f"Connected to session: {session.id}")

def on_session_disconnected(session):
    """Handle session disconnection"""
    print(f"Disconnected from session: {session.id}")

def on_ready_for_audio(session):
    """Called when the audio system is ready"""
    print("Audio system ready - can now add audio")

def on_connection_created(session, connection):
    """Handle new participant joining"""
    print(f"Participant joined: {connection.id}")
    print(f"Connection data: {connection.data}")

def on_connection_dropped(session, connection):
    """Handle participant leaving"""
    print(f"Participant left: {connection.id}")

def on_stream_received(session, stream):
    """Handle new streams from other participants"""
    print(f"Stream received: {stream.id} from connection {stream.connection.id}")
    # Decide whether to subscribe based on your application logic

def on_stream_dropped(session, stream):
    """Handle streams being removed"""
    print(f"Stream dropped: {stream.id}")

Publisher callbacks

Registered in publish():

Callback Signature
on_error_cb (publisher: Publisher, description: str, code: int) -> None
on_stream_created_cb (publisher: Publisher) -> None
on_stream_destroyed_cb (publisher: Publisher) -> None

Handle publishing events:

def on_publisher_error(publisher, error_description, error_code):
    """Handle publisher errors"""
    print(f"Publisher error: {error_description} (Code: {error_code})")

def on_stream_created(publisher):
    """Handle successful stream creation"""
    print(f"Published stream created: {publisher.stream.id}")

def on_stream_destroyed(publisher):
    """Handle stream destruction"""
    print(f"Published stream destroyed: {publisher.stream.id}")

Subscriber callbacks

Registered in subscribe():

Callback Signature
on_error_cb (subscriber: Subscriber, description: str, code: int) -> None
on_connected_cb (subscriber: Subscriber) -> None
on_disconnected_cb (subscriber: Subscriber) -> None
on_render_frame_cb (subscriber: Subscriber, video_frame: VideoFrame) -> None
on_audio_data_cb (subscriber: Subscriber, audio_data: AudioData) -> None
on_caption_text_cb (subscriber: Subscriber, captions_data: CaptionsData) -> None

Handle subscription events:

def on_subscriber_error(subscriber, error_description, error_code):
    """Handle subscriber errors"""
    print(f"Subscriber error: {error_description} (Code: {error_code})")

def on_subscriber_connected(subscriber):
    """Handle successful subscription"""
    print(f"Subscribed to stream: {subscriber.stream.id}")

def on_subscriber_disconnected(subscriber):
    """Handle subscription disconnection"""
    print(f"Unsubscribed from stream: {subscriber.stream.id}")

def on_render_frame(subscriber, video_frame):
    """Handle incoming video frames"""
    width = video_frame.resolution.width
    height = video_frame.resolution.height
    print(f"Video frame: {width}x{height} in {video_frame.format} format")

def on_caption_text(subscriber, captions_data):
    """Handle incoming caption text"""
    status = "final" if captions_data.is_final else "interim"
    print(f"Caption ({status}) from stream {subscriber.stream.id}: {captions_data.text}")

Error handling

The library reports problems in four distinct ways.

ValidationError when a model is constructed. Every settings and media model is a pydantic model that validates its own fields, so invalid values are rejected where the object is created rather than when it is handed to the client. Sample rates, channel counts, log levels, pixel formats, frame rates, resolutions, buffer element size, and buffer capacity are all checked.

from pydantic import ValidationError

from vonage_video_connector.models import PublisherSettings, SessionAudioSettings

try:
    publisher_settings = PublisherSettings(name="Bot", has_audio=False, has_video=False)
except ValidationError as e:
    # "At least one of has_audio(False) or has_video (False) must be set to true."
    print(f"Invalid publisher settings: {e}")

try:
    audio_settings = SessionAudioSettings(sample_rate=44000)
except ValidationError as e:
    # "44000 is not one of the allowed: [8000, 12000, 16000, 24000, 32000, 44100, 48000]"
    print(f"Invalid audio settings: {e}")

Note that format and level values are case insensitive and are normalized to upper case, so format="yuv420p" and LoggingSettings(level="info") are both accepted.

TypeError and AttributeError from client methods. The native layer reads the attributes it needs off the objects you pass. It raises AttributeError when an expected attribute is missing, and TypeError when an argument or attribute has the wrong type, including when a callback is not callable.

try:
    client.add_audio(audio_data)
except (TypeError, AttributeError) as e:
    print(f"Malformed audio data: {e}")

False return values for failed operations. Every client method returns a bool rather than raising on operational failure. connect() returns False if the client is already connected to a session, publish() returns False if it is already publishing, and add_audio() and add_video() return False if the publishing pipeline is not ready. Check the result rather than assuming success.

if not client.connect(
    application_id="your_application_id",
    session_id="your_session_id",
    token="your_token",
    session_settings=session_settings,
):
    print("Failed to start connecting - the client may already be connected")

on_error_cb callbacks for runtime errors. Failures that happen after a call has been accepted — including the connection attempt itself failing — are reported to the on_error_cb registered for that scope, with a description and a numeric code. Session, publisher, and subscriber scopes each have their own.

def on_session_error(session, error_description, error_code):
    print(f"Session error: {error_description} (Code: {error_code})")

client.connect(
    application_id="your_application_id",
    session_id="your_session_id",
    token="your_token",
    on_error_cb=on_session_error,
)

Resource cleanup

Always clean up resources properly:

try:
    # Your application logic
    connecting = client.connect(
        application_id="your_application_id",
        session_id="your_session_id",
        token="your_token",
        session_settings=session_settings,
    )
    # ... do work ...

except Exception as e:
    print(f"Application error: {e}")

finally:
    # Clean up resources
    if client.is_publishing():
        client.unpublish()

    if client.is_connected():
        client.disconnect()