var clientState = streamVideo.state;
var callState = call.state;
var participants = call.state.value.callParticipants;
Call and Participant State
When you join a call, we’ll automatically expose state in 3 different places: the Stream Video Client, the Call, and the participants.
Call State
When a Call
is created, users can subscribe to receive notifications about any changes that may occur during the call’s lifecycle.
To access the state of a call, use call.state.value
to obtain the latest CallState
snapshot, or use valueStream
to listen for real-time changes to the CallState
.
This functionality is particularly useful for determining which parts of the UI or application to render based on the current state or lifecycle of the ongoing call.
For example, you may want to display an indicator to users when a call is being recorded:
StreamBuilder<CallState>(
stream: call.state.valueStream, // Subscribe to state changes
builder: (context, snapshot) {
final state = snapshot.data;
if (state.isRecording) {
return CallRecordingUI();
} else {
return RegularCallUI();
}
},
),
Optimizing State Updates with Partial State
The call state contains a lot of different fields, and therefore updates often. In the example above we are only interested in isRecording
, but the builder will still be called every time anything in the state changes. To improve this the Call
object also contains a property for partialState
. The partialState
requires a selector callback which you can use to filter the data needed.
For example, you can get a Stream that indicates if the call is being recorded or not:
Stream<bool> isRecordingStream = call.partialState((state) => state.isRecording);
You can also use records to return multiple values, for example for a livestream:
Stream<({bool isBackstage, DateTime? endedAt})> partialStateStream =
call.partialState((state) => (isBackstage: state.isBackstage, endedAt: state.endedAt));
If you use a custom class to filter the data, make sure that class implements equality correctly, for example by using equatable
.
If you create a new object without equality checks the Stream might be updated on every state change.
Using PartialCallStateBuilder Widget
To use the partialState
in your widgets you can use the regular StreamBuilder
or the PartialCallStateBuilder
to make your life a bit easier.
PartialCallStateBuilder(
call: call,
selector: (state) => state.isRecording,
builder: (context, isRecording) =>
isRecording ? CallRecordingUI() : RegularCallUI(),
);
Call State Properties
The following fields are available on the call state:
Attribute | Description |
---|---|
callCid | The type and id of the call. |
currentUserId | The user ID of the local user. |
createdByUser | The user that created the call. |
createdByUserId | The id of a user that created the call. |
isRingingFlow | If this call has ringing set to true. |
sessionId | The current session ID for the call. |
status | The current call state - see next section for more information. |
settings | The settings for this call. |
preferences | The call preferences - see below for more information. |
egress | Contains URL for playlist of recording. |
rtmpIngress | Contains the RTMP ingest URL used for live streaming. |
isRecording | If the call is being recorded or not. |
isBroadcasting | If a call is broadcasting (to HLS) or not. |
isTranscribing | If transcriptions are active or not for this call. |
isCaptioning | If closed captions are active or not for this call. |
isBackstage | If a call is in backstage mode or not. |
isAudioProcessing | If audio processing (e.g., noise cancellation) is active. |
videoInputDevice | Video input device currently set for the call. |
audioInputDevice | Audio input device currently set for the call. |
audioOutputDevice | Audio output device currently set for the call. |
ownCapabilities | Which actions current user have permission to do. |
callParticipants | The list of call participants. |
callMembers | The list of call members (including those not currently in the call). |
createdAt | When the call was created. |
startsAt | When the call is scheduled to start. |
endedAt | When the call ended. |
updatedAt | When the call was updated. |
startedAt | When the call session was started. |
liveStartedAt | When call was set as live. |
liveEndedAt | When call was set as not live. |
timerEndsAt | Timestamp when the call will end, if maxDuration was set for the call. |
blockedUserIds | Ids of blocked users for this call. |
participantCount | Current participants count on this call. |
anonymousParticipantCount | Current anonymous participants count on this call. |
iOSMultitaskingCameraAccessEnabled | Whether multitasking camera access is enabled on iOS. |
capabilitiesByRole | What different roles (user, admin, moderator etc.) are allowed to do. |
custom | Custom data provided for this call. |
Call Preferences
Call preferences configure various aspects of call behavior and performance including timeouts, reaction behavior, statistics reporting, and video publishing options. For comprehensive information about configuring and using call preferences, see the Call Preferences Configuration guide.
Computed Properties
Some properties are computed from the state for convenience:
// Get the local participant
var localParticipant = call.state.value.localParticipant;
// Get all participants except yourself
var otherParticipants = call.state.value.otherParticipants;
// Get participants who are currently speaking
var activeSpeakers = call.state.value.activeSpeakers;
// Get members who are being called but haven't answered
var ringingMembers = call.state.value.ringingMembers;
// Check if you created the call
var createdByMe = call.state.value.createdByMe;
Understanding Call Status
The status
property of the CallState
object indicates the current state of the call. Depending on where you are in the call lifecycle, CallStatus
can have one of the following possible values.
Call Status | Description |
---|---|
CallStatusIdle | Indicates that there is no active call at the moment. |
CallStatusIncoming | Indicates that there’s an incoming call, and you need to display an incoming call screen. |
CallStatusOutgoing | Indicates that the user is making an outgoing call, and you need to display an outgoing call screen. |
CallStatusConnecting | Indicates that the SDK is attempting to connect to the call. |
CallStatusReconnecting | Indicates that the SDK is attempting to reconnect to the call. The number of attempts can be set via the attempt property. |
CallStatusReconnectionFailed | Indicates that the SDK failed to reconnect. |
CallStatusMigrating | Indicates that the SDK is attempting to migrate from one SFU to another. |
CallStatusConnected | Indicates that the user is connected to the call and is ready to send and receive tracks. |
CallStatusDisconnected | Indicates that the call has ended, failed, or has been canceled. The exact reason can be accessed via the DisconnectedReason property. |
CallStatusJoining | Indicates that the user is in the process of joining the call. |
CallStatusJoined | Indicates that the user has successfully joined the call. |
By checking the CallStatus
value in the CallState
object, you can determine the current state of the call and adjust your UI accordingly.
// Example of using call status
PartialCallStateBuilder(
call: call,
selector: (state) => state.status,
builder: (context, status) {
if (status.isConnecting) {
return ConnectingUI();
} else if (status.isConnected) {
return ConnectedCallUI();
} else if (status.isReconnecting) {
return ReconnectingUI(attempt: status.attempt);
}
return DefaultCallUI();
},
);
Understanding CallStatusDisconnected Reasons
When a call becomes disconnected, the CallStatusDisconnected
status contains a DisconnectReason
that explains why the disconnection occurred. Understanding these reasons allows you to handle different scenarios appropriately in your application.
Available Disconnect Reasons
Disconnect Reason | Description |
---|---|
DisconnectReasonTimeout | The call timed out. |
DisconnectReasonFailure | A general failure occurred during the call. |
DisconnectReasonSfuError | An SFU server error occurred. |
DisconnectReasonRejected | Can happen when the ringing call is triggered and the last participant rejects the call. |
DisconnectReasonBlocked | The user was blocked from the call. |
DisconnectReasonCancelled | The call was cancelled by the caller. |
DisconnectReasonEnded | The call was ended. |
DisconnectReasonReplaced | The call was replaced by another call. |
DisconnectReasonLastParticipantLeft | The last participant left the call and the dropIfAloneInRingingFlow was set to true. |
DisconnectReasonReconnectionFailed | Failed to reconnect to the call after network issues. |
Listening to Call Disconnection
You can listen to call state changes and handle disconnection using partial state:
// Listen to call status changes and handle disconnection
PartialCallStateBuilder(
call: call,
selector: (state) => state.status,
builder: (context, status) {
if (status.isDisconnected) {
final disconnectedStatus = status as CallStatusDisconnected;
final reason = disconnectedStatus.reason;
return DisconnectedCallUI(reason: reason);
}
// Handle other statuses...
return RegularCallUI();
},
);
Using onCallDisconnected Callback
Both StreamCallContainer
and LivestreamPlayer
provide an onCallDisconnected
callback that you can use to handle disconnection events:
// In StreamCallContainer
StreamCallContainer(
call: call,
onCallDisconnected: (CallDisconnectedProperties properties) {
final reason = properties.reason;
final call = properties.call;
// Handle the disconnection
_handleCallDisconnected(reason);
// Navigate away or show appropriate UI
Navigator.of(context).pushReplacementNamed('/home');
},
);
// In LivestreamPlayer
LivestreamPlayer(
call: call,
onCallDisconnected: (CallDisconnectedProperties properties) {
final reason = properties.reason;
// Handle livestream disconnection
if (reason is DisconnectReasonEnded) {
_showStreamEndedDialog();
} else {
// Handle other disconnection reasons
}
},
);
Handling Participant Limit Reached
When a call has a maximum participant limit configured, you can specifically handle the participant limit reached scenario:
// Complete example with pattern matching
void _handleCallDisconnected(DisconnectReason reason) {
switch (reason) {
case DisconnectReasonSfuError(error: final sfuError):
if (sfuError.code == SfuErrorCode.callParticipantLimitReached) {
// Handle participant limit reached
_showDialog(
title: 'Call Full',
message: 'This call has reached its participant limit. Please try again later.',
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
);
}
break;
// Handle other disconnect reasons...
}
}
Participant State
var participants = call.state.value.callParticipants;
var localParticipant = call.state.value.localParticipant;
In the call state, you can find the parameter callParticipants
. This parameter allows you to access and manipulate the participants present on the call. By using callParticipants
, you can easily map over the participants and observe changes in their configuration.
For instance, you can keep track of which participant is currently speaking, which participant is the dominant speaker, and which participant is pinned to the call. Additionally, callParticipants
allows you to monitor other changes to the call’s configuration as well.
Overall, callParticipants
is a powerful tool that provides you with a lot of control and insight into the call’s current state and configuration. By leveraging this parameter effectively, you can create more advanced and robust call applications.
for (final participant in call.state.value.callParticipants) {
if (participant.isDominantSpeaker) {
setState(() => dominantSpeaker = participant);
}
}
Participant State Properties
The following fields are available on each participant:
Attribute | Description |
---|---|
userId | The unique user ID of the participant. |
roles | The user’s roles in the call. |
name | The participant’s display name. |
image | The participant’s profile image URL. |
custom | Any custom data added to the user. |
sessionId | The session ID of the participant. |
trackIdPrefix | Returns the user’s track ID prefix. |
publishedTracks | Returns the participant’s published tracks (video, audio, screen). |
isSpeaking | Returns whether the participant is speaking. |
isDominantSpeaker | Returns whether the participant is a dominant speaker. |
isPinned | Returns whether the participant is pinned. |
pin | The pin information if the participant is pinned (is it a local or global pin). |
isLocal | Returns whether the participant is the local user. |
isOnline | Returns whether the participant is online. |
connectionQuality | The participant’s connection quality. |
audioLevel | The current audio level for the user. |
audioLevels | List of the last 10 audio levels. |
reaction | The current reaction added by the user. |
viewportVisibility | The user’s visibility on the screen. |
Track Information
Participants have track information for different media types:
// Access specific tracks
var videoTrack = participant.videoTrack;
var audioTrack = participant.audioTrack;
var screenShareTrack = participant.screenShareTrack;
// Check if tracks are enabled
var isVideoEnabled = participant.isVideoEnabled;
var isAudioEnabled = participant.isAudioEnabled;
var isScreenShareEnabled = participant.isScreenShareEnabled;
Listening to Participant Changes
You can listen to changes in participants using partial state:
// Listen to changes in speaking participants
PartialCallStateBuilder(
call: call,
selector: (state) => state.activeSpeakers,
builder: (context, activeSpeakers) {
return Column(
children: activeSpeakers.map((participant) =>
Text('${participant.name} is speaking')
).toList(),
);
},
);
// Listen to dominant speaker changes
PartialCallStateBuilder(
call: call,
selector: (state) => state.callParticipants
.where((p) => p.isDominantSpeaker)
.firstOrNull,
builder: (context, dominantSpeaker) {
return dominantSpeaker != null
? Text('Dominant speaker: ${dominantSpeaker.name}')
: const SizedBox.shrink();
},
);
Combining CallState
and CallParticipantState
makes building custom UIs and integrations a breeze.
If there is a property or API that is not exposed for your specific use case, feel free to reach out to us.
We are constantly iterating and exposing APIs based on your feedback.
Client State
// Client state is available in the client object
var clientState = StreamVideo.instance.state;
Attribute | Description |
---|---|
user | The user you’re currently authenticated as. |
connection | The connection state of Stream Video. |
activeCalls | The calls you’ve currently joined (when multiple calls are enabled). |
activeCall | The call you’ve currently joined (when single call mode is enabled). |
incomingCall | Contains the incoming call if ringing is set to true. |
outgoingCall | Contains the outgoing call if ringing is set to true. |