Skip to content

Commit 5c1e168

Browse files
committed
feat: add WebSocketOptions for configurable WebSocket connections
1 parent 4e39d0c commit 5c1e168

File tree

4 files changed

+61
-2
lines changed

4 files changed

+61
-2
lines changed

src/fishaudio/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from ._version import __version__
3030
from .client import AsyncFishAudio, FishAudio
3131
from .core.iterators import AsyncAudioStream, AudioStream
32+
from .core.websocket_options import WebSocketOptions
3233
from .exceptions import (
3334
APIError,
3435
AuthenticationError,
@@ -41,7 +42,7 @@
4142
ValidationError,
4243
WebSocketError,
4344
)
44-
from .types import FlushEvent, TextEvent
45+
from .types import FlushEvent, ReferenceAudio, TextEvent, TTSConfig
4546
from .utils import play, save, stream
4647

4748
# Main exports
@@ -56,8 +57,12 @@
5657
# Audio streams
5758
"AudioStream",
5859
"AsyncAudioStream",
60+
# Configuration
61+
"TTSConfig",
62+
"WebSocketOptions",
5963
# Types
6064
"FlushEvent",
65+
"ReferenceAudio",
6166
"TextEvent",
6267
# Exceptions
6368
"APIError",

src/fishaudio/core/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
from .client_wrapper import AsyncClientWrapper, ClientWrapper
44
from .omit import OMIT
55
from .request_options import RequestOptions
6+
from .websocket_options import WebSocketOptions
67

78
__all__ = [
89
"AsyncClientWrapper",
910
"ClientWrapper",
1011
"OMIT",
1112
"RequestOptions",
13+
"WebSocketOptions",
1214
]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""WebSocket-level options for WebSocket connections."""
2+
3+
from typing import Any, Dict, Optional
4+
5+
6+
class WebSocketOptions:
7+
"""
8+
Options that can be provided to configure WebSocket connections.
9+
10+
Attributes:
11+
keepalive_ping_timeout_seconds: Maximum time to wait for a pong response
12+
to a keepalive ping before considering the connection dead (default: 20s)
13+
keepalive_ping_interval_seconds: Interval between keepalive pings (default: 20s)
14+
max_message_size_bytes: Maximum size for incoming messages (default: 65,536 bytes)
15+
queue_size: Size of the message receive queue (default: 512)
16+
"""
17+
18+
def __init__(
19+
self,
20+
*,
21+
keepalive_ping_timeout_seconds: Optional[float] = None,
22+
keepalive_ping_interval_seconds: Optional[float] = None,
23+
max_message_size_bytes: Optional[int] = None,
24+
queue_size: Optional[int] = None,
25+
):
26+
self.keepalive_ping_timeout_seconds = keepalive_ping_timeout_seconds
27+
self.keepalive_ping_interval_seconds = keepalive_ping_interval_seconds
28+
self.max_message_size_bytes = max_message_size_bytes
29+
self.queue_size = queue_size
30+
31+
def to_httpx_ws_kwargs(self) -> Dict[str, Any]:
32+
"""Convert to kwargs dict for httpx_ws aconnect_ws/connect_ws."""
33+
kwargs = {}
34+
if self.keepalive_ping_timeout_seconds is not None:
35+
kwargs["keepalive_ping_timeout_seconds"] = self.keepalive_ping_timeout_seconds
36+
if self.keepalive_ping_interval_seconds is not None:
37+
kwargs["keepalive_ping_interval_seconds"] = self.keepalive_ping_interval_seconds
38+
if self.max_message_size_bytes is not None:
39+
kwargs["max_message_size_bytes"] = self.max_message_size_bytes
40+
if self.queue_size is not None:
41+
kwargs["queue_size"] = self.queue_size
42+
return kwargs

src/fishaudio/resources/tts.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from httpx_ws import AsyncWebSocketSession, WebSocketSession, aconnect_ws, connect_ws
99

1010
from .realtime import aiter_websocket_audio, iter_websocket_audio
11-
from ..core import AsyncClientWrapper, ClientWrapper, RequestOptions
11+
from ..core import AsyncClientWrapper, ClientWrapper, RequestOptions, WebSocketOptions
1212
from ..core.iterators import AsyncAudioStream, AudioStream
1313
from ..types import (
1414
AudioFormat,
@@ -215,6 +215,7 @@ def stream_websocket(
215215
config: TTSConfig = TTSConfig(),
216216
model: Model = "s1",
217217
max_workers: int = 10,
218+
ws_options: Optional[WebSocketOptions] = None,
218219
) -> Iterator[bytes]:
219220
"""
220221
Stream text and receive audio in real-time via WebSocket.
@@ -305,6 +306,9 @@ def text_generator():
305306
speed, base=config.prosody
306307
)
307308

309+
# Prepare WebSocket connection kwargs
310+
ws_kwargs = ws_options.to_httpx_ws_kwargs() if ws_options else {}
311+
308312
executor = ThreadPoolExecutor(max_workers=max_workers)
309313

310314
try:
@@ -316,6 +320,7 @@ def text_generator():
316320
"model": model,
317321
"Authorization": f"Bearer {self._client.api_key}",
318322
},
323+
**ws_kwargs,
319324
) as ws:
320325

321326
def sender():
@@ -502,6 +507,7 @@ async def stream_websocket(
502507
speed: Optional[float] = None,
503508
config: TTSConfig = TTSConfig(),
504509
model: Model = "s1",
510+
ws_options: Optional[WebSocketOptions] = None,
505511
):
506512
"""
507513
Stream text and receive audio in real-time via WebSocket (async).
@@ -591,11 +597,15 @@ async def text_generator():
591597
speed, base=config.prosody
592598
)
593599

600+
# Prepare WebSocket connection kwargs
601+
ws_kwargs = ws_options.to_httpx_ws_kwargs() if ws_options else {}
602+
594603
ws: AsyncWebSocketSession
595604
async with aconnect_ws(
596605
"/v1/tts/live",
597606
client=self._client.client,
598607
headers={"model": model, "Authorization": f"Bearer {self._client.api_key}"},
608+
**ws_kwargs,
599609
) as ws:
600610

601611
async def sender():

0 commit comments

Comments
 (0)