Exchanging State and Data

In addition to media, LiveKit comes with powerful capabilities that allow you to exchange and synchronize data between participants.

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 changes
room.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 participant
room.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 real-time 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.

note:

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 it
const data = encoder.encode(strData);
// Publish lossy data to the entire room
room.localParticipant.publishData(data, {reliable: false})
// Publish reliable data to a set of participants
room.localParticipant.publishData(data, {reliable: true, destinationIdentities: ['my-participant-identity']})
// Receive data from other participants
room.on(RoomEvent.DataReceived, (payload: Uint8Array, participant: Participant, kind: DataPacket_Kind) => {
const strData = decoder.decode(payload)
...
})