[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"article-doc:docs\u002Fguides\u002Findex":3},"---\ntitle: Browser Live Captions\ndescription: Browser microphone → Lanson → caption UI.\n---\n\n# Browser Live Captions\n\nBuild a browser-based live caption interface using the LansonAI realtime API.\n\n## Architecture\n\n```\nBrowser                        Your backend                  LansonAI\n  │                                │                            │\n  ├── request session token ─────→ POST session-token ───────→ rt_... token\n  │←── rt_... token ──────────────│                            │\n  │                                │                            │\n  ├── open WebSocket ─────────────────────────────────────────→ session.created\n  │   ?access_token=rt_...                                       │\n  │                                │                            │\n  ├── capture mic (16kHz mono) ───│                            │\n  ├── send PCM16 frames ──────────────────────────────────────→ VAD + STT\n   │←── conversation.item.input_audio_transcription.completed ───────────────────────────│\n  │                                │                            │\n  └── display captions              │                            │\n```\n\n## Step 1: Get a session token\n\nYour backend exchanges the long-lived API key for a 60-second session token:\n\n```javascript\n\u002F\u002F backend route\napp.post('\u002Fsession-token', async (req, res) => {\n  const resp = await fetch('https:\u002F\u002Faudio.lansonai.com\u002Fv1\u002Faudio\u002Ftranscriptions\u002Fsession-token', {\n    method: 'POST',\n    headers: { Authorization: `Bearer ${process.env.LANSON_AUDIO_API_KEY}` },\n  });\n  res.json(await resp.json());\n});\n```\n\n::callout{icon=\"i-lucide-triangle-alert\" color=\"amber\"}\nNever expose `sk-...` in frontend code. Always proxy through your backend.\n::\n\n## Step 2: Open WebSocket\n\n```javascript\nconst { token } = await fetch('\u002Fsession-token', { method: 'POST' }).then(r => r.json());\nconst ws = new WebSocket(\n  `wss:\u002F\u002Faudio.lansonai.com\u002Fv1\u002Faudio\u002Ftranscriptions\u002Fstream?access_token=${token}`\n);\n```\n\n## Step 3: Capture and send audio\n\n```javascript\nconst audioContext = new AudioContext({ sampleRate: 16000 });\nconst stream = await navigator.mediaDevices.getUserMedia({ audio: true });\nconst source = audioContext.createMediaStreamSource(stream);\n\n\u002F\u002F Use AudioWorklet to get PCM16LE frames\nawait audioContext.audioWorklet.addModule('pcm-processor.js');\nconst node = new AudioWorkletNode(audioContext, 'pcm-processor');\nsource.connect(node);\n\n\u002F\u002F pcm-processor.js posts PCM16LE buffers to main thread\nnode.port.onmessage = (e) => {\n  \u002F\u002F Send as binary frame\n  ws.send(e.data); \u002F\u002F ArrayBuffer of PCM16LE data\n};\n```\n\nMinimal `pcm-processor.js`:\n\n```javascript\nclass PcmProcessor extends AudioWorkletProcessor {\n  process(inputs) {\n    const input = inputs[0][0]; \u002F\u002F mono, 16kHz\n    if (input) {\n      const pcm16 = new Int16Array(input.length);\n      for (let i = 0; i \u003C input.length; i++) {\n        const s = Math.max(-1, Math.min(1, input[i]));\n        pcm16[i] = s \u003C 0 ? s * 0x8000 : s * 0x7fff;\n      }\n      this.port.postMessage(pcm16.buffer);\n    }\n    return true;\n  }\n}\nregisterProcessor('pcm-processor', PcmProcessor);\n```\n\n## Step 4: Display captions\n\n```javascript\nws.onmessage = (event) => {\n  const data = JSON.parse(event.data);\n  if (data.type === 'conversation.item.input_audio_transcription.completed') {\n    const div = document.getElementById('captions');\n    const p = document.createElement('p');\n    p.textContent = data.text;\n    div.appendChild(p);\n  }\n};\n```\n\n## Browser security\n\n- **Never** put `sk-...` in frontend code\n- Always obtain `rt_...` tokens from your backend\n- Tokens expire in 60 seconds — get a fresh one for each connection\n- WebSocket `?access_token` keeps the long-lived key off the client\n\n## Related\n\n- [Build a Stable Caption UI](\u002Fdocs\u002Fguides\u002Fstable-caption-ui) — advanced UI patterns\n- [Authentication](\u002Fdocs\u002Fstart\u002Fauthentication) — session token details\n- [Realtime Quickstart](\u002Fdocs\u002Frealtime\u002Fquickstart) — more examples\n",1790059118953]