const handler = room.registerTextStreamHandler('lk...
# ask-ai
m
const handler = room.registerTextStreamHandler('lk.transcription', async (reader, participantInfo) => { const participant = participantInfo?.identity || 'unknown'; let partialBuffer = ''; let currentSegmentId: string | null = null; console.log('[LiveKit] Text stream handler invoked for:', participant); console.log('[LiveKit] Reader attributes:', reader.info?.attributes); try { // Process each chunk as it arrives using for await for await (const chunk of reader) { console.log('[LiveKit] Received chunk:', chunk); const isFinal = reader.info?.attributes?.['lk.transcription_final'] === 'true'; const segmentId = reader.info?.attributes?.['lk.segment_id']; [LiveKit] Failed to register text stream handler: [DataStreamError: A text stream handler for topic "lk.transcription" has already been set.] Code: index.tsx 230 | console.log('[LiveKit] Registering text stream handler for lk.transcription'); 231 |
232 | const handler = room.registerTextStreamHandler('lk.transcription', async (reader, participantInfo) => {
| ^ 233 | const participant = participantInfo?.identity || 'unknown'; 234 | let partialBuffer = ''; 235 | let currentSegmentId: string | null = null;
t
Thanks for reaching out, Rishabh. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m looking into the error you shared: "A text stream handler for topic 'lk.transcription' has already been set." That means LiveKit will only allow one handler to be registered for a given text stream topic, and something else in your app already registered a handler for
lk.transcription
before your code ran. To help you fix this quickly, I need a bit more context so I can recommend the best approach (reuse, replace, or fan-out). Can you answer the short list below? 1. Are you using any LiveKit UI libraries or components anywhere in your app (for example
@livekit/components-react
,
useTranscriptions
, or a prebuilt transcription component)? 2. Where are you calling
room.registerTextStreamHandler('lk.transcription', ...)
in your codebase — a single component, multiple components, or central initialization code? If possible, paste the surrounding code or describe when it runs (mount/unmount lifecycle). 3. Is your goal to replace an existing handler at runtime, or to have multiple independent consumers of the same transcription stream? 4. Which LiveKit SDK and components package versions are you using (for example
livekit-client
and
@livekit/components-react
versions)? 5. If you can, search your repo for other calls to
registerTextStreamHandler('lk.transcription')
and tell me whether any are found (or paste the search results). What I’ll do next depending on your answers: • If a LiveKit UI component (like
useTranscriptions
) is in use: I’ll show how to reuse that existing subscription instead of registering a new handler. • If you need multiple consumers: I’ll suggest a single handler that fans out events internally (brief example). • If you truly must swap handlers at runtime: I’ll explain safe unregister/register patterns or where to centralize registration so it only runs once. Reply with those details and I’ll provide the specific code changes or examples you can apply. Sources: Text and transcriptions | LiveKit Documentation
m
We have a number of letters in the running registry on global number of group on group. Is it probably that Python agent has run by the phone?
t
Thanks — I want to make sure I understand what you’re asking so I can help. You mentioned “a number of letters in the running registry on global number of group on group” and asked whether a “Python agent has run by the phone.” That phrasing is unclear to me, so I have a few short clarifying questions to get on the same page: 1. By “letters in the running registry” do you mean entries in some registry/log, text messages, transcription segments, or something else? 2. What do you mean by “global number of group on group”? Is that a specific data structure, a group/room in LiveKit, or a phone/system grouping in your app? 3. When you say “Python agent,” are you referring to a service you run (a Python process) that interacts with LiveKit or another part of your system? If so, is it expected to register a transcription handler or send text streams? 4. What do you mean by “run by the phone”? Do you mean the Python agent is running on a mobile device, is triggered by a phone call, or is interacting with telephony infrastructure? If it helps, tell me: • Where you see the error (browser console, server logs, etc.). • Any relevant snippets showing where you call
room.registerTextStreamHandler('lk.transcription', ...)
in your JS/TS code and whether you also have any Python services that might interact with LiveKit or your signaling layer. Answering any of the above will let me recommend the next steps — for example, checking for duplicate handler registrations, reusing an existing handler, or confirming whether a Python service could be the source of the duplicate registration.
m
What I meant was I am building this in React Native and there's only one place where I have created this particular text stream handler. Is it possible that Python agent has one that it runs on the back end automatically?
t
Thanks — that clarification helps. You're correct that registering the same topic handler twice (client + backend) will cause that error. Yes — it’s very likely your Python agent is automatically registering a handler for
lk.transcription
(or
lk.chat
) and causing the conflict with the one you register in React Native. What you can do next 1. Check the Python agent config/code for automatic text handling: ◦ If you’re using
AgentSession
or the LiveKit Python agents, they enable text handling and publish/subscribe to text streams by default (including
lk.transcription
for STT). Look for options like
text_input
or code paths that publish transcription or register handlers. 1. Disable or change the agent behavior if you don’t need it: ◦ Disable automatic text input handling in the agent (example:
room_options=RoomOptions(text_input=False)
when starting the session). 1. Use separate topics if both sides must handle text: ◦ If both frontend and agent need independent handlers, have one side use a custom topic (for example,
custom.transcription
) so there’s no collision. 1. Centralize handling and fan-out: ◦ Keep a single registered handler (either on the client or server) and have it dispatch events internally to multiple consumers. This avoids duplicate registrations while allowing multiple components to react to the same stream. 1. Quick repo checks to confirm the source: ◦ Search your backend for
register_text_stream_handler
,
lk.transcription
, or any code that publishes to
lk.transcription
. ◦ In React Native, confirm the handler registration is executed only once (e.g., in a top-level initialization or a component mount that runs once). If you want, paste: • The snippet where you start the Python agent/session (or the agent config) so I can point to the exact option to change. • The React Native snippet where you call
room.registerTextStreamHandler('lk.transcription', ...)
and describe when it runs (component mount, navigation event, etc.). With those two snippets I’ll recommend the exact change (disable agent handling, change topic, or centralize registration) and provide a small code example you can apply. Sources: Text and transcriptions | LiveKit Documentation
m
Give me code in React Native which basically handles this logic without having to register a text stream handler. I'm okay using the one that Python already creates on the backend.
t
Thanks — I’ll give you a React Native approach that consumes the transcription stream the Python agent already publishes without calling
room.registerTextStreamHandler(...)
. Below are two safe patterns you can use depending on your app architecture. Which to pick • If you use LiveKit’s React components/hooks (or a library that already exposes transcription events), use that hook to read the transcription state. This avoids registering a handler. • If you use the raw client in React Native and there isn’t a helper hook available, read transcription updates from the participant/room state or from text messages/events the backend publishes (instead of registering another handler). The backend’s existing handler can publish transcription segments into room state, participant metadata, or a separate custom text topic — you can subscribe to those without registering a conflicting handler. Pattern A — Use a transcription hook (recommended if available) If you have access to a hook like
useTranscriptions
from your LiveKit React package, it already reads the transcription segments the agent publishes. This consumes the backend-created transcription stream without registering another handler. Example (pseudo-React Native component using a transcription hook):
Copy code
import React from 'react';
import { View, Text, FlatList } from 'react-native';
import { useTranscriptions } from '@livekit/components-react'; // adjust import to your library

export default function TranscriptionView() {
  const transcriptions = useTranscriptions(); // read-only subscription

  return (
    <View style={{ flex: 1, padding: 12 }}>
      <FlatList
        data={transcriptions}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <View style={{ marginBottom: 8 }}>
            <Text style={{ fontWeight: '600' }}>
              {item.participant?.identity ?? 'agent'}
            </Text>
            <Text>{item.text}</Text>
          </View>
        )}
        ListEmptyComponent={<Text>No transcriptions yet</Text>}
      />
    </View>
  );
}
Notes: • This does not call
room.registerTextStreamHandler('lk.transcription', ...)
. • Replace
useTranscriptions
import with the actual hook provided by your LiveKit React Native/React package. The hook reads whatever the backend publishes. Pattern B — Read published updates from room/participant state or custom topic If a helper hook isn’t available, have the backend publish transcription segments somewhere your client can read without registering a handler. Common options: • Backend appends transcription segments to an in-room data structure (e.g., participant metadata, a server-maintained store) and your client fetches/updates periodically or uses an existing event stream the backend already uses. • Backend publishes to a custom text topic (not
lk.transcription
) and your client listens via existing UI-provided subscription utilities (not via
registerTextStreamHandler
). Example approach using an event emitter or state sync (pseudo-code): 1) Backend: continues publishing
lk.transcription
(agent-created). Additionally, the backend writes transcription segments to a room-level store or emits them over an existing signaling channel your client can observe (no extra handler required on client). 2) React Native: subscribe to that existing room state or global event that the app already uses (WebSocket, Redux, or LiveKit-provided room events) to receive transcription updates. Pseudo-code React Native component that consumes a shared transcription store:
Copy code
import React, { useEffect, useState } from 'react';
import { View, Text, FlatList } from 'react-native';
import { useRoom } from '@livekit/components-react'; // or your own room hook
// Or subscribe to your app's transcription store (e.g., Redux, Context, EventEmitter)

export default function TranscriptionView() {
  const room = useRoom(); // optional, only if you have it
  const [segments, setSegments] = useState([]);

  useEffect(() => {
    // Example: your app exposes an event emitter that backend updates
    const onSegment = (segment) => {
      setSegments((s) => [...s, segment]);
    };

    // Subscribe to your app-level transcription emitter
    AppTranscriptionEmitter.addListener('segment', onSegment);

    return () => {
      AppTranscriptionEmitter.removeListener('segment', onSegment);
    };
  }, []);

  return (
    <View style={{ flex: 1, padding: 12 }}>
      <FlatList
        data={segments}
        keyExtractor={(item) => item.id}
        renderItem={({ item }) => (
          <>
            <Text style={{ fontWeight: '600' }}>
              {item.participant?.identity ?? 'agent'}
            </Text>
            <Text>{item.text}</Text>
          </>
        )}
      />
    </View>
  );
}
Practical next steps I can help with • If you tell me which LiveKit React/React Native package and version you use, I’ll give the exact hook/import to use. • If you want a small example where the backend writes segments to a room-level store or to a custom topic and the RN client consumes them, I can provide a concrete code pair (backend + client) showing that flow. • If you prefer the simplest immediate step: search your app for any existing transcription hook (e.g.,
useTranscriptions
) and use it instead of
registerTextStreamHandler
. If you share which SDK/component version you’re on or paste your current RN snippet where you register the handler, I’ll return a tailored code example you can drop in to replace the handler registration.
m
useTranscriptions only works in React. I need stuff that can work with react native expo SDK. Give me that code.
t
Thanks — great details. I can produce the exact Expo / React Native code you need, but first I need two quick clarifications so I return code that matches how your Python agent actually publishes transcriptions. Please answer these 2 short questions: 1. Does your Python agent publish transcription segments to the LiveKit text stream topic
lk.transcription
(the default), or does it also mirror transcription data to another channel the client can read without a text-stream handler — for example: ◦ room/participant metadata, ◦ LiveKit data messages on a different topic, ◦ or a separate custom text topic (not
lk.transcription
)? If you’re not sure, paste the small Python snippet where the agent sends/exports transcriptions (or tell me whether it uses
AgentSession
defaults). 1. Which client packages and versions are you using in your Expo app? For example, the
package.json
entries for: ◦
livekit-client
◦
livekit-react-native
(or any LiveKit wrapper) If you don’t know versions, telling me the package names you installed is fine. Why I need this • If the agent already publishes to
lk.transcription
, the only way for the client to read those segments is via a text stream reader (which requires
registerTextStreamHandler
) or by the backend mirroring those segments to something the client can read without a handler (data messages, metadata, or a custom topic). • If you cannot call
registerTextStreamHandler
at all, I’ll show a tiny backend change (example) to mirror transcriptions to data messages (or metadata) and then give an Expo client component to consume those (no text stream handler). • If it’s acceptable to register one centralized handler in your Expo app (once, centrally), I can give a simple ready-to-drop component that registers once and fans out segments to the rest of your app. Reply with the two answers and I’ll return the exact Expo-compatible code (client-only or client+backend example) you can drop into your app. Sources: How to build an agent with speech-to-text input and text-only output | Live... | How to Set Up Speech-to-Text Input with Text-Only Output in LiveKit Agents | Text and transcriptions | LiveKit Documentation | Pipeline nodes and hooks | LiveKit Documentation | LiveKit coming to React Native!
m
"livekit-client": "^2.16.1", The Python agent already publishes to LK.Transcription to the back and automatically because I am using AgentSession.
t
Thanks — helpful info. You’re right: with
livekit-client
the client can only read
lk.transcription
via a text-stream reader, and your Python AgentSession is already publishing to
lk.transcription
. I can give you exact code next — but first one quick confirmation so I return the right implementation: Please choose one of the two options below: 1) Client-only: I’ll provide a small Expo/React Native component that centrally registers a single
room.registerTextStreamHandler('lk.transcription', ...)
(safe, minimal change). This reads the agent’s transcriptions directly on the client. -or- 2) Backend-mirror: I’ll provide a tiny Python snippet to mirror each transcription segment into a channel the Expo app can read without a text-stream handler (for example: LiveKit data messages via
room.sendData
on a custom topic), plus the Expo client code to consume those messages. If you choose option 2, tell me which mirror channel you prefer: • LiveKit data messages on a custom topic (recommended), or • participant/room metadata, or • your own backend WebSocket/HTTP push endpoint. Reply with your choice and (for option 2) the preferred mirror channel and I’ll send the exact, copy-pasteable Expo React Native code and any minimal Python snippet needed.