frames.Frame values, and send serialized frames back. Each connection gets exactly one transport and one goroutine-isolated session.
The Transport Interface
All transports implement the same four-method interface defined inpkg/transport/transport.go:
One transport = one session. When a connection drops or
Close is called, both the Input and Output channels are closed, signaling the pipeline to terminate. There is no reconnect logic at the transport level — reconnection must be handled by the client.WebSocket Transport
ConnTransport
ConnTransport in pkg/transport/websocket/websocket.go is the primary transport for browser and mobile clients. It wraps a single *websocket.Conn (gorilla/websocket) and enforces strict concurrency discipline: a single readLoop goroutine owns all reads, and a single writeLoop goroutine owns all writes. No other code touches the connection.
Start(ctx), two goroutines are launched:
readLoop— callsconn.ReadMessage()in a tight loop, deserializes each message via the configuredSerializer, and pushes the resultingFrameontoinCh. On any read error, the loop returns and triggersClose().writeLoop— reads frames fromoutCh, serializes them, and writes to the WebSocket. Supports optional write coalescing (see below). On any write error, the loop returns and triggersClose().
ctx.Done() so that context cancellation propagates to Close(), and another optional goroutine monitors inactivity via LastActivity().
The maximum read message size is fixed at 1 MiB (DefaultReadLimit) to prevent memory exhaustion from oversized client frames.
Wire Formats
The serializer is selected per-connection based on the upgrade request. Pass query parameters when opening the WebSocket to choose the format.- JSON (default)
- Protobuf
- RTVI
The default format requires no query parameter. All messages are WebSocket text frames carrying a JSON envelope:The
type field identifies the frame kind. The data object carries frame-specific fields. This format is compatible with any language or SDK that can open a WebSocket and parse JSON.Write Coalescing
In high-throughput scenarios, many small frames (e.g. audio chunks) can saturate the WebSocket write path with syscalls. Write coalescing batches consecutive frames into a single write window. Configure it inconfig.json:
Session Lifecycle
Close() is called, both channels are closed, and the pipeline goroutine exits. There is no transport-level reconnect. Clients that want to resume must open a new connection and start a new session.
The server enforces an inactivity timeout (SessionTimeout, default 5m) by polling LastActivity() at half the timeout interval. Any successfully read or written frame resets the timer.
WebRTC Transport (SmallWebRTC)
SDP Offer / Answer
WebRTC sessions use a standard SDP offer/answer handshake before any audio flows. The client sends an HTTP POST:"both" to accept WebSocket and WebRTC clients simultaneously.
Audio Flow
WebRTC delivers audio as Opus-encoded RTP packets. Voxray decodes, resamples, and re-encodes at each boundary:ICE / STUN / TURN Configuration
stun.l.google.com:19302). STUN is sufficient for clients on open networks, but add a TURN server for any production deployment where clients may be behind symmetric NAT, corporate firewalls, or mobile carrier-grade NAT. Without TURN, ICE will fail for a significant fraction of real-world users.
Memory Transport
The memory transport (pkg/transport/memory) connects two in-process pipelines directly via Go channels. It is intended exclusively for unit tests and integration tests — frames are never serialized or sent over a network.
Telephony Adapters
How Telephony Connections Work
Telephony providers (Twilio, Telnyx, Plivo, Exotel) connect to Voxray in two phases:- Webhook phase: The provider receives an inbound call and sends
POST /to your server. Voxray responds with XML (TwiML for Twilio, equivalent for others) instructing the provider to open a media WebSocket. - Media phase: The provider opens a WebSocket to
GET /telephony/wsand streams audio in real time. Voxray wraps this connection in aConnTransportwith a provider-specific serializer.
Supported Providers
Audio Format
Telephony audio arrives as G.711 μ-law (PCMU), 8 kHz, mono. Voxray handles all codec conversion internally:- Inbound: μ-law decode → resample 8 kHz → 16 kHz →
AudioRawFrame(PCM 16-bit LE, 16 kHz) - Outbound:
TTSAudioRawFrame(PCM 16-bit LE, 24 kHz) → resample 24 kHz → 8 kHz → μ-law encode → telephony WebSocket
runner_transport value.
Runner Session Mode (Horizontal Scaling)
Standard WebRTC requires the client to know the server address upfront. For deployments where you want to scale across multiple Voxray instances, use the Runner session mode:- Client calls
POST /start— Voxray creates a session entry in theSessionStore(in-memory or Redis) and returns asessionId. - Client sends
POST /sessions/{id}/api/offerwith the SDP offer. - Voxray retrieves the session, completes the WebRTC handshake, and starts the pipeline.
WebSocket Reconnection Helpers (for External Services)
When Voxray itself connects to external WebSocket APIs (e.g., streaming STT or LLM services), it usesWebsocketServiceBase from pkg/transport/websocket/reconnect.go for resilient connections with automatic reconnect and exponential backoff.
WebSocketConnector Interface
Implement this interface in your service:Key Methods
Usage Pattern
WebsocketServiceBase is used internally by services like OpenAI Realtime and Sarvam streaming. Embed or compose it in any service that holds a long-lived outbound WebSocket connection.