Skip to content

Commit 0780b64

Browse files
authored
Merge pull request #184 from GetStream/feature/uni-136-change-getlocalparticipant-to-not-throw-exceptions
Change GetLocalParticipant to return null if not found instead of thr…
2 parents 18de883 + 84b063d commit 0780b64

File tree

4 files changed

+114
-43
lines changed

4 files changed

+114
-43
lines changed

Packages/StreamVideo/Runtime/Core/LowLevelClient/RtcSession.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ public Camera VideoSceneInput
213213

214214
#endregion
215215

216-
public string SessionId { get; private set; }
216+
public string SessionId { get; private set; } = "(empty)";
217217

218218
public RtcSession(SfuWebSocket sfuWebSocket, Func<IStreamCall, HttpClient> httpClientFactory, ILogs logs,
219219
ISerializer serializer, ITimeService timeService,

Packages/StreamVideo/Runtime/Core/Models/CallSession.cs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
using StreamVideo.Core.State;
77
using StreamVideo.Core.State.Caches;
88
using StreamVideo.Core.StatefulModels;
9-
using StreamVideo.Core.Utils;
109
using SfuCallState = StreamVideo.v1.Sfu.Models.CallState;
11-
using SfuParticipant = StreamVideo.v1.Sfu.Models.Participant;
1210
using SfuParticipantCount = StreamVideo.v1.Sfu.Models.ParticipantCount;
1311

1412
namespace StreamVideo.Core.Models
@@ -56,14 +54,18 @@ void IStateLoadableFrom<CallSessionResponseInternalDTO, CallSession>.LoadFromDto
5654

5755
// CallSessionResponseInternalDTO usually (or always?) contains no participants. Participants are updated from the SFU join response
5856
// But SFU response can arrive before API response, so we can't override participants here because this clears the list
59-
foreach (var dtoParticipant in dto.Participants)
60-
{
61-
var participant = cache.TryCreateOrUpdate(dtoParticipant);
62-
if (!_participants.Contains(participant))
63-
{
64-
_participants.Add(participant);
65-
}
66-
}
57+
58+
//StreamTODO: temp remove this. This seems to be only messing up the participants list. We're testing updating the participants only based on SFU data.
59+
// But we need to check how this will work with GetCall where there's not SFU connection
60+
61+
// foreach (var dtoParticipant in dto.Participants)
62+
// {
63+
// var participant = cache.TryCreateOrUpdate(dtoParticipant);
64+
// if (!_participants.Contains(participant))
65+
// {
66+
// _participants.Add(participant);
67+
// }
68+
// }
6769

6870
// StreamTODO: figure out how to best handle this. Should we update it from coordinator or only the SFU
6971
//_participantsCountByRole.TryReplaceValuesFromDto(dto.ParticipantsCountByRole);
@@ -82,6 +84,9 @@ void IStateLoadableFrom<SfuCallState, CallSession>.LoadFromDto(SfuCallState dto,
8284
StartedAt = dto.StartedAt.ToDateTimeOffset();
8385
}
8486

87+
// Treat SFU as the most updated source of truth for participants
88+
_participants.Clear();
89+
8590
// dto.CallState.Participants may not contain all participants
8691
foreach (var dtoParticipant in dto.Participants)
8792
{
@@ -117,7 +122,11 @@ internal void UpdateFromSfu(HealthCheckResponse healthCheckResponse, ICache cach
117122
internal (string sessionId, string userId) UpdateFromSfu(ParticipantLeft participantLeft, ICache cache)
118123
{
119124
var participant = cache.TryCreateOrUpdate(participantLeft.Participant);
120-
_participants.Remove(participant);
125+
126+
if (!participant.IsLocalParticipant)
127+
{
128+
_participants.Remove(participant);
129+
}
121130

122131
return (participantLeft.Participant.SessionId, participantLeft.Participant.UserId);
123132
}

Packages/StreamVideo/Runtime/Core/StatefulModels/StreamCall.cs

Lines changed: 92 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ internal sealed class StreamCall : StreamStatefulModelBase<StreamCall>,
6262

6363
//StreamTodo: Maybe add OtherParticipants -> All participants except for the local participant?
6464
public IReadOnlyList<IStreamVideoCallParticipant> Participants => Session?.Participants;
65-
65+
6666
public ParticipantCount ParticipantCount => Session.ParticipantCount;
6767

6868
public bool IsLocalUserOwner
@@ -453,31 +453,41 @@ public IStreamVideoCallParticipant GetLocalParticipant()
453453
{
454454
tempSb.AppendLine(log);
455455
}
456-
456+
457457
Logs.Error(tempSb.ToString());
458458
}
459-
throw new InvalidOperationException("No participants in the call.");
459+
460+
return null;
460461
}
461-
462+
462463
var localParticipant = Participants.FirstOrDefault(p => p.IsLocalParticipant);
463464
if (localParticipant == null)
464465
{
465-
using (new StringBuilderPoolScope(out var sb))
466+
try
466467
{
467-
var currentSessionId = LowLevelClient.RtcSession.SessionId;
468-
sb.AppendLine($"Local participant not found. Local Session ID: {currentSessionId}. Participants in the call:");
469-
foreach (var p in Participants)
468+
using (new StringBuilderPoolScope(out var sb))
470469
{
471-
sb.AppendLine($" - UserId: {p.UserId}, SessionId: {p.SessionId}, IsLocalParticipant: {p.IsLocalParticipant}");
472-
}
470+
var currentSessionId = LowLevelClient.RtcSession.SessionId;
471+
sb.AppendLine(
472+
$"Local participant not found. Local Session ID: {currentSessionId}. Participants in the call:");
473+
foreach (var p in Participants)
474+
{
475+
sb.AppendLine(
476+
$" - UserId: {p.UserId}, SessionId: {p.SessionId}, IsLocalParticipant: {p.IsLocalParticipant}");
477+
}
473478

474-
sb.AppendLine("Last operations leading to this state:");
475-
foreach (var log in _tempLogs.GetLogs())
476-
{
477-
sb.AppendLine(log);
479+
sb.AppendLine("Last operations leading to this state:");
480+
foreach (var log in _tempLogs.GetLogs())
481+
{
482+
sb.AppendLine(log);
483+
}
484+
485+
Logs.Error(sb.ToString());
478486
}
479-
480-
Logs.Error(sb.ToString());
487+
}
488+
catch (Exception e)
489+
{
490+
Logs.Warning($"Error while generating log for {nameof(GetLocalParticipant)}: " + e.Message);
481491
}
482492
}
483493

@@ -487,6 +497,8 @@ public IStreamVideoCallParticipant GetLocalParticipant()
487497
void IUpdateableFrom<CallResponseInternalDTO, StreamCall>.UpdateFromDto(CallResponseInternalDTO dto,
488498
ICache cache)
489499
{
500+
var wasBefore = IsLocalParticipantIncluded();
501+
490502
Backstage = dto.Backstage;
491503
_blockedUserIds.TryReplaceValuesFromDto(dto.BlockedUserIds);
492504
Cid = dto.Cid;
@@ -506,27 +518,33 @@ void IUpdateableFrom<CallResponseInternalDTO, StreamCall>.UpdateFromDto(CallResp
506518
Type = new StreamCallType(dto.Type);
507519
UpdatedAt = dto.UpdatedAt;
508520

521+
var isAfter = IsLocalParticipantIncluded();
522+
509523
try
510524
{
525+
var localParticipantId = LowLevelClient.RtcSession.SessionId;
511526
// Ignore the IDE warning, this can be null
512527
if (dto.Session != null)
513528
{
514529
using (new StringBuilderPoolScope(out var tempSb))
515530
{
516-
tempSb.Append($"`UpdateFromDto(CallResponseInternalDTO dto` - dto participants: {dto.Session.Participants?.Count}, call participants: {Session.Participants.Count}. Dto participants: ");
531+
tempSb.Append(
532+
$"`UpdateFromDto(CallResponseInternalDTO dto` - dto participants: {dto.Session.Participants?.Count}, call participants: {Session.Participants.Count}. ");
533+
tempSb.Append(
534+
$"IsLocalParticipantIncluded ({localParticipantId}) before: {wasBefore}, after: {isAfter}. ");
535+
tempSb.Append("Dto participants:");
517536
foreach (var p in dto.Session.Participants)
518537
{
519-
tempSb.Append($"[UserSessionId: {p.UserSessionId}, SessionId: {p.User?.Id}");
538+
tempSb.Append($"[UserSessionId: {p.UserSessionId}, SessionId: {p.User?.Id}, ");
520539
}
521-
540+
522541
_tempLogs.Add(tempSb.ToString());
523542
}
524543
}
525-
526544
}
527545
catch (Exception e)
528546
{
529-
Logs.Exception(e);
547+
Logs.Warning("Failed to log participants in UpdateFromDto: " + e.Message);
530548
}
531549

532550
// Depends on Session.Participants so load as last
@@ -595,21 +613,39 @@ internal StreamCall(string uniqueId, ICacheRepository<StreamCall> repository,
595613
//StreamTodo: solve with a generic interface and best to be handled by cache layer
596614
internal void UpdateFromSfu(JoinResponse joinResponse)
597615
{
616+
var wasBefore = IsLocalParticipantIncluded();
617+
598618
((IStateLoadableFrom<CallState, CallSession>)Session).LoadFromDto(joinResponse.CallState, Cache);
599619
UpdateServerPins(joinResponse.CallState.Pins);
600620

621+
var isAfter = IsLocalParticipantIncluded();
622+
601623
try
602624
{
625+
var localParticipantId = LowLevelClient.RtcSession.SessionId;
603626
using (new StringBuilderPoolScope(out var tempSb))
604627
{
628+
tempSb.Append("`UpdateFromSfu(JoinResponse joinResponse)` - ");
629+
tempSb.Append(
630+
$"IsLocalParticipantIncluded ({localParticipantId}) before: {wasBefore}, after: {isAfter}. ");
605631
tempSb.Append("`UpdateFromSfu(JoinResponse joinResponse)` - joinResponse participants: ");
606-
if(joinResponse.CallState !=null && joinResponse.CallState.Participants != null)
632+
if (joinResponse.CallState != null && joinResponse.CallState.Participants != null)
607633
{
608634
foreach (var p in joinResponse.CallState.Participants)
609635
{
610636
tempSb.Append($"[UserId: {p.UserId}, SessionId: {p.SessionId}, ");
611637
}
612638
}
639+
else
640+
{
641+
tempSb.Append("joinResponse.CallState not null:");
642+
tempSb.Append(joinResponse.CallState != null);
643+
tempSb.Append("joinResponse.CallState.Participants not null: ");
644+
tempSb.Append(joinResponse.CallState?.Participants != null);
645+
tempSb.Append("count: ");
646+
tempSb.Append(joinResponse.CallState?.Participants?.Count);
647+
}
648+
613649
_tempLogs.Add(tempSb.ToString());
614650
}
615651
}
@@ -628,6 +664,20 @@ internal void UpdateFromSfu(ParticipantJoined participantJoined, ICache cache)
628664

629665
internal void UpdateFromSfu(ParticipantLeft participantLeft, ICache cache)
630666
{
667+
try
668+
{
669+
var p = cache.TryCreateOrUpdate(participantLeft.Participant);
670+
if (p.IsLocalParticipant)
671+
{
672+
_tempLogs.Add(
673+
"`UpdateFromSfu(ParticipantLeft participantLeft)` - ERROR - local participant is leaving the call.");
674+
}
675+
}
676+
catch (Exception e)
677+
{
678+
Logs.Warning("Error when generating debug log: " + e.Message);
679+
}
680+
631681
var participant = Session.UpdateFromSfu(participantLeft, cache);
632682

633683
_localPinsSessionIds.RemoveAll(participant.sessionId);
@@ -666,23 +716,23 @@ internal void UpdateFromSfu(HealthCheckResponse healthCheckResponse, ICache cach
666716
{
667717
Session?.UpdateFromSfu(healthCheckResponse, cache);
668718
}
669-
719+
670720
internal void UpdateFromCoordinator(CallSessionParticipantCountsUpdatedEventInternalDTO eventData)
671721
{
672722
Session?.UpdateFromCoordinator(eventData, Client.InternalLowLevelClient.RtcSession.CallState);
673723
}
674-
724+
675725
internal void UpdateFromCoordinator(CallSessionParticipantJoinedEventInternalDTO eventData, ICache cache)
676726
{
677727
Session?.UpdateFromCoordinator(eventData, cache, Client.InternalLowLevelClient.RtcSession.CallState);
678-
728+
679729
//StreamTodo: we should extract AddParticipant logic from SFU and whatever is received first (SFU or Coordinator) should handle it
680730
}
681-
731+
682732
internal void UpdateFromCoordinator(CallSessionParticipantLeftEventInternalDTO eventData, ICache cache)
683733
{
684734
Session?.UpdateFromCoordinator(eventData, cache, Client.InternalLowLevelClient.RtcSession.CallState);
685-
735+
686736
//StreamTodo: we should extract RemoveParticipant logic from SFU and whatever is received first (SFU or Coordinator) should handle it
687737
}
688738

@@ -759,12 +809,14 @@ internal void InternalHandleCallRecordingStartedEvent(CallReactionEventInternalD
759809

760810
//StreamTodo: NullReferenceException here because _client is never set
761811
var participant
762-
= Client.InternalLowLevelClient.RtcSession.ActiveCall.Participants.FirstOrDefault(p => p.UserId == reaction.User.Id);
812+
= Client.InternalLowLevelClient.RtcSession.ActiveCall.Participants.FirstOrDefault(p
813+
=> p.UserId == reaction.User.Id);
763814
if (participant == null)
764815
{
765816
Logs.ErrorIfDebug(
766817
$"Failed to find participant for reaction. UserId: {reaction.User.Id}, Participants: " +
767-
string.Join(", ", Client.InternalLowLevelClient.RtcSession.ActiveCall.Participants.Select(p => p.UserId)));
818+
string.Join(", ",
819+
Client.InternalLowLevelClient.RtcSession.ActiveCall.Participants.Select(p => p.UserId)));
768820
return;
769821
}
770822

@@ -928,7 +980,7 @@ private void UpdateCapabilitiesByRole(Dictionary<string, List<string>> capabilit
928980
tempRolesToRemove.Add(role);
929981
}
930982
}
931-
983+
932984
foreach (var role in tempRolesToRemove)
933985
{
934986
_capabilitiesByRole.Remove(role);
@@ -987,5 +1039,15 @@ private void GetOrCreateParticipantsCustomDataSection(IStreamVideoCallParticipan
9871039

9881040
participantCustomData = allParticipantsCustomData[participant.SessionId];
9891041
}
1042+
1043+
private bool IsLocalParticipantIncluded()
1044+
{
1045+
if (Session == null || Session.Participants == null || Session.Participants.Count == 0)
1046+
{
1047+
return false;
1048+
}
1049+
1050+
return Session.Participants.FirstOrDefault(p => p.IsLocalParticipant) != null;
1051+
}
9901052
}
9911053
}

Packages/StreamVideo/Runtime/Core/Utils/DebugLogBuffer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace StreamVideo.Core.Utils
44
{
55
internal class DebugLogBuffer
66
{
7-
private const int MaxSize = 10;
7+
private const int MaxSize = 15;
88
private readonly string[] _buffer = new string[MaxSize];
99
private int _index;
1010
private int _count;

0 commit comments

Comments
 (0)