UNPKG

@livekit/components-core

Version:

This package is a wrapper around the [livekit/client-sdk-js](https://github.com/livekit/client-sdk-js) package. It transforms the event based logic into simple to use observable state and component level APIs. This package is the core for all framework sp

126 lines (106 loc) 4.15 kB
import { RoomEvent, type Room, type TextStreamInfo } from 'livekit-client'; import { from, scan, Subject, type Observable } from 'rxjs'; import { share, tap } from 'rxjs/operators'; import { ParticipantAgentAttributes } from '../helper'; export interface TextStreamData { text: string; participantInfo: { identity: string }; // Replace with the correct type from livekit-client streamInfo: TextStreamInfo; } // Singleton getters for lazy initialization let observableCacheInstance: Map<string, Observable<TextStreamData[]>> | null = null; let roomInstanceMapInstance: WeakMap<Room, string> | null = null; let nextRoomId = 0; // Get or create the observable cache function getObservableCache(): Map<string, Observable<TextStreamData[]>> { if (!observableCacheInstance) { observableCacheInstance = new Map<string, Observable<TextStreamData[]>>(); } return observableCacheInstance; } // Get or create the room instance map function getRoomInstanceMap(): WeakMap<Room, string> { if (!roomInstanceMapInstance) { roomInstanceMapInstance = new WeakMap<Room, string>(); } return roomInstanceMapInstance; } // Helper to generate cache key function getCacheKey(room: Room, topic: string): string { const instanceMap = getRoomInstanceMap(); // Get or create a unique ID for this room instance let roomId = instanceMap.get(room); if (!roomId) { roomId = `room_${nextRoomId++}`; instanceMap.set(room, roomId); } return `${roomId}:${topic}`; } export function setupTextStream(room: Room, topic: string): Observable<TextStreamData[]> { const cacheKey = getCacheKey(room, topic); const observableCache = getObservableCache(); // Check if we already have an observable for this room and topic const existingObservable = observableCache.get(cacheKey); if (existingObservable) { return existingObservable; } const textStreamsSubject = new Subject<TextStreamData[]>(); let textStreams: TextStreamData[] = []; const segmentAttribute = ParticipantAgentAttributes.TranscriptionSegmentId; // Create shared observable and store in cache const sharedObservable = textStreamsSubject.pipe( tap({ subscribe: () => { room.registerTextStreamHandler(topic, async (reader, participantInfo) => { // Create an observable from the reader const streamObservable = from(reader).pipe( scan((acc: string, chunk: string) => { return acc + chunk; }, ''), ); const isTranscription = !!reader.info.attributes?.[segmentAttribute]; // Subscribe to the stream and update our array when new chunks arrive streamObservable.subscribe((accumulatedText) => { // Find and update the stream in our array const index = textStreams.findIndex( (stream) => stream.streamInfo.id === reader.info.id || (isTranscription && stream.streamInfo.attributes?.[segmentAttribute] === reader.info.attributes?.[segmentAttribute]), ); if (index !== -1) { textStreams[index] = { ...textStreams[index], text: accumulatedText, }; // Emit the updated array textStreamsSubject.next([...textStreams]); } else { // Handle case where stream ID wasn't found (new stream) textStreams.push({ text: accumulatedText, participantInfo, streamInfo: reader.info, }); // Emit the updated array with the new stream textStreamsSubject.next([...textStreams]); } }); }); }, finalize: () => { room.unregisterTextStreamHandler(topic); }, }), share(), ); observableCache.set(cacheKey, sharedObservable); // Add cleanup when room is disconnected room.on(RoomEvent.Disconnected, () => { getObservableCache().delete(cacheKey); textStreams = []; textStreamsSubject.next([]); }); return sharedObservable; }