future-umbrella-45124
03/09/2026, 10:51 AMtall-airline-16756
03/09/2026, 10:51 AMtall-airline-16756
03/09/2026, 10:51 AMlk.transcription). This produces metadata such as lk.transcribed_track_id, lk.segment_id, and lk.transcription_final, which let you associate each transcript with the originating participant and know when the transcript is final.
1. Listen for transcription events on the Agent
◦ In your Agent code subscribe to the transcription event (e.g., user_input_transcribed).
◦ Each event includes the transcribed text, whether it is final, and participant info. Persist these records to your backend (database or files) with timestamps and participant identity.
Example flow (high level)
1. Participant publishes audio track -> LiveKit Server.
2. Agent subscribes to participant audio tracks.
3. Agent streams audio to STT provider, receives transcriptions.
4. Agent emits transcription text streams (lk.transcription) and triggers user_input_transcribed events.
5. Your Agent code saves each final transcript segment mapped to the participant identity.
Handling SIP participants
• SIP participants are handled the same way as standard participants: the Agent can subscribe to their published audio track and transcribe it.
• Note that SIP audio is often narrowband (8 kHz); confirm your chosen STT provider supports that sampling rate.
When audio is mixed (multiple speakers on one track)
• If you have a mixed/multi-party audio track (e.g., a room mic), per-participant transcripts are harder.
• Use speaker diarization if you must transcribe mixed audio and separate speakers. LiveKit supports a MultiSpeakerAdapter for diarization (Python) with providers that offer diarization (Deepgram, Speechmatics).
• Best practice: avoid mixing and prefer each participant publishing their own track.
Frontend delivery
• Use the LiveKit text stream handler on clients to receive real-time transcriptions (topic lk.transcription) and display or store them as they arrive.
• Use the metadata (lk.transcription_final, lk.transcribed_track_id) to update UI only when segments are final, or show interim text if you want live updating.
Persistence and reliability
• Persist final transcription segments server-side from the Agent. This prevents loss if clients disconnect and gives you authoritative meeting records.
• Store: participant id/identity, track id, segment id, text, is_final, timestamps.
Optional improvements
• Use diarization if you must handle mixed audio.
• Normalize timestamps and store language or STT confidence scores if the provider returns them.
• Batch or stream-save transcripts depending on latency vs durability needs.
Next step questions to tailor this to you
• Are your participants currently publishing separate microphone tracks, or are you using a single mixed track?
• Which STT provider do you prefer, or do you want recommendations based on cost/accuracy/languages?
• Are you building the Agent in Python or another language?
If you tell me which of the above applies, I’ll give a concise code example and specific configuration settings you can copy into your Agent.
Sources: sip | livekit/livekit_composite | DeepWiki | Text and transcriptions | LiveKit Documentation | Transcriber | LiveKit Documentation | Pi Zero Transcriber | LiveKit Documentation | Speech-to-text (STT) models overview | LiveKit Documentationfuture-umbrella-45124
03/09/2026, 11:19 AMtall-airline-16756
03/09/2026, 11:20 AMlk.transcription) so all participants can receive them, or persist them directly to your backend.
Two main client-side STT options
• Browser-native (low effort): Web Speech API (good for quick proof-of-concept; limited browser support and quality varies).
• Client-powered STT (recommended for production):
◦ On-device WASM/JS models (e.g., whisper.wasm) to avoid uploading audio to servers and lower recurring costs.
◦ Cloud STT from the client (Deepgram, Google, AssemblyAI) by streaming audio from the client to the provider — good accuracy but increases bandwidth and may raise security/latency considerations.
High-level implementation flow
1. Subscribe to remote audio tracks:
◦ For each RemoteParticipant audio track, get a MediaStream or AudioNode and feed it to your STT pipeline.
1. Run STT per track:
◦ Use Web Speech API, a WASM model, or stream to a cloud STT. Produce interim and final results as available.
1. Publish results:
◦ Use room.localParticipant.sendText(transcript, { topic: 'lk.transcription', attributes: { 'lk.participant_identity': origin }})
◦ Or save final segments to your backend directly (recommended for durable transcripts).
1. Receive/display:
◦ Other clients register a text stream handler for lk.transcription to show real-time or final transcripts.
Considerations and trade-offs
• Scalability: This moves CPU and possibly network work to clients. Works well for large meetings because the server CPU stays low, but user devices may struggle on low-end phones or heavy meetings.
• Accuracy & audio quality: Per-participant (separate published mic tracks) gives best accuracy. If audio is mixed server-side or via a single room mic, you’ll need speaker diarization (harder on client).
• Latency & bandwidth: Sending audio to cloud STT from each client increases outbound bandwidth and adds latency. WASM on-device models avoid this.
• Browser support: The Web Speech API is not uniformly supported across all browsers.
• Security & privacy: Client-side cloud STT sends user audio to third-party services directly from the client — ensure you have consent and consider compliance needs.
Short code outline (Web)
• Subscribe to remote audio tracks and create a MediaStream for STT.
• Example pattern:
a. On TrackSubscribed, obtain the RemoteAudioTrack.mediaStream.
b. Pass the MediaStream to your STT pipeline (Web Speech API or WASM).
c. Send final transcripts via sendText() to topic lk.transcription with participant identity in attributes.
When client-side transcription may not work well
• If many participants are on low-powered devices (mobile), doing heavy STT on each client could degrade user experience.
• If you need centralized durable transcripts regardless of client connectivity, you should still persist transcripts server-side when clients publish them, or have a fallback server-side agent.
Next step — a couple quick questions so I can give code you can drop into your app
1. Which platforms do you need this for? (Web, iOS, Android)
2. Do you prefer an on-device approach (WASM/embedded model) or streaming from client to a cloud STT provider?
Answer those and I’ll provide a concise code example and recommended STT libraries for your target platform.
Sources: Live Conversations with AI using ChatGPT and WebRTC | Live Conversations with AI using ChatGPT and WebRTC | Stream music over WebRTC using React and WebAudio | Stream music over WebRTC using React and WebAudio | How to build an agent with speech-to-text input and text-only output | Live...future-umbrella-45124
03/09/2026, 11:42 AMtall-airline-16756
03/09/2026, 11:43 AMroom.localParticipant.sendText() (topic lk.transcription) including attributes like lk.participant_identity and lk.transcription_final.
◦ Also persist final segments server-side if you need durable storage (recommended).
Small code outline (Web)
• On track subscribe:
room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => {
if (track.kind === Track.Kind.Audio) {
const mediaStream = (track as RemoteAudioTrack).mediaStream;
startTranscriptionForStream(mediaStream, participant.identity);
}
});
• In your transcription function, produce interim/final text and send:
room.localParticipant.sendText(transcript, {
topic: 'lk.transcription',
attributes: { 'lk.participant_identity': participantIdentity, 'lk.transcription_final': isFinal ? 'true' : 'false' }
});
Important considerations when including SIP
• Audio quality: SIP often uses narrowband encoding (8 kHz). Make sure your STT solution supports that sample rate or resample/normalize before sending to the STT model.
• Processing power: SIP transcription still shifts CPU to clients that must transcribe multiple remote tracks. Consider which clients will do transcription (all clients, just hosts, or a subset).
• Mixed/multi-speaker tracks: If a SIP endpoint is mixed into a single room mic, you’ll need diarization to separate speakers — diarization is harder client-side.
• Privacy & compliance: Streaming raw SIP audio from clients to cloud STT providers may have consent or compliance implications; handle accordingly.
• Fallback/robustness: If some clients cannot run STT (old browsers, low-power devices), consider a hybrid fallback: allow a light-weight server agent to transcribe those specific streams or persist client transcripts to your server so others can access them.
When you might treat SIP differently
• If you need better accuracy for SIP (call recordings, legal requirements), prefer resampling/normalization and a robust cloud STT.
• If SIP audio is transcoded by LiveKit (noise suppression, codecs), test the delivered track quality before selecting the STT approach.
Would you like:
• A specific code example for whisper.wasm or Deepgram streaming from the browser?
• Guidance on resampling narrowband SIP audio for better STT accuracy?
Tell me which STT approach (on-device WASM vs cloud streaming) and target platform (web / mobile), and I’ll give a concise code sample tailored to that.
Sources: livekit/livekit_composite | DeepWiki | Job lifecycle | LiveKit Documentation | SIP Lifecycle Management Agent | LiveKit Documentation | SIP Telephony and Encryptionfuture-umbrella-45124
03/09/2026, 12:22 PMtall-airline-16756
03/09/2026, 12:22 PMroom.localParticipant.sendText() with topic lk.transcription) or pushes the final segments to your backend for storage.
Common deployment patterns
• Host/presenter publishes: The meeting host’s client handles transcription for everyone (including SIP). Pros: fewer devices doing heavy STT; consistent transcript quality. Cons: host device can become a bottleneck.
• Designated transcription client(s): One or a few dedicated clients (could be headless browsers or a powerful participant) subscribe to all audio and publish transcripts. Pros: predictable resources and reliability. Cons: requires those clients to remain connected.
• Every client publishes their own view: Each web client transcribes the remote tracks they hear and publishes transcripts (can lead to duplicate transcripts unless you de-duplicate). Pros: fully distributed, no single point of failure. Cons: higher network/compute usage across clients and risk of duplicates.
• Hybrid: Clients publish their own local speech; a designated client or server persists and deduplicates final transcripts for archival.
How to map transcripts to the SIP participant
• Use the participant metadata attached to the RemoteAudioTrack to label transcripts. Include participant.identity (and optionally participant.kind or track ID) when publishing:
◦ Example attributes: lk.participant_identity, lk.transcribed_track_id, lk.transcription_final.
• This ensures consumers know the transcript origin even though a different client published it.
Practical publish flow (client-side)
1. Client subscribes to SIP audio track.
2. Client runs STT on that track (Web Speech API, WASM, or cloud).
3. Client sends interim/final results:
◦ Interim: publish with lk.transcription_final: false.
◦ Final: publish with lk.transcription_final: true and persist server-side if you need durability.
1. Consumers read the text stream and map to the SIP participant via attributes.
Recommendation
• For reliability at scale, use a designated transcription client or a small set of transcription clients that subscribe to all participant audio (including SIP) and publish final transcripts to the room and/or your backend. This avoids duplicated transcripts, centralizes persistence, and keeps low-powered user devices free from heavy processing.
Would you like a short code snippet showing how a client publishes a transcript for a SIP participant (attributes to include and example sendText call)? If so, tell me whether you prefer JavaScript/TypeScript or another platform.future-umbrella-45124
03/13/2026, 2:03 PMtall-airline-16756
03/13/2026, 2:03 PMsegment_id or timestamps.
C. Implementation details — what to publish and how to avoid duplication
• Use attributes to identify original speaker and publisher:
◦ include lk.participant_identity (SIP identity), lk.transcribed_track_id, lk.transcription_publisher (who published the text), and lk.transcription_final.
• Use unique segment IDs and timestamps so downstream systems can deduplicate and stitch segments.
• If multiple clients may publish the same SIP participant:
◦ Add a publisher_priority attribute (lower = authoritative) or a sequence number to choose which segment to accept.
◦ Prefer a persistence step on your backend: accept the first final transcript for a segment, ignore duplicates.
D. Detection & takeover timing
• Heartbeat interval: 2–5s; consider timeout = 2–3× interval.
• Try to keep takeover latency under 3–5s for a smooth experience.
• On takeover, fetch recent audio/segment context if you keep server-side buffers; otherwise start fresh and accept a small gap.
E. Choosing a strategy (trade-offs)
• If you want simplest reliability: use a small backend coordinator to assign a single transcription client and handle failover. Minimal complexity on clients.
• If you prefer no backend: implement leader election among designated clients with heartbeats and deterministic tie-breaking.
• If you need horizontal scalability (many SIP lines): use partitioned assignment plus multiple transcribers with a coordinator or deterministic hashing.
F. Concrete next steps you can implement now
1. Decide assignment model: single host, dedicated pool, or partitioned.
2. Implement heartbeats: publisher sends periodic presence; watchers detect timeouts.
3. Implement takeover:
◦ On timeout, next candidate subscribes to missing SIP tracks and starts STT.
◦ Publish a takeover event to the room (text stream topic like lk.transcription.control) including new publisher id and affected SIP ids.
1. Persist final segments server-side with deduplication by segment id + participant id.
2. Optionally add a short overlap window and a dedupe rule on the backend: accept the earliest final segment per segment id.
G. Example minimal failover flow (designated pool without backend)
1. Start: Client A is leader and transcribes SIP1..SIPn, sending heartbeat every 3s.
2. Client B and C are standbys watching heartbeat.
3. Client A disconnects: B detects no heartbeat for 9s and elects itself (deterministic tie-breaker).
4. B subscribes to SIP tracks and publishes takeover control message.
5. B begins publishing transcripts; backend accepts B as publisher for these SIP ids.
If you want, I can:
• Provide a small JavaScript/TypeScript code sample for heartbeat + takeover using LiveKit text streams for the control channel.
• Sketch a backend-based assignment API (tiny Redis lock + HTTP endpoints) if you prefer a coordinator.
Which would help you more — a pure client-side leader-election snippet, or a lightweight backend coordinator example?future-umbrella-45124
03/13/2026, 2:06 PMtall-airline-16756
03/13/2026, 2:07 PMtranscription_publisher attribute so you can choose the canonical source.
Trade-offs & guidance
• If real-time transcription is critical even when all clients drop, you must run an always-on server-side transcriber.
• If you can tolerate post-call transcription, recording + batch STT is a cost-effective alternative.
• Headless clients let you reuse your client-side STT stack but require orchestration; LiveKit Agents give you a more plug-and-play server solution.
If you want, I can:
• Provide a small architecture sketch and a sample sequence of LiveKit events and API calls to start/stop a fallback transcriber.
• Share a concise JavaScript/TypeScript example for a headless standby client that joins a room and subscribes to all SIP tracks.
• Outline the simplest backend webhook logic to detect “no web clients” and spin up fallback transcribers.
Which of these would you like next — a code example for a headless standby client, or a simple backend flow (webhook + start/stop transcriber) you can copy into your system?