EasyVoice
VoicesNewArabicNewPricingAPI
EasyVoice

Free text-to-speech powered by open source AI.

Product

  • Voices
  • Pricing
  • API

Resources

  • Blog
  • Documentation
  • About

Legal

  • Privacy Policy
  • Terms of Service

© 2026 EasyVoice. Powered by Kokoro-82M (Apache 2.0).

Built with ❤️ and open source AI.

Built by InfoDriven

Dubai, United Arab Emirates · Support@infodriven.ae · infodriven.ae

  1. Home
  2. /OpenAI TTS Alternative
  3. /OpenAI TTS streaming alternative — chunked audio with ReadableStream and iter_content

OpenAI TTS streaming alternative — chunked audio with ReadableStream and iter_content

Both OpenAI's audio.speech.create and EasyVoice's TTS API deliver audio via HTTP chunked transfer encoding — the same transport mechanism, not a proprietary streaming protocol. This page covers how to consume that stream in practice: the JavaScript pattern using the Fetch API and ReadableStream, the Python pattern using requests with stream=True and iter_content, which response_format values work best for streaming, and how to handle buffer flushing and chunk piping in real-world applications. This page is about the transport mechanism — how to consume the chunked stream in practice — not about latency benchmarking or migration code steps. For migration steps from existing OpenAI TTS code, see the migration-guide spoke. Everything here assumes a working EasyVoice API key and a basic understanding of HTTP; no SDK installation is needed on either vendor.

5,000 characters per day free, no credit card. Pro $9.99/mo unlimited vs OpenAI $15/1M (tts-1) / $30/1M (tts-1-hd).

Part of the OpenAI TTS alternative hub — voice mapping, 5-line migration guide, and the breakeven pricing calculator for migrating off OpenAI's tts-1 / tts-1-hd.

Does EasyVoice support streaming/chunked TTS responses?

Yes. EasyVoice's TTS API endpoint at https://easyvoice.ae/api/v1/audio/speech returns audio via HTTP chunked transfer encoding by default — the same transport mechanism as OpenAI's audio.speech.create. The response begins as soon as the first audio chunks are synthesized. The HTTP response header Transfer-Encoding: chunked is present on every successful response, confirming that audio bytes are flowing incrementally rather than being buffered server-side until synthesis is complete.

Chunked transfer encoding means: the server does not need to know the total audio file size before sending the first byte. It sends chunks as they are generated. Each chunk is a raw fragment of the audio bytes — not a JSON object, not a base64 string. Your client accumulates chunks until the connection closes (end of synthesis) or pipes them to a media player as they arrive for real-time playback.

Any standard HTTP library handles chunked responses without special configuration. The fetch API in JavaScript exposes the body as a ReadableStream. The requests library in Python supports it with stream=True and iter_content(). Go's net/http.Client streams by default. You do not need WebSockets, Server-Sent Events, or gRPC for streaming TTS. Standard HTTP is the transport.

Consuming the stream in JavaScript — ReadableStream

In JavaScript (Node.js 18+ or browser), the fetch API returns a response object whose body property is a ReadableStream. Call response.body.getReader() to get a reader, then loop: call reader.read(), receive a {value, done} object on each iteration, where value is a Uint8Array chunk of audio bytes and done is true when the stream is complete. Pipe each chunk to your audio context, a writable stream, or a file.

The ReadableStream pattern works identically in Node.js and in modern browsers. In the browser, you can pipe chunks to a MediaSource buffer for real-time playback without waiting for the full file. In Node.js, you can pipe to a Writable stream (fs.createWriteStream) or pass chunks to a WebSocket for real-time distribution to connected clients. The code example below shows the basic reader loop; adapting it to your specific output target is a matter of replacing the audioQueue.push(chunk) call.

One important detail: close the reader when done is true. Leaving a reader open on a completed stream does not cause errors in most environments, but it is good practice. In the example below, the break statement exits the loop when done is true, which allows the garbage collector to release the reader and the underlying connection.

Consuming the stream in Python — iter_content

In Python, the requests library supports streaming responses with stream=True on the requests.post call. This keeps the response connection open and allows you to iterate over the body with res.iter_content(chunk_size=4096). Each iteration yields a bytes object of up to chunk_size bytes. A chunk_size of 4096 bytes is a practical default — it corresponds to roughly 200–400 milliseconds of audio at typical MP3 bitrates.

Pipe chunks to pyaudio for real-time playback, write them to a file incrementally with a file handle opened in binary write mode, or pass them to ffmpeg's stdin for transcoding or processing. The iter_content loop terminates when the server closes the connection (end of synthesis). The with statement on the requests.post call ensures the connection is closed and resources are released after the loop completes, even if an exception is raised mid-stream.

