LiveKit has several built-in capabilities that enable sharing application-specific data between participants. These include:
- One-to-one and one-to-many data messages (using WebRTC data channels)
- Participant attributes: key/value attributes on a participant
- Participant metadata: opaque metadata on a participant
- Room metadata: opaque metadata on the room
Deciding what to use
Which data capabilities to use depends on the use case. Here are some considerations:
Is it a message or state?
The first question to ask is whether the data you are exchanging is transient or stateful. Messages can only be delivered to participants who are currently connected. However, state, when set, will be synchronized to new participants who join the room later.
Frequency of updates
How often is the data changing? It’s not advisable to change attributes or metadata more than once every few seconds due to the synchronization overhead.
Data messages are sent over UDP, making them much more efficient. They can support a much higher data rate (multiple updates per second). This is suitable for scenarios like updating a user’s coordinates in a virtual world, where it doesn’t matter if some messages are missed as long as the latest ones are delivered.
Does the state belong to room or participant?
If you do need to set state, another key decision is whether the state should be set on the participant or the room. From a performance perspective, participant state is faster to manipulate as it does not rely on cross-server synchronization.
Participant attributes and metadata
Each LiveKit participant has two fields for application-specific data:
- Participant.attributes: A key-value store used to store participant state.
- Participant.metadata: An opaque string that can store any data.
Both fields can be encoded in a participant’s access token, making them available immediately when the participant connects to the room.
A key advantage of using attributes
is that each key can be updated independently without affecting other keys. This helps to avoid collisions and race conditions when clients are updating different parts of the state.
They can also be updated from any client connected via realtime SDKs, or from the backend via server APIs.
Deleting attributes
To delete an attribute key, set its value to an empty string ("").
Limits
There is a 64kB
limit for metadata or attributes stored on each participant. For attributes, this limit includes the combined size of all keys and values.
Usage from realtime clients
Realtime clients are notified of attributes and metadata changes, with events emitted for both the local participant and any remote participants in the room.
In order for participants to update their own attributes or metadata, the canUpdateOwnMetadata
permission is required in the participant's access token.
// receiving changesroom.on(RoomEvent.ParticipantAttributesChanged,(changed: Record<string, string>, participant: Participant) => {console.log('participant attributes changed', changed, 'all attributes', participant.attributes);},);room.on(RoomEvent.ParticipantMetadataChanged,(oldMetadata: string | undefined, participant: Participant) => {console.log('metadata changed from', oldMetadata, participant.metadata);},);// updating local participantroom.localParticipant.setAttributes({myKey: 'myValue',myOtherKey: 'otherValue',});room.localParticipant.setMetadata(JSON.stringify({some: "values",}));
Updating with server API
From the server-side, you may update attributes or metadata of any participant in the room using the RoomService.UpdateParticipant
API.
import { RoomServiceClient } from 'livekit-server-sdk';const client = new RoomServiceClient("myhost", "api-key", "my secret")client.updateParticipant("room", "identity", {attributes: {"myKey": "myValue"},metadata: "updated metadata",})
Room metadata
Similar to Participant metadata, Rooms also feature a metadata field where you can store data specific to your application. This can be used to store data about the Room that is visible to all participants.
You can set Room metadata using the CreateRoom API, and updated with the UpdateRoomMetadata API.
When there's a change in the Room metadata, a RoomMetadataChanged
event is triggered, notifying all participants within the room.
Data messages
From both server and clients, LiveKit lets you publish arbitrary data messages to any participants in the room via the LocalParticipant.publishData
API. Room data is published to the SFU via WebRTC data channels; and LiveKit server would forward that data to one or more participants in the room.
From the server side, this API is exposed on RoomService
as SendData
Limits
Data messages are sent over WebRTC data channels. Due to limitations of the SCTP protocol, it’s not practical to use data channels for messages larger than 16 KiB, including LiveKit’s protocol wrapper. We recommend keeping messages under 15 KiB. Read more about data channel size limits.
Delivery options
Since the data is sent via UDP, you have a flexibility in regard to the reliability of delivery. LiveKit offers two types of delivery mechanisms:
- Lossy: Data is sent once, without ordering guarantees. This is ideal for realtime updates where speed of delivery is a priority.
- Reliable: Data is sent with up to three retransmissions and ordering guarantees. This is preferable for scenarios where delivery is prioritized over latency, such as in-room chat.
When using lossy delivery, we suggest keeping data packets under 1300 bytes to stay within the network MTU of 1400. If a message is fragmented into multiple packets and one packet fails to arrive, the entire message will be lost.
Reliable delivery indicates best-effort-delivery. However, it does not guarantee that the data will be delivered. LiveKit does not buffer data messages on the server. This means that if a receiver is not connected the moment the message is sent, it will not receive the message.
Selective delivery
With data messages, the sender can choose to send data either to the entire room or to a subset of participants by specifying the destinationIdentities
parameter when calling publishData
.
If destinationIdentities
is left blank, the data is sent to the entire room.
Topic
It can be useful to send different types of data messages within the same room. To facilitate this, you can specify a topic
when sending data messages. This allows receipients to filter data messages based on the topic when receiving them.
From realtime clients
const strData = JSON.stringify({some: "data"})const encoder = new TextEncoder()const decoder = new TextDecoder()// publishData takes in a Uint8Array, so we need to convert itconst data = encoder.encode(strData);// Publish lossy data to the entire roomroom.localParticipant.publishData(data, {reliable: false})// Publish reliable data to a set of participantsroom.localParticipant.publishData(data, {reliable: true, destinationIdentities: ['my-participant-identity']})// Receive data from other participantsroom.on(RoomEvent.DataReceived, (payload: Uint8Array, participant: Participant, kind: DataPacket_Kind) => {const strData = decoder.decode(payload)...})