> ## Documentation Index
> Fetch the complete documentation index at: https://hanabiaiinc-codex-pronunciation-dictionary-instructions.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Realtime Streaming

> Stream audio as it generates for the lowest latency

Start playing audio before the whole clip is ready. Fish Audio streams speech in chunks, so your users hear the first words in a fraction of a second — essential for voice agents and live narration. Two modes: **HTTP streaming** for text you already have, and **WebSocket** for text that arrives incrementally (like LLM tokens).

<CardGroup cols={3}>
  <Card title="API reference" icon="brackets-curly" href="/api-reference/endpoint/websocket/tts-live">
    The live TTS WebSocket protocol.
  </Card>

  <Card title="Cookbooks" icon="book-open" href="/developer-guide/sdk-guide/cookbook/realtime-llm-to-speech">
    LLM-to-speech and voice agents.
  </Card>

  <Card title="Best practices" icon="gauge-high" href="/developer-guide/best-practices/real-time-streaming">
    Tuning latency for production.
  </Card>
</CardGroup>

## When to use it

<CardGroup cols={2}>
  <Card title="Voice agents" icon="headset">
    Conversational AI where time-to-first-audio matters.
  </Card>

  <Card title="LLM to speech" icon="robot">
    Speak tokens as your model produces them — no waiting for the full reply.
  </Card>

  <Card title="Live narration" icon="tower-broadcast">
    Long-form content that should start playing immediately.
  </Card>

  <Card title="Interactive apps" icon="bolt">
    Anywhere a few hundred milliseconds of latency is noticeable.
  </Card>
</CardGroup>

## Stream text you already have

When you have the full string, stream the audio chunks as they generate and write or play them immediately.

<CodeGroup>
  ```python Python theme={null}
  from fishaudio import FishAudio

  client = FishAudio()  # reads FISH_API_KEY

  with open("out.mp3", "wb") as f:
      for chunk in client.tts.stream(text="Streaming keeps latency low."):
          f.write(chunk)  # or send to a speaker / socket as it arrives

  # Or collect the whole stream into one bytes object:
  audio = client.tts.stream(text="Streaming keeps latency low.").collect()
  ```

  ```bash API (curl) theme={null}
  curl --request POST https://api.fish.audio/v1/tts \
    --header "Authorization: Bearer $FISH_API_KEY" \
    --header "Content-Type: application/json" \
    --header "model: s2-pro" \
    --no-buffer \
    --data '{ "text": "Streaming keeps latency low.", "format": "mp3" }' \
    --output out.mp3
  ```

  ```javascript JavaScript theme={null}
  import { FishAudioClient } from "fish-audio";
  import { createWriteStream } from "fs";

  const client = new FishAudioClient({ apiKey: process.env.FISH_API_KEY });

  // convert() returns a ReadableStream<Uint8Array> — write each chunk the
  // moment it arrives instead of waiting for the whole clip.
  const stream = await client.textToSpeech.convert(
    { text: "Streaming keeps latency low.", format: "mp3" },
    "s2-pro"
  );

  const file = createWriteStream("out.mp3");
  for await (const chunk of stream) {
    file.write(Buffer.from(chunk)); // or forward to a speaker / socket as it arrives
  }
  file.end();
  ```
</CodeGroup>

`--no-buffer` tells curl to write each chunk as it arrives instead of waiting for the full response.

## Stream from an LLM

When text arrives token by token, feed a generator to `stream_websocket`. It opens a WebSocket, sends text as you produce it, and yields audio chunks back — so speech keeps pace with your model.

<CodeGroup>
  ```python Python theme={null}
  from fishaudio import FishAudio
  from fishaudio.utils import play

  client = FishAudio()

  def llm_tokens():
      # Replace with your real streaming LLM call
      for token in ["The ", "first ", "move ", "sets ", "everything ", "in ", "motion."]:
          yield token

  for chunk in client.tts.stream_websocket(llm_tokens(), reference_id="YOUR_VOICE_ID"):
      play(chunk)  # play each chunk the moment it arrives
  ```

  ```bash API (WebSocket) theme={null}
  # Token-level streaming uses the WebSocket endpoint, not curl.
  # The Python SDK's stream_websocket() handles the protocol for you.
  # To build it directly, see the WebSocket reference:
  #   /api-reference/endpoint/websocket/tts-live
  ```
</CodeGroup>

## Implementation details

### Which mode to use