For asynchronous Python (asyncio / httpx), the pattern is equivalent: use httpx.AsyncClient and aiter_bytes() on the response object instead of iter_content. The async pattern integrates with FastAPI, Starlette, or any async framework that needs to forward streaming TTS audio to a client without buffering the full response.

Which response_format works best for streaming?

MP3 is the most broadly compatible format for streaming in general-purpose applications. Most audio players and browser Audio APIs decode chunked MP3 streams correctly without requiring the complete file before playback begins. MP3 frames are self-contained: a decoder can start playback from any chunk boundary without needing the file header from the beginning. This makes mp3 the right default for streaming to a web browser or a mobile app.

Opus is the best choice for WebRTC or latency-sensitive transport. Opus frames are designed for packet-loss resilience and low-latency streaming. If you are building a real-time voice application (voice chatbot, live translation, real-time accessibility read-aloud), response_format: 'opus' delivers lower effective latency per frame than mp3 because Opus frames are smaller and decoded faster. The trade-off is that Opus playback requires a browser or media player with native Opus support (all modern browsers qualify).

WAV (raw PCM) is suitable for audio processing pipelines where you need to manipulate the raw waveform — applying effects, mixing, transcoding — but is not the right format for streaming to end users. WAV files are uncompressed: a 60-second WAV is roughly 10 MB, versus 1 MB for the equivalent MP3. Streaming a large WAV to a browser that plays it inline increases buffering time and bandwidth usage. Use wav when the downstream consumer is an audio processing library, not an end-user media player.

Buffer flushing and chunk handling in practice

When piping streamed audio to a real-time playback system, buffer management determines whether playback is smooth or stuttery. If you push chunks to an audio buffer faster than the playback system can consume them, you accumulate a growing buffer. If you push chunks slower than playback (network delay, slow synthesis), the playback system runs out of audio to play and produces a dropout.

For smooth real-time playback, a common pattern is a double-buffer: maintain a primary buffer (what is currently playing) and a secondary buffer (what is pre-fetched). When the primary buffer is nearly empty, swap in the secondary and fetch the next chunk into a new secondary buffer. This smooths out network jitter and synthesis latency variation without adding significant end-to-end latency.

For file-write applications (generating an audio file, not playing in real-time), buffering is not a concern: write each chunk to the file as it arrives with f.write(chunk) in Python or stream.write(chunk) in Node.js. The file is complete and playable when the loop terminates. No flush() call is needed between chunks — the OS handles write buffering — but a final f.flush() or stream.end() after the loop ensures all bytes are committed to disk.

Switch to EasyVoice streaming if you need an OpenAI-compatible chunked TTS endpoint that works with standard HTTP libraries (ReadableStream in JS, iter_content in Python) without a proprietary SDK, on a $9.99/mo flat-rate plan. Stay on OpenAI streaming if you are already consuming audio.speech.create's chunked response successfully and the cost at your volume is under $9.99/mo (below 666K chars/mo on tts-1, below 333K chars/mo on tts-1-hd).

Code samples

Real working code, not pseudo-code. Every request below assumes you've set EASYVOICE_API_KEY and OPENAI_API_KEY as env vars where shown.

Streaming — JavaScript (Node.js / browser) with ReadableStream

Consume the EasyVoice chunked stream using fetch and ReadableStream.getReader()
const res = await fetch("https://easyvoice.ae/api/v1/audio/speech", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.EASYVOICE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    voice: "af_alloy",
    input: "Streaming audio chunk by chunk over HTTP chunked transfer encoding.",
    response_format: "mp3",
  }),
});

if (!res.ok) {
  throw new Error(`EasyVoice API error: ${res.status}`);
}

// res.body is a ReadableStream — same pattern as OpenAI audio.speech.create
const reader = res.body.getReader();
const chunks = [];

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  // value is a Uint8Array chunk of audio bytes
  chunks.push(value);
  // For real-time playback: pipe value to AudioContext or MediaSource buffer here
}

// Combine all chunks into a single buffer (for file write or Blob URL)
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
const audioBytes = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
  audioBytes.set(chunk, offset);
  offset += chunk.length;
}
// Write to file (Node.js) or create a Blob URL (browser)

Streaming — Python with iter_content

Consume the EasyVoice chunked stream using requests stream=True and iter_content
import os
import requests

