Text stream handler being called twice for single ...
# ask-ai
j
Text stream handler being called twice for single message - React Native Hi, I'm experiencing an issue where my text stream handler is being invoked twice for every single transcript message, resulting in duplicate processing. This happens in both development and production builds. Setup: - LiveKit React Native client (livekit-client v2.15.3) - Python agent using LiveKit's TranscriptSynchronizer - Handler registered with: room.registerTextStreamHandler("lk.transcription", handler) Issue: When a transcript is sent (either from user or agent), my handler is called twice with the exact same message: LOG [useLiveKitVoice] Processing transcript - User: true, Text: "Test one, two, three..." LOG [useLiveKitVoice] Processing transcript - User: true, Text: "Test one, two, three..." What I've tried: 1. Ensuring handler is only registered once 2. Attempting to unregister with room.unregisterTextStreamHandler("lk.transcription") before registering 3. Verified the Python agent is only sending each transcript once Questions: 1. Is there something special about the "lk.transcription" topic that could cause duplicate handler invocations? 2. What's the correct pattern for registering/unregistering text stream handlers to ensure only one is active? 3. Could this be related to how TranscriptSynchronizer sends messages on the agent side? 4. Is there a way to debug how many handlers are registered for a given topic? Any guidance would be greatly appreciated!
t
Thanks for reaching out, Albert. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
To help address the issue of your text stream handler being called twice, could you clarify the following: • Are you using the same handler function across multiple components or instances in your React Native app? • Have you verified that the handler is not being registered multiple times due to re-renders or component lifecycle events? • Could you share more details about how the handler is registered and unregistered in your code? This information will help narrow down potential causes and provide more targeted troubleshooting steps. Sources: Sending text | LiveKit Docs
j
Thank you for the quick response! Here are the clarifications: 1. Handler registration location: The handler is registered in only ONE place - in a custom hook called useLiveKitVoice.ts. This hook is used by a single component (voice-note.tsx). No other components register text stream handlers. 2. Component lifecycle and re-renders: Yes, I suspect this might be the issue. Here's our current flow: // In voice-note.tsx useEffect(() => { const initializeVoiceNote = async () => { if (!profile?.id || !threadId) return; await endVoiceCall(); await new Promise(resolve => setTimeout(resolve, 100)); startVoiceCall({ enableMicrophone: false }); }; initializeVoiceNote(); return () => { endVoiceCall(); }; }, [profile?.id, threadId]); 3. Handler registration code: // In useLiveKitVoice.ts - inside startVoiceCall function newRoom.registerTextStreamHan dler("lk.transcription", async (reader, participant) => { const text = await reader.readAll(); console.log(
Copy code
Processing
  transcript - User:
  ${participant.identity ===
  patientId}
); // Save to backend... }); Current cleanup attempts: - We tried storing the room reference and calling room.unregisterTextStreamHand ler("lk.transcription") in cleanup - But this resulted in NO transcripts being received at all - Without cleanup, we get duplicates Key observation: The duplicate happens even when navigating to a completely new thread/room, suggesting handlers might persist across room instances somehow. Is there a recommended pattern for ensuring only one handler per room, especially with React's lifecycle complexities?
t
Hey there! To make sure you have just one text stream handler active per room in your React Native app, here are some friendly tips and best practices you might find helpful: 1. Use React's
useEffect
with Proper Cleanup
It’s a good idea to register and unregister the handler inside a
useEffect
hook. This way, the handler gets cleaned up nicely when your component unmounts or when dependencies change:
Copy code
useEffect(() => {
  if (!room) return;

  const handler = async (reader, participant) => {
    const text = await reader.readAll();
    console.log(`Processing transcript - User: ${participant.identity}`);
    // Process the text...
  };

  // Register the handler
  room.registerTextStreamHandler("lk.transcription", handler);

  // Cleanup function to unregister the handler
  return () => {
    room.unregisterTextStreamHandler("lk.transcription");
  };
}, [room]);
2. Handle Room Disconnection To keep things tidy and avoid memory leaks, make sure to clean up handlers when the room disconnects:
Copy code
useEffect(() => {
  if (!room) return;

  const cleanup = () => {
    room.unregisterTextStreamHandler("lk.transcription");
  };

  // Register handler
  room.registerTextStreamHandler("lk.transcription", handler);

  // Cleanup on room disconnection
  room.on(RoomEvent.Disconnected, cleanup);

  return () => {
    cleanup();
    room.off(RoomEvent.Disconnected, cleanup);
  };
}, [room]);
3. Use Built-in Error Protection Good news! The LiveKit SDK helps prevent multiple handlers for the same topic. If you try to register a handler when one’s already there, it’ll throw a
TypeError
. Just make sure your code handles this smoothly. 4. Consider Using the
useTextStream
Hook
If you want to keep things simple, the LiveKit components library offers a handy
useTextStream
hook that takes care of managing the lifecycle of text stream handlers for you. Here’s a quick look:
Copy code
import { useTextStream } from '@livekit/components-react';

function MyComponent() {
  const { textStreams } = useTextStream("lk.transcription");

  return (
    <div>
      {textStreams.map((textStream) => (
        <div key={textStream.streamInfo.id}>
          {textStream.text}
        </div>
      ))}
    </div>
  );
}
5. Debugging and Observations Since you noticed duplicates sticking around across room instances, here are a couple of things to double-check: • Make sure the
room
object updates correctly and gets cleaned up when you switch between threads. • Confirm that handlers aren’t being registered multiple times because of re-renders or outdated references. By following these friendly pointers, you’ll have just one handler running per room and avoid those pesky duplicate calls. If you run into any hiccups, I’m here to help! Sources: packages/core/src/components/chat.ts | src/room/Room.ts | avatars/hedra/education_avatar/education-frontend/hooks/useTextStreamLogger...