* **HTTP streaming (`tts.stream`)** — you have the full text up front and want low time-to-first-audio. Simplest option.
* **WebSocket (`tts.stream_websocket`)** — text is still being produced (LLM output, live captions). Lets you start speaking before the sentence is finished.

### Lower the latency further

* Use a streaming-friendly format like `mp3` or `pcm`.
* Keep the connection warm for back-to-back generations.
* Pair with a cloned voice via `reference_id` — see [Voice Cloning](/features/voice-cloning).

## Control where audio generates

The WebSocket buffers incoming text and generates audio once it has enough context for natural-sounding speech, so you don't need to batch tokens yourself. When you *do* want a clean break — end of a sentence, a deliberate pause, or the end of a turn — yield a `FlushEvent` to force generation immediately. Wrap text in a `TextEvent` if you prefer explicit events over bare strings.

```python theme={null}
from fishaudio import FishAudio
from fishaudio.types import TextEvent, FlushEvent

client = FishAudio()

def script():
    yield TextEvent(text="First sentence. ")
    yield "Second sentence. "
    yield FlushEvent()        # generate everything buffered so far, now
    yield "Third sentence."

for chunk in client.tts.stream_websocket(script(), reference_id="YOUR_VOICE_ID"):
    ...  # play or forward each chunk
```

## Tune latency vs. quality

Both streaming paths take a `latency` mode:

* `latency="balanced"` (default) — lowest time-to-first-audio. Use it for voice agents and live LLM output.
* `latency="normal"` — slightly higher latency, best audio quality. Use it for narration where you can afford a beat.

```python theme={null}
for chunk in client.tts.stream_websocket(llm_tokens(), latency="balanced"):
    ...
```

For finer control, pass a `TTSConfig` with chunk tuning. Smaller chunks emit audio sooner (lower latency); larger chunks give the model more context (smoother prosody):

```python theme={null}
from fishaudio.types import TTSConfig

config = TTSConfig(
    latency="balanced",
    chunk_length=200,       # target tokens per generated chunk
    min_chunk_length=100,   # don't emit a chunk shorter than this
)

for chunk in client.tts.stream(text="...", config=config):
    ...
```

## Stream asynchronously

For asyncio apps, `AsyncFishAudio` exposes the same streaming methods. `stream_websocket` accepts an async generator, so you can pipe an async LLM client straight into speech.

```python theme={null}
import asyncio
from fishaudio import AsyncFishAudio

async def main():
    client = AsyncFishAudio()

    async def llm_tokens():
        async for token in your_async_llm():
            yield token

    # stream_websocket is an async generator — iterate it, don't await the call
    async for chunk in client.tts.stream_websocket(
        llm_tokens(), reference_id="YOUR_VOICE_ID", latency="balanced"
    ):
        ...  # play or forward each chunk

asyncio.run(main())
```

## Direct API (no SDK)

Token-level streaming runs over the WebSocket endpoint — the SDK's `stream_websocket()` handles framing for you. To speak the protocol directly, send MessagePack frames over the socket; the same `application/msgpack` payload format also works for one-shot HTTP streaming, which is faster to serialize than JSON for large reference audio:

```python theme={null}
import os
import httpx
import ormsgpack

payload = {"text": "Streaming keeps latency low.", "format": "mp3", "latency": "balanced"}

with httpx.stream(
    "POST",
    "https://api.fish.audio/v1/tts",
    headers={
        "Authorization": f"Bearer {os.environ['FISH_API_KEY']}",
        "Content-Type": "application/msgpack",
        "model": "s2-pro",
    },
    content=ormsgpack.packb(payload),
) as r:
    for chunk in r.iter_bytes():
        ...  # write each chunk as it arrives
```

For the full WebSocket frame sequence, see the [live TTS protocol reference](/api-reference/endpoint/websocket/tts-live).

## Going further

<CardGroup cols={2}>
  <Card title="Text to Speech" icon="microphone" href="/features/text-to-speech">
    Voices, formats, and prosody for every generation.
  </Card>

  <Card title="WebSocket reference" icon="plug" href="/api-reference/endpoint/websocket/tts-live">
    The live TTS protocol, message by message.
  </Card>

  <Card title="Streaming best practices" icon="gauge-high" href="/developer-guide/best-practices/real-time-streaming">
    Tuning latency for production voice apps.
  </Card>

  <Card title="Python reference" icon="python" href="/api-reference/sdk/python/resources">
    `tts.stream` and `tts.stream_websocket`.
  </Card>
</CardGroup>