with requests.post(
    "https://easyvoice.ae/api/v1/audio/speech",
    headers={
        "Authorization": f"Bearer {os.environ['EASYVOICE_API_KEY']}",
        "Content-Type": "application/json",
    },
    json={
        "voice": "af_alloy",
        "input": "Streaming audio chunk by chunk with iter_content.",
        "response_format": "mp3",
    },
    stream=True,  # keeps connection open; prevents buffering full response
) as res:
    res.raise_for_status()  # raise on 401/400/429/5xx before iterating

    # Write chunks to file incrementally (no need to buffer full response in memory)
    with open("out.mp3", "wb") as f:
        for chunk in res.iter_content(chunk_size=4096):
            # chunk is a bytes object of up to 4096 bytes of audio
            if chunk:  # filter out keep-alive empty chunks
                f.write(chunk)
                # For real-time playback: pipe chunk to pyaudio stream here

# out.mp3 is complete and playable after the with block

Voices to try on the free tier

Every voice below is callable via the same voice parameter — preview audio samples and read the full character profile.

Alloy
American English · af_alloy
HeartFree
American English · af_heart
Echo
American English · am_echo

Frequently asked questions

Does EasyVoice support streaming/chunked TTS responses?▾

Yes. EasyVoice's /api/v1/audio/speech endpoint returns audio via HTTP chunked transfer encoding — the same transport mechanism as OpenAI's audio.speech.create. The Transfer-Encoding: chunked response header confirms streaming is active. Any standard HTTP library (fetch, requests, http.Client) handles the stream without special configuration or SDK.

How do I consume the EasyVoice TTS stream in JavaScript?▾

Call fetch() and read from res.body.getReader(). Each reader.read() call returns a Uint8Array chunk of audio bytes. Loop until done is true, piping each chunk to an AudioContext, a MediaSource buffer, or a file write stream. The ReadableStream pattern works identically in Node.js 18+ and modern browsers — no SDK or special configuration required.

How do I consume the EasyVoice TTS stream in Python?▾

Use requests.post() with stream=True, then iterate with res.iter_content(chunk_size=4096). Each iteration yields a bytes object of audio. Write chunks to a file with f.write(chunk), pipe them to pyaudio for real-time playback, or pass them to ffmpeg's stdin. The iter_content loop terminates when synthesis is complete and the server closes the connection.

Which response_format works best for streaming audio?▾

MP3 is the best default — MP3 frames are self-contained and most audio players decode chunked MP3 streams correctly without the full file. Opus is better for WebRTC or latency-sensitive transport (smaller frames, lower per-frame decode time, native browser support). WAV is suitable for audio processing pipelines but too large for streaming to end users.

Related OpenAI migration guides

OpenAI TTS API reference — audio.speech.create mapped to EasyVoice

OpenAI TTS API reference mapped to EasyVoice. audio.speech.create params, error codes 401/400/429/5xx, request/response shapes. OpenAI-compatible endpoint.

OpenAI TTS quickstart — first audio in 5 steps, no credit card

OpenAI TTS quickstart alternative. EasyVoice: 5 steps, no credit card, first audio in under 2 minutes. Account, API key, curl request, mp3 playback — free tier.

Migrate from OpenAI TTS to EasyVoice in 5 lines

OpenAI TTS to EasyVoice migration guide: 5-line code diff in Python + JS. Model, voice, response_format mapping. Streaming compatible. $9.99 flat vs $15/1M.

Vendor comparison: EasyVoice vs OpenAI TTS

Side-by-side feature comparison covering voices, languages, pricing tiers, free limits, API surface, and the why-people-look / where-each-wins breakdown.

Developer-focused OpenAI migration in /tts-api

The developer-onboarding angle of the same migration — request body compatibility deep-dive, streaming behavior, ChatGPT plugin/Realtime API guidance, and the official OpenAI SDK constraint.

Start migrating off OpenAI TTS today

5,000 characters per day free, no credit card. Pro $9.99/mo unlimited replaces OpenAI's $15-$300/mo bills once you cross 666K characters per month.

More OpenAI alternative guides

← OpenAI alternative hubOpenAI TTS voices, mapped to free Kokoro alternativesMigrate from OpenAI TTS to EasyVoice in 5 linesOpenAI TTS pricing vs EasyVoice — when flat-rate winsOpenAI TTS has no free tier — EasyVoice gives you 5,000 chars/day, permanentlyCheapest high-volume TTS API — EasyVoice $9.99 flat vs OpenAI's $750/mo at scaleOpenAI TTS voices 2026 — 6 options vs EasyVoice's 56-voice catalogOpenAI TTS commercial use vs EasyVoice — Apache-2.0 license, AudioSeal watermark, consent gateOpenAI TTS API reference — audio.speech.create mapped to EasyVoiceOpenAI TTS quickstart — first audio in 5 steps, no credit cardTTS API hub →