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

Call & Participant State

Video Call State

When you join a call, we'll automatically expose 3 StateFlow objects:

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

Call State

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

val call = client.call("default", "mycall")
val joinResult = call.join(create=true)
// state is now available at
val state = call.state

The following fields are available on the call

Attribute Description
connection Your connection state if you're currently connected to the call
participants The list of call participants, ordered by the active sort preset. For most apps, this is the only list you need.
totalParticipants The count of the total number of participants. This includes anonymous participants
me Shortcut to your own participant state
remoteParticipants The list of call participants other than yourself, in the same sort order as participants
activeSpeakers The list of participants who are currently speaking
dominantSpeaker The dominant speaker
sortedParticipants Deprecated — alias for participants (which is itself sorted). Use participants directly.
members The list of call members
screenSharingSession If someone is screensharing, it will be available here
recording Boolean if the call is being recorded or not
blockedUsers The users who are blocked from this call
ringingState For ringing calls we expose additional state
settings The settings for this call
e2eeEnabled Whether an E2EE manager is attached to this call. Use it for a lock indicator. The call type's encryption mode lives on settings.value?.encryption?.mode. See End-to-End Encryption.
ownCapabilities Which actions you have permission to do
hasPermission function for checking if you have a certain permission
capabilitiesByRole What different roles (user, admin, moderator etc.) are allowed to do
permissionRequests If there are active permission requests
backstage If a call is in backstage mode or not
broadcasting If a call is broadcasting (to HLS) or not
createdAt When the call was created
updatedAt When the call was updated
startsAt When the call is scheduled to start
endedAt When the call ended
endedByUser User who ended the call
custom Custom data on the call
team Team that the call is restricted to. Default to null.
createdBy Who created the call
ingress If there is an active ingress session to this call. IE if you're sending RTMP into the call
reactions List of reactions this call received
errors Any errors we've encountered during this call
isCaptioning Tracks whether closed captioning is currently active for the call.
closedCaptions Holds the current list of closed captions. This list is updated dynamically and contains at most ClosedCaptionsSettings.maxVisibleCaptions captions.
ccMode Holds the current closed caption mode for the video call. This object contains information about closed captioning feature availability.
Possible values:
- ClosedCaptionMode.Available
- ClosedCaptionMode.Disabled
- ClosedCaptionMode.AutoOn
- ClosedCaptionMode.Unknown

Participant State

The ParticipantsState 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 renderers, availabilities of audio & video, the screen sharing session, reactions, and etc. Here's how you iterate over the participants:

// all participants
val participants: StateFlow<List<ParticipantState>> = call.state.participants
coroutineScope.launch {
    participants.collectLatest { participants ->
        // ..
    }
}

// all participants in Jetpack Compose
val participants: List<ParticipantState> by call.state.participants.collectAsState()
participants.forEach { participantState ->
    // ..
}

// you
val me: StateFlow<ParticipantState?> = call.state.me

In Jetpack Compose, you can observe the ParticipantsState and render videos like the example below:

// all participants
val participants by call.state.participants.collectAsState()

LazyColumn {
    items(items = participants, key = { it.sessionId }) { participant ->
        ParticipantVideo(call = call, participant = participant)
    }
}

// you
val me by call.state.me.collectAsState()

ParticipantVideo(
    call = call,
    participant = me
)

The following fields are available on the participant

Attribute Description
user The user object for this participant
video The video object for this participant
audio The participant's audio object
screenSharing The screensharing object
joinedAt When this participant joined
audioLevel How loudly this user is talking. Float
audioLevels A list of the last 10 audio levels. Convenient for audio visualizations
speaking If the user is speaking
connectionQuality The participant's connection quality
dominantSpeaker If this participant is the dominant speaker or not
lastSpeakingAt When this user last spoke (used for sorting in some apps)

Participants Sorting

call.state.participants is always sorted by the active sort preset. The preset is picked up automatically from the call type (e.g. livestream calls use LivestreamOrAudioRoom; other types use Default). You can switch the preset at any time, or pass a custom comparator.

Available presets

Preset Use case
SortPreset.Default Continuous-scroll grid. Screen-sharing → pinned → activity (dominant speaker, speaking, raised hand, ingress source, video, audio).
SortPreset.SpeakerLayout Speaker-focused layout. Dominant speaker is outside the visibility guard so the spotlight always reflects who's talking.
SortPreset.LivestreamOrAudioRoom Livestream and audio-room calls. Activity-based ordering with admin / host / speaker role priority. Mirrors iOS and React.

Switching the preset

call.state.setSortPreset(SortPreset.SpeakerLayout)

Custom comparator

For ad-hoc orderings that don't fit a preset, use updateParticipantSortingOrder. The example below sorts participants alphabetically by name:

call.state.updateParticipantSortingOrder(
    compareByDescending {
        it.name.value
    },
)

Calling setSortPreset(...) again clears any custom comparator.

Composing your own preset

If you want to build a custom comparator from the same primitives the built-in presets use, the io.getstream.video.android.core.sorting package exposes them: dominantSpeaker, speaking, screenSharing, publishingVideo, publishingAudio, raisedHand, byUserId, byRole(vararg), bySourcePriority(vararg ParticipantSource), plus the combinators combineComparators, conditional, ifInvisible, and ifInvisibleOrUnknown. The surface mirrors the iOS and React SDKs.

Detecting participant source

Participants can be created from different sources (WebRTC, RTMP/OBS, WHIP, SIP, etc...). The source property of the ParticipantState object indicates the source of the participant.

val participants = call.state.participants.value
// participants joining through OBS have RTMP source
val rtmpParticipants = participants.filter {
    it.source == ParticipantSource.PARTICIPANT_SOURCE_RTMP
}

Client State

// client state is available on the client object
val state = client.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
callConfigRegistry A registry for managing call configurations related to different call types like default, livestream, audio, etc.