Overview
Text streams provide a simple way to send text between participants in realtime, supporting use cases such as chat, streamed LLM responses, and more. Each individual stream is associated with a topic, and you must register a handler to receive incoming streams for that topic. Streams can target specific participants or the entire room.
To send other kinds of data, use byte streams instead.
Sending all at once
Use the sendText
method when the whole string is available up front. The input string is automatically chunked and streamed so there is no limit on string size.
const text = 'Lorem ipsum dolor sit amet...';const info = await room.localParticipant.sendText(text, {topic: 'my-topic',});console.log(`Sent text with stream ID: ${info.id}`);
Streaming incrementally
If your text is generated incrementally, use streamText
to open a stream writer. You must explicitly close the stream when you are done sending data.
const streamWriter = await room.localParticipant.streamText({topic: 'my-topic',});console.log(`Opened text stream with ID: ${streamWriter.info.id}`);// In a real app, you would generate this text asynchronously / incrementally as wellconst textChunks = ["Lorem ", "ipsum ", "dolor ", "sit ", "amet..."]for (const chunk of textChunks) {await streamWriter.write(chunk)}// The stream must be explicitly closed when doneawait streamWriter.close();console.log(`Closed text stream with ID: ${streamWriter.info.id}`);
Handling incoming streams
Whether the data was sent with sendText
or streamText
, it is always received as a stream. You must register a handler to receive it.
room.registerTextStreamHandler('my-topic', (reader, participantInfo) => {const info = reader.info;console.log(`Received text stream from ${participantInfo.identity}\n` +` Topic: ${info.topic}\n` +` Timestamp: ${info.timestamp}\n` +` ID: ${info.id}\n` +` Size: ${info.size}` // Optional, only available if the stream was sent with `sendText`);// Option 1: Process the stream incrementally using a for-await loop.for await (const chunk of reader) {console.log(`Next chunk: ${chunk}`);}// Option 2: Get the entire text after the stream completes.const text = await reader.readAll();console.log(`Received text: ${text}`);});
Stream properties
These are all of the properties available on a text stream, and can be set from the send/stream methods or read from the handler.
Property | Description | Type |
---|---|---|
id | Unique identifier for this stream. | string |
topic | Topic name used to route the stream to the appropriate handler. | string |
timestamp | When the stream was created. | number |
size | Total expected size in bytes (UTF-8), if known. | number |
attributes | Additional attributes as needed for your application. | string dict |
destinationIdentities | Identities of the participants to send the stream to. If empty, is sent to all. | array |
Concurrency
Multiple streams can be written or read concurrently. If you call sendText
or streamText
multiple times on the same topic, the recipient's handler will be invoked multiple times, once for each stream. These invocations will occur in the same order as the streams were opened by the sender, and the stream readers will be closed in the same order in which the streams were closed by the sender.
Joining mid-stream
Participants who join a room after a stream has been initiated will not receive any of it. Only participants connected at the time the stream is opened are eligible to receive it.
No message persistence
LiveKit does not include long-term persistence for text streams. All data is transmitted in real-time between connected participants only. If you need message history, you'll need to implement storage yourself using a database or other persistence layer.
Chat components
LiveKit provides pre-built React components for common text streaming use cases like chat. For details, see the Chat component and useChat hook.
Streams are a simple and powerful way to send text, but if you need precise control over individual packet behavior, the lower-level data packets API may be more appropriate.