Skip to content
6 changes: 6 additions & 0 deletions video/src/vonage_video/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
AudioConnectorData,
AudioConnectorOptions,
AudioConnectorWebSocket,
AudioTransportConfiguration,
)
from .broadcast import (
Broadcast,
Expand All @@ -22,6 +23,8 @@
ArchiveMode,
ArchiveStatus,
AudioSampleRate,
AudioTransportEncoding,
AudioTransportTransport,
ExperienceComposerStatus,
LanguageCode,
LayoutType,
Expand All @@ -48,6 +51,9 @@
"AudioConnectorData",
"AudioConnectorOptions",
"AudioConnectorWebSocket",
"AudioTransportConfiguration",
"AudioTransportEncoding",
"AudioTransportTransport",
"Archive",
"ListArchivesFilter",
"Transcription",
Expand Down
36 changes: 34 additions & 2 deletions video/src/vonage_video/models/audio_connector.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,35 @@
from typing import Optional

from pydantic import BaseModel, Field
from vonage_video.models.enums import AudioSampleRate
from pydantic import BaseModel, Field, model_validator
from vonage_video.models.enums import (
AudioSampleRate,
AudioTransportEncoding,
AudioTransportTransport,
)
Comment thread
superchilled marked this conversation as resolved.


class AudioTransportConfiguration(BaseModel):
"""The audio transport configuration.

Args:
transport (AudioTransportTransport): 'binary' (raw PCM16, the default) or 'json'.
encoding (AudioTransportEncoding): Required when transport is 'json'. Set to 'base64'.
audio_field (str): The JSON key for the outbound audio data. Defaults to 'audio'.
receive_audio_field (str): The JSON key for inbound audio data (when bidirectional is enabled). Defaults to the same value as audio_field.
static_fields (dict): A dictionary of extra key-value pairs included in every outbound JSON audio message.
"""

transport: Optional[AudioTransportTransport] = None
encoding: Optional[AudioTransportEncoding] = None
audio_field: Optional[str] = None
receive_audio_field: Optional[str] = None
static_fields: Optional[dict] = None

@model_validator(mode='after')
def encoding_must_be_specified_for_json_transport(self):
if self.transport == AudioTransportTransport.JSON and not self.encoding:
raise ValueError("encoding must be specified when transport is JSON")
return self


class AudioConnectorWebSocket(BaseModel):
Expand All @@ -13,6 +41,7 @@ class AudioConnectorWebSocket(BaseModel):
headers (dict): The headers to send to your WebSocket server.
audio_rate (AudioSampleRate): The audio sample rate in Hertz.
bidirectional (bool): Whether the websocket is bidirectional.
audio_transport (AudioTransportConfiguration): The audio transport configuration. Configures how audio is serialized on the WebSocket wire. By default, audio is sent as raw binary PCM 16-bit frames.
"""

uri: str
Expand All @@ -22,6 +51,9 @@ class AudioConnectorWebSocket(BaseModel):
bidirectional: Optional[bool] = Field(
None, description="Whether the websocket is bidirectional."
)
audio_transport: Optional[AudioTransportConfiguration] = Field(
None, serialization_alias='audioTransport'
)

def model_dump(self, *args, **kwargs):
data = super().model_dump(*args, **kwargs)
Expand Down
13 changes: 13 additions & 0 deletions video/src/vonage_video/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ class AudioSampleRate(int, Enum):
KHZ_16 = 16000


class AudioTransportEncoding(str, Enum):
"""Audio encoding type when using JSON transport."""

BASE64 = 'base64'


class AudioTransportTransport(str, Enum):
"""Audio transport type for the WebSocket connection."""

BINARY = 'binary'
JSON = 'json'


class VideoResolution(str, Enum):
"""The resolution of the archive or broadcast.

Expand Down
54 changes: 53 additions & 1 deletion video/tests/test_audio_connector.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from os.path import abspath

import pytest
import responses
from pydantic import ValidationError
from vonage_http_client import HttpClient
from vonage_video import (
AudioConnectorOptions,
Expand All @@ -10,6 +12,8 @@
TokenRole,
Video,
)
from vonage_video.models.audio_connector import AudioTransportConfiguration
from vonage_video.models.enums import AudioTransportEncoding, AudioTransportTransport

from testutils import build_response, get_mock_jwt_auth

Expand Down Expand Up @@ -44,7 +48,7 @@ def test_audio_connector_options_model():
),
)

actual = options.model_dump(by_alias=True)
actual = options.model_dump(by_alias=True, exclude_none=True)
expected = {
'sessionId': 'test_session_id',
'token': 'test_token',
Expand All @@ -59,6 +63,54 @@ def test_audio_connector_options_model():
assert actual == expected


def test_audio_connector_options_model_with_audio_transport():
options = AudioConnectorOptions(
session_id='test_session_id',
token='test_token',
websocket=AudioConnectorWebSocket(
uri='test_uri',
streams=['test_stream_id'],
headers={'test_header': 'test_value'},
audio_rate=AudioSampleRate.KHZ_16,
bidirectional=True,
audio_transport=AudioTransportConfiguration(
transport=AudioTransportTransport.JSON,
encoding=AudioTransportEncoding.BASE64,
audio_field='audio',
receive_audio_field='audio',
static_fields={'foo': 'bar'},
),
),
)

actual = options.model_dump(by_alias=True, exclude_none=True)
expected = {
'sessionId': 'test_session_id',
'token': 'test_token',
'websocket': {
'uri': 'test_uri',
'streams': ['test_stream_id'],
'headers': {'test_header': 'test_value'},
'audioRate': 16000,
'bidirectional': True,
'audioTransport': {
'transport': 'json',
'encoding': 'base64',
'audio_field': 'audio',
'receive_audio_field': 'audio',
'static_fields': {'foo': 'bar'},
Comment thread
superchilled marked this conversation as resolved.
},
},
}
assert actual == expected


def test_audio_transport_configuration_model_with_json_transport_and_encoding_not_set():
with pytest.raises(ValidationError) as err:
config = AudioTransportConfiguration(transport=AudioTransportTransport.JSON)
assert "encoding must be specified when transport is JSON" in str(err.value)


@responses.activate
def test_start_audio_connector():
build_response(
Expand Down
Loading