Participants represent users in a LiveKit room. There are two types: LocalParticipant (the current user) and RemoteParticipant (other users).
Participant Base Class
Both local and remote participants inherit from the Participant class, which provides common properties and functionality.
Common Properties
participant.sid // Server-assigned participant ID
participant.identity // Unique participant identity
participant.name // Participant display name
participant.metadata // Custom metadata string
participant.attributes // Key-value attributes [String: String]
participant.kind // Participant kind (.standard, .agent, etc.)
// Audio state
participant.audioLevel // Current audio level (0.0 to 1.0)
participant.isSpeaking // Whether participant is currently speaking
participant.lastSpokeAt // Date of last speech activity
// Connection
participant.state // Participant state (.joining, .active, etc.)
participant.connectionQuality // Connection quality (.excellent, .good, .poor)
participant.joinedAt // Date participant joined the room
// Permissions
participant.permissions // Participant permissions
// Tracks
participant.trackPublications // All track publications [Track.Sid: TrackPublication]
participant.audioTracks // Audio track publications only
participant.videoTracks // Video track publications only
Convenience Methods
Check if specific tracks are enabled:
participant.isCameraEnabled() // true if camera track is unmuted
participant.isMicrophoneEnabled() // true if microphone track is unmuted
participant.isScreenShareEnabled() // true if screen share track is unmuted
Participant State
Participants go through different states:
public enum ParticipantState {
case unknown
case joining // Participant is joining
case active // Participant is active in the room
case disconnected // Participant has disconnected
}
LocalParticipant
The LocalParticipant represents the current user. Access it through the room:
let localParticipant = room.localParticipant
Publishing Tracks
Publish camera, microphone, or custom tracks:
// Publish video track
let cameraTrack = LocalVideoTrack.createCameraTrack()
let publication = try await localParticipant.publish(videoTrack: cameraTrack)
// Publish audio track
let audioTrack = LocalAudioTrack.createTrack()
let publication = try await localParticipant.publish(audioTrack: audioTrack)
Simplified Publishing API
Use convenience methods for common scenarios:
// Enable camera
try await localParticipant.setCamera(enabled: true)
// Enable microphone
try await localParticipant.setMicrophone(enabled: true)
// Enable screen share
try await localParticipant.setScreenShare(enabled: true)
// Disable camera (mutes the track)
try await localParticipant.setCamera(enabled: false)
With custom options:
let captureOptions = CameraCaptureOptions(
position: .front,
preferredFormat: .preset1920x1080
)
let publishOptions = VideoPublishOptions(
preferredCodec: .vp8,
simulcast: true
)
try await localParticipant.setCamera(
enabled: true,
captureOptions: captureOptions,
publishOptions: publishOptions
)
The simplified API automatically creates tracks, manages their lifecycle, and handles mute/unmute operations.
Publishing Data
Send data messages to other participants:
let data = "Hello, world!".data(using: .utf8)!
// Send to all participants
try await localParticipant.publish(data: data)
// Send to specific participants
let options = DataPublishOptions(
destinationIdentities: [participant1.identity!, participant2.identity!],
topic: "chat"
)
try await localParticipant.publish(data: data, options: options)
Update participant metadata and attributes:
// Update metadata (requires CanUpdateOwnMetadata permission)
try await localParticipant.set(metadata: "{\"status\": \"online\"}")
// Update name
try await localParticipant.set(name: "John Doe")
// Update attributes
try await localParticipant.set(attributes: ["role": "moderator"])
Updating metadata and name requires the CanUpdateOwnMetadata permission encoded in the JWT token.
Track Subscription Permissions
Control who can subscribe to your published tracks:
// Allow all participants to subscribe to all tracks (default)
try await localParticipant.setTrackSubscriptionPermissions(
allParticipantsAllowed: true
)
// Restrict subscriptions to specific participants/tracks
let permissions = [
ParticipantTrackPermission(
participantIdentity: "user123",
allTracksAllowed: true
),
ParticipantTrackPermission(
participantIdentity: "user456",
allowedTrackSids: [cameraPublication.sid]
)
]
try await localParticipant.setTrackSubscriptionPermissions(
allParticipantsAllowed: false,
trackPermissions: permissions
)
Local Track Publications
Access published local tracks:
localParticipant.localAudioTracks // [LocalTrackPublication]
localParticipant.localVideoTracks // [LocalTrackPublication]
RemoteParticipant
Remote participants represent other users in the room. Access them through the room:
// Get all remote participants
let remoteParticipants = room.remoteParticipants // [Identity: RemoteParticipant]
// Get specific participant by identity
if let participant = room.remoteParticipants[identity] {
print("Found participant: \(participant.name ?? "unknown")")
}
Observing Remote Participants
Monitor when participants join or leave:
class MyRoomDelegate: RoomDelegate {
func room(_ room: Room, participantDidJoin participant: RemoteParticipant) {
print("\(participant.identity!) joined")
// Add delegate to receive participant events
participant.delegates.add(delegate: self)
}
func room(_ room: Room, participantDidDisconnect participant: RemoteParticipant) {
print("\(participant.identity!) disconnected")
}
}
Remote Track Publications
Remote participants publish tracks that you can subscribe to:
func room(_ room: Room, participant: RemoteParticipant, didPublishTrack publication: RemoteTrackPublication) {
print("\(participant.identity!) published \(publication.source) track")
// Auto-subscribe is enabled by default
// Manually subscribe if needed:
// try await publication.set(subscribed: true)
}
func room(_ room: Room, participant: RemoteParticipant, didSubscribeTrack publication: RemoteTrackPublication) {
print("Subscribed to \(publication.name)")
if let videoTrack = publication.track as? VideoTrack {
videoTrack.add(videoRenderer: videoView)
} else if let audioTrack = publication.track as? AudioTrack {
// Audio tracks are automatically played
}
}
Participant Events
Implement ParticipantDelegate to receive participant-specific events:
@objc protocol ParticipantDelegate {
// Metadata events
optional func participant(_ participant: Participant, didUpdateMetadata metadata: String)
optional func participant(_ participant: Participant, didUpdateName name: String)
optional func participant(_ participant: Participant, didUpdateAttributes attributes: AttributesDiff)
// Speaking events
optional func participant(_ participant: Participant, didUpdateIsSpeaking isSpeaking: Bool)
// Connection events
optional func participant(_ participant: Participant, didUpdateState state: ParticipantState)
optional func participant(_ participant: Participant, didUpdateConnectionQuality quality: ConnectionQuality)
// Permission events
optional func participant(_ participant: LocalParticipant, didUpdatePermissions permissions: ParticipantPermissions)
// Track events
optional func participant(_ participant: Participant, didPublishTrack publication: TrackPublication)
optional func participant(_ participant: Participant, didUnpublishTrack publication: TrackPublication)
optional func participant(_ participant: RemoteParticipant, didSubscribeTrack publication: RemoteTrackPublication)
optional func participant(_ participant: RemoteParticipant, didUnsubscribeTrack publication: RemoteTrackPublication)
// Track state events
optional func participant(_ participant: Participant, trackPublication: TrackPublication, didUpdateIsMuted isMuted: Bool)
optional func participant(_ participant: RemoteParticipant, trackPublication: RemoteTrackPublication, didUpdateStreamState streamState: StreamState)
}
Example Usage
class ParticipantObserver: ParticipantDelegate {
func participant(_ participant: Participant, didUpdateIsSpeaking isSpeaking: Bool) {
if isSpeaking {
print("\(participant.identity!) started speaking")
// Update UI to highlight speaking participant
}
}
func participant(_ participant: Participant, didUpdateConnectionQuality quality: ConnectionQuality) {
switch quality {
case .poor:
print("\(participant.identity!) has poor connection")
case .excellent:
print("\(participant.identity!) has excellent connection")
default:
break
}
}
}
Participant Permissions
Access participant permissions:
let permissions = participant.permissions
if permissions.canPublish {
print("Can publish tracks")
}
if permissions.canSubscribe {
print("Can subscribe to tracks")
}
if permissions.canPublishData {
print("Can send data messages")
}
// Check specific source permissions
let canPublishSources = permissions.canPublishSources // [String]
if canPublishSources.contains(Track.Source.camera.rawValue) {
print("Can publish camera")
}
Active Speakers
Get the list of currently active speakers:
let activeSpeakers = room.activeSpeakers // [Participant]
for speaker in activeSpeakers {
print("\(speaker.identity!) is speaking (level: \(speaker.audioLevel))")
}
Monitor active speaker changes:
func room(_ room: Room, didUpdateSpeakingParticipants participants: [Participant]) {
// Update UI to show active speakers
for participant in participants {
if participant.isSpeaking {
highlightParticipant(participant)
}
}
}
Best Practices
- Add delegates early: Attach participant delegates when participants join to avoid missing events
- Handle disconnections: Clean up UI state when participants disconnect
- Check permissions: Verify permissions before attempting operations
- Monitor connection quality: Provide user feedback for poor connections
- Use identity for identification: Don’t rely on array indices, use participant identity as the unique identifier