I am doing a livekit meeting serivce, there are we...
# ask-ai
f
I am doing a livekit meeting serivce, there are web clients and there are also SIP participants. I want to be able to maintain every participant's transcript. What do you recommend i do to solve this
t
Thanks for reaching out, Tushar. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I understand you want to maintain transcripts for every participant in a LiveKit meeting (both web clients and SIP callers). Below is a clear, practical plan you can implement right away, plus best practices and optional improvements. What I recommend • Run a LiveKit Agent configured with a Speech-to-Text (STT) provider (e.g., Deepgram, Google, AssemblyAI). The Agent subscribes to participant audio and produces transcriptions in real time. • The Agent will receive audio from both STANDARD (web) participants and SIP participants, so a single Agent can cover all meeting participants. How to capture per-participant transcripts 1. Use per-participant audio tracks (recommended) ◦ Ensure each participant publishes their own microphone track. This gives the cleanest per-speaker audio and makes mapping transcripts to participants straightforward. 1. Agent setup: enable text output ◦ Configure the Agent/room to publish transcription text streams (topic
lk.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 Documentation
f
Actually for a meeting with a lot of participants.. vad get's cpu heavy, so I prefer doing this on the client side and don't want to use agents. Is there a way?
t
Thanks — you can absolutely avoid server-side agents and do transcription client‑side to reduce server CPU. Below I’ll summarize the practical approaches, trade-offs, and a short implementation plan you can use immediately. I’ll finish by asking two quick questions so I can give a focused code example for your platform. Quick summary — how to do client-side transcription • Capture audio tracks in the client (each participant’s remote audio or the local mic), run STT locally or from the client, then publish/save the resulting transcripts. • Use RemoteAudioTrack / MediaStream from LiveKit to get audio for each participant. • Send transcripts to the room using LiveKit text streams (e.g., sendText with topic
lk.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...
f
But you didn't tell me how can I do transcription for SIP client in this same case.
t
Thanks — great question. I’ll be direct: you can transcribe SIP participants the same way you transcribe web participants in a client-side setup because SIP audio arrives at web clients as normal remote WebRTC audio tracks. Below is exactly how to do it and what to watch for. How SIP audio appears to the client • SIP participants publish audio into the LiveKit room and web clients receive that audio via the normal RemoteAudioTrack/WebRTC subscription flow. • From the web client’s perspective, SIP audio is just another remote audio track (no special handling required). How to include SIP participants in client-side transcription 1. Subscribe to audio tracks (this will include SIP participants) ◦ When a TrackSubscribed/TrackPublished event fires, handle audio tracks the same way for any participant. 1. Feed the track into your STT pipeline ◦ Get the track’s MediaStream or use Web Audio API (createMediaStreamSource) to feed into: ▪︎ Web Speech API (browser-native, POC) ▪︎ WASM/JS models (e.g., whisper.wasm) for on-device transcription ▪︎ Client-to-cloud streaming (Deepgram/Google) if you stream audio from client 1. Identify the speaker ◦ Use the participant object attached to the track to map transcripts to participant.identity. ◦ Participant.kind will indicate SIP vs STANDARD if you need to treat SIP participants differently. 1. Publish or persist results ◦ Publish transcripts back to the room with
room.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:
Copy code
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:
Copy code
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 Encryption
f
No, but who exactly will publish the SIP participant's transcripts
t
Good question — in a client-side transcription setup (no server agents), the client that receives and processes the SIP audio is responsible for publishing the transcripts. Here’s exactly how that typically works and the options you can choose from. Who publishes SIP transcripts • The web client (or other client) that subscribes to the SIP participant’s RemoteAudioTrack and runs the STT pipeline publishes the transcript. • That publishing client sends the transcription back into the room (for example using
room.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.
f
How do I decide, which web client will which sip client's transcription. what do i do if the designated client disconnects,,, who takes over? What do you say?
t
Great questions — these are exactly the operational details that make a client-side transcription system reliable in real meetings. Below I’ll give practical strategies for (A) assigning which web client transcribes which SIP client, (B) failover options if the designated transcriber disconnects, and (C) implementation tips so you can pick the approach that fits your system. A. Strategies for assigning transcription responsibility • Host / presenter model (single designated client) ◦ One known client (host) subscribes to all audio and publishes transcripts for everyone. ◦ Simple to implement and easy to reason about. ◦ Good when the host is a stable, powerful machine (desktop, server-like browser). • Dedicated transcription clients (one or a few) ◦ A pool of one or more designated transcribers (could be headless browsers or trusted participants). ◦ Use leader election to choose the active transcriber and keep hot-standbys. ◦ Balances load and adds redundancy. • Partitioned assignment (round-robin or per-participant) ◦ Split responsibility across multiple clients: each transcribes a subset of SIP participants (e.g., by participant ID hash or round-robin). ◦ Reduces per-client CPU and network usage. ◦ Requires tracking assignments centrally or with distributed agreement. • Everyone transcribes (peer publish) ◦ Every client transcribes what they hear and publishes results. ◦ Easiest to start but creates duplicate transcripts and requires deduplication/consensus for a canonical transcript. B. Failover and takeover patterns You need a reliable way to detect disconnection and promote another client to take over. Common patterns: 1) Heartbeat + Leader election (recommended for designated/dedicated models) • Active transcriber periodically writes a heartbeat (small text stream or presence state) to the room or a backend. • Other designated clients watch heartbeats. If heartbeat stops for N seconds, a takeover occurs. • Leader election methods: ◦ Simple timestamp: the first available standby with the earliest join time promotes itself after timeout. ◦ Deterministic selection: pick the client with lowest hashed id among active standbys. ◦ Consensus (if you already use a backend): request lock from server to become leader. 2) Central coordinator (backend-driven takeover) • A lightweight backend coordinates assignment and reassigns if the active client disconnects. • Clients register as available; the backend assigns/publishes which client should transcribe which SIP participant. • Reliable but adds a small server component. 3) Soft handoff with overlap • When takeover happens, the new transcriber subscribes and begins producing interim transcripts immediately while old transcriber stops (if still connected). • Keep short overlap to avoid gaps. Deduplicate by
segment_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?
f
If i am not wrong, all strategies will go down the drain if all the webclients disconnect and only sip participants are left.
t
You’re correct — if you rely only on client-side transcription, and every web client disconnects leaving only SIP participants in the room, there will be nobody left to receive the SIP audio and run STT. In that scenario client-side strategies cannot transcribe SIP participants. Below are clear mitigation options, trade-offs, and a recommended resilient architecture so transcription continues even if all web clients drop. Why client-only fails when all web clients disconnect • Client-side transcription requires a client to subscribe to the SIP participant’s RemoteAudioTrack and run STT. • If no clients are connected, there is no process subscribing to those tracks, so no audio is available to transcribe locally. Options to handle this edge case • 1) Always-on server-side transcriber (recommended for reliability) ◦ Run a small server-side process (headless browser or a LiveKit Agent) that stays connected to the room and subscribes to SIP audio. ◦ Pros: reliable, continues transcribing when web clients disconnect, centralizes persistence. ◦ Cons: uses server CPU — but you can limit work to only SIP streams or to rooms flagged as requiring guaranteed transcription. • 2) Lightweight "transcription standby" clients (headless or hosted) ◦ Deploy one or more always-on headless clients (e.g., headless Chrome or a small Node process using WebRTC) that subscribe only to SIP participants and perform STT. ◦ Pros: avoids heavy LiveKit Agent/VAD logic on the server if you prefer using client STT stacks, still reliable. ◦ Cons: you must manage those processes and ensure they reconnect after failure. • 3) Hybrid: client-side primary, server fallback ◦ Primary: distribute transcription to clients as you designed. ◦ Fallback: when room occupancy shows no eligible clients (or all clients disconnect), start a server-side fallback transcriber automatically. ◦ Pros: best of both worlds — saves client CPU most of the time, guarantees transcription during outages. ◦ Cons: more moving parts (need logic to detect no clients and trigger fallback). • 4) Post-call transcription from recordings ◦ Record the SIP audio server-side (e.g., record the room or SIP leg) and run batch transcription later. ◦ Pros: no always-on CPU required during the call, good for compliance/archival transcripts. ◦ Cons: not real-time; introduces delay and possible privacy/consent concerns. How to detect “no web clients” and trigger fallback • Use presence/participant counts from LiveKit: ◦ Check participant.kind: if only SIP participants remain or the count of STANDARD participants drops to zero, trigger fallback. • Heartbeat/availability channel: ◦ Clients/standbys publish heartbeats; absence of heartbeats triggers the fallback. • Server-driven monitoring: ◦ A backend subscribed to room events (or the LiveKit webhook) watches room membership and starts a fallback transcriber when needed. Recommended resilient architecture (practical) 1. Primary: let capable web clients transcribe in real time (saves server CPU). 2. Monitoring: a small backend monitors room membership (via webhooks or API). 3. Fallback: if the backend detects no eligible web transcribers and SIP participants exist, it starts a designated transcription worker: ◦ Option A: start a LiveKit Agent (server-side) to subscribe to SIP audio and run STT. ◦ Option B: spin up a headless client that uses your same client-side STT pipeline (WASM or cloud streaming). 1. Persistence: have the transcriber (client or fallback) push final segments to your backend to ensure durable transcripts and simplify deduplication. 2. Failback: when a web client rejoins and is the selected primary, it can publish a takeover control message and the server fallback can gracefully stop. Minimal practical policy you can implement now • Always keep one standby transcriber per room (or one global standby that can join rooms on demand). • Backend logic: ◦ If STANDARD participant count > 0: rely on client-side transcription. ◦ If STANDARD participant count == 0 and SIP participants exist: ensure fallback transcriber joins. • Persist every final segment server-side, and use
transcription_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?