Skip to main content

Receiving media

While connected to a room, the server may send down one or more audio/video/data tracks at any time. By default, a client automatically subscribes to a received track, and lets your app know by invoking callbacks on the room object and associated participant who published the track.

Track subscription

Receiving tracks from the server starts with a subscription.

As mentioned in our guide on publishing media, LiveKit models tracks with two constructs: TrackPublication and Track. Think of a TrackPublication as metadata for a track registered with the server and Track as the raw media stream.

Track subscription callbacks provide your app with both the Track and TrackPublication objects.

Subscribed callback will be fired on both Room and RemoteParticipant objects.

TypeScript
import {
connect,
RoomEvent,
} from 'livekit-client';

connect('ws://your_host', token, {
audio: true,
video: true,
}).then((room) => {
room
.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
});

function handleTrackSubscribed(
track: RemoteTrack,
publication: RemoteTrackPublication,
participant: RemoteParticipant
) {
/* do things with track, publication or participant */
}

Media playback

Once subscribed to an audio or video track, a client usually wants to start playback of the live stream:

note

On mobile (iOS, Android, and Flutter), once subscribed to an audio track, live playback begins automatically.

TypeScript
function handleTrackSubscribed(
track: RemoteTrack,
publication: RemoteTrackPublication,
participant: RemoteParticipant
) {
// attach track to a new HTMLVideoElement or HTMLAudioElement
const element = track.attach();
parentElement.appendChild(element);
// or attach to existing element
// track.attach(element)
}

Speaker detection

When audio tracks are published, LiveKit will detect participants whome are speaking. Speaker updates are sent for both local and remote participants. They will fire on both, Room and Participant objects.

TypeScript
room.on(RoomEvent.ActiveSpeakersChanged, (speakers: Participant[]) => {
// do something with the active speakers
})

participant.on(ParticipantEvent.IsSpeakingChanged, (speaking: boolean) => {
console.log(`${participant.identity} is ${speaking ? "now" : "no longer"} speaking. audio level: ${participant.audioLevel}`)
})

Subscriber controls

While the default behavior for LiveKit clients is to subscribe to all newly published tracks, LiveKit supports selective subscription. Both client and server APIs are available to setup a connection for selective subscription and once configured, only explicitly subscribed tracks are sent down to the client.

Client-side selective subscription

TypeScript
let room = await room.connect(url, token, {
autoSubscribe: false,
})

room.on(RoomEvent.TrackPublished, (track, publication, participant) => {
publication.setSubscribed(true)
})

Server-side selective subscription

These controls are also available with the server APIs.

TypeScript
import { RoomServiceClient } from 'livekit-server-sdk';

const client = new RoomServiceClient("myhost", "api-key", "my secret")

// subscribe to new track
client.updateSubscriptions("myroom", "receiving-participant-identity", ["TR_TRACKID"], true)

// unsubscribe from existing track
client.updateSubscriptions("myroom", "receiving-participant-identity", ["TR_TRACKID"], false)

Enabling/disabling tracks

Client implementations seeking fine-grain control over bandwidth consumption can enable or disable tracks at their discretion. A track is enabled by default. When a track is disabled, the client will not receive any new data for that track. If a disabled track is subsequently enabled, new data will be received by the client.

The disable action is useful when optimzing for a client's bandwidth consumption. For example, if a particular user's video track is offscreen, disabling this track will reduce bytes from being sent by the LiveKit server until the track's data is needed again.

TypeScript
import {
connect,
RoomEvent,
} from 'livekit-client';

const room = await connect('ws://your_host', token);
room.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)

function handleTrackSubscribed(
track: RemoteTrack,
publication: RemoteTrackPublication,
participant: RemoteParticipant
) {
publication.setEnabled(false)
}
note

You may be wondering how subscribe and unsubscribe differs from enable and disable. A track must be subscribed to and enabled for data to be received by the client. If a track has not been subscribed to (or was unsubscribed) or disabled, the client performing these actions will not receive that track's data.

The difference between these two actions is negotiation. Subscribing requires a negotiation handshake with the LiveKit server, while enable/disable does not. Depending on one's use case, this can make enable/disable more efficient, especially when a track may be turned on or off frequently.

Simulcast controls

If a video track has simulcast enabled, a receiving client may want to manually specify the maximum receivable quality. This would result a quality and bandwidth reduction for the target track. This might come in handy, for instance, when an application's user interface is displaying a small thumbnail for a particular user's video track.

You can see how this is used in our React component.

TypeScript
import {
connect,
RoomEvent,
} from 'livekit-client';

connect('ws://your_host', token, {
audio: true,
video: true,
}).then((room) => {
room
.on(RoomEvent.TrackSubscribed, handleTrackSubscribed)
});

function handleTrackSubscribed(
track: RemoteTrack,
publication: RemoteTrackPublication,
participant: RemoteParticipant
) {
if (track.kind === Track.Kind.Video) {
publication.setVideoQuality(VideoQuality.LOW)
}
}