From 011718da2208052a8c9500cc671e63ac5bb2f6c3 Mon Sep 17 00:00:00 2001 From: Alvaro Navarro Date: Fri, 27 Feb 2026 16:24:39 +0100 Subject: [PATCH 1/2] feat(voice): add support for 24k audio in Websocket --- users/src/vonage_users/common.py | 2 +- users/tests/test_websocket_channel_24k.py | 8 ++++++++ voice/src/vonage_voice/models/common.py | 14 +++++++------- voice/src/vonage_voice/models/connect_endpoints.py | 10 +++++----- voice/tests/test_ncco_actions.py | 7 +++++++ 5 files changed, 28 insertions(+), 13 deletions(-) create mode 100644 users/tests/test_websocket_channel_24k.py diff --git a/users/src/vonage_users/common.py b/users/src/vonage_users/common.py index 0fb785a8..e213af4b 100644 --- a/users/src/vonage_users/common.py +++ b/users/src/vonage_users/common.py @@ -50,7 +50,7 @@ class WebsocketChannel(BaseModel): uri: str = Field(pattern=r'^(ws|wss):\/\/[a-zA-Z0-9~#%@&-_?\/.,:;)(\]\[]*$') content_type: Optional[str] = Field( - None, alias='content-type', pattern='^audio/l16;rate=(8000|16000)$' + None, alias='content-type', pattern='^audio/l16;rate=(8000|16000|24000)$' ) headers: Optional[dict] = None diff --git a/users/tests/test_websocket_channel_24k.py b/users/tests/test_websocket_channel_24k.py new file mode 100644 index 00000000..a869a86c --- /dev/null +++ b/users/tests/test_websocket_channel_24k.py @@ -0,0 +1,8 @@ +from vonage_users.common import WebsocketChannel + + +def test_websocket_channel_24k_audio(): + channel = WebsocketChannel( + uri="wss://example.com/socket", content_type="audio/l16;rate=24000" + ) + assert channel.model_dump(by_alias=True)["content-type"] == "audio/l16;rate=24000" diff --git a/voice/src/vonage_voice/models/common.py b/voice/src/vonage_voice/models/common.py index 1a155af2..77013692 100644 --- a/voice/src/vonage_voice/models/common.py +++ b/voice/src/vonage_voice/models/common.py @@ -37,15 +37,15 @@ class Websocket(BaseModel): Args: uri (str): The URI of the WebSocket connection. - content_type (Literal['audio/l16;rate=8000', 'audio/l16;rate=16000']): The content - type of the audio stream. + content_type (Literal['audio/l16;rate=8000', 'audio/l16;rate=16000', 'audio/l16;rate=24000']): + The content type of the audio stream. headers (Optional[dict]): The headers to include with the WebSocket connection. """ uri: str = Field(..., min_length=1) - content_type: Literal['audio/l16;rate=8000', 'audio/l16;rate=16000'] = Field( - 'audio/l16;rate=16000', serialization_alias='content-type' - ) + content_type: Literal[ + "audio/l16;rate=8000", "audio/l16;rate=16000", "audio/l16;rate=24000" + ] = Field("audio/l16;rate=16000", serialization_alias="content-type") headers: Optional[dict] = None type: Channel = Channel.WEBSOCKET @@ -74,6 +74,6 @@ class AdvancedMachineDetection(BaseModel): machine beep to be detected. """ - behavior: Optional[Literal['continue', 'hangup']] = None - mode: Optional[Literal['default', 'detect', 'detect_beep']] = None + behavior: Optional[Literal["continue", "hangup"]] = None + mode: Optional[Literal["default", "detect", "detect_beep"]] = None beep_timeout: Optional[int] = Field(None, ge=45, le=120) diff --git a/voice/src/vonage_voice/models/connect_endpoints.py b/voice/src/vonage_voice/models/connect_endpoints.py index d1c15715..05f7d026 100644 --- a/voice/src/vonage_voice/models/connect_endpoints.py +++ b/voice/src/vonage_voice/models/connect_endpoints.py @@ -52,15 +52,15 @@ class WebsocketEndpoint(BaseModel): Args: uri (str): The URI of the WebSocket connection. - contentType (Literal['audio/l16;rate=8000', 'audio/l16;rate=16000']): The internet - media type for the audio you are streaming. + contentType (Literal['audio/l16;rate=8000', 'audio/l16;rate=16000', 'audio/l16;rate=24000']): + The internet media type for the audio you are streaming. headers (Optional[dict]): The headers to include with the WebSocket connection. """ uri: str - contentType: Literal['audio/l16;rate=16000', 'audio/l16;rate=8000'] = Field( - None, serialization_alias='content-type' - ) + contentType: Literal[ + 'audio/l16;rate=8000', 'audio/l16;rate=16000', 'audio/l16;rate=24000' + ] = Field(None, serialization_alias='content-type') headers: Optional[dict] = None type: ConnectEndpointType = ConnectEndpointType.WEBSOCKET diff --git a/voice/tests/test_ncco_actions.py b/voice/tests/test_ncco_actions.py index 4e1a3c75..57a09055 100644 --- a/voice/tests/test_ncco_actions.py +++ b/voice/tests/test_ncco_actions.py @@ -112,6 +112,13 @@ def test_create_connect_endpoints(): 'type': 'websocket', } + ws_24k = connect_endpoints.WebsocketEndpoint( + uri='wss://example.com', + contentType='audio/l16;rate=24000', + headers={'asdf': 'qwer'}, + ) + assert ws_24k.model_dump(by_alias=True)['content-type'] == 'audio/l16;rate=24000' + assert connect_endpoints.SipEndpoint( uri='sip:example@sip.example.com', headers={'qwer': 'asdf'}, From c7d96a1337e5b7afce30b42bf349c00f9ba6bb53 Mon Sep 17 00:00:00 2001 From: Alvaro Navarro Date: Fri, 27 Feb 2026 17:15:56 +0100 Subject: [PATCH 2/2] fix: unit test --- users/src/vonage_users/common.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/users/src/vonage_users/common.py b/users/src/vonage_users/common.py index e213af4b..b7af86c7 100644 --- a/users/src/vonage_users/common.py +++ b/users/src/vonage_users/common.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel, Field, model_validator +from pydantic import BaseModel, ConfigDict, Field, model_validator from vonage_utils.models import ResourceLink from vonage_utils.types import PhoneNumber @@ -48,6 +48,8 @@ class WebsocketChannel(BaseModel): headers (dict, Optional): Headers sent to the WebSocket. """ + model_config = ConfigDict(populate_by_name=True) + uri: str = Field(pattern=r'^(ws|wss):\/\/[a-zA-Z0-9~#%@&-_?\/.,:;)(\]\[]*$') content_type: Optional[str] = Field( None, alias='content-type', pattern='^audio/l16;rate=(8000|16000|24000)$'