GEMINI LABJP
SUNSET — The image generation models shut down today, August 17: imagen-4.0-generate-001, ultra, fast, and the Gemini 3 Image family, and calls after that fail with a hard errorSCALE — Gemini crossed one billion monthly active users on August 11ASSISTANT — Starting September 4, Gemini replaces Google Assistant on Android and Wear OS, a rollout expected to take several weeks and one you cannot reverse on a deviceDEVICES — The change covers Android phones and tablets, Wear OS watches, Assistant-enabled headphones, and phone-projected Android Auto; cars with Google built-in keep workingSPARK — Since August 13, Gemini 3.7 Flash has powered Gemini Spark for AI Pro and Ultra subscribers across more than 160 countriesPRICE — Gemini 3.7 Flash carries introductory pricing of $0.75 per million input tokens and $3.75 output through December 31, moving to $1.50 and $7.50 after thatSUNSET — The image generation models shut down today, August 17: imagen-4.0-generate-001, ultra, fast, and the Gemini 3 Image family, and calls after that fail with a hard errorSCALE — Gemini crossed one billion monthly active users on August 11ASSISTANT — Starting September 4, Gemini replaces Google Assistant on Android and Wear OS, a rollout expected to take several weeks and one you cannot reverse on a deviceDEVICES — The change covers Android phones and tablets, Wear OS watches, Assistant-enabled headphones, and phone-projected Android Auto; cars with Google built-in keep workingSPARK — Since August 13, Gemini 3.7 Flash has powered Gemini Spark for AI Pro and Ultra subscribers across more than 160 countriesPRICE — Gemini 3.7 Flash carries introductory pricing of $0.75 per million input tokens and $3.75 output through December 31, moving to $1.50 and $7.50 after that
Articles/Dev Tools
Dev Tools/2026-07-03Advanced

Stop Making Listeners Wait for the Whole File — Wiring Gemini TTS Streaming into Your Delivery Path

gemini-3.1-flash-tts-preview now streams audio via streamGenerateContent. A delivery path with 1.8s to first sound, covering PCM boundary handling, sentence-level resume, and a fallback for preview shutdown.

Gemini API213TTS2streaming28audio generationFastAPI

Premium Article

As an indie developer, I have been quietly experimenting with making the articles on my sites listenable as audio.

The bottleneck was never generation quality — it was waiting. Feeding a 3,800-character draft to batch TTS took an average of 41 seconds before a finished file existed. That is fine for podcast-style pre-rendering. It is not fine for a reader who just pressed a "listen" button on the page.

The July 2026 update changed the premise: gemini-3.1-flash-tts-preview now supports streaming audio generation through streamGenerateContent, so you can deliver audio while it is being made instead of after (Gemini API changelog).

I rebuilt my delivery path around it. The SDK call itself is easy; the design decisions live downstream, in how you actually deliver the bytes. This article documents the configuration I settled on, with code and measured numbers.

What Actually Changes Between "Render Then Deliver" and "Deliver While Rendering"

Batch and streaming TTS look similar but have different centers of gravity. Sorting this out first keeps later decisions honest.

AspectBatch (render, then deliver)Streaming (deliver while rendering)
Time to first soundFull render must finish (measured 41s / 3,800 chars)First chunk arrival (measured 1.8s)
ArtifactA finished file (WAV/MP3)A sequence of PCM chunks; the file is assembled later, if at all
Failure semanticsRegenerate from scratch — idempotentThe listener already heard part of it; you must decide where to resume
Best fitPodcasts, video narration, pre-rendered archivesListen buttons, conversational UI, on-the-spot playback

My conclusion up front: I kept batch for archived audio and switched only the on-the-spot listening path to streaming. There is no need to force everything onto one mode.

The Intake — Pulling PCM Chunks Out of streamGenerateContent

The server-side intake is short. The key fact: what you get from each chunk is raw PCM — 24kHz, 16-bit, mono.

# tts_stream.py — streaming TTS intake
from google import genai
from google.genai import types
 
client = genai.Client()  # reads GEMINI_API_KEY from the environment
 
TTS_MODEL = "gemini-3.1-flash-tts-preview"  # keep this in config — see the last section
 
def stream_tts(text: str):
    """Turn text into a generator of audio chunks (24kHz 16-bit mono PCM)."""
    stream = client.models.generate_content_stream(
        model=TTS_MODEL,
        contents=text,
        config=types.GenerateContentConfig(
            response_modalities=["AUDIO"],
            speech_config=types.SpeechConfig(
                voice_config=types.VoiceConfig(
                    prebuilt_voice_config=types.PrebuiltVoiceConfig(
                        voice_name="Kore"
                    )
                )
            ),
        ),
    )
    for chunk in stream:
        if not chunk.candidates:
            continue
        part = chunk.candidates[0].content.parts[0]
        if part.inline_data and part.inline_data.data:
            yield part.inline_data.data

Putting it on HTTP is plain chunked transfer with FastAPI:

# server.py — deliver over chunked transfer
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from tts_stream import stream_tts
 
app = FastAPI()
 
@app.get("/tts")
def tts(text: str):
    return StreamingResponse(
        stream_tts(text),
        media_type="audio/L16;rate=24000;channels=1",
        headers={"Cache-Control": "no-store"},
    )

The audio/L16 content type is deliberate — which brings us to the question of why we are not simply returning a WAV.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A minimal FastAPI setup that relays streamGenerateContent audio chunks over chunked transfer, with a measured 1.8s to first sound — roughly 23x faster than batch
Three options for the 'WAV header needs a length you don't have yet' problem, why raw PCM plus client-side playback won, and the Int16 boundary carry-over buffer you'll need
A resume design that restarts from sentence boundaries instead of byte offsets, plus an automatic batch fallback for the day the preview model goes away
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-08-17
Your Shipped App Still Remembers the Retired Model
Finishing the server-side migration is only half of a model retirement. The older builds of your app still hold the old model name, and once the cutoff passes, rolling back stops being a recovery option. Here is how I moved model resolution onto the server.
Dev Tools2026-08-05
Green Tests, Dead Production — How Recorded Fixtures Hide a Model Retirement, and a Freshness Gate to Catch It
A test suite that replays recorded API responses will sail straight past a model retirement. I reproduce the failure in a minimal setup and build a cassette freshness gate, with measured overhead.
Dev Tools2026-07-25
The Day I Stopped Tracking gemini-flash-latest: Batch Design That Survives Silent Model Swaps
A silent model swap pushed my batch rejection rate from 2.1% to 9.8% overnight. The pinning-plus-canary design I moved to, with the harness and numbers.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →