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

AttributeDescription
connectionYour connection state if you're currently connected to the call
participantsThe list of call participants, ordered by the active sort preset. For most apps, this is the only list you need.
totalParticipantsThe count of the total number of participants. This includes anonymous participants
meShortcut to your own participant state
remoteParticipantsThe list of call participants other than yourself, in the same sort order as participants
activeSpeakersThe list of participants who are currently speaking
dominantSpeakerThe dominant speaker
sortedParticipantsDeprecated — alias for participants (which is itself sorted). Use participants directly.
membersThe list of call members
screenSharingSessionIf someone is screensharing, it will be available here
recordingBoolean if the call is being recorded or not
blockedUsersThe users who are blocked from this call
ringingStateFor ringing calls we expose additional state
settingsThe settings for this call
ownCapabilitiesWhich actions you have permission to do
hasPermissionfunction for checking if you have a certain permission
capabilitiesByRoleWhat different roles (user, admin, moderator etc.) are allowed to do
permissionRequestsIf there are active permission requests
backstageIf a call is in backstage mode or not
broadcastingIf a call is broadcasting (to HLS) or not
createdAtWhen the call was created
updatedAtWhen the call was updated
startsAtWhen the call is scheduled to start
endedAtWhen the call ended
endedByUserUser who ended the call
customCustom data on the call
teamTeam that the call is restricted to. Default to null.
createdByWho created the call
ingressIf there is an active ingress session to this call. IE if you're sending RTMP into the call
reactionsList of reactions this call received
errorsAny errors we've encountered during this call
isCaptioningTracks whether closed captioning is currently active for the call.
closedCaptionsHolds the current list of closed captions. This list is updated dynamically and contains at most ClosedCaptionsSettings.maxVisibleCaptions captions.
ccModeHolds 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

AttributeDescription
userThe user object for this participant
videoThe video object for this participant
audioThe participant's audio object
screenSharingThe screensharing object
joinedAtWhen this participant joined
audioLevelHow loudly this user is talking. Float
audioLevelsA list of the last 10 audio levels. Convenient for audio visualizations
speakingIf the user is speaking
connectionQualityThe participant's connection quality
dominantSpeakerIf this participant is the dominant speaker or not
lastSpeakingAtWhen 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

PresetUse case
SortPreset.DefaultContinuous-scroll grid. Screen-sharing → pinned → activity (dominant speaker, speaking, raised hand, ingress source, video, audio).
SortPreset.SpeakerLayoutSpeaker-focused layout. Dominant speaker is outside the visibility guard so the spotlight always reflects who's talking.
SortPreset.LivestreamOrAudioRoomLivestream 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:

AttributeDescription
userThe user you're currently authenticated as
connectionThe connection state. See ConnectionState
activeCallThe call you've currently joined
ringingCallContains the call if you're calling someone or someone is calling you
callConfigRegistryA registry for managing call configurations related to different call types like default, livestream, audio, etc.