Stream audio from your microphone with keyterms prompting for domain-specific accuracy, ideal for live events, accessibility, and broadcast captioning. Products used: Streaming STT + Universal-3 Pro + keyterms prompting Model selection: UsesDocumentation Index
Fetch the complete documentation index at: https://assemblyai.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
u3-rt-pro for sub-300ms latency with format_turns enabled for clean, readable captions.
- Python
- JavaScript
# pip install pyaudio websocket-client
import pyaudio
import websocket
import json
import threading
import time
from urllib.parse import urlencode
# ── Config ────────────────────────────────────────────────────
YOUR_API_KEY = "YOUR_API_KEY"
# Add domain-specific terms to boost recognition accuracy
KEYTERMS = ["AssemblyAI", "Universal-3 Pro", "LLM Gateway", "speech-to-text"]
CONNECTION_PARAMS = {
"sample_rate": 16000,
"speech_model": "u3-rt-pro",
"format_turns": True,
"keyterms_prompt": KEYTERMS,
}
API_ENDPOINT = (
f"wss://streaming.assemblyai.com/v3/ws?{urlencode(CONNECTION_PARAMS, doseq=True)}"
)
# Audio settings
FRAMES_PER_BUFFER = 800
SAMPLE_RATE = 16000
stop_event = threading.Event()
caption_count = 0
def on_open(ws):
print(f"Live captioning started — keyterms: {', '.join(KEYTERMS)}")
print("Speak into your microphone. Press Ctrl+C to stop.\n")
print("-" * 60)
def stream_audio():
audio = pyaudio.PyAudio()
stream = audio.open(
input=True, frames_per_buffer=FRAMES_PER_BUFFER,
channels=1, format=pyaudio.paInt16, rate=SAMPLE_RATE,
)
while not stop_event.is_set():
try:
data = stream.read(FRAMES_PER_BUFFER, exception_on_overflow=False)
ws.send(data, websocket.ABNF.OPCODE_BINARY)
except Exception:
break
stream.stop_stream()
stream.close()
audio.terminate()
threading.Thread(target=stream_audio, daemon=True).start()
def on_message(ws, message):
global caption_count
data = json.loads(message)
if data.get("type") == "Turn":
transcript = data.get("transcript", "")
if data.get("end_of_turn") and transcript:
caption_count += 1
print(f"\r[{caption_count:03d}] {transcript}")
elif transcript:
# Show partial (live) caption
print(f"\r >> {transcript[-70:]}", end="", flush=True)
elif data.get("type") == "Termination":
duration = data.get("audio_duration_seconds", 0)
print(f"\n{'=' * 60}")
print(f"Session ended — {caption_count} captions, {duration}s of audio")
def on_error(ws, error):
print(f"\nError: {error}")
stop_event.set()
def on_close(ws, code, msg):
stop_event.set()
ws_app = websocket.WebSocketApp(
API_ENDPOINT,
header={"Authorization": YOUR_API_KEY},
on_open=on_open, on_message=on_message,
on_error=on_error, on_close=on_close,
)
ws_thread = threading.Thread(target=ws_app.run_forever, daemon=True)
ws_thread.start()
try:
while ws_thread.is_alive():
time.sleep(0.1)
except KeyboardInterrupt:
print("\n\nStopping...")
stop_event.set()
if ws_app.sock and ws_app.sock.connected:
ws_app.send(json.dumps({"type": "Terminate"}))
time.sleep(2)
ws_app.close()
// npm install ws mic
const WebSocket = require("ws");
const mic = require("mic");
const querystring = require("querystring");
// ── Config ────────────────────────────────────────────────────
const YOUR_API_KEY = "YOUR_API_KEY";
// Add domain-specific terms to boost recognition accuracy
const KEYTERMS = ["AssemblyAI", "Universal-3 Pro", "LLM Gateway", "speech-to-text"];
const CONNECTION_PARAMS = {
sample_rate: 16000,
speech_model: "u3-rt-pro",
format_turns: true,
keyterms_prompt: KEYTERMS,
};
const API_ENDPOINT = `wss://streaming.assemblyai.com/v3/ws?${querystring.stringify(
CONNECTION_PARAMS
)}`;
let micInstance;
let captionCount = 0;
const ws = new WebSocket(API_ENDPOINT, {
headers: { Authorization: YOUR_API_KEY },
});
ws.on("open", () => {
console.log(`Live captioning started — keyterms: ${KEYTERMS.join(", ")}`);
console.log("Speak into your microphone. Press Ctrl+C to stop.\n");
console.log("-".repeat(60));
micInstance = mic({ rate: "16000", channels: "1", debug: false });
const micStream = micInstance.getAudioStream();
micStream.on("data", (data) => {
if (ws.readyState === WebSocket.OPEN) ws.send(data);
});
micInstance.start();
});
ws.on("message", (message) => {
const data = JSON.parse(message);
if (data.type === "Turn") {
const transcript = data.transcript || "";
if (data.end_of_turn && transcript) {
captionCount++;
process.stdout.write(
`\r${String(captionCount).padStart(3, "0")} ${transcript}\n`
);
} else if (transcript) {
process.stdout.write(`\r >> ${transcript.slice(-70)}`);
}
} else if (data.type === "Termination") {
console.log(`\n${"=".repeat(60)}`);
console.log(
`Session ended — ${captionCount} captions, ${data.audio_duration_seconds || 0}s of audio`
);
}
});
ws.on("error", (err) => console.error(`Error: ${err}`));
ws.on("close", () => {
if (micInstance) micInstance.stop();
});
process.on("SIGINT", () => {
console.log("\n\nStopping...");
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "Terminate" }));
}
setTimeout(() => {
if (micInstance) micInstance.stop();
ws.close();
process.exit(0);
}, 2000);
});
Example output
Example output
Live captioning started — keyterms: AssemblyAI, Universal-3 Pro, LLM Gateway, speech-to-text
Speak into your microphone. Press Ctrl+C to stop.
------------------------------------------------------------
[001] Welcome everyone to today's demo of AssemblyAI's speech-to-text platform.
[002] We'll be showing you how Universal-3 Pro handles real-time transcription.
[003] The LLM Gateway integration lets you add AI analysis on top of your
transcripts without switching providers.
============================================================
Session ended — 3 captions, 24s of audio
See the End-to-end examples overview for all available pipelines.