Realtime Text to Speech
Build streaming text-to-speech apps with realtime AI models.
Overview
Realtime TTS models convert text into streaming audio. Unlike standard TTS that processes a full prompt and returns an audio file, realtime TTS streams AI-generated speech as a continuous PCM audio stream over a WebSocket connection — in real time. The text prompt is submitted via POST /Run, not over the WebSocket. The WebSocket carries only task events (task_info, task_stream_ready, etc.), binary
audio frames, and control messages (task_session_end).
No microphone is required. The flow is one-directional: text goes in, audio comes out.
The flow is:
- Run the realtime TTS model via POST /Run with your text prompt in the parameters
- Connect to the WebSocket and send
task_infowith yoursocketaccesstoken - Wait for
task_stream_ready— the model has loaded and is generating audio - Receive AI audio as binary frames and play them
- End the session with
task_session_endor wait for the stream to finish naturally
How It Differs from Realtime Voice Conversation
| Realtime Voice Conversation | Realtime Text to Speech | |
|---|---|---|
| Input | Microphone audio (streamed) | Text (sent with the run request) |
| Output | AI audio + transcripts | AI audio only |
| Direction | Bidirectional (client ↔ server) | Server → client only |
| Microphone | Required | Not required |
| Transcripts | TRANSCRIPT_USER: / TRANSCRIPT_AI: via task_output |
None |
| Use case | Interactive voice chat | Narration, voiceover, assistants |
Run Parameters
Each realtime TTS model has its own set of parameters. Use POST /Tool/Detail to discover the exact parameters for a specific model. See Model Parameters for parameter types.
Browse available realtime TTS models on the models page (filter by Realtime TTS category). Common parameters typically include the input text and an optional reference audio clip for voice cloning. Example run for MOSS-TTS-Realtime:
curl -X POST "https://api.wiro.ai/v1/Run/openmoss/moss-tts-realtime" \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"text": "Hello, this is a realtime text-to-speech demo."
}'
Important: Parameters vary per model. Always check the model's detail page or use /Tool/Detail to get the exact parameter list before integrating. The run response returns a socketaccesstoken used to subscribe to the WebSocket.
Connection & Registration
After running the task, connect to the WebSocket and register with task_info:
var ws = new WebSocket("wss://socket.wiro.ai/v1");
ws.onopen = function() {
ws.send(JSON.stringify({
type: "task_info",
tasktoken: "YOUR_SOCKET_ACCESS_TOKEN"
}));
};
Note: Both standard and realtime models use type: "task_info" with tasktoken to register on the WebSocket. The registration flow is identical to Realtime Voice Conversation.
Realtime Events
During a realtime TTS session, you'll receive these WebSocket events:
| Event | Description |
|---|---|
task_stream_ready |
Session is ready — the model is generating audio and will begin sending chunks |
task_stream_end |
The model finished generating audio for the current segment |
task_cost |
Cost update — includes turnCost, cumulativeCost, and usage (raw cost breakdown from the model provider) |
task_end |
The model process has exited. Post-processing follows — wait for task_postprocess_end to close the connection. |
task_postprocess_end |
Post-processing is complete. Safe to close the WebSocket connection. |
No task_output events. Unlike voice conversation, TTS sessions do not produce transcript events. The input text is already known (you provided it), and the AI output is audio, not text.
Event Sequence
A typical TTS session produces events in this order:
task_stream_ready ← model is ready, audio chunks start arriving
[binary frames] ← PCM audio data (many frames)
task_stream_end ← audio generation complete for this segment
task_cost ← cost for this segment
task_end ← model process exiting
task_postprocess_end ← safe to close WebSocket
Audio Format
Audio flows in one direction only: server → client. The client does not send any audio.
| Property | Value |
|---|---|
| Format | PCM (raw, uncompressed) |
| Bit depth | 16-bit signed integer (Int16) |
| Sample rate | 24,000 Hz (24 kHz) |
| Channels | Mono (1 channel) |
| Byte order | Little-endian |
| Chunk size | Variable (typically 200 ms = 4,800 samples = 9,600 bytes) |
Binary Frame Format
Every binary WebSocket frame from the server is structured as:
[tasktoken]|[PCM audio data]
The pipe character | (0x7C) separates the token from the raw audio bytes. To extract the audio:
- Find the first
|byte in the binary frame - Everything after it is raw PCM Int16 audio data
- Convert Int16 samples to your playback format (e.g., Float32 for Web Audio API)
Client → server: In TTS mode, you do not send binary audio frames. The only messages you send are task_info (to register) and task_session_end (to end the session).
Receiving AI Audio
AI speech arrives as binary WebSocket frames in PCM Int16 24 kHz format. To play them:
- Check if the incoming message is binary (a
Blobin JavaScript,bytesin Python) before attempting JSON parse - Find the pipe
|separator and extract audio data after it - Convert Int16 → Float32 and create an
AudioBuffer - Schedule gapless playback using
AudioBufferSourceNodeto avoid clicks between chunks
Gapless Playback
Audio arrives in many small chunks. To play them seamlessly:
- Track a
nextPlayTimevariable initialized to0 - For each chunk, schedule it at
max(audioContext.currentTime, nextPlayTime) - Advance
nextPlayTimeby the chunk's duration - This ensures chunks play back-to-back with no gaps or overlaps
Ending a Session
To gracefully end a realtime TTS session, send task_session_end:
{
"type": "task_session_end",
"tasktoken": "YOUR_SOCKET_ACCESS_TOKEN"
}
After sending this, the server will finish any in-progress generation, send final cost events, and then emit task_postprocess_end. Wait for task_postprocess_end before closing the WebSocket.
For TTS sessions, the stream often ends naturally when the model finishes generating audio for the provided text. In this case, you'll receive task_stream_end followed by task_end without needing to send task_session_end. However, it's good practice to send it explicitly for a clean shutdown, especially if you want to stop playback early.
Safety: If the client disconnects without sending task_session_end, the server automatically terminates the session to prevent the pipeline from running indefinitely (and the provider from continuing to charge). Always send task_session_end explicitly for a clean shutdown.
Insufficient balance: The session is charged turn by turn as it runs. When a charged turn uses up your available balance (the team wallet's balance when you use a team project's API key), the server stops the session right after that turn. That turn is charged in full, and so is any final usage reported while the session closes, so your balance can end slightly below zero. You still receive the final task_cost and task_end events.