Skip to content
Platform docs
Auth, users, webhooks & more

Call & Participant State

Video Call State

When you join a call, we'll automatically expose observable objects in 3 different places:

let clientState = streamVideo.state
let callState = call.state
let participants = call.state.participants

Call State

The call state is kept in sync (updates are received) only when you join the call.

Here's an example of how you can access the call state:

let call = streamVideo.call(callType: "default", callId: "mycall")
let joinResult = try await call.join(create: true)
// state is now available at
let state = call.state

The following fields are available on the call:

Attribute Description
participants For calls with less than 250 participants, the property contains all participants. For calls with more than 250 participants, the property contains the first 250 participants.
participantsMap A dictionary mapping participant IDs to CallParticipant objects.
localParticipant The current user’s participant object.
remoteParticipants List of participants other than the current user.
activeSpeakers List of participants who are actively speaking.
dominantSpeaker The participant recognized as the dominant speaker.
members The list of call members.
screenSharingSession Information about the active screen sharing session, if any.
recordingState The current recording state of the call.
blockedUserIds A set of user IDs that are blocked in the call.
settings The call settings fetched from the backend.
ownCapabilities The capabilities the current user has in the call.
capabilitiesByRole A map of role to allowed capabilities.
backstage Indicates whether the call is in backstage mode.
broadcasting Whether the call is broadcasting (e.g., via RTMP or HLS).
createdAt The date when the call was created.
updatedAt The date when the call was last updated.
startsAt The scheduled start time of the call, if any.
startedAt The actual time the call started.
endedAt The time when the call ended.
endedBy The user who ended the call.
custom Custom data associated with the call.
team Team restriction on the call, if any.
createdBy The user who created the call.
ingress RTMP ingress configuration if active.
permissionRequests List of pending permission requests.
transcribing Indicates if transcriptions are active.
captioning Indicates if closed captions are active.
egress Information about egress (recording, broadcasting) of the call.
session The session information for the ongoing call.
reconnectionStatus The current reconnection status of the call.
anonymousParticipantCount Number of anonymous participants.
participantCount Total number of participants (connected and anonymous).
isInitialized Indicates whether the call has been initialized.
callSettings The call settings currently active.
isSpeakingWhileMuted Indicates whether the local user is speaking while their microphone is muted. See the speaking while muted cookbook entry.
isCurrentUserScreensharing Indicates if the current user is sharing their screen.
duration Duration of the current session in seconds.
statsReport Periodic stats report updated at regular intervals.
closedCaptions List of active closed caption entries.
statsCollectionInterval Interval at which call stats are collected, in seconds.
incomingVideoQualitySettings Incoming video policy applied for the session.
disconnectionError Error describing the reason for a disconnection, if any.

Participant State

The CallParticipant is the most essential component used to render a participant in a call. It contains all of the information to render a participant, such as audio & video tracks, availabilities of audio & video, the screen sharing session, reactions, and etc. Here's how you can subscribe to participants updates:

// all participants
let cancellable = call.state.$participants.sink { participants in
    // ..
}

Filtering of the participants is also supported. You can get all the participants with the role "host", with the following code:

var hosts: [CallParticipant] {
    call.state.participants.filter { $0.roles.contains("host") }
}

When you join a call with many participants, maximum of 250 participants are returned in the join response. The list of participants is updated dynamically when there are join call events.

The participants that are publishing video, audio or screensharing are prioritized over the other participants in the list.

The total number of participants is updated realtime via health check events. This value is available from the call state's participantCount property.

You can get the current user with the following code:

let localParticipant: CallParticipant? = call.state.localParticipant

The following fields are available on the participant:

Attribute Description
user The User object for the participant.
id The unique call id of the participant.
roles The user's roles in the call.
trackLookupPrefix The id of the track that's connected to the participant.
hasVideo Returns whether the participant has video.
hasAudio Returns whether the participant has audio.
isScreensharing Returns whether the participant is screen sharing.
track Returns the participant's video track.
trackSize Returns the size of the track for the participant.
screenshareTrack Returns the screensharing track for the participant.
showTrack Returns whether the track should be shown.
isSpeaking Returns whether the participant is speaking.
isDominantSpeaker Returns whether the participant is a dominant speaker.
sessionId Returns the session id of the participant.
connectionQuality The participant's connection quality.
joinedAt Returns the date when the user joined the call.
audioLevel The audio level for the user.
audioLevels A list of the last 10 audio levels. Convenient for audio visualizations.
pin Holds pinning information.
isPinned Returns whether the user is pinned.
pausedTracks The set of media track types currently paused for this participant.

Participants Sorting

If you want to change the default sorting of the participants, you can use the Call object's method updateParticipantsSorting.

Here's an example that will sort participants alphabetically, by their name:

let nameComparator: StreamSortComparator<CallParticipant> = {
    comparison($0, $1, keyPath: \.name)
}
let call = streamVideo.call(callType: callType, callId: callId)
call.updateParticipantsSorting(with: [nameComparator])

Client State

// client state is available on the client object
let state = streamVideo.state

And contains these fields:

Attribute Description
user The user you're currently authenticated as.
connection The connection state. See ConnectionState.
activeCall The call you've currently joined.
ringingCall Contains the call if you're calling someone or someone is calling you.

Subscribing to Multiple State Changes

You can combine multiple state observations using Combine:

import Combine

class CallObserver {
    private var cancellables = Set<AnyCancellable>()

    func observeCallState(call: Call) {
        // Observe participants
        call.state.$participants
            .sink { participants in
                print("Participants updated: \(participants.count)")
            }
            .store(in: &cancellables)

        // Observe recording state
        call.state.$recordingState
            .sink { state in
                print("Recording state: \(state)")
            }
            .store(in: &cancellables)

        // Observe connection quality
        call.state.$reconnectionStatus
            .sink { status in
                if status == .disconnected {
                    print("Connection lost")
                }
            }
            .store(in: &cancellables)
    }
}