Skip to main content
The RemoteParticipant class represents another user in the LiveKit room. It provides access to the remote participant’s published tracks and metadata.

Overview

RemoteParticipant extends Participant and represents other users connected to the room. Remote participants are automatically created when users join and are accessible via room.remoteParticipants.

Properties

Inherited from Participant

sid
Participant.Sid?
Server-assigned unique identifier
identity
Participant.Identity?
Unique identity of the participant
name
String?
Display name of the participant
metadata
String?
Custom metadata associated with the participant
attributes
[String: String]
Key-value attributes for the participant
audioLevel
Float
Current audio level (0.0 to 1.0)
isSpeaking
Bool
Whether the participant is currently speaking
lastSpokeAt
Date?
Timestamp when the participant last spoke
state
ParticipantState
Current state of the participant (.joining, .joined, .active, .disconnected)
connectionQuality
ConnectionQuality
Connection quality indicator (.excellent, .good, .poor, .lost, .unknown)
permissions
ParticipantPermissions
Participant’s permissions
joinedAt
Date?
Timestamp when the participant joined the room
kind
Kind
Kind of participant (.standard, .ingress, .egress, .sip, .agent)
trackPublications
[Track.Sid: TrackPublication]
Dictionary of all track publications, keyed by track SID
audioTracks
[TrackPublication]
Array of audio track publications
videoTracks
[TrackPublication]
Array of video track publications

Accessing Remote Participants

Remote participants are accessed through the Room instance:
// Access all remote participants
for (identity, participant) in room.remoteParticipants {
    print("Participant: \(participant.name ?? identity.stringValue)")
    print("Audio tracks: \(participant.audioTracks.count)")
    print("Video tracks: \(participant.videoTracks.count)")
}

Subscribing to Tracks

By default, the SDK automatically subscribes to all remote tracks. You can control subscription behavior using RemoteTrackPublication:
func room(_ room: Room, participant: RemoteParticipant, didPublishTrack publication: RemoteTrackPublication) {
    // Manually control subscription
    Task {
        if publication.kind == .video {
            try await publication.set(subscribed: true)
        }
    }
}

Rendering Video Tracks

import LiveKit
import UIKit

class ParticipantCell: UICollectionViewCell {
    let videoView = VideoView()
    
    func configure(with participant: RemoteParticipant) {
        // Find the camera video track
        if let publication = participant.getTrackPublication(source: .camera),
           let track = publication.track as? VideoTrack {
            videoView.track = track
        }
    }
}

Helper Methods

isCameraEnabled()

Returns whether the participant’s camera is enabled. Returns: Bool - true if camera track exists and is not muted
if participant.isCameraEnabled() {
    print("Camera is enabled")
}

isMicrophoneEnabled()

Returns whether the participant’s microphone is enabled. Returns: Bool - true if microphone track exists and is not muted
if participant.isMicrophoneEnabled() {
    print("Microphone is enabled")
}

isScreenShareEnabled()

Returns whether the participant is sharing their screen. Returns: Bool - true if screen share track exists and is not muted
if participant.isScreenShareEnabled() {
    print("Screen share is active")
}

getTrackPublication(source:)

Get a track publication by source type.
source
Track.Source
required
The source type (.camera, .microphone, .screenShareVideo, etc.)
Returns: TrackPublication? - The publication if found
if let cameraPublication = participant.getTrackPublication(source: .camera) {
    print("Camera track: \(cameraPublication.name)")
}

Delegate Events

Remote participant events are delivered through RoomDelegate:
extension MyClass: RoomDelegate {
    func room(_ room: Room, participantDidConnect participant: RemoteParticipant) {
        print("Participant joined: \(participant.identity?.stringValue ?? "unknown")")
    }
    
    func room(_ room: Room, participantDidDisconnect participant: RemoteParticipant) {
        print("Participant left: \(participant.identity?.stringValue ?? "unknown")")
    }
    
    func room(_ room: Room, participant: RemoteParticipant, didPublishTrack publication: RemoteTrackPublication) {
        print("Track published: \(publication.name)")
    }
    
    func room(_ room: Room, participant: RemoteParticipant, didSubscribeTrack publication: RemoteTrackPublication) {
        if let videoTrack = publication.track as? VideoTrack {
            // Render video track
        }
    }
    
    func room(_ room: Room, participant: RemoteParticipant, didUpdateMetadata metadata: String) {
        print("Metadata updated: \(metadata)")
    }
    
    func room(_ room: Room, participant: RemoteParticipant, didUpdateIsSpeaking isSpeaking: Bool) {
        print("Speaking: \(isSpeaking)")
    }
    
    func room(_ room: Room, participant: RemoteParticipant, didUpdateConnectionQuality quality: ConnectionQuality) {
        print("Connection quality: \(quality)")
    }
}

Usage Example

import LiveKit

class RoomManager: RoomDelegate {
    let room = Room(delegate: self)
    var videoViews: [Participant.Identity: VideoView] = [:]
    
    func room(_ room: Room, participant: RemoteParticipant, didSubscribeTrack publication: RemoteTrackPublication) {
        guard let identity = participant.identity,
              let videoTrack = publication.track as? VideoTrack else { return }
        
        DispatchQueue.main.async {
            let videoView = VideoView()
            videoView.track = videoTrack
            self.videoViews[identity] = videoView
            // Add videoView to your UI
        }
    }
    
    func room(_ room: Room, participantDidDisconnect participant: RemoteParticipant) {
        guard let identity = participant.identity else { return }
        
        DispatchQueue.main.async {
            self.videoViews.removeValue(forKey: identity)
        }
    }
    
    func room(_ room: Room, participant: RemoteParticipant, didReceiveData data: Data, forTopic topic: String) {
        if let message = String(data: data, encoding: .utf8) {
            print("Received message from \(participant.name ?? "unknown"): \(message)")
        }
    }
}

Active Speakers

The SDK automatically detects active speakers based on audio levels:
func room(_ room: Room, didUpdateActiveSpeakers speakers: [Participant]) {
    for speaker in speakers {
        if let remoteSpeaker = speaker as? RemoteParticipant {
            print("\(remoteSpeaker.name ?? "unknown") is speaking")
        }
    }
}

Connection Quality

Monitor the connection quality of remote participants:
for participant in room.remoteParticipants.values {
    switch participant.connectionQuality {
    case .excellent:
        print("Excellent connection")
    case .good:
        print("Good connection")
    case .poor:
        print("Poor connection")
    case .lost:
        print("Connection lost")
    case .unknown:
        print("Unknown quality")
    }
}

See Also

Build docs developers (and LLMs) love