> ## Documentation Index > Fetch the complete documentation index at: https://docs.fish.audio/llms.txt > Use this file to discover all available pages before exploring further. # WebSocket Streaming > Stream text-to-speech in real-time with WebSocket connections export const AudioTranscript = ({voices = []}) => { const [selectedVoice, setSelectedVoice] = useState(0); const [isPlaying, setIsPlaying] = useState(false); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const audioRef = useRef(null); const dropdownRef = useRef(null); useEffect(() => { const audio = audioRef.current; if (!audio) return; const updateTime = () => setCurrentTime(audio.currentTime); const updateDuration = () => setDuration(audio.duration); const handleEnded = () => setIsPlaying(false); audio.addEventListener('timeupdate', updateTime); audio.addEventListener('loadedmetadata', updateDuration); audio.addEventListener('ended', handleEnded); return () => { audio.removeEventListener('timeupdate', updateTime); audio.removeEventListener('loadedmetadata', updateDuration); audio.removeEventListener('ended', handleEnded); }; }, []); useEffect(() => { const handleClickOutside = event => { if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { setIsDropdownOpen(false); } }; if (isDropdownOpen) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isDropdownOpen]); useEffect(() => { if (audioRef.current) { audioRef.current.pause(); audioRef.current.load(); setIsPlaying(false); setCurrentTime(0); } }, [selectedVoice]); const togglePlay = () => { if (isPlaying) { audioRef.current.pause(); } else { audioRef.current.play(); } setIsPlaying(!isPlaying); }; const handleProgressChange = e => { const newTime = parseFloat(e.target.value); audioRef.current.currentTime = newTime; setCurrentTime(newTime); }; const formatTime = time => { if (isNaN(time)) return '0:00'; const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return `${minutes}:${seconds.toString().padStart(2, '0')}`; }; const currentVoice = voices[selectedVoice]; return
{}
Listen to Page Powered by Fish Audio S1 {voices.length > 1 ?
{isDropdownOpen &&
{voices.map((voice, index) => )}
}
:
}
{}
; }; ## Prerequisites Sign up for a free Fish Audio account to get started with our API. 1. Go to [fish.audio/auth/signup](https://fish.audio/auth/signup) 2. Fill in your details to create an account, complete steps to verify your account. 3. Log in to your account and navigate to the [API section](https://fish.audio/app/api-keys) Once you have an account, you'll need an API key to authenticate your requests. 1. Log in to your [Fish Audio Dashboard](https://fish.audio/app/api-keys/) 2. Navigate to the API Keys section 3. Click "Create New Key" and give it a descriptive name, set a expiration if desired 4. Copy your key and store it securely Keep your API key secret! Never commit it to version control or share it publicly. ## Overview Use [`stream_websocket()`](/api-reference/sdk/python/resources#stream_websocket) for real-time text streaming with LLMs and live captions. The connection automatically buffers incoming text and generates audio as it becomes available. ## Basic Usage Stream text chunks and receive audio in real-time: ```python Synchronous focus={5-17} theme={null} from fishaudio import FishAudio from fishaudio.utils import play client = FishAudio() # Define text generator def text_chunks(): yield "Hello, " yield "this is " yield "real-time " yield "streaming!" # Stream audio via WebSocket audio_stream = client.tts.stream_websocket( text_chunks(), latency="balanced" # Use "balanced" for real-time, "normal" for quality ) # Play streamed audio play(audio_stream) ``` ```python Asynchronous focus={8-20} theme={null} import asyncio from fishaudio import AsyncFishAudio from fishaudio.utils import play async def main(): client = AsyncFishAudio() # Define async text generator async def text_chunks(): yield "Hello, " yield "this is " yield "real-time " yield "streaming!" # Stream audio via WebSocket audio_stream = await client.tts.stream_websocket( text_chunks(), latency="balanced" # Use "balanced" for real-time, "normal" for quality ) # Play streamed audio play(audio_stream) asyncio.run(main()) ``` For details on audio formats, voice selection, and advanced configuration options like `TTSConfig`, see the [Text-to-Speech guide](/developer-guide/sdk-guide/python/text-to-speech). ## Using FlushEvent Force immediate audio generation to create pauses using [`FlushEvent`](/api-reference/sdk/python/types#flushevent-objects): ```python Synchronous focus={6-12} theme={null} from fishaudio import FishAudio from fishaudio.types import FlushEvent client = FishAudio() def text_with_flush(): yield "First sentence. " yield "Second sentence. " yield FlushEvent() # Forces generation NOW yield "Third sentence." audio_stream = client.tts.stream_websocket(text_with_flush()) ``` ```python Asynchronous focus={8-14} theme={null} import asyncio from fishaudio import AsyncFishAudio from fishaudio.types import FlushEvent async def main(): client = AsyncFishAudio() async def text_with_flush(): yield "First sentence. " yield "Second sentence. " yield FlushEvent() # Forces generation NOW yield "Third sentence." audio_stream = await client.tts.stream_websocket(text_with_flush()) asyncio.run(main()) ``` See [Text-to-Speech guide](/developer-guide/sdk-guide/python/text-to-speech#understanding-flushevent) for detailed FlushEvent usage and advanced examples. ## LLM Integration WebSocket streaming is designed for integrating with LLM streaming responses. The TTS engine automatically buffers incoming text chunks and generates audio when it has enough context for natural speech: ```python Synchronous focus={5-21} theme={null} from fishaudio import FishAudio from fishaudio.utils import play client = FishAudio() # Simulate streaming LLM response def llm_stream(): """Simulates text chunks from an LLM.""" tokens = [ "The ", "weather ", "today ", "is ", "sunny ", "with ", "clear ", "skies. ", "Perfect ", "for ", "outdoor ", "activities!" ] for token in tokens: yield token # Stream to speech in real-time audio_stream = client.tts.stream_websocket( llm_stream(), latency="balanced" ) play(audio_stream) ``` ```python Asynchronous focus={7-23} theme={null} import asyncio from fishaudio import AsyncFishAudio from fishaudio.utils import play async def main(): client = AsyncFishAudio() # Simulate streaming LLM response async def llm_stream(): """Simulates text chunks from an LLM.""" tokens = [ "The ", "weather ", "today ", "is ", "sunny ", "with ", "clear ", "skies. ", "Perfect ", "for ", "outdoor ", "activities!" ] for token in tokens: yield token # Stream to speech in real-time audio_stream = await client.tts.stream_websocket( llm_stream(), latency="balanced" ) play(audio_stream) asyncio.run(main()) ``` The WebSocket connection automatically buffers incoming text and generates audio when it has accumulated enough context for natural-sounding speech. You don't need to manually batch tokens unless you want to force generation at specific points using `FlushEvent`. ## Next Steps Learn about non-streaming TTS options, audio formats, TextEvent vs plain strings, and advanced configuration Use custom voices in streams and learn about voice selection Complete streaming API documentation Production streaming optimization ## Related Resources * [WebSocket Types](/api-reference/sdk/python/types#tts) - TextEvent, FlushEvent, and more * [Utils Reference](/api-reference/sdk/python/utils) - Audio playback utilities * [Error Handling](/api-reference/sdk/python/exceptions) - WebSocket exception handling * [Fine-grained Control](/developer-guide/core-features/fine-grained-control) - Advanced speech control