Skip to content

Commit

Permalink
Improve participant count logic
Browse files Browse the repository at this point in the history
  • Loading branch information
liviu-timar committed Oct 31, 2024
1 parent 5f5d8fc commit b5e11da
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import io.getstream.video.android.compose.ui.components.call.lobby.CallLobby
import io.getstream.video.android.core.Call
import io.getstream.video.android.core.call.state.ToggleCamera
import io.getstream.video.android.core.call.state.ToggleMicrophone
import io.getstream.video.android.core.events.ParticipantCount
import io.getstream.video.android.mock.StreamPreviewDataUtils
import io.getstream.video.android.mock.previewCall
import io.getstream.video.android.mock.previewUsers
Expand Down Expand Up @@ -270,23 +271,26 @@ private fun CallLobbyBody(
private fun LobbyDescription(
callLobbyViewModel: CallLobbyViewModel,
) {
val session by callLobbyViewModel.call.state.session.collectAsState()
val participantsSize = session?.participants?.size ?: 0
val participantCounts by callLobbyViewModel.call.state.participantCounts.collectAsState()

LobbyDescriptionContent(participantsSize = participantsSize) {
LobbyDescriptionContent(participantCounts = participantCounts) {
callLobbyViewModel.handleUiEvent(
CallLobbyEvent.JoinCall,
)
}
}

@Composable
private fun LobbyDescriptionContent(participantsSize: Int, onClick: () -> Unit) {
val text = if (participantsSize > 0) {
private fun LobbyDescriptionContent(participantCounts: ParticipantCount?, onClick: () -> Unit) {
val totalParticipants = participantCounts?.total ?: 0
val anonParticipants = participantCounts?.anonymous ?: 0

val text = if (totalParticipants != 0) {
Pair(
stringResource(
id = R.string.join_call_description,
participantsSize,
totalParticipants,
anonParticipants,
),
stringResource(id = R.string.join_call),
)
Expand Down Expand Up @@ -384,8 +388,7 @@ private fun CallLobbyBodyPreview() {
onToggleMicrophone = {},
onToggleCamera = {},
) {
LobbyDescriptionContent(participantsSize = 0) {
}
LobbyDescriptionContent(participantCounts = ParticipantCount(1, 1)) {}
}
}
}
2 changes: 1 addition & 1 deletion demo-app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<string name="join_description">Start a new call, join a meeting by \nentering the call ID or by scanning \na QR code.</string>
<string name="join_call">Join Call</string>
<string name="join_call_by_qr_code">Scan QR meeting code</string>
<string name="join_call_description">You are about to join a call. %d more people are in the call.</string>
<string name="join_call_description">You are about to join a call. %d more people are in the call (%d anonymous).</string>
<string name="join_call_no_id_hint">Don\'t have a Call ID?</string>
<string name="join_call_call_id_hint">Call ID</string>
<string name="start_a_new_call">Start a New Call</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ public class CallState(
}

is SFUHealthCheckEvent -> {
call.state._participantCounts.value = event.participantCount
updateParticipantCounts(sfuHealthCheckEvent = event)
}

is ICETrickleEvent -> {
Expand Down Expand Up @@ -877,23 +877,26 @@ public class CallState(
)
}

// When in JOINED state, we should use the participant from SFU health check event, as it's more accurate.
if (connection.value !is RealtimeConnection.Joined) {
_participantCounts.value = ParticipantCount(
total = event.anonymousParticipantCount + event.participantsCountByRole.values.sum(),
anonymous = event.anonymousParticipantCount,
)
}
updateParticipantCounts(session = session.value)
}

is CallSessionParticipantLeftEvent -> {
_session.value?.let { callSessionResponse ->
val newList = callSessionResponse.participants.toMutableList()
newList.removeIf { it.userSessionId == event.participant.userSessionId }

val newMap = callSessionResponse.participantsCountByRole.toMutableMap()
newMap
.computeIfPresent(event.participant.role) { _, v -> maxOf(v - 1, 0) }
.also { if (it == 0) newMap.remove(event.participant.role) }

_session.value = callSessionResponse.copy(
participants = newList.toList(),
participants = newList,
participantsCountByRole = newMap,
)
}

updateParticipantCounts(session = session.value)
}

is CallSessionParticipantJoinedEvent -> {
Expand All @@ -902,26 +905,37 @@ public class CallState(
val participant = CallParticipantResponse(
user = event.participant.user,
joinedAt = event.createdAt,
role = "user",
role = event.participant.user.role,
userSessionId = event.participant.userSessionId,
)
val newMap = callSessionResponse.participantsCountByRole.toMutableMap()
newMap.merge(event.participant.role, 1, Int::plus)

// It could happen that the backend delivers the same participant more than once.
// Once with the call.session_started event and once again with the
// call.session_participant_joined event. In this case,
// we should update the existing participant and prevent duplicating it.
val index = newList.indexOfFirst { user.id == event.participant.user.id }
if (index == -1) {
newList.add(participant)
} else {
newList[index] = participant
}

_session.value = callSessionResponse.copy(
participants = newList.toList(),
participants = newList,
participantsCountByRole = newMap,
)
}

updateParticipantCounts(session = session.value)
updateRingingState()
}
}
}

private fun updateServerSidePins(pins: List<PinUpdate>) {
// Update particioants that are still in the call
// Update participants that are still in the call
val pinnedInCall = pins.filter {
_participants.value.containsKey(it.sessionId)
}
Expand Down Expand Up @@ -1045,6 +1059,19 @@ public class CallState(
upsertParticipants(participantStates)
}

private fun updateParticipantCounts(session: CallSessionResponse? = null, sfuHealthCheckEvent: SFUHealthCheckEvent? = null) {
// When in JOINED state, we should use the participant from SFU health check event, as it's more accurate.

if (sfuHealthCheckEvent != null) {
_participantCounts.value = sfuHealthCheckEvent.participantCount
} else if (session != null && connection.value !is RealtimeConnection.Joined) {
_participantCounts.value = ParticipantCount(
total = session.anonymousParticipantCount + session.participantsCountByRole.values.sum(),
anonymous = session.anonymousParticipantCount,
)
}
}

fun markSpeakingAsMuted() {
_speakingWhileMuted.value = true
speakingWhileMutedResetJob?.cancel()
Expand Down

0 comments on commit b5e11da

Please sign in to comment